KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > apache > struts > tiles > xmlDefinition > I18nFactorySet


1 /*
2  * $Id: I18nFactorySet.java 105785 2004-11-19 06:58:36Z mrdon $
3  *
4  * Copyright 1999-2004 The Apache Software Foundation.
5  *
6  * Licensed under the Apache License, Version 2.0 (the "License");
7  * you may not use this file except in compliance with the License.
8  * You may obtain a copy of the License at
9  *
10  * http://www.apache.org/licenses/LICENSE-2.0
11  *
12  * Unless required by applicable law or agreed to in writing, software
13  * distributed under the License is distributed on an "AS IS" BASIS,
14  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15  * See the License for the specific language governing permissions and
16  * limitations under the License.
17  */

18
19 package org.apache.struts.tiles.xmlDefinition;
20
21 import java.io.FileNotFoundException JavaDoc;
22 import java.io.IOException JavaDoc;
23 import java.io.InputStream JavaDoc;
24 import java.util.ArrayList JavaDoc;
25 import java.util.HashMap JavaDoc;
26 import java.util.Iterator JavaDoc;
27 import java.util.List JavaDoc;
28 import java.util.Locale JavaDoc;
29 import java.util.Map JavaDoc;
30 import java.util.StringTokenizer JavaDoc;
31
32 import javax.servlet.ServletContext JavaDoc;
33 import javax.servlet.ServletRequest JavaDoc;
34 import javax.servlet.http.HttpServletRequest JavaDoc;
35 import javax.servlet.http.HttpSession JavaDoc;
36
37 import org.apache.commons.logging.Log;
38 import org.apache.commons.logging.LogFactory;
39 import org.apache.struts.taglib.tiles.ComponentConstants;
40 import org.apache.struts.tiles.DefinitionsFactoryException;
41 import org.apache.struts.tiles.FactoryNotFoundException;
42 import org.xml.sax.SAXException JavaDoc;
43
44 /**
45  * Definitions factory.
46  * This implementation allows to have a set of definition factories.
47  * There is a main factory and one factory for each file associated to a Locale.
48  *
49  * To retrieve a definition, we first search for the appropriate factory using
50  * the Locale found in session context. If no factory is found, use the
51  * default one. Then we ask the factory for the definition.
52  *
53  * A definition factory file is loaded using main filename extended with locale code
54  * (ex : <code>templateDefinitions_fr.xml</code>). If no file is found under this name, use default file.
55  */

56 public class I18nFactorySet extends FactorySet {
57
58     /**
59      * Commons Logging instance.
60      */

61     protected static Log log = LogFactory.getLog(I18nFactorySet.class);
62
63     /**
64      * Config file parameter name.
65      */

66     public static final String JavaDoc DEFINITIONS_CONFIG_PARAMETER_NAME =
67         "definitions-config";
68
69     /**
70      * Config file parameter name.
71      */

72     public static final String JavaDoc PARSER_DETAILS_PARAMETER_NAME =
73         "definitions-parser-details";
74
75     /**
76      * Config file parameter name.
77      */

78     public static final String JavaDoc PARSER_VALIDATE_PARAMETER_NAME =
79         "definitions-parser-validate";
80
81     /**
82      * Possible definition filenames.
83      */

84     public static final String JavaDoc DEFAULT_DEFINITION_FILENAMES[] =
85         {
86             "/WEB-INF/tileDefinitions.xml",
87             "/WEB-INF/componentDefinitions.xml",
88             "/WEB-INF/instanceDefinitions.xml" };
89
90     /**
91      * Maximum length of one branch of the resource search path tree.
92      * Used in getBundle().
93      */

94     private static final int MAX_BUNDLES_SEARCHED = 2;
95
96     /**
97      * Default filenames extension.
98      */

99     public static final String JavaDoc FILENAME_EXTENSION = ".xml";
100
101     /**
102      * Default factory.
103      */

104     protected DefinitionsFactory defaultFactory = null;
105
106     /**
107      * XML parser used.
108      * Attribute is transient to allow serialization. In this implementaiton,
109      * xmlParser is created each time we need it ;-(.
110      */

111     protected transient XmlParser xmlParser;
112
113     /**
114      * Do we want validating parser. Default is <code>false</code>.
115      * Can be set from servlet config file.
116      */

117     protected boolean isValidatingParser = false;
118
119     /**
120      * Parser detail level. Default is 0.
121      * Can be set from servlet config file.
122      */

123     protected int parserDetailLevel = 0;
124
125     /**
126      * Names of files containing instances descriptions.
127      */

128     private List JavaDoc filenames = null;
129
130     /**
131      * Collection of already loaded definitions set, referenced by their suffix.
132      */

133     private Map JavaDoc loaded = null;
134
135     /**
136      * Parameterless Constructor.
137      * Method {@link #initFactory} must be called prior to any use of created factory.
138      */

139     public I18nFactorySet() {
140         super();
141     }
142
143     /**
144      * Constructor.
145      * Init the factory by reading appropriate configuration file.
146      * @param servletContext Servlet context.
147      * @param properties Map containing all properties.
148      * @throws FactoryNotFoundException Can't find factory configuration file.
149      */

150     public I18nFactorySet(ServletContext JavaDoc servletContext, Map JavaDoc properties)
151         throws DefinitionsFactoryException {
152
153         initFactory(servletContext, properties);
154     }
155
156     /**
157      * Initialization method.
158      * Init the factory by reading appropriate configuration file.
159      * This method is called exactly once immediately after factory creation in
160      * case of internal creation (by DefinitionUtil).
161      * @param servletContext Servlet Context passed to newly created factory.
162      * @param properties Map of name/property passed to newly created factory. Map can contains
163      * more properties than requested.
164      * @throws DefinitionsFactoryException An error occur during initialization.
165      */

166     public void initFactory(ServletContext JavaDoc servletContext, Map JavaDoc properties)
167         throws DefinitionsFactoryException {
168
169         // Set some property values
170
String JavaDoc value = (String JavaDoc) properties.get(PARSER_VALIDATE_PARAMETER_NAME);
171         if (value != null) {
172             isValidatingParser = Boolean.valueOf(value).booleanValue();
173         }
174
175         value = (String JavaDoc) properties.get(PARSER_DETAILS_PARAMETER_NAME);
176         if (value != null) {
177             try {
178                 parserDetailLevel = Integer.valueOf(value).intValue();
179
180             } catch (NumberFormatException JavaDoc ex) {
181                 log.error(
182                     "Bad format for parameter '"
183                         + PARSER_DETAILS_PARAMETER_NAME
184                         + "'. Integer expected.");
185             }
186         }
187
188         // init factory withappropriate configuration file
189
// Try to use provided filename, if any.
190
// If no filename are provided, try to use default ones.
191
String JavaDoc filename = (String JavaDoc) properties.get(DEFINITIONS_CONFIG_PARAMETER_NAME);
192         if (filename != null) { // Use provided filename
193
try {
194                 initFactory(servletContext, filename);
195                 if (log.isDebugEnabled()) {
196                     log.debug("Factory initialized from file '" + filename + "'.");
197                 }
198
199             } catch (FileNotFoundException JavaDoc ex) { // A filename is specified, throw appropriate error.
200
log.error(ex.getMessage() + " : Can't find file '" + filename + "'");
201                 throw new FactoryNotFoundException(
202                     ex.getMessage() + " : Can't find file '" + filename + "'");
203             }
204
205         } else { // try each default file names
206
for (int i = 0; i < DEFAULT_DEFINITION_FILENAMES.length; i++) {
207                 filename = DEFAULT_DEFINITION_FILENAMES[i];
208                 try {
209                     initFactory(servletContext, filename);
210                     if (log.isInfoEnabled()) {
211                         log.info(
212                             "Factory initialized from file '" + filename + "'.");
213                     }
214                 } catch (FileNotFoundException JavaDoc ex) {
215                     // Do nothing
216
}
217             }
218         }
219
220     }
221
222     /**
223      * Initialization method.
224      * Init the factory by reading appropriate configuration file.
225      * This method is called exactly once immediately after factory creation in
226      * case of internal creation (by DefinitionUtil).
227      * @param servletContext Servlet Context passed to newly created factory.
228      * @param proposedFilename File names, comma separated, to use as base file names.
229      * @throws DefinitionsFactoryException An error occur during initialization.
230      */

231     protected void initFactory(
232         ServletContext JavaDoc servletContext,
233         String JavaDoc proposedFilename)
234         throws DefinitionsFactoryException, FileNotFoundException JavaDoc {
235
236         // Init list of filenames
237
StringTokenizer JavaDoc tokenizer = new StringTokenizer JavaDoc(proposedFilename, ",");
238         this.filenames = new ArrayList JavaDoc(tokenizer.countTokens());
239         while (tokenizer.hasMoreTokens()) {
240             this.filenames.add(tokenizer.nextToken().trim());
241         }
242
243         loaded = new HashMap JavaDoc();
244         defaultFactory = createDefaultFactory(servletContext);
245         if (log.isDebugEnabled())
246             log.debug("default factory:" + defaultFactory);
247     }
248
249     /**
250      * Get default factory.
251      * @return Default factory
252      */

253     protected DefinitionsFactory getDefaultFactory() {
254         return defaultFactory;
255     }
256
257     /**
258      * Create default factory .
259      * Create InstancesMapper for specified Locale.
260      * If creation failes, use default mapper and log error message.
261      * @param servletContext Current servlet context. Used to open file.
262      * @return Created default definition factory.
263      * @throws DefinitionsFactoryException If an error occur while creating factory.
264      * @throws FileNotFoundException if factory can't be loaded from filenames.
265      */

266     protected DefinitionsFactory createDefaultFactory(ServletContext JavaDoc servletContext)
267         throws DefinitionsFactoryException, FileNotFoundException JavaDoc {
268
269         XmlDefinitionsSet rootXmlConfig = parseXmlFiles(servletContext, "", null);
270         if (rootXmlConfig == null) {
271             throw new FileNotFoundException JavaDoc();
272         }
273
274         rootXmlConfig.resolveInheritances();
275
276         if (log.isDebugEnabled()) {
277             log.debug(rootXmlConfig);
278         }
279
280         DefinitionsFactory factory = new DefinitionsFactory(rootXmlConfig);
281         if (log.isDebugEnabled()) {
282             log.debug("factory loaded : " + factory);
283         }
284
285         return factory;
286     }
287
288     /**
289      * Extract key that will be used to get the sub factory.
290      * @param name Name of requested definition
291      * @param request Current servlet request.
292      * @param servletContext Current servlet context.
293      * @return the key or <code>null</code> if not found.
294      */

295     protected Object JavaDoc getDefinitionsFactoryKey(
296         String JavaDoc name,
297         ServletRequest JavaDoc request,
298         ServletContext JavaDoc servletContext) {
299
300         Locale JavaDoc locale = null;
301         try {
302             HttpSession JavaDoc session = ((HttpServletRequest JavaDoc) request).getSession(false);
303             if (session != null) {
304                 locale = (Locale JavaDoc) session.getAttribute(ComponentConstants.LOCALE_KEY);
305             }
306
307         } catch (ClassCastException JavaDoc ex) {
308             log.error("I18nFactorySet.getDefinitionsFactoryKey");
309             ex.printStackTrace();
310         }
311
312         return locale;
313     }
314
315     /**
316      * Create a factory for specified key.
317     * If creation failes, return default factory and log an error message.
318     * @param key The key.
319     * @param request Servlet request.
320     * @param servletContext Servlet context.
321     * @return Definition factory for specified key.
322     * @throws DefinitionsFactoryException If an error occur while creating factory.
323      */

324     protected DefinitionsFactory createFactory(
325         Object JavaDoc key,
326         ServletRequest JavaDoc request,
327         ServletContext JavaDoc servletContext)
328         throws DefinitionsFactoryException {
329
330         if (key == null) {
331             return getDefaultFactory();
332         }
333
334         // Build possible postfixes
335
List JavaDoc possiblePostfixes = calculatePostixes("", (Locale JavaDoc) key);
336
337         // Search last postix corresponding to a config file to load.
338
// First check if something is loaded for this postfix.
339
// If not, try to load its config.
340
XmlDefinitionsSet lastXmlFile = null;
341         DefinitionsFactory factory = null;
342         String JavaDoc curPostfix = null;
343         int i = 0;
344
345         for (i = possiblePostfixes.size() - 1; i >= 0; i--) {
346             curPostfix = (String JavaDoc) possiblePostfixes.get(i);
347
348             // Already loaded ?
349
factory = (DefinitionsFactory) loaded.get(curPostfix);
350             if (factory != null) { // yes, stop search
351
return factory;
352             }
353
354             // Try to load it. If success, stop search
355
lastXmlFile = parseXmlFiles(servletContext, curPostfix, null);
356             if (lastXmlFile != null) {
357                 break;
358             }
359         }
360
361         // Have we found a description file ?
362
// If no, return default one
363
if (lastXmlFile == null) {
364             return getDefaultFactory();
365         }
366
367         // We found something. Need to load base and intermediate files
368
String JavaDoc lastPostfix = curPostfix;
369         XmlDefinitionsSet rootXmlConfig = parseXmlFiles(servletContext, "", null);
370         for (int j = 0; j < i; j++) {
371             curPostfix = (String JavaDoc) possiblePostfixes.get(j);
372             parseXmlFiles(servletContext, curPostfix, rootXmlConfig);
373         }
374
375         rootXmlConfig.extend(lastXmlFile);
376         rootXmlConfig.resolveInheritances();
377
378         factory = new DefinitionsFactory(rootXmlConfig);
379         loaded.put(lastPostfix, factory);
380
381         if (log.isDebugEnabled()) {
382             log.debug("factory loaded : " + factory);
383         }
384
385         // return last available found !
386
return factory;
387     }
388
389     /**
390      * Calculate the postixes along the search path from the base bundle to the
391      * bundle specified by baseName and locale.
392      * Method copied from java.util.ResourceBundle
393      * @param baseName the base bundle name
394      * @param locale the locale
395      */

396     private static List JavaDoc calculatePostixes(String JavaDoc baseName, Locale JavaDoc locale) {
397         final List JavaDoc result = new ArrayList JavaDoc(MAX_BUNDLES_SEARCHED);
398         final String JavaDoc language = locale.getLanguage();
399         final int languageLength = language.length();
400         final String JavaDoc country = locale.getCountry();
401         final int countryLength = country.length();
402         final String JavaDoc variant = locale.getVariant();
403         final int variantLength = variant.length();
404
405         if (languageLength + countryLength + variantLength == 0) {
406             //The locale is "", "", "".
407
return result;
408         }
409
410         final StringBuffer JavaDoc temp = new StringBuffer JavaDoc(baseName);
411         temp.append('_');
412         temp.append(language);
413
414         if (languageLength > 0)
415             result.add(temp.toString());
416
417         if (countryLength + variantLength == 0)
418             return result;
419
420         temp.append('_');
421         temp.append(country);
422
423         if (countryLength > 0)
424             result.add(temp.toString());
425
426         if (variantLength == 0) {
427             return result;
428         } else {
429             temp.append('_');
430             temp.append(variant);
431             result.add(temp.toString());
432             return result;
433         }
434     }
435
436     /**
437      * Parse files associated to postix if they exist.
438      * For each name in filenames, append postfix before file extension,
439      * then try to load the corresponding file.
440      * If file doesn't exist, try next one. Each file description is added to
441      * the XmlDefinitionsSet description.
442      * The XmlDefinitionsSet description is created only if there is a definition file.
443      * Inheritance is not resolved in the returned XmlDefinitionsSet.
444      * If no description file can be opened and no definiion set is provided, return <code>null</code>.
445      * @param postfix Postfix to add to each description file.
446      * @param xmlDefinitions Definitions set to which definitions will be added. If <code>null</code>, a definitions
447      * set is created on request.
448      * @return XmlDefinitionsSet The definitions set created or passed as parameter.
449      * @throws DefinitionsFactoryException On errors parsing file.
450      */

451     private XmlDefinitionsSet parseXmlFiles(
452         ServletContext JavaDoc servletContext,
453         String JavaDoc postfix,
454         XmlDefinitionsSet xmlDefinitions)
455         throws DefinitionsFactoryException {
456
457         if (postfix != null && postfix.length() == 0) {
458             postfix = null;
459         }
460
461         // Iterate throw each file name in list
462
Iterator JavaDoc i = filenames.iterator();
463         while (i.hasNext()) {
464             String JavaDoc filename = concatPostfix((String JavaDoc) i.next(), postfix);
465             xmlDefinitions = parseXmlFile(servletContext, filename, xmlDefinitions);
466         }
467
468         return xmlDefinitions;
469     }
470
471     /**
472      * Parse specified xml file and add definition to specified definitions set.
473      * This method is used to load several description files in one instances list.
474      * If filename exists and definition set is <code>null</code>, create a new set. Otherwise, return
475      * passed definition set (can be <code>null</code>).
476      * @param servletContext Current servlet context. Used to open file.
477      * @param filename Name of file to parse.
478      * @param xmlDefinitions Definitions set to which definitions will be added. If null, a definitions
479      * set is created on request.
480      * @return XmlDefinitionsSet The definitions set created or passed as parameter.
481      * @throws DefinitionsFactoryException On errors parsing file.
482      */

483     private XmlDefinitionsSet parseXmlFile(
484         ServletContext JavaDoc servletContext,
485         String JavaDoc filename,
486         XmlDefinitionsSet xmlDefinitions)
487         throws DefinitionsFactoryException {
488
489         try {
490             InputStream JavaDoc input = servletContext.getResourceAsStream(filename);
491             // Try to load using real path.
492
// This allow to load config file under websphere 3.5.x
493
// Patch proposed Houston, Stephen (LIT) on 5 Apr 2002
494
if (null == input) {
495                 try {
496                     input =
497                         new java.io.FileInputStream JavaDoc(
498                             servletContext.getRealPath(filename));
499                 } catch (Exception JavaDoc e) {
500                 }
501             }
502             
503             // If the config isn't in the servlet context, try the class loader
504
// which allows the config files to be stored in a jar
505
if (input == null) {
506                 input = getClass().getResourceAsStream(filename);
507             }
508
509             // If still nothing found, this mean no config file is associated
510
if (input == null) {
511                 if (log.isDebugEnabled()) {
512                     log.debug("Can't open file '" + filename + "'");
513                 }
514                 return xmlDefinitions;
515             }
516
517             // Check if parser already exist.
518
// Doesn't seem to work yet.
519
//if( xmlParser == null )
520
if (true) {
521                 xmlParser = new XmlParser();
522                 xmlParser.setValidating(isValidatingParser);
523             }
524
525             // Check if definition set already exist.
526
if (xmlDefinitions == null) {
527                 xmlDefinitions = new XmlDefinitionsSet();
528             }
529
530             xmlParser.parse(input, xmlDefinitions);
531
532         } catch (SAXException JavaDoc ex) {
533             if (log.isDebugEnabled()) {
534                 log.debug("Error while parsing file '" + filename + "'.");
535                 ex.printStackTrace();
536             }
537             throw new DefinitionsFactoryException(
538                 "Error while parsing file '" + filename + "'. " + ex.getMessage(),
539                 ex);
540
541         } catch (IOException JavaDoc ex) {
542             throw new DefinitionsFactoryException(
543                 "IO Error while parsing file '" + filename + "'. " + ex.getMessage(),
544                 ex);
545         }
546
547         return xmlDefinitions;
548     }
549
550     /**
551      * Concat postfix to the name. Take care of existing filename extension.
552      * Transform the given name "name.ext" to have "name" + "postfix" + "ext".
553      * If there is no ext, return "name" + "postfix".
554      * @param name Filename.
555      * @param postfix Postfix to add.
556      * @return Concatenated filename.
557      */

558     private String JavaDoc concatPostfix(String JavaDoc name, String JavaDoc postfix) {
559         if (postfix == null) {
560             return name;
561         }
562
563         // Search file name extension.
564
// take care of Unix files starting with .
565
int dotIndex = name.lastIndexOf(".");
566         int lastNameStart = name.lastIndexOf(java.io.File.pathSeparator);
567         if (dotIndex < 1 || dotIndex < lastNameStart) {
568             return name + postfix;
569         }
570
571         String JavaDoc ext = name.substring(dotIndex);
572         name = name.substring(0, dotIndex);
573         return name + postfix + ext;
574     }
575
576     /**
577      * Return String representation.
578      * @return String representation.
579      */

580     public String JavaDoc toString() {
581         StringBuffer JavaDoc buff = new StringBuffer JavaDoc("I18nFactorySet : \n");
582         buff.append("--- default factory ---\n");
583         buff.append(defaultFactory.toString());
584         buff.append("\n--- other factories ---\n");
585         Iterator JavaDoc i = factories.values().iterator();
586         while (i.hasNext()) {
587             buff.append(i.next().toString()).append("---------- \n");
588         }
589         return buff.toString();
590     }
591
592 }
593
Popular Tags