View Javadoc

1   /*
2    * $Header: /usr/local/cvsroot/dev/madcache/src/com/macvu/tiles/xmlDefinition/CacheI18nFactorySet.java,v 1.1 2004/04/08 06:24:44 macvu Exp $
3    * $Revision: 1.1 $
4    * $Date: 2004/04/08 06:24:44 $
5    *
6    * ====================================================================
7    *
8    * The Apache Software License, Version 1.1
9    *
10   * Copyright (c) 1999-2002 The Apache Software Foundation.  All rights
11   * reserved.
12   *
13   * Redistribution and use in source and binary forms, with or without
14   * modification, are permitted provided that the following conditions
15   * are met:
16   *
17   * 1. Redistributions of source code must retain the above copyright
18   *    notice, this list of conditions and the following disclaimer.
19   *
20   * 2. Redistributions in binary form must reproduce the above copyright
21   *    notice, this list of conditions and the following disclaimer in
22   *    the documentation and/or other materials provided with the
23   *    distribution.
24   *
25   * 3. The end-user documentation included with the redistribution, if
26   *    any, must include the following acknowlegement:
27   *       "This product includes software developed by the
28   *        Apache Software Foundation (http://www.apache.org/)."
29   *    Alternately, this acknowlegement may appear in the software itself,
30   *    if and wherever such third-party acknowlegements normally appear.
31   *
32   * 4. The names "The Jakarta Project", "Struts", and "Apache Software
33   *    Foundation" must not be used to endorse or promote products derived
34   *    from this software without prior written permission. For written
35   *    permission, please contact apache@apache.org.
36   *
37   * 5. Products derived from this software may not be called "Apache"
38   *    nor may "Apache" appear in their names without prior written
39   *    permission of the Apache Group.
40   *
41   * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
42   * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
43   * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
44   * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
45   * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
46   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
47   * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
48   * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
49   * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
50   * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
51   * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
52   * SUCH DAMAGE.
53   * ====================================================================
54   *
55   * This software consists of voluntary contributions made by many
56   * individuals on behalf of the Apache Software Foundation.  For more
57   * information on the Apache Software Foundation, please see
58   * <http://www.apache.org/>.
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      //public static final String DEFAULT_DEFINITIONS_FILE_NAME = "/WEB-INF/componentDefinitions.xml";
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         // Set some property values
197         String value = (String) properties.get(PARSER_VALIDATE_PARAMETER_NAME);
198         if (value != null) {
199             isValidatingParser = Boolean.valueOf(value).booleanValue();
200         }  // end if
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         }  // end if
211 
212         // init factory withappropriate configuration file
213         // Try to use provided filename, if any.
214         // If no filename are provided, try to use default ones.
215         String filename = (String) properties.get(DEFINITIONS_CONFIG_PARAMETER_NAME);
216 
217         if (filename != null) { // Use provided filename
218             try {
219                 initFactory(servletContext, filename);
220                 if (log.isDebugEnabled())
221                     log.debug("Factory initialized from file '" + filename + "'.");
222             } catch (FileNotFoundException ex) { // A filename is specified, throw appropriate error.
223                 log.error(ex.getMessage() + " : Can't find file '" + filename + "'");
224                 throw new FactoryNotFoundException(ex.getMessage() + " : Can't find file '" + filename + "'");
225             } // end catch
226         } else { // try each default file names
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) { // Do nothing
235                 } // end catch
236             } // end loop
237         } // end if
238 
239         // Save the file name list so we can use latter to save the files.
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         // Init list of filenames
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         // Build possible postfixes
343         List possiblePostfixes = calculatePostixes("", (Locale) key);
344 
345         // Search last postix corresponding to a config file to load.
346         // First check if something is loaded for this postfix.
347         // If not, try to load its config.
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             // Already loaded ?
356             factory = (CacheDefinitionFactory) loaded.get(curPostfix);
357             if (factory != null) { // yes, stop search
358                 return factory;
359             } // end if
360             // Try to load it. If success, stop search
361             lastXmlFile = parseXmlFiles(servletContext, curPostfix, null);
362             if (lastXmlFile != null)
363                 break;
364         } // end loop
365 
366         // Have we found a description file ?
367         // If no, return default one
368         if (lastXmlFile == null) {
369             return getDefaultFactory();
370         }
371 
372         // We found something. Need to load base and intermediate files
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         } // end loop
379 
380         rootXmlConfig.extend(lastXmlFile);
381         rootXmlConfig.resolveInheritances();
382 
383         factory = new CacheDefinitionFactory(rootXmlConfig);
384         loaded.put(lastPostfix, factory);
385         // User help
386         if (log.isDebugEnabled())
387             log.debug("factory loaded : " + factory);
388         // return last available found !
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             //The locale is "", "", "".
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         // Iterate throw each file name in list
461         Iterator i = filenames.iterator();
462         while (i.hasNext()) {
463             String filename = concatPostfix((String) i.next(), postfix);
464             xmlDefinitions = parseXmlFile(servletContext, filename, xmlDefinitions);
465         } // end loop
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             // Try to load using real path.
487             // This allow to load config file under websphere 3.5.x
488             // Patch proposed Houston, Stephen (LIT) on 5 Apr 2002
489             if (null == input) {
490                 try {
491                     input = new java.io.FileInputStream(servletContext.getRealPath(filename));
492                 } catch (Exception e) {
493                 }
494             }
495             // If still nothing found, this mean no config file is associated
496             if (input == null) {
497                 if (log.isDebugEnabled())
498                     log.debug("Can't open file '" + filename + "'");
499                 return xmlDefinitions;
500             }
501 
502             // Check if parser already exist.
503             // Doesn't seem to work yet.
504             //if( xmlParser == null )
505             if (true) {  // create it
506                 //if(log.isDebugEnabled())
507                 //log.debug( "Create xmlParser");
508                 xmlParser = new XmlCacheParser();
509                 xmlParser.setValidating(isValidatingParser);
510                 xmlParser.setDetailLevel(parserDetailLevel);
511             }
512             // Check if definition set already exist.
513             if (xmlDefinitions == null) {  // create it
514                 //if(log.isDebugEnabled())
515                 //log.debug( "Create xmlDefinitions");
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         // Search file name extension.
547         // take care of Unix files starting with .
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