1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 package org.apache.struts.tiles.xmlDefinition;
23
24 import java.io.FileNotFoundException;
25 import java.io.IOException;
26 import java.io.InputStream;
27 import java.util.ArrayList;
28 import java.util.HashMap;
29 import java.util.Iterator;
30 import java.util.List;
31 import java.util.Locale;
32 import java.util.Map;
33 import java.util.StringTokenizer;
34
35 import javax.servlet.ServletContext;
36 import javax.servlet.ServletRequest;
37 import javax.servlet.http.HttpServletRequest;
38 import javax.servlet.http.HttpSession;
39
40 import org.apache.commons.logging.Log;
41 import org.apache.commons.logging.LogFactory;
42 import org.apache.struts.tiles.taglib.ComponentConstants;
43 import org.apache.struts.tiles.DefinitionsFactoryException;
44 import org.apache.struts.tiles.FactoryNotFoundException;
45 import org.xml.sax.SAXException;
46
47 /**
48 * Definitions factory.
49 * This implementation allows to have a set of definition factories.
50 * There is a main factory and one factory for each file associated to a Locale.
51 *
52 * To retrieve a definition, we first search for the appropriate factory using
53 * the Locale found in session context. If no factory is found, use the
54 * default one. Then we ask the factory for the definition.
55 *
56 * A definition factory file is loaded using main filename extended with locale code
57 * (ex : <code>templateDefinitions_fr.xml</code>). If no file is found under this name, use default file.
58 */
59 public class I18nFactorySet extends FactorySet {
60
61 /**
62 * Commons Logging instance.
63 */
64 protected static Log log = LogFactory.getLog(I18nFactorySet.class);
65
66 /**
67 * Config file parameter name.
68 */
69 public static final String DEFINITIONS_CONFIG_PARAMETER_NAME =
70 "definitions-config";
71
72 /**
73 * Config file parameter name.
74 */
75 public static final String PARSER_DETAILS_PARAMETER_NAME =
76 "definitions-parser-details";
77
78 /**
79 * Config file parameter name.
80 */
81 public static final String PARSER_VALIDATE_PARAMETER_NAME =
82 "definitions-parser-validate";
83
84 /**
85 * Possible definition filenames.
86 */
87 public static final String DEFAULT_DEFINITION_FILENAMES[] =
88 {
89 "/WEB-INF/tileDefinitions.xml",
90 "/WEB-INF/componentDefinitions.xml",
91 "/WEB-INF/instanceDefinitions.xml" };
92
93 /**
94 * Default filenames extension.
95 */
96 public static final String FILENAME_EXTENSION = ".xml";
97
98 /**
99 * Default factory.
100 */
101 protected DefinitionsFactory defaultFactory = null;
102
103 /**
104 * XML parser used.
105 * Attribute is transient to allow serialization. In this implementaiton,
106 * xmlParser is created each time we need it ;-(.
107 */
108 protected transient XmlParser xmlParser;
109
110 /**
111 * Do we want validating parser. Default is <code>false</code>.
112 * Can be set from servlet config file.
113 */
114 protected boolean isValidatingParser = false;
115
116 /**
117 * Parser detail level. Default is 0.
118 * Can be set from servlet config file.
119 */
120 protected int parserDetailLevel = 0;
121
122 /**
123 * Names of files containing instances descriptions.
124 */
125 private List filenames = null;
126
127 /**
128 * Collection of already loaded definitions set, referenced by their suffix.
129 */
130 private Map loaded = null;
131
132 /**
133 * Parameterless Constructor.
134 * Method {@link #initFactory} must be called prior to any use of created factory.
135 */
136 public I18nFactorySet() {
137 super();
138 }
139
140 /**
141 * Constructor.
142 * Init the factory by reading appropriate configuration file.
143 * @param servletContext Servlet context.
144 * @param properties Map containing all properties.
145 * @throws FactoryNotFoundException Can't find factory configuration file.
146 */
147 public I18nFactorySet(ServletContext servletContext, Map properties)
148 throws DefinitionsFactoryException {
149
150 initFactory(servletContext, properties);
151 }
152
153 /**
154 * Initialization method.
155 * Init the factory by reading appropriate configuration file.
156 * This method is called exactly once immediately after factory creation in
157 * case of internal creation (by DefinitionUtil).
158 * @param servletContext Servlet Context passed to newly created factory.
159 * @param properties Map of name/property passed to newly created factory. Map can contains
160 * more properties than requested.
161 * @throws DefinitionsFactoryException An error occur during initialization.
162 */
163 public void initFactory(ServletContext servletContext, Map properties)
164 throws DefinitionsFactoryException {
165
166
167 String value = (String) properties.get(PARSER_VALIDATE_PARAMETER_NAME);
168 if (value != null) {
169 isValidatingParser = Boolean.valueOf(value).booleanValue();
170 }
171
172 value = (String) properties.get(PARSER_DETAILS_PARAMETER_NAME);
173 if (value != null) {
174 try {
175 parserDetailLevel = Integer.valueOf(value).intValue();
176
177 } catch (NumberFormatException ex) {
178 log.error(
179 "Bad format for parameter '"
180 + PARSER_DETAILS_PARAMETER_NAME
181 + "'. Integer expected.");
182 }
183 }
184
185
186
187
188 String filename = (String) properties.get(DEFINITIONS_CONFIG_PARAMETER_NAME);
189 if (filename != null) {
190 try {
191 initFactory(servletContext, filename);
192 if (log.isDebugEnabled()) {
193 log.debug("Factory initialized from file '" + filename + "'.");
194 }
195
196 } catch (FileNotFoundException ex) {
197 log.error(ex.getMessage() + " : Can't find file '" + filename + "'");
198 throw new FactoryNotFoundException(
199 ex.getMessage() + " : Can't find file '" + filename + "'");
200 }
201
202 } else {
203 for (int i = 0; i < DEFAULT_DEFINITION_FILENAMES.length; i++) {
204 filename = DEFAULT_DEFINITION_FILENAMES[i];
205 try {
206 initFactory(servletContext, filename);
207 if (log.isInfoEnabled()) {
208 log.info(
209 "Factory initialized from file '" + filename + "'.");
210 }
211 } catch (FileNotFoundException ex) {
212
213 }
214 }
215 }
216
217 }
218
219 /**
220 * Initialization method.
221 * Init the factory by reading appropriate configuration file.
222 * This method is called exactly once immediately after factory creation in
223 * case of internal creation (by DefinitionUtil).
224 * @param servletContext Servlet Context passed to newly created factory.
225 * @param proposedFilename File names, comma separated, to use as base file names.
226 * @throws DefinitionsFactoryException An error occur during initialization.
227 */
228 protected void initFactory(
229 ServletContext servletContext,
230 String proposedFilename)
231 throws DefinitionsFactoryException, FileNotFoundException {
232
233
234 StringTokenizer tokenizer = new StringTokenizer(proposedFilename, ",");
235 this.filenames = new ArrayList(tokenizer.countTokens());
236 while (tokenizer.hasMoreTokens()) {
237 this.filenames.add(tokenizer.nextToken().trim());
238 }
239
240 loaded = new HashMap();
241 defaultFactory = createDefaultFactory(servletContext);
242 if (log.isDebugEnabled())
243 log.debug("default factory:" + defaultFactory);
244 }
245
246 /**
247 * Get default factory.
248 * @return Default factory
249 */
250 protected DefinitionsFactory getDefaultFactory() {
251 return defaultFactory;
252 }
253
254 /**
255 * Create default factory .
256 * Create InstancesMapper for specified Locale.
257 * If creation failes, use default mapper and log error message.
258 * @param servletContext Current servlet context. Used to open file.
259 * @return Created default definition factory.
260 * @throws DefinitionsFactoryException If an error occur while creating factory.
261 * @throws FileNotFoundException if factory can't be loaded from filenames.
262 */
263 protected DefinitionsFactory createDefaultFactory(ServletContext servletContext)
264 throws DefinitionsFactoryException, FileNotFoundException {
265
266 XmlDefinitionsSet rootXmlConfig = parseXmlFiles(servletContext, "", null);
267 if (rootXmlConfig == null) {
268 throw new FileNotFoundException();
269 }
270
271 rootXmlConfig.resolveInheritances();
272
273 if (log.isDebugEnabled()) {
274 log.debug(rootXmlConfig);
275 }
276
277 DefinitionsFactory factory = new DefinitionsFactory(rootXmlConfig);
278 if (log.isDebugEnabled()) {
279 log.debug("factory loaded : " + factory);
280 }
281
282 return factory;
283 }
284
285 /**
286 * Extract key that will be used to get the sub factory.
287 * @param name Name of requested definition
288 * @param request Current servlet request.
289 * @param servletContext Current servlet context.
290 * @return the key or <code>null</code> if not found.
291 */
292 protected Object getDefinitionsFactoryKey(
293 String name,
294 ServletRequest request,
295 ServletContext servletContext) {
296
297 Locale locale = null;
298 try {
299 HttpSession session = ((HttpServletRequest) request).getSession(false);
300 if (session != null) {
301 locale = (Locale) session.getAttribute(ComponentConstants.LOCALE_KEY);
302 }
303
304 } catch (ClassCastException ex) {
305 log.error("I18nFactorySet.getDefinitionsFactoryKey");
306 ex.printStackTrace();
307 }
308
309 return locale;
310 }
311
312 /**
313 * Create a factory for specified key.
314 * If creation failes, return default factory and log an error message.
315 * @param key The key.
316 * @param request Servlet request.
317 * @param servletContext Servlet context.
318 * @return Definition factory for specified key.
319 * @throws DefinitionsFactoryException If an error occur while creating factory.
320 */
321 protected DefinitionsFactory createFactory(
322 Object key,
323 ServletRequest request,
324 ServletContext servletContext)
325 throws DefinitionsFactoryException {
326
327 if (key == null) {
328 return getDefaultFactory();
329 }
330
331
332 List possiblePostfixes = calculateSuffixes((Locale) key);
333
334
335
336
337 XmlDefinitionsSet lastXmlFile = null;
338 DefinitionsFactory factory = null;
339 String curPostfix = null;
340 int i = 0;
341
342 for (i = possiblePostfixes.size() - 1; i >= 0; i--) {
343 curPostfix = (String) possiblePostfixes.get(i);
344
345
346 factory = (DefinitionsFactory) loaded.get(curPostfix);
347 if (factory != null) {
348 return factory;
349 }
350
351
352 lastXmlFile = parseXmlFiles(servletContext, curPostfix, null);
353 if (lastXmlFile != null) {
354 break;
355 }
356 }
357
358
359
360 if (lastXmlFile == null) {
361 return getDefaultFactory();
362 }
363
364
365 String lastPostfix = curPostfix;
366 XmlDefinitionsSet rootXmlConfig = parseXmlFiles(servletContext, "", null);
367 for (int j = 0; j < i; j++) {
368 curPostfix = (String) possiblePostfixes.get(j);
369 parseXmlFiles(servletContext, curPostfix, rootXmlConfig);
370 }
371
372 rootXmlConfig.extend(lastXmlFile);
373 rootXmlConfig.resolveInheritances();
374
375 factory = new DefinitionsFactory(rootXmlConfig);
376 loaded.put(lastPostfix, factory);
377
378 if (log.isDebugEnabled()) {
379 log.debug("factory loaded : " + factory);
380 }
381
382
383 return factory;
384 }
385
386 /**
387 * Calculate the suffixes based on the locale.
388 * @param locale the locale
389 */
390 private List calculateSuffixes(Locale locale) {
391
392 List suffixes = new ArrayList(3);
393 String language = locale.getLanguage();
394 String country = locale.getCountry();
395 String variant = locale.getVariant();
396
397 StringBuffer suffix = new StringBuffer();
398 suffix.append('_');
399 suffix.append(language);
400 if (language.length() > 0) {
401 suffixes.add(suffix.toString());
402 }
403
404 suffix.append('_');
405 suffix.append(country);
406 if (country.length() > 0) {
407 suffixes.add(suffix.toString());
408 }
409
410 suffix.append('_');
411 suffix.append(variant);
412 if (variant.length() > 0) {
413 suffixes.add(suffix.toString());
414 }
415
416 return suffixes;
417
418 }
419
420 /**
421 * Parse files associated to postix if they exist.
422 * For each name in filenames, append postfix before file extension,
423 * then try to load the corresponding file.
424 * If file doesn't exist, try next one. Each file description is added to
425 * the XmlDefinitionsSet description.
426 * The XmlDefinitionsSet description is created only if there is a definition file.
427 * Inheritance is not resolved in the returned XmlDefinitionsSet.
428 * If no description file can be opened and no definiion set is provided, return <code>null</code>.
429 * @param postfix Postfix to add to each description file.
430 * @param xmlDefinitions Definitions set to which definitions will be added. If <code>null</code>, a definitions
431 * set is created on request.
432 * @return XmlDefinitionsSet The definitions set created or passed as parameter.
433 * @throws DefinitionsFactoryException On errors parsing file.
434 */
435 protected XmlDefinitionsSet parseXmlFiles(
436 ServletContext servletContext,
437 String postfix,
438 XmlDefinitionsSet xmlDefinitions)
439 throws DefinitionsFactoryException {
440
441 if (postfix != null && postfix.length() == 0) {
442 postfix = null;
443 }
444
445
446 Iterator i = filenames.iterator();
447 while (i.hasNext()) {
448 String filename = concatPostfix((String) i.next(), postfix);
449 xmlDefinitions = parseXmlFile(servletContext, filename, xmlDefinitions);
450 }
451
452 return xmlDefinitions;
453 }
454
455 /**
456 * Parse specified xml file and add definition to specified definitions set.
457 * This method is used to load several description files in one instances list.
458 * If filename exists and definition set is <code>null</code>, create a new set. Otherwise, return
459 * passed definition set (can be <code>null</code>).
460 * @param servletContext Current servlet context. Used to open file.
461 * @param filename Name of file to parse.
462 * @param xmlDefinitions Definitions set to which definitions will be added. If null, a definitions
463 * set is created on request.
464 * @return XmlDefinitionsSet The definitions set created or passed as parameter.
465 * @throws DefinitionsFactoryException On errors parsing file.
466 */
467 protected XmlDefinitionsSet parseXmlFile(
468 ServletContext servletContext,
469 String filename,
470 XmlDefinitionsSet xmlDefinitions)
471 throws DefinitionsFactoryException {
472
473 try {
474 InputStream input = servletContext.getResourceAsStream(filename);
475
476
477
478 if (null == input) {
479 try {
480 input =
481 new java.io.FileInputStream(
482 servletContext.getRealPath(filename));
483 } catch (Exception e) {
484 }
485 }
486
487
488
489 if (input == null) {
490 input = getClass().getResourceAsStream(filename);
491 }
492
493
494 if (input == null) {
495 if (log.isDebugEnabled()) {
496 log.debug("Can't open file '" + filename + "'");
497 }
498 return xmlDefinitions;
499 }
500
501
502
503
504 if (true) {
505 xmlParser = new XmlParser();
506 xmlParser.setValidating(isValidatingParser);
507 }
508
509
510 if (xmlDefinitions == null) {
511 xmlDefinitions = new XmlDefinitionsSet();
512 }
513
514 xmlParser.parse(input, xmlDefinitions);
515
516 } catch (SAXException ex) {
517 if (log.isDebugEnabled()) {
518 log.debug("Error while parsing file '" + filename + "'.");
519 ex.printStackTrace();
520 }
521 throw new DefinitionsFactoryException(
522 "Error while parsing file '" + filename + "'. " + ex.getMessage(),
523 ex);
524
525 } catch (IOException ex) {
526 throw new DefinitionsFactoryException(
527 "IO Error while parsing file '" + filename + "'. " + ex.getMessage(),
528 ex);
529 }
530
531 return xmlDefinitions;
532 }
533
534 /**
535 * Concat postfix to the name. Take care of existing filename extension.
536 * Transform the given name "name.ext" to have "name" + "postfix" + "ext".
537 * If there is no ext, return "name" + "postfix".
538 * @param name Filename.
539 * @param postfix Postfix to add.
540 * @return Concatenated filename.
541 */
542 private String concatPostfix(String name, String postfix) {
543 if (postfix == null) {
544 return name;
545 }
546
547
548
549 int dotIndex = name.lastIndexOf(".");
550 int lastNameStart = name.lastIndexOf(java.io.File.pathSeparator);
551 if (dotIndex < 1 || dotIndex < lastNameStart) {
552 return name + postfix;
553 }
554
555 String ext = name.substring(dotIndex);
556 name = name.substring(0, dotIndex);
557 return name + postfix + ext;
558 }
559
560 /**
561 * Return String representation.
562 * @return String representation.
563 */
564 public String toString() {
565 StringBuffer buff = new StringBuffer("I18nFactorySet : \n");
566 buff.append("--- default factory ---\n");
567 buff.append(defaultFactory.toString());
568 buff.append("\n--- other factories ---\n");
569 Iterator i = factories.values().iterator();
570 while (i.hasNext()) {
571 buff.append(i.next().toString()).append("---------- \n");
572 }
573 return buff.toString();
574 }
575
576 }