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 }