    001    /*
    002     * Licensed to the Apache Software Foundation (ASF) under one or more
    003     * contributor license agreements.  See the NOTICE file distributed with
    004     * this work for additional information regarding copyright ownership.
    005     * The ASF licenses this file to You under the Apache License, Version 2.0
    006     * (the "License"); you may not use this file except in compliance with
    007     * the License.  You may obtain a copy of the License at
    008     *
    009     *     http://www.apache.org/licenses/LICENSE-2.0
    010     *
    011     * Unless required by applicable law or agreed to in writing, software
    012     * distributed under the License is distributed on an "AS IS" BASIS,
    013     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    014     * See the License for the specific language governing permissions and
    015     * limitations under the License.
    016     */
    017    package org.apache.commons.configuration.beanutils;
    018
    019    import java.util.ArrayList;
    020    import java.util.HashMap;
    021    import java.util.Iterator;
    022    import java.util.List;
    023    import java.util.Map;
    024
    025    import org.apache.commons.configuration.ConfigurationRuntimeException;
    026    import org.apache.commons.configuration.HierarchicalConfiguration;
    027    import org.apache.commons.configuration.PropertyConverter;
    028    import org.apache.commons.configuration.SubnodeConfiguration;
    029    import org.apache.commons.configuration.tree.ConfigurationNode;
    030    import org.apache.commons.configuration.tree.DefaultConfigurationNode;
    031
    032    /**
    033     * <p>
    034     * An implementation of the {@code BeanDeclaration} interface that is
    035     * suitable for XML configuration files.
    036     * </p>
    037     * <p>
    038     * This class defines the standard layout of a bean declaration in an XML
    039     * configuration file. Such a declaration must look like the following example
    040     * fragment:
    041     * </p>
    042     * <p>
    043     *
    044     * <pre>
    045     *   ...
    046     *   &lt;personBean config-class=&quot;my.model.PersonBean&quot;
    047     *       lastName=&quot;Doe&quot; firstName=&quot;John&quot;&gt;
    048     *       &lt;address config-class=&quot;my.model.AddressBean&quot;
    049     *           street=&quot;21st street 11&quot; zip=&quot;1234&quot;
    050     *           city=&quot;TestCity&quot;/&gt;
    051     *   &lt;/personBean&gt;
    052     * </pre>
    053     *
    054     * </p>
    055     * <p>
    056     * The bean declaration can be contained in an arbitrary element. Here it is the
    057     * {@code personBean} element. In the attributes of this element
    058     * there can occur some reserved attributes, which have the following meaning:
    059     * <dl>
    060     * <dt>{@code config-class}</dt>
    061     * <dd>Here the full qualified name of the bean's class can be specified. An
    062     * instance of this class will be created. If this attribute is not specified,
    063     * the bean class must be provided in another way, e.g. as the
    064     * {@code defaultClass} passed to the {@code BeanHelper} class.</dd>
    065     * <dt>{@code config-factory}</dt>
    066     * <dd>This attribute can contain the name of the
    067     * {@link BeanFactory} that should be used for creating the bean.
    068     * If it is defined, a factory with this name must have been registered at the
    069     * {@code BeanHelper} class. If this attribute is missing, the default
    070     * bean factory will be used.</dd>
    071     * <dt>{@code config-factoryParam}</dt>
    072     * <dd>With this attribute a parameter can be specified that will be passed to
    073     * the bean factory. This may be useful for custom bean factories.</dd>
    074     * </dl>
    075     * </p>
    076     * <p>
    077     * All further attributes starting with the {@code config-} prefix are
    078     * considered as meta data and will be ignored. All other attributes are treated
    079     * as properties of the bean to be created, i.e. corresponding setter methods of
    080     * the bean will be invoked with the values specified here.
    081     * </p>
    082     * <p>
    083     * If the bean to be created has also some complex properties (which are itself
    084     * beans), their values cannot be initialized from attributes. For this purpose
    085     * nested elements can be used. The example listing shows how an address bean
    086     * can be initialized. This is done in a nested element whose name must match
    087     * the name of a property of the enclosing bean declaration. The format of this
    088     * nested element is exactly the same as for the bean declaration itself, i.e.
    089     * it can have attributes defining meta data or bean properties and even further
    090     * nested elements for complex bean properties.
    091     * </p>
    092     * <p>
    093     * A {@code XMLBeanDeclaration} object is usually created from a
    094     * {@code HierarchicalConfiguration}. From this it will derive a
    095     * {@code SubnodeConfiguration}, which is used to access the needed
    096     * properties. This subnode configuration can be obtained using the
    097     * {@link #getConfiguration()} method. All of its properties can
    098     * be accessed in the usual way. To ensure that the property keys used by this
    099     * class are understood by the configuration, the default expression engine will
    100     * be set.
    101     * </p>
    102     *
    103     * @since 1.3
    104     * @author <a
    105     * href="http://commons.apache.org/configuration/team-list.html">Commons
    106     * Configuration team</a>
    107     * @version $Id: XMLBeanDeclaration.java 1208761 2011-11-30 20:39:47Z oheger $
    108     */
    109    public class XMLBeanDeclaration implements BeanDeclaration
    110    {
    111        /** Constant for the prefix of reserved attributes. */
    112        public static final String RESERVED_PREFIX = "config-";
    113
    114        /** Constant for the prefix for reserved attributes.*/
    115        public static final String ATTR_PREFIX = "[@" + RESERVED_PREFIX;
    116
    117        /** Constant for the bean class attribute. */
    118        public static final String ATTR_BEAN_CLASS = ATTR_PREFIX + "class]";
    119
    120        /** Constant for the bean factory attribute. */
    121        public static final String ATTR_BEAN_FACTORY = ATTR_PREFIX + "factory]";
    122
    123        /** Constant for the bean factory parameter attribute. */
    124        public static final String ATTR_FACTORY_PARAM = ATTR_PREFIX
    125                + "factoryParam]";
    126
    127        /** Stores the associated configuration. */
    128        private SubnodeConfiguration configuration;
    129
    130        /** Stores the configuration node that contains the bean declaration. */
    131        private ConfigurationNode node;
    132
    133        /**
    134         * Creates a new instance of {@code XMLBeanDeclaration} and
    135         * initializes it from the given configuration. The passed in key points to
    136         * the bean declaration.
    137         *
    138         * @param config the configuration
    139         * @param key the key to the bean declaration (this key must point to
    140         * exactly one bean declaration or a {@code IllegalArgumentException}
    141         * exception will be thrown)
    142         */
    143        public XMLBeanDeclaration(HierarchicalConfiguration config, String key)
    144        {
    145            this(config, key, false);
    146        }
    147
    148        /**
    149         * Creates a new instance of {@code XMLBeanDeclaration} and
    150         * initializes it from the given configuration. The passed in key points to
    151         * the bean declaration. If the key does not exist and the boolean argument
    152         * is <b>true</b>, the declaration is initialized with an empty
    153         * configuration. It is possible to create objects from such an empty
    154         * declaration if a default class is provided. If the key on the other hand
    155         * has multiple values or is undefined and the boolean argument is <b>false</b>,
    156         * a {@code IllegalArgumentException} exception will be thrown.
    157         *
    158         * @param config the configuration
    159         * @param key the key to the bean declaration
    160         * @param optional a flag whether this declaration is optional; if set to
    161         * <b>true</b>, no exception will be thrown if the passed in key is
    162         * undefined
    163         */
    164        public XMLBeanDeclaration(HierarchicalConfiguration config, String key,
    165                boolean optional)
    166        {
    167            if (config == null)
    168            {
    169                throw new IllegalArgumentException(
    170                        "Configuration must not be null!");
    171            }
    172
    173            try
    174            {
    175                configuration = config.configurationAt(key);
    176                node = configuration.getRootNode();
    177            }
    178            catch (IllegalArgumentException iex)
    179            {
    180                // If we reach this block, the key does not have exactly one value
    181                if (!optional || config.getMaxIndex(key) > 0)
    182                {
    183                    throw iex;
    184                }
    185                configuration = config.configurationAt(null);
    186                node = new DefaultConfigurationNode();
    187            }
    188            initSubnodeConfiguration(getConfiguration());
    189        }
    190
    191        /**
    192         * Creates a new instance of {@code XMLBeanDeclaration} and
    193         * initializes it from the given configuration. The configuration's root
    194         * node must contain the bean declaration.
    195         *
    196         * @param config the configuration with the bean declaration
    197         */
    198        public XMLBeanDeclaration(HierarchicalConfiguration config)
    199        {
    200            this(config, (String) null);
    201        }
    202
    203        /**
    204         * Creates a new instance of {@code XMLBeanDeclaration} and
    205         * initializes it with the configuration node that contains the bean
    206         * declaration.
    207         *
    208         * @param config the configuration
    209         * @param node the node with the bean declaration.
    210         */
    211        public XMLBeanDeclaration(SubnodeConfiguration config,
    212                ConfigurationNode node)
    213        {
    214            if (config == null)
    215            {
    216                throw new IllegalArgumentException(
    217                        "Configuration must not be null!");
    218            }
    219            if (node == null)
    220            {
    221                throw new IllegalArgumentException("Node must not be null!");
    222            }
    223
    224            this.node = node;
    225            configuration = config;
    226            initSubnodeConfiguration(config);
    227        }
    228
    229        /**
    230         * Returns the configuration object this bean declaration is based on.
    231         *
    232         * @return the associated configuration
    233         */
    234        public SubnodeConfiguration getConfiguration()
    235        {
    236            return configuration;
    237        }
    238
    239        /**
    240         * Returns the node that contains the bean declaration.
    241         *
    242         * @return the configuration node this bean declaration is based on
    243         */
    244        public ConfigurationNode getNode()
    245        {
    246            return node;
    247        }
    248
    249        /**
    250         * Returns the name of the bean factory. This information is fetched from
    251         * the {@code config-factory} attribute.
    252         *
    253         * @return the name of the bean factory
    254         */
    255        public String getBeanFactoryName()
    256        {
    257            return getConfiguration().getString(ATTR_BEAN_FACTORY);
    258        }
    259
    260        /**
    261         * Returns a parameter for the bean factory. This information is fetched
    262         * from the {@code config-factoryParam} attribute.
    263         *
    264         * @return the parameter for the bean factory
    265         */
    266        public Object getBeanFactoryParameter()
    267        {
    268            return getConfiguration().getProperty(ATTR_FACTORY_PARAM);
    269        }
    270
    271        /**
    272         * Returns the name of the class of the bean to be created. This information
    273         * is obtained from the {@code config-class} attribute.
    274         *
    275         * @return the name of the bean's class
    276         */
    277        public String getBeanClassName()
    278        {
    279            return getConfiguration().getString(ATTR_BEAN_CLASS);
    280        }
    281
    282        /**
    283         * Returns a map with the bean's (simple) properties. The properties are
    284         * collected from all attribute nodes, which are not reserved.
    285         *
    286         * @return a map with the bean's properties
    287         */
    288        public Map<String, Object> getBeanProperties()
    289        {
    290            Map<String, Object> props = new HashMap<String, Object>();
    291            for (ConfigurationNode attr : getNode().getAttributes())
    292            {
    293                if (!isReservedNode(attr))
    294                {
    295                    props.put(attr.getName(), interpolate(attr .getValue()));
    296                }
    297            }
    298
    299            return props;
    300        }
    301
    302        /**
    303         * Returns a map with bean declarations for the complex properties of the
    304         * bean to be created. These declarations are obtained from the child nodes
    305         * of this declaration's root node.
    306         *
    307         * @return a map with bean declarations for complex properties
    308         */
    309        public Map<String, Object> getNestedBeanDeclarations()
    310        {
    311            Map<String, Object> nested = new HashMap<String, Object>();
    312            for (ConfigurationNode child : getNode().getChildren())
    313            {
    314                if (!isReservedNode(child))
    315                {
    316                    if (nested.containsKey(child.getName()))
    317                    {
    318                        Object obj = nested.get(child.getName());
    319                        List<BeanDeclaration> list;
    320                        if (obj instanceof List)
    321                        {
    322                            // Safe because we created the lists ourselves.
    323                            @SuppressWarnings("unchecked")
    324                            List<BeanDeclaration> tmpList = (List<BeanDeclaration>) obj;
    325                            list = tmpList;
    326                        }
    327                        else
    328                        {
    329                            list = new ArrayList<BeanDeclaration>();
    330                            list.add((BeanDeclaration) obj);
    331                            nested.put(child.getName(), list);
    332                        }
    333                        list.add(createBeanDeclaration(child));
    334                    }
    335                    else
    336                    {
    337                        nested.put(child.getName(), createBeanDeclaration(child));
    338                    }
    339                }
    340            }
    341
    342            return nested;
    343        }
    344
    345        /**
    346         * Performs interpolation for the specified value. This implementation will
    347         * interpolate against the current subnode configuration's parent. If sub
    348         * classes need a different interpolation mechanism, they should override
    349         * this method.
    350         *
    351         * @param value the value that is to be interpolated
    352         * @return the interpolated value
    353         */
    354        protected Object interpolate(Object value)
    355        {
    356            return PropertyConverter.interpolate(value, getConfiguration()
    357                    .getParent());
    358        }
    359
    360        /**
    361         * Checks if the specified node is reserved and thus should be ignored. This
    362         * method is called when the maps for the bean's properties and complex
    363         * properties are collected. It checks whether the given node is an
    364         * attribute node and if its name starts with the reserved prefix.
    365         *
    366         * @param nd the node to be checked
    367         * @return a flag whether this node is reserved (and does not point to a
    368         * property)
    369         */
    370        protected boolean isReservedNode(ConfigurationNode nd)
    371        {
    372            return nd.isAttribute()
    373                    && (nd.getName() == null || nd.getName().startsWith(
    374                            RESERVED_PREFIX));
    375        }
    376
    377        /**
    378         * Creates a new {@code BeanDeclaration} for a child node of the
    379         * current configuration node. This method is called by
    380         * {@code getNestedBeanDeclarations()} for all complex sub properties
    381         * detected by this method. Derived classes can hook in if they need a
    382         * specific initialization. This base implementation creates a
    383         * {@code XMLBeanDeclaration} that is properly initialized from the
    384         * passed in node.
    385         *
    386         * @param node the child node, for which a {@code BeanDeclaration} is
    387         *        to be created
    388         * @return the {@code BeanDeclaration} for this child node
    389         * @since 1.6
    390         */
    391        protected BeanDeclaration createBeanDeclaration(ConfigurationNode node)
    392        {
    393            List<HierarchicalConfiguration> list = getConfiguration().configurationsAt(node.getName());
    394            if (list.size() == 1)
    395            {
    396                return new XMLBeanDeclaration((SubnodeConfiguration) list.get(0), node);
    397            }
    398            else
    399            {
    400                Iterator<HierarchicalConfiguration> iter = list.iterator();
    401                while (iter.hasNext())
    402                {
    403                    SubnodeConfiguration config = (SubnodeConfiguration) iter.next();
    404                    if (config.getRootNode().equals(node))
    405                    {
    406                        return new XMLBeanDeclaration(config, node);
    407                    }
    408                }
    409                throw new ConfigurationRuntimeException("Unable to match node for " + node.getName());
    410            }
    411        }
    412
    413        /**
    414         * Initializes the internally managed subnode configuration. This method
    415         * will set some default values for some properties.
    416         *
    417         * @param conf the configuration to initialize
    418         */
    419        private void initSubnodeConfiguration(SubnodeConfiguration conf)
    420        {
    421            conf.setThrowExceptionOnMissing(false);
    422            conf.setExpressionEngine(null);
    423        }
    424    }