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  package org.apache.commons.configuration.beanutils;
    18
    19  import java.util.HashMap;
    20  import java.util.Iterator;
    21  import java.util.Map;
    22
    23  import org.apache.commons.configuration.HierarchicalConfiguration;
    24  import org.apache.commons.configuration.PropertyConverter;
    25  import org.apache.commons.configuration.SubnodeConfiguration;
    26  import org.apache.commons.configuration.tree.ConfigurationNode;
    27  import org.apache.commons.configuration.tree.DefaultConfigurationNode;
    28
    29  /***
    30   * <p>
    31   * An implementation of the <code>BeanDeclaration</code> interface that is
    32   * suitable for XML configuration files.
    33   * </p>
    34   * <p>
    35   * This class defines the standard layout of a bean declaration in an XML
    36   * configuration file. Such a declaration must look like the following example
    37   * fragement:
    38   * </p>
    39   * <p>
    40   *
    41   * <pre>
    42   *   ...
    43   *   &lt;personBean config-class=&quot;my.model.PersonBean&quot;
    44   *       lastName=&quot;Doe&quot; firstName=&quot;John&quot;&gt;
    45   *       &lt;address config-class=&quot;my.model.AddressBean&quot;
    46   *           street=&quot;21st street 11&quot; zip=&quot;1234&quot;
    47   *           city=&quot;TestCity&quot;/&gt;
    48   *   &lt;/personBean&gt;
    49   * </pre>
    50   *
    51   * </p>
    52   * <p>
    53   * The bean declaration can be contained in an arbitrary element. Here it is the
    54   * <code>&lt;personBean&gt;</code> element. In the attributes of this element
    55   * there can occur some reserved attributes, which have the following meaning:
    56   * <dl>
    57   * <dt><code>config-class</code></dt>
    58   * <dd>Here the full qualified name of the bean's class can be specified. An
    59   * instance of this class will be created. If this attribute is not specified,
    60   * the bean class must be provided in another way, e.g. as the
    61   * <code>defaultClass</code> passed to the <code>BeanHelper</code> class.</dd>
    62   * <dt><code>config-factory</code></dt>
    63   * <dd>This attribute can contain the name of the
    64   * <code>{@link BeanFactory}</code> that should be used for creating the bean.
    65   * If it is defined, a factory with this name must have been registered at the
    66   * <code>BeanHelper</code> class. If this attribute is missing, the default
    67   * bean factory will be used.</dd>
    68   * <dt><code>config-factoryParam</code></dt>
    69   * <dd>With this attribute a parameter can be specified that will be passed to
    70   * the bean factory. This may be useful for custom bean factories.</dd>
    71   * </dl>
    72   * </p>
    73   * <p>
    74   * All further attributes starting with the <code>config-</code> prefix are
    75   * considered as meta data and will be ignored. All other attributes are treated
    76   * as properties of the bean to be created, i.e. corresponding setter methods of
    77   * the bean will be invoked with the values specified here.
    78   * </p>
    79   * <p>
    80   * If the bean to be created has also some complex properties (which are itself
    81   * beans), their values cannot be initialized from attributes. For this purpose
    82   * nested elements can be used. The example listing shows how an address bean
    83   * can be initialized. This is done in a nested element whose name must match
    84   * the name of a property of the enclosing bean declaration. The format of this
    85   * nested element is exactly the same as for the bean declaration itself, i.e.
    86   * it can have attributes defining meta data or bean properties and even further
    87   * nested elements for complex bean properties.
    88   * </p>
    89   * <p>
    90   * A <code>XMLBeanDeclaration</code> object is usually created from a
    91   * <code>HierarchicalConfiguration</code>. From this it will derive a
    92   * <code>SubnodeConfiguration</code>, which is used to access the needed
    93   * properties. This subnode configuration can be obtained using the
    94   * <code>{@link #getConfiguration()}</code> method. All of its properties can
    95   * be accessed in the usual way. To ensure that the property keys used by this
    96   * class are understood by the configuration, the default expression engine will
    97   * be set.
    98   * </p>
    99   *
    100  * @since 1.3
    101  * @author Oliver Heger
    102  * @version $Id: XMLBeanDeclaration.java 670739 2008-06-23 20:36:37Z oheger $
    103  */
    104 public class XMLBeanDeclaration implements BeanDeclaration
    105 {
    106     /*** Constant for the prefix of reserved attributes. */
    107     public static final String RESERVED_PREFIX = "config-";
    108
    109     /*** Constant for the prefix for reserved attributes.*/
    110     public static final String ATTR_PREFIX = "[@" + RESERVED_PREFIX;
    111
    112     /*** Constant for the bean class attribute. */
    113     public static final String ATTR_BEAN_CLASS = ATTR_PREFIX + "class]";
    114
    115     /*** Constant for the bean factory attribute. */
    116     public static final String ATTR_BEAN_FACTORY = ATTR_PREFIX + "factory]";
    117
    118     /*** Constant for the bean factory parameter attribute. */
    119     public static final String ATTR_FACTORY_PARAM = ATTR_PREFIX
    120             + "factoryParam]";
    121
    122     /*** Stores the associated configuration. */
    123     private SubnodeConfiguration configuration;
    124
    125     /*** Stores the configuration node that contains the bean declaration. */
    126     private ConfigurationNode node;
    127
    128     /***
    129      * Creates a new instance of <code>XMLBeanDeclaration</code> and
    130      * initializes it from the given configuration. The passed in key points to
    131      * the bean declaration.
    132      *
    133      * @param config the configuration
    134      * @param key the key to the bean declaration (this key must point to
    135      * exactly one bean declaration or a <code>IllegalArgumentException</code>
    136      * exception will be thrown)
    137      */
    138     public XMLBeanDeclaration(HierarchicalConfiguration config, String key)
    139     {
    140         this(config, key, false);
    141     }
    142
    143     /***
    144      * Creates a new instance of <code>XMLBeanDeclaration</code> and
    145      * initializes it from the given configuration. The passed in key points to
    146      * the bean declaration. If the key does not exist and the boolean argument
    147      * is <b>true</b>, the declaration is initialized with an empty
    148      * configuration. It is possible to create objects from such an empty
    149      * declaration if a default class is provided. If the key on the other hand
    150      * has multiple values or is undefined and the boolean argument is <b>false</b>,
    151      * a <code>IllegalArgumentException</code> exception will be thrown.
    152      *
    153      * @param config the configuration
    154      * @param key the key to the bean declaration
    155      * @param optional a flag whether this declaration is optional; if set to
    156      * <b>true</b>, no exception will be thrown if the passed in key is
    157      * undefined
    158      */
    159     public XMLBeanDeclaration(HierarchicalConfiguration config, String key,
    160             boolean optional)
    161     {
    162         if (config == null)
    163         {
    164             throw new IllegalArgumentException(
    165                     "Configuration must not be null!");
    166         }
    167
    168         try
    169         {
    170             configuration = config.configurationAt(key);
    171             node = configuration.getRootNode();
    172         }
    173         catch (IllegalArgumentException iex)
    174         {
    175             // If we reach this block, the key does not have exactly one value
    176             if (!optional || config.getMaxIndex(key) > 0)
    177             {
    178                 throw iex;
    179             }
    180             configuration = config.configurationAt(null);
    181             node = new DefaultConfigurationNode();
    182         }
    183         initSubnodeConfiguration(getConfiguration());
    184     }
    185
    186     /***
    187      * Creates a new instance of <code>XMLBeanDeclaration</code> and
    188      * initializes it from the given configuration. The configuration's root
    189      * node must contain the bean declaration.
    190      *
    191      * @param config the configuration with the bean declaration
    192      */
    193     public XMLBeanDeclaration(HierarchicalConfiguration config)
    194     {
    195         this(config, (String) null);
    196     }
    197
    198     /***
    199      * Creates a new instance of <code>XMLBeanDeclaration</code> and
    200      * initializes it with the configuration node that contains the bean
    201      * declaration.
    202      *
    203      * @param config the configuration
    204      * @param node the node with the bean declaration.
    205      */
    206     public XMLBeanDeclaration(SubnodeConfiguration config,
    207             ConfigurationNode node)
    208     {
    209         if (config == null)
    210         {
    211             throw new IllegalArgumentException(
    212                     "Configuration must not be null!");
    213         }
    214         if (node == null)
    215         {
    216             throw new IllegalArgumentException("Node must not be null!");
    217         }
    218
    219         this.node = node;
    220         configuration = config;
    221         initSubnodeConfiguration(config);
    222     }
    223
    224     /***
    225      * Returns the configuration object this bean declaration is based on.
    226      *
    227      * @return the associated configuration
    228      */
    229     public SubnodeConfiguration getConfiguration()
    230     {
    231         return configuration;
    232     }
    233
    234     /***
    235      * Returns the node that contains the bean declaration.
    236      *
    237      * @return the configuration node this bean declaration is based on
    238      */
    239     public ConfigurationNode getNode()
    240     {
    241         return node;
    242     }
    243
    244     /***
    245      * Returns the name of the bean factory. This information is fetched from
    246      * the <code>config-factory</code> attribute.
    247      *
    248      * @return the name of the bean factory
    249      */
    250     public String getBeanFactoryName()
    251     {
    252         return getConfiguration().getString(ATTR_BEAN_FACTORY);
    253     }
    254
    255     /***
    256      * Returns a parameter for the bean factory. This information is fetched
    257      * from the <code>config-factoryParam</code> attribute.
    258      *
    259      * @return the parameter for the bean factory
    260      */
    261     public Object getBeanFactoryParameter()
    262     {
    263         return getConfiguration().getProperty(ATTR_FACTORY_PARAM);
    264     }
    265
    266     /***
    267      * Returns the name of the class of the bean to be created. This information
    268      * is obtained from the <code>config-class</code> attribute.
    269      *
    270      * @return the name of the bean's class
    271      */
    272     public String getBeanClassName()
    273     {
    274         return getConfiguration().getString(ATTR_BEAN_CLASS);
    275     }
    276
    277     /***
    278      * Returns a map with the bean's (simple) properties. The properties are
    279      * collected from all attribute nodes, which are not reserved.
    280      *
    281      * @return a map with the bean's properties
    282      */
    283     public Map getBeanProperties()
    284     {
    285         Map props = new HashMap();
    286         for (Iterator it = getNode().getAttributes().iterator(); it.hasNext();)
    287         {
    288             ConfigurationNode attr = (ConfigurationNode) it.next();
    289             if (!isReservedNode(attr))
    290             {
    291                 props.put(attr.getName(), interpolate(attr .getValue()));
    292             }
    293         }
    294
    295         return props;
    296     }
    297
    298     /***
    299      * Returns a map with bean declarations for the complex properties of the
    300      * bean to be created. These declarations are obtained from the child nodes
    301      * of this declaration's root node.
    302      *
    303      * @return a map with bean declarations for complex properties
    304      */
    305     public Map getNestedBeanDeclarations()
    306     {
    307         Map nested = new HashMap();
    308         for (Iterator it = getNode().getChildren().iterator(); it.hasNext();)
    309         {
    310             ConfigurationNode child = (ConfigurationNode) it.next();
    311             if (!isReservedNode(child))
    312             {
    313                 nested.put(child.getName(), createBeanDeclaration(child));
    314             }
    315         }
    316
    317         return nested;
    318     }
    319
    320     /***
    321      * Performs interpolation for the specified value. This implementation will
    322      * interpolate against the current subnode configuration's parent. If sub
    323      * classes need a different interpolation mechanism, they should override
    324      * this method.
    325      *
    326      * @param value the value that is to be interpolated
    327      * @return the interpolated value
    328      */
    329     protected Object interpolate(Object value)
    330     {
    331         return PropertyConverter.interpolate(value, getConfiguration()
    332                 .getParent());
    333     }
    334
    335     /***
    336      * Checks if the specified node is reserved and thus should be ignored. This
    337      * method is called when the maps for the bean's properties and complex
    338      * properties are collected. It checks whether the given node is an
    339      * attribute node and if its name starts with the reserved prefix.
    340      *
    341      * @param nd the node to be checked
    342      * @return a flag whether this node is reserved (and does not point to a
    343      * property)
    344      */
    345     protected boolean isReservedNode(ConfigurationNode nd)
    346     {
    347         return nd.isAttribute()
    348                 && (nd.getName() == null || nd.getName().startsWith(
    349                         RESERVED_PREFIX));
    350     }
    351
    352     /***
    353      * Creates a new <code>BeanDeclaration</code> for a child node of the
    354      * current configuration node. This method is called by
    355      * <code>getNestedBeanDeclarations()</code> for all complex sub properties
    356      * detected by this method. Derived classes can hook in if they need a
    357      * specific initialization. This base implementation creates a
    358      * <code>XMLBeanDeclaration</code> that is properly initialized from the
    359      * passed in node.
    360      *
    361      * @param node the child node, for which a <code>BeanDeclaration</code> is
    362      *        to be created
    363      * @return the <code>BeanDeclaration</code> for this child node
    364      * @since 1.6
    365      */
    366     protected BeanDeclaration createBeanDeclaration(ConfigurationNode node)
    367     {
    368         return new XMLBeanDeclaration(getConfiguration().configurationAt(
    369                 node.getName()), node);
    370     }
    371
    372     /***
    373      * Initializes the internally managed subnode configuration. This method
    374      * will set some default values for some properties.
    375      *
    376      * @param conf the configuration to initialize
    377      */
    378     private void initSubnodeConfiguration(SubnodeConfiguration conf)
    379     {
    380         conf.setThrowExceptionOnMissing(false);
    381         conf.setExpressionEngine(null);
    382     }
    383 }