View Javadoc

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