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