Docjar: A Java Source and Docuemnt Enginecom.*    java.*    javax.*    org.*    all    new    plug-in

Quick Search    Search Deep

Source code: org/apache/struts/tiles/xmlDefinition/I18nFactorySet.java


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;
22  import java.io.IOException;
23  import java.io.InputStream;
24  import java.util.ArrayList;
25  import java.util.HashMap;
26  import java.util.Iterator;
27  import java.util.List;
28  import java.util.Locale;
29  import java.util.Map;
30  import java.util.StringTokenizer;
31  
32  import javax.servlet.ServletContext;
33  import javax.servlet.ServletRequest;
34  import javax.servlet.http.HttpServletRequest;
35  import javax.servlet.http.HttpSession;
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;
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 DEFINITIONS_CONFIG_PARAMETER_NAME =
67          "definitions-config";
68  
69      /** 
70       * Config file parameter name. 
71       */
72      public static final String PARSER_DETAILS_PARAMETER_NAME =
73          "definitions-parser-details";
74  
75      /** 
76       * Config file parameter name. 
77       */
78      public static final String PARSER_VALIDATE_PARAMETER_NAME =
79          "definitions-parser-validate";
80  
81      /** 
82       * Possible definition filenames. 
83       */
84      public static final String 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 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 filenames = null;
129 
130     /** 
131      * Collection of already loaded definitions set, referenced by their suffix. 
132      */
133     private Map 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 servletContext, Map 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 servletContext, Map properties)
167         throws DefinitionsFactoryException {
168 
169         // Set some property values
170         String value = (String) properties.get(PARSER_VALIDATE_PARAMETER_NAME);
171         if (value != null) {
172             isValidatingParser = Boolean.valueOf(value).booleanValue();
173         }
174 
175         value = (String) properties.get(PARSER_DETAILS_PARAMETER_NAME);
176         if (value != null) {
177             try {
178                 parserDetailLevel = Integer.valueOf(value).intValue();
179 
180             } catch (NumberFormatException 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 filename = (String) 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 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 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 servletContext,
233         String proposedFilename)
234         throws DefinitionsFactoryException, FileNotFoundException {
235 
236         // Init list of filenames
237         StringTokenizer tokenizer = new StringTokenizer(proposedFilename, ",");
238         this.filenames = new ArrayList(tokenizer.countTokens());
239         while (tokenizer.hasMoreTokens()) {
240             this.filenames.add(tokenizer.nextToken().trim());
241         }
242 
243         loaded = new HashMap();
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 servletContext)
267         throws DefinitionsFactoryException, FileNotFoundException {
268 
269         XmlDefinitionsSet rootXmlConfig = parseXmlFiles(servletContext, "", null);
270         if (rootXmlConfig == null) {
271             throw new FileNotFoundException();
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 getDefinitionsFactoryKey(
296         String name,
297         ServletRequest request,
298         ServletContext servletContext) {
299 
300         Locale locale = null;
301         try {
302             HttpSession session = ((HttpServletRequest) request).getSession(false);
303             if (session != null) {
304                 locale = (Locale) session.getAttribute(ComponentConstants.LOCALE_KEY);
305             }
306 
307         } catch (ClassCastException 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 key,
326         ServletRequest request,
327         ServletContext servletContext)
328         throws DefinitionsFactoryException {
329 
330         if (key == null) {
331             return getDefaultFactory();
332         }
333 
334         // Build possible postfixes
335         List possiblePostfixes = calculatePostixes("", (Locale) 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 curPostfix = null;
343         int i = 0;
344 
345         for (i = possiblePostfixes.size() - 1; i >= 0; i--) {
346             curPostfix = (String) 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 lastPostfix = curPostfix;
369         XmlDefinitionsSet rootXmlConfig = parseXmlFiles(servletContext, "", null);
370         for (int j = 0; j < i; j++) {
371             curPostfix = (String) 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 calculatePostixes(String baseName, Locale locale) {
397         final List result = new ArrayList(MAX_BUNDLES_SEARCHED);
398         final String language = locale.getLanguage();
399         final int languageLength = language.length();
400         final String country = locale.getCountry();
401         final int countryLength = country.length();
402         final String 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 temp = new StringBuffer(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 servletContext,
453         String 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 i = filenames.iterator();
463         while (i.hasNext()) {
464             String filename = concatPostfix((String) 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 servletContext,
485         String filename,
486         XmlDefinitionsSet xmlDefinitions)
487         throws DefinitionsFactoryException {
488 
489         try {
490             InputStream 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(
498                             servletContext.getRealPath(filename));
499                 } catch (Exception 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 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 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 concatPostfix(String name, String 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 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 toString() {
581         StringBuffer buff = new StringBuffer("I18nFactorySet : \n");
582         buff.append("--- default factory ---\n");
583         buff.append(defaultFactory.toString());
584         buff.append("\n--- other factories ---\n");
585         Iterator i = factories.values().iterator();
586         while (i.hasNext()) {
587             buff.append(i.next().toString()).append("---------- \n");
588         }
589         return buff.toString();
590     }
591 
592 }