View Javadoc

1   /*
2    * Copyright 2001-2004 The Apache Software Foundation.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License")
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *     http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  package org.apache.commons.configuration;
18  
19  import java.io.File;
20  import java.io.IOException;
21  import java.io.InputStream;
22  import java.net.URL;
23  import java.util.Collection;
24  import java.util.Iterator;
25  import java.util.LinkedList;
26  import java.util.Stack;
27  
28  import org.apache.commons.digester.AbstractObjectCreationFactory;
29  import org.apache.commons.digester.Digester;
30  import org.apache.commons.digester.ObjectCreationFactory;
31  import org.apache.commons.digester.xmlrules.DigesterLoader;
32  import org.apache.commons.lang.StringUtils;
33  import org.apache.commons.logging.Log;
34  import org.apache.commons.logging.LogFactory;
35  import org.xml.sax.Attributes;
36  import org.xml.sax.SAXException;
37  
38  /***
39   * Factory class to create a CompositeConfiguration from a .xml file using
40   * Digester.  By default it can handle the Configurations from commons-
41   * configuration.  If you need to add your own, then you can pass in your own
42   * digester rules to use.  It is also namespace aware, by providing a
43   * digesterRuleNamespaceURI.
44   *
45   * @author <a href="mailto:epugh@upstate.com">Eric Pugh</a>
46   * @author <a href="mailto:hps@intermeta.de">Henning P. Schmiedehausen</a>
47   * @author <a href="mailto:oliver.heger@t-online.de">Oliver Heger</a>
48   * @version $Id: ConfigurationFactory.java 155408 2005-02-26 12:56:39Z dirkv $
49   */
50  public class ConfigurationFactory
51  {
52      /*** Constant for the root element in the info file.*/
53      private static final String SEC_ROOT = "configuration/";
54  
55      /*** Constant for the override section.*/
56      private static final String SEC_OVERRIDE = SEC_ROOT + "override/";
57  
58      /*** Constant for the additional section.*/
59      private static final String SEC_ADDITIONAL = SEC_ROOT + "additional/";
60      
61      /*** Constant for the optional attribute.*/
62      private static final String ATTR_OPTIONAL = "optional";
63      
64      /*** Constant for the fileName attribute.*/
65      private static final String ATTR_FILENAME = "fileName";
66  
67      /*** Constant for the default base path (points to actual directory).*/
68      private static final String DEF_BASE_PATH = ".";
69  
70      /*** static logger */
71      private static Log log = LogFactory.getLog(ConfigurationFactory.class);
72  
73      /*** The XML file with the details about the configuration to load */
74      private String configurationFileName;
75  
76      /*** The URL to the XML file with the details about the configuration to load. */
77      private URL configurationURL;
78  
79      /***
80       * The implicit base path for included files. This path is determined by
81       * the configuration to load and used unless no other base path was
82       * explicitely specified.
83       */
84      private String implicitBasePath;
85  
86      /*** The basePath to prefix file paths for file based property files. */
87      private String basePath;
88  
89      /*** URL for xml digester rules file */
90      private URL digesterRules;
91  
92      /*** The digester namespace to parse */
93      private String digesterRuleNamespaceURI;
94  
95      /***
96       * Constructor
97       */
98      public ConfigurationFactory()
99      {
100         setBasePath(DEF_BASE_PATH);
101     }
102     /***
103      * Constructor with ConfigurationFile Name passed
104      *
105      * @param configurationFileName The path to the configuration file
106      */
107     public ConfigurationFactory(String configurationFileName)
108     {
109         this.configurationFileName = configurationFileName;
110     }
111 
112     /***
113      * Return the configuration provided by this factory. It loads the
114      * configuration file which is a XML description of the actual
115      * configurations to load. It can contain various different types of
116      * configuration, currently Properties, XML and JNDI.
117      *
118      * @return A Configuration object
119      * @throws ConfigurationException A generic exception that we had trouble during the
120      * loading of the configuration data.
121      */
122     public Configuration getConfiguration() throws ConfigurationException
123     {
124         Digester digester;
125         InputStream input = null;
126         ConfigurationBuilder builder = new ConfigurationBuilder();
127         URL url = getConfigurationURL();
128         try
129         {
130             if (url == null)
131             {
132                 url = ConfigurationUtils.locate(implicitBasePath, getConfigurationFileName());
133             }
134             input = url.openStream();
135         }
136         catch (Exception e)
137         {
138             log.error("Exception caught opening stream to URL", e);
139             throw new ConfigurationException("Exception caught opening stream to URL", e);
140         }
141 
142         if (getDigesterRules() == null)
143         {
144             digester = new Digester();
145             configureNamespace(digester);
146             initDefaultDigesterRules(digester);
147         }
148         else
149         {
150             digester = DigesterLoader.createDigester(getDigesterRules());
151             // This might already be too late. As far as I can see, the namespace
152             // awareness must be configured before the digester rules are loaded.
153             configureNamespace(digester);
154         }
155         
156         // Configure digester to always enable the context class loader
157         digester.setUseContextClassLoader(true);
158         // Put the composite builder object below all of the other objects.
159         digester.push(builder);
160         // Parse the input stream to configure our mappings
161         try
162         {
163             digester.parse(input);
164             input.close();
165         }
166         catch (SAXException saxe)
167         {
168             log.error("SAX Exception caught", saxe);
169             throw new ConfigurationException("SAX Exception caught", saxe);
170         }
171         catch (IOException ioe)
172         {
173             log.error("IO Exception caught", ioe);
174             throw new ConfigurationException("IO Exception caught", ioe);
175         }
176         return builder.getConfiguration();
177     }
178 
179     /***
180      * Returns the configurationFile.
181      *
182      * @return The name of the configuration file. Can be null.
183      */
184     public String getConfigurationFileName()
185     {
186         return configurationFileName;
187     }
188 
189     /***
190      * Sets the configurationFile.
191      *
192      * @param configurationFileName  The name of the configurationFile to use.
193      */
194     public void setConfigurationFileName(String configurationFileName)
195     {
196         File file = new File(configurationFileName).getAbsoluteFile();
197         this.configurationFileName = file.getName();
198         implicitBasePath = file.getParent();
199     }
200 
201     /***
202      * Returns the URL of the configuration file to be loaded.
203      *
204      * @return the URL of the configuration to load
205      */
206     public URL getConfigurationURL()
207     {
208         return configurationURL;
209     }
210 
211     /***
212      * Sets the URL of the configuration to load. This configuration can be
213      * either specified by a file name or by a URL.
214      *
215      * @param url the URL of the configuration to load
216      */
217     public void setConfigurationURL(URL url)
218     {
219         configurationURL = url;
220         implicitBasePath = url.toString();
221 
222         // The following is a hack caused by the need to keep backwards
223         // compatibility: Per default the base path is set to the current
224         // directory. For loading from a URL this makes no sense. So
225         // unless no specific base path was set we clear it.
226         if (DEF_BASE_PATH.equals(getBasePath()))
227         {
228             setBasePath(null);
229         }
230     }
231 
232     /***
233      * Returns the digesterRules.
234      *
235      * @return URL
236      */
237     public URL getDigesterRules()
238     {
239         return digesterRules;
240     }
241 
242     /***
243      * Sets the digesterRules.
244      *
245      * @param digesterRules The digesterRules to set
246      */
247     public void setDigesterRules(URL digesterRules)
248     {
249         this.digesterRules = digesterRules;
250     }
251 
252     /***
253      * Initializes the parsing rules for the default digester
254      *
255      * This allows the Configuration Factory to understand the
256      * default types: Properties, XML and JNDI. Two special sections are
257      * introduced: <code>&lt;override&gt;</code> and
258      * <code>&lt;additional&gt;</code>.
259      *
260      * @param digester The digester to configure
261      */
262     protected void initDefaultDigesterRules(Digester digester)
263     {
264         initDigesterSectionRules(digester, SEC_ROOT, false);
265         initDigesterSectionRules(digester, SEC_OVERRIDE, false);
266         initDigesterSectionRules(digester, SEC_ADDITIONAL, true);
267     }
268 
269     /***
270      * Sets up digester rules for a specified section of the configuration
271      * info file.
272      *
273      * @param digester the current digester instance
274      * @param matchString specifies the section
275      * @param additional a flag if rules for the additional section are to be
276      * added
277      */
278     protected void initDigesterSectionRules(Digester digester, String matchString, boolean additional)
279     {
280         setupDigesterInstance(
281             digester,
282             matchString + "properties",
283             new FileConfigurationFactory(PropertiesConfiguration.class),
284             null,
285             additional);
286 
287         setupDigesterInstance(
288             digester,
289             matchString + "xml",
290             new FileConfigurationFactory(XMLConfiguration.class),
291             null,
292             additional);
293 
294         setupDigesterInstance(
295             digester,
296             matchString + "hierarchicalXml",
297             new FileConfigurationFactory(XMLConfiguration.class),
298             null,
299             additional);
300 
301         setupDigesterInstance(
302             digester,
303             matchString + "jndi",
304             new JNDIConfigurationFactory(),
305             null,
306             additional);
307 
308         setupDigesterInstance(
309             digester,
310             matchString + "system",
311             new SystemConfigurationFactory(),
312             null,
313             additional);
314     }
315 
316     /***
317      * Sets up digester rules for a configuration to be loaded.
318      *
319      * @param digester the current digester
320      * @param matchString the pattern to match with this rule
321      * @param factory an ObjectCreationFactory instance to use for creating new
322      * objects
323      * @param method the name of a method to be called or <b>null</b> for none
324      * @param additional a flag if rules for the additional section are to be
325      * added
326      */
327     protected void setupDigesterInstance(
328             Digester digester,
329             String matchString,
330             ObjectCreationFactory factory,
331             String method,
332             boolean additional)
333     {
334         if (additional)
335         {
336             setupUnionRules(digester, matchString);
337         }
338         digester.addFactoryCreate(matchString, factory);
339         digester.addSetProperties(matchString);
340         if (method != null)
341         {
342             digester.addCallMethod(matchString, method);
343         }
344         digester.addSetNext(
345             matchString,
346             "addConfiguration",
347             Configuration.class.getName());
348     }
349 
350     /***
351      * Sets up rules for configurations in the additional section.
352      *
353      * @param digester the current digester
354      * @param matchString the pattern to match with this rule
355      */
356     protected void setupUnionRules(Digester digester, String matchString)
357     {
358         digester.addObjectCreate(matchString,
359         AdditionalConfigurationData.class);
360         digester.addSetProperties(matchString);
361         digester.addSetNext(matchString, "addAdditionalConfig",
362         AdditionalConfigurationData.class.getName());
363     }
364 
365     /***
366      * Returns the digesterRuleNamespaceURI.
367      *
368      * @return A String with the digesterRuleNamespaceURI.
369      */
370     public String getDigesterRuleNamespaceURI()
371     {
372         return digesterRuleNamespaceURI;
373     }
374 
375     /***
376      * Sets the digesterRuleNamespaceURI.
377      *
378      * @param digesterRuleNamespaceURI The new digesterRuleNamespaceURI to use
379      */
380     public void setDigesterRuleNamespaceURI(String digesterRuleNamespaceURI)
381     {
382         this.digesterRuleNamespaceURI = digesterRuleNamespaceURI;
383     }
384 
385     /***
386      * Configure the current digester to be namespace aware and to have
387      * a Configuration object to which all of the other configurations
388      * should be added
389      *
390      * @param digester The Digester to configure
391      */
392     private void configureNamespace(Digester digester)
393     {
394         if (getDigesterRuleNamespaceURI() != null)
395         {
396             digester.setNamespaceAware(true);
397             digester.setRuleNamespaceURI(getDigesterRuleNamespaceURI());
398         }
399         else
400         {
401             digester.setNamespaceAware(false);
402         }
403         digester.setValidating(false);
404     }
405 
406     /***
407      * Returns the Base path from which this Configuration Factory operates.
408      * This is never null. If you set the BasePath to null, then a base path
409      * according to the configuration to load is returned.
410      *
411      * @return The base Path of this configuration factory.
412      */
413     public String getBasePath()
414     {
415         String path = StringUtils.isEmpty(basePath) ? implicitBasePath : basePath;
416         return StringUtils.isEmpty(path) ? "." : path;
417     }
418 
419     /***
420      * Sets the basePath for all file references from this Configuration Factory.
421      * Normally a base path need not to be set because it is determined by
422      * the location of the configuration file to load. All relative pathes in
423      * this file are resolved relative to this file. Setting a base path makes
424      * sense if such relative pathes should be otherwise resolved, e.g. if
425      * the configuration file is loaded from the class path and all sub
426      * configurations it refers to are stored in a special config directory.
427      *
428      * @param basePath The new basePath to set.
429      */
430     public void setBasePath(String basePath)
431     {
432         this.basePath = basePath;
433     }
434 
435     /***
436      * A base class for digester factory classes. This base class maintains
437      * a default class for the objects to be created.
438      * There will be sub classes for specific configuration implementations.
439      */
440     public class DigesterConfigurationFactory extends AbstractObjectCreationFactory
441     {
442         /*** Actual class to use. */
443         private Class clazz;
444 
445         /***
446          * Creates a new instance of <code>DigesterConfigurationFactory</code>.
447          *
448          * @param clazz the class which we should instantiate
449          */
450         public DigesterConfigurationFactory(Class clazz)
451         {
452             this.clazz = clazz;
453         }
454 
455         /***
456          * Creates an instance of the specified class.
457          *
458          * @param attribs the attributes (ignored)
459          * @return the new object
460          * @throws Exception if object creation fails
461          */
462         public Object createObject(Attributes attribs) throws Exception
463         {
464             return clazz.newInstance();
465         }
466     }
467 
468     /***
469      * A tiny inner class that allows the Configuration Factory to
470      * let the digester construct FileConfiguration objects
471      * that already have the correct base Path set.
472      *
473      */
474     public class FileConfigurationFactory extends DigesterConfigurationFactory
475     {
476         /***
477          * C'tor
478          *
479          * @param clazz The class which we should instantiate.
480          */
481         public FileConfigurationFactory(Class clazz)
482         {
483             super(clazz);
484         }
485 
486         /***
487          * Gets called by the digester.
488          *
489          * @param attributes the actual attributes
490          * @return the new object
491          * @throws Exception Couldn't instantiate the requested object.
492          */
493         public Object createObject(Attributes attributes) throws Exception
494         {
495             FileConfiguration conf = (FileConfiguration) super.createObject(attributes);
496             conf.setBasePath(getBasePath());
497             conf.setFileName(attributes.getValue(ATTR_FILENAME));
498             try
499             {
500                 log.info("Trying to load configuration " + conf.getFileName());
501                 conf.load();
502             }
503             catch(ConfigurationException cex)
504             {
505                 if(attributes.getValue(ATTR_OPTIONAL) != null
506                         && PropertyConverter.toBoolean(attributes.getValue(ATTR_OPTIONAL)).booleanValue())
507                 {
508                     log.warn("Could not load optional configuration " + conf.getFileName());
509                 }
510                 else
511                 {
512                     throw cex;
513                 }
514             }
515             return conf;
516         }
517     }
518 
519     /***
520      * A tiny inner class that allows the Configuration Factory to
521      * let the digester construct JNDIConfiguration objects.
522      */
523     private class JNDIConfigurationFactory extends DigesterConfigurationFactory
524     {
525         public JNDIConfigurationFactory()
526         {
527             super(JNDIConfiguration.class);
528         }
529     }
530 
531     /***
532      * A tiny inner class that allows the Configuration Factory to
533      * let the digester construct SystemConfiguration objects.
534      */
535     private class SystemConfigurationFactory extends DigesterConfigurationFactory
536     {
537         public SystemConfigurationFactory()
538         {
539             super(SystemConfiguration.class);
540         }
541     }
542 
543     /***
544      * A simple data class that holds all information about a configuration
545      * from the <code>&lt;additional&gt;</code> section.
546      */
547     public static class AdditionalConfigurationData
548     {
549         /*** Stores the configuration object.*/
550         private Configuration configuration;
551 
552         /*** Stores the location of this configuration in the global tree.*/
553         private String at;
554 
555         /***
556          * Returns the value of the <code>at</code> attribute.
557          *
558          * @return the at attribute
559          */
560         public String getAt()
561         {
562             return at;
563         }
564 
565         /***
566          * Sets the value of the <code>at</code> attribute.
567          *
568          * @param string the attribute value
569          */
570         public void setAt(String string)
571         {
572             at = string;
573         }
574 
575         /***
576          * Returns the configuration object.
577          *
578          * @return the configuration
579          */
580         public Configuration getConfiguration()
581         {
582             return configuration;
583         }
584 
585         /***
586          * Sets the configuration object. Note: Normally this method should be
587          * named <code>setConfiguration()</code>, but the name
588          * <code>addConfiguration()</code> is required by some of the digester
589          * rules.
590          *
591          * @param config the configuration to set
592          */
593         public void addConfiguration(Configuration config)
594         {
595             configuration = config;
596         }
597     }
598 
599     /***
600      * An internally used helper class for constructing the composite
601      * configuration object.
602      */
603     public static class ConfigurationBuilder
604     {
605         /*** Stores the composite configuration.*/
606         private CompositeConfiguration config;
607 
608         /*** Stores a collection with the configs from the additional section.*/
609         private Collection additionalConfigs;
610 
611         /***
612          * Creates a new instance of <code>ConfigurationBuilder</code>.
613          */
614         public ConfigurationBuilder()
615         {
616             config = new CompositeConfiguration();
617             additionalConfigs = new LinkedList();
618         }
619 
620         /***
621          * Adds a new configuration to this object. This method is called by
622          * Digester.
623          *
624          * @param conf the configuration to be added
625          */
626         public void addConfiguration(Configuration conf)
627         {
628             config.addConfiguration(conf);
629         }
630 
631         /***
632          * Adds information about an additional configuration. This method is
633          * called by Digester.
634          *
635          * @param data the data about the additional configuration
636          */
637         public void addAdditionalConfig(AdditionalConfigurationData data)
638         {
639             additionalConfigs.add(data);
640         }
641 
642         /***
643          * Returns the final composite configuration.
644          *
645          * @return the final configuration object
646          */
647         public CompositeConfiguration getConfiguration()
648         {
649             if (!additionalConfigs.isEmpty())
650             {
651                 Configuration unionConfig = createAdditionalConfiguration(additionalConfigs);
652                 if (unionConfig != null)
653                 {
654                     addConfiguration(unionConfig);
655                 }
656                 additionalConfigs.clear();
657             }
658 
659             return config;
660         }
661 
662         /***
663          * Creates a configuration object with the union of all properties
664          * defined in the <code>&lt;additional&gt;</code> section. This
665          * implementation returns a <code>HierarchicalConfiguration</code>
666          * object.
667          *
668          * @param configs a collection with
669          * <code>AdditionalConfigurationData</code> objects
670          * @return the union configuration (can be <b>null</b>)
671          */
672         protected Configuration createAdditionalConfiguration(Collection configs)
673         {
674             HierarchicalConfiguration result = new HierarchicalConfiguration();
675 
676             for (Iterator it = configs.iterator(); it.hasNext();)
677             {
678                 AdditionalConfigurationData cdata =
679                 (AdditionalConfigurationData) it.next();
680                 result.addNodes(cdata.getAt(),
681                 createRootNode(cdata).getChildren());
682             }
683 
684             return result.isEmpty() ? null : result;
685         }
686 
687         /***
688          * Creates a configuration root node for the specified configuration.
689          *
690          * @param cdata the configuration data object
691          * @return a root node for this configuration
692          */
693         private HierarchicalConfiguration.Node createRootNode(AdditionalConfigurationData cdata)
694         {
695             if (cdata.getConfiguration() instanceof HierarchicalConfiguration)
696             {
697                 // we can directly use this configuration's root node
698                 return ((HierarchicalConfiguration) cdata.getConfiguration()).getRoot();
699             }
700             else
701             {
702                 // transform configuration to a hierarchical root node
703                 HierarchicalConfigurationNodeConverter conv =
704                 new HierarchicalConfigurationNodeConverter();
705                 conv.process(cdata.getConfiguration());
706                 return conv.getRootNode();
707             }
708         }
709     }
710 
711     /***
712      * A specialized <code>HierarchicalConfigurationConverter</code> class
713      * that creates a <code>HierarchicalConfiguration</code> root node from
714      * an arbitrary <code>Configuration</code> object. This class is used to
715      * add additional configuration objects to the hierarchical configuration
716      * managed by the <code>ConfigurationBuilder</code>.
717      */
718     static class HierarchicalConfigurationNodeConverter extends HierarchicalConfigurationConverter
719     {
720         /*** A stack for constructing the hierarchy.*/
721         private Stack nodes;
722 
723         /*** Stores the root node.*/
724         private HierarchicalConfiguration.Node root;
725 
726         /***
727          * Default constructor.
728          */
729         public HierarchicalConfigurationNodeConverter()
730         {
731             nodes = new Stack();
732             root = new HierarchicalConfiguration.Node();
733             nodes.push(root);
734         }
735 
736         /***
737          * Callback for an element start event. Creates a new node and adds
738          * it to the actual parent.
739          *
740          * @param name the name of the new node
741          * @param value the node's value
742          */
743         protected void elementStart(String name, Object value)
744         {
745             HierarchicalConfiguration.Node parent = (HierarchicalConfiguration.Node) nodes.peek();
746             HierarchicalConfiguration.Node child = new HierarchicalConfiguration.Node(name);
747             if (value != null)
748             {
749                 child.setValue(value);
750             }
751             parent.addChild(child);
752             nodes.push(child);
753         }
754 
755         /***
756          * Callback for an element end event. Clears the stack.
757          *
758          * @param name the name of the element
759          */
760         protected void elementEnd(String name)
761         {
762             nodes.pop();
763         }
764 
765         /***
766          * Returns the constructed root node.
767          *
768          * @return the root node
769          */
770         public HierarchicalConfiguration.Node getRootNode()
771         {
772             return root;
773         }
774     }
775 }