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