Source code
Description: source: package: org.apache.commons.configuration2.beanutils, class: XMLBeanDeclaration

    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 *     https://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 */
    017package org.apache.commons.configuration2.beanutils;
    018
    019import java.util.ArrayList;
    020import java.util.Collection;
    021import java.util.HashMap;
    022import java.util.LinkedList;
    023import java.util.List;
    024import java.util.Map;
    025import java.util.Set;
    026import java.util.function.Function;
    027import java.util.stream.Collectors;
    028
    029import org.apache.commons.configuration2.BaseHierarchicalConfiguration;
    030import org.apache.commons.configuration2.HierarchicalConfiguration;
    031import org.apache.commons.configuration2.ex.ConfigurationRuntimeException;
    032import org.apache.commons.configuration2.interpol.ConfigurationInterpolator;
    033import org.apache.commons.configuration2.tree.NodeHandler;
    034import org.apache.commons.lang3.StringUtils;
    035
    036/**
    037 * <p>
    038 * An implementation of the {@code BeanDeclaration} interface that is suitable for XML configuration files.
    039 * </p>
    040 * <p>
    041 * This class defines the standard layout of a bean declaration in an XML configuration file. Such a declaration must
    042 * look like the following example fragment:
    043 * </p>
    044 *
    045 * <pre>
    046 *   ...
    047 *   &lt;personBean config-class=&quot;my.model.PersonBean&quot;
    048 *       lastName=&quot;Doe&quot; firstName=&quot;John&quot;&gt;
    049 *       &lt;config-constrarg config-value=&quot;ID03493&quot; config-type=&quot;java.lang.String&quot;/&gt;
    050 *       &lt;address config-class=&quot;my.model.AddressBean&quot;
    051 *           street=&quot;21st street 11&quot; zip=&quot;1234&quot;
    052 *           city=&quot;TestCity&quot;/&gt;
    053 *   &lt;/personBean&gt;
    054 * </pre>
    055 *
    056 * <p>
    057 * The bean declaration can be contained in an arbitrary element. Here it is the {@code personBean} element. In the
    058 * attributes of this element there can occur some reserved attributes, which have the following meaning:
    059 * </p>
    060 * <dl>
    061 * <dt>{@code config-class}</dt>
    062 * <dd>Here the full qualified name of the bean's class can be specified. An instance of this class will be created. If
    063 * this attribute is not specified, the bean class must be provided in another way, for example as the {@code defaultClass}
    064 * passed to the {@code BeanHelper} class.</dd>
    065 * <dt>{@code config-factory}</dt>
    066 * <dd>This attribute can contain the name of the {@link BeanFactory} that should be used for creating the bean. If it
    067 * is defined, a factory with this name must have been registered at the {@code BeanHelper} class. If this attribute is
    068 * missing, the default bean factory will be used.</dd>
    069 * <dt>{@code config-factoryParam}</dt>
    070 * <dd>With this attribute a parameter can be specified that will be passed to the bean factory. This may be useful for
    071 * custom bean factories.</dd>
    072 * </dl>
    073 * <p>
    074 * All further attributes starting with the {@code config-} prefix are considered as meta data and will be ignored. All
    075 * other attributes are treated as properties of the bean to be created, i.e. corresponding setter methods of the bean
    076 * will be invoked with the values specified here.
    077 * </p>
    078 * <p>
    079 * If the bean to be created has also some complex properties (which are itself beans), their values cannot be
    080 * initialized from attributes. For this purpose nested elements can be used. The example listing shows how an address
    081 * bean can be initialized. This is done in a nested element whose name must match the name of a property of the
    082 * enclosing bean declaration. The format of this nested element is exactly the same as for the bean declaration itself,
    083 * i.e. it can have attributes defining meta data or bean properties and even further nested elements for complex bean
    084 * properties.
    085 * </p>
    086 * <p>
    087 * If the bean should be created using a specific constructor, the constructor arguments have to be specified. This is
    088 * done by an arbitrary number of nested {@code <config-constrarg>} elements. Each element can either have the
    089 * {@code config-value} attribute - then it defines a simple value - or must be again a bean declaration (conforming to
    090 * the format defined here) defining the complex value of this constructor argument.
    091 * </p>
    092 * <p>
    093 * A {@code XMLBeanDeclaration} object is usually created from a {@code HierarchicalConfiguration}. From this it will
    094 * derive a {@code SubnodeConfiguration}, which is used to access the needed properties. This subnode configuration can
    095 * be obtained using the {@link #getConfiguration()} method. All of its properties can be accessed in the usual way. To
    096 * ensure that the property keys used by this class are understood by the configuration, the default expression engine
    097 * will be set.
    098 * </p>
    099 *
    100 * @since 1.3
    101 */
    102public class XMLBeanDeclaration implements BeanDeclaration {
    103
    104    /**
    105     * An internal helper class which wraps the node with the bean declaration and the corresponding node handler.
    106     *
    107     * @param <T> the type of the node
    108     */
    109    static class NodeData<T> {
    110
    111        /** The wrapped node. */
    112        private final T node;
    113
    114        /** The node handler for interacting with this node. */
    115        private final NodeHandler<T> nodeHandler;
    116
    117        /**
    118         * Constructs a new instance of {@code NodeData}.
    119         *
    120         * @param node the node
    121         * @param nodeHandler the node handler
    122         */
    123        NodeData(final T node, final NodeHandler<T> nodeHandler) {
    124            this.node = node;
    125            this.nodeHandler = nodeHandler;
    126        }
    127
    128        /**
    129         * Returns the unescaped name of the node stored in this data object. This method handles the case that the node name
    130         * may contain reserved characters with a special meaning for the current expression engine. In this case, the
    131         * characters affected have to be escaped accordingly.
    132         *
    133         * @param config the configuration
    134         * @return the escaped node name
    135         */
    136        String escapedNodeName(final HierarchicalConfiguration<?> config) {
    137            return config.getExpressionEngine().nodeKey(node, StringUtils.EMPTY, nodeHandler);
    138        }
    139
    140        /**
    141         * Gets the value of the attribute with the given name of the wrapped node.
    142         *
    143         * @param key the key of the attribute
    144         * @return the value of this attribute
    145         */
    146        Object getAttribute(final String key) {
    147            return nodeHandler.getAttributeValue(node, key);
    148        }
    149
    150        /**
    151         * Gets a set with the names of the attributes of the wrapped node.
    152         *
    153         * @return the attribute names of this node
    154         */
    155        Set<String> getAttributes() {
    156            return nodeHandler.getAttributes(node);
    157        }
    158
    159        /**
    160         * Gets a list with the children of the wrapped node, again wrapped into {@code NodeData} objects.
    161         *
    162         * @return a list with the children
    163         */
    164        List<NodeData<T>> getChildren() {
    165            return wrapInNodeData(nodeHandler.getChildren(node));
    166        }
    167
    168        /**
    169         * Gets a list with the children of the wrapped node with the given name, again wrapped into {@code NodeData}
    170         * objects.
    171         *
    172         * @param name the name of the desired child nodes
    173         * @return a list with the children with this name
    174         */
    175        List<NodeData<T>> getChildren(final String name) {
    176            return wrapInNodeData(nodeHandler.getChildren(node, name));
    177        }
    178
    179        /**
    180         * Returns a flag whether the wrapped node is the root node of the passed in configuration.
    181         *
    182         * @param config the configuration
    183         * @return a flag whether this node is the configuration's root node
    184         */
    185        boolean matchesConfigRootNode(final HierarchicalConfiguration<?> config) {
    186            return config.getNodeModel().getNodeHandler().getRootNode().equals(node);
    187        }
    188
    189        /**
    190         * Returns the name of the wrapped node.
    191         *
    192         * @return the node name
    193         */
    194        String nodeName() {
    195            return nodeHandler.nodeName(node);
    196        }
    197
    198        /**
    199         * Wraps the passed in list of nodes in {@code NodeData} objects.
    200         *
    201         * @param nodes the list with nodes
    202         * @return the wrapped nodes
    203         */
    204        List<NodeData<T>> wrapInNodeData(final List<T> nodes) {
    205            return nodes.stream().map(n -> new NodeData<>(n, nodeHandler)).collect(Collectors.toList());
    206        }
    207    }
    208
    209    /** Constant for the prefix of reserved attributes. */
    210    public static final String RESERVED_PREFIX = "config-";
    211
    212    /** Constant for the prefix for reserved attributes. */
    213    public static final String ATTR_PREFIX = "[@" + RESERVED_PREFIX;
    214
    215    /** Constant for the bean class attribute. */
    216    public static final String ATTR_BEAN_CLASS = ATTR_PREFIX + "class]";
    217
    218    /** Constant for the bean factory attribute. */
    219    public static final String ATTR_BEAN_FACTORY = ATTR_PREFIX + "factory]";
    220
    221    /** Constant for the bean factory parameter attribute. */
    222    public static final String ATTR_FACTORY_PARAM = ATTR_PREFIX + "factoryParam]";
    223
    224    /** Constant for the name of the bean class attribute. */
    225    private static final String ATTR_BEAN_CLASS_NAME = RESERVED_PREFIX + "class";
    226
    227    /** Constant for the name of the element for constructor arguments. */
    228    private static final String ELEM_CTOR_ARG = RESERVED_PREFIX + "constrarg";
    229
    230    /**
    231     * Constant for the name of the attribute with the value of a constructor argument.
    232     */
    233    private static final String ATTR_CTOR_VALUE = RESERVED_PREFIX + "value";
    234
    235    /**
    236     * Constant for the name of the attribute with the data type of a constructor argument.
    237     */
    238    private static final String ATTR_CTOR_TYPE = RESERVED_PREFIX + "type";
    239
    240    /**
    241     * Creates a {@code NodeData} object from the root node of the given configuration.
    242     *
    243     * @param config the configuration
    244     * @param <T> the type of the nodes
    245     * @return the {@code NodeData} object
    246     */
    247    private static <T> NodeData<T> createNodeDataFromConfiguration(final HierarchicalConfiguration<T> config) {
    248        final NodeHandler<T> handler = config.getNodeModel().getNodeHandler();
    249        return new NodeData<>(handler.getRootNode(), handler);
    250    }
    251
    252    /**
    253     * Tests whether the constructor argument represented by the given configuration node is a bean declaration.
    254     *
    255     * @param nodeData the configuration node in question
    256     * @return a flag whether this constructor argument is a bean declaration
    257     */
    258    private static boolean isBeanDeclarationArgument(final NodeData<?> nodeData) {
    259        return !nodeData.getAttributes().contains(ATTR_BEAN_CLASS_NAME);
    260    }
    261
    262    /** Stores the associated configuration. */
    263    private final HierarchicalConfiguration<?> configuration;
    264
    265    /** Stores the configuration node that contains the bean declaration. */
    266    private final NodeData<?> nodeData;
    267
    268    /** The name of the default bean class. */
    269    private final String defaultBeanClassName;
    270
    271    /**
    272     * Constructs a new instance of {@code XMLBeanDeclaration} and initializes it with the configuration node that contains the
    273     * bean declaration. This constructor is used internally.
    274     *
    275     * @param config the configuration
    276     * @param node the node with the bean declaration.
    277     */
    278    XMLBeanDeclaration(final HierarchicalConfiguration<?> config, final NodeData<?> node) {
    279        this.nodeData = node;
    280        configuration = config;
    281        defaultBeanClassName = null;
    282        initSubnodeConfiguration(config);
    283    }
    284
    285    /**
    286     * Constructs a new instance of {@code XMLBeanDeclaration} and initializes it from the given configuration. The
    287     * configuration's root node must contain the bean declaration.
    288     *
    289     * @param config the configuration with the bean declaration
    290     * @param <T> the node type of the configuration
    291     */
    292    public <T> XMLBeanDeclaration(final HierarchicalConfiguration<T> config) {
    293        this(config, (String) null);
    294    }
    295
    296    /**
    297     * Constructs a new instance of {@code XMLBeanDeclaration} and initializes it from the given configuration. The passed in
    298     * key points to the bean declaration.
    299     *
    300     * @param config the configuration (must not be <strong>null</strong>)
    301     * @param key the key to the bean declaration (this key must point to exactly one bean declaration or a
    302     *        {@code IllegalArgumentException} exception will be thrown)
    303     * @param <T> the node type of the configuration
    304     * @throws IllegalArgumentException if required information is missing to construct the bean declaration
    305     */
    306    public <T> XMLBeanDeclaration(final HierarchicalConfiguration<T> config, final String key) {
    307        this(config, key, false);
    308    }
    309
    310    /**
    311     * Constructs a new instance of {@code XMLBeanDeclaration} and initializes it from the given configuration supporting
    312     * optional declarations.
    313     *
    314     * @param config the configuration (must not be <strong>null</strong>)
    315     * @param key the key to the bean declaration
    316     * @param optional a flag whether this declaration is optional; if set to <strong>true</strong>, no exception will be thrown if
    317     *        the passed in key is undefined
    318     * @param <T> the node type of the configuration
    319     * @throws IllegalArgumentException if required information is missing to construct the bean declaration
    320     */
    321    public <T> XMLBeanDeclaration(final HierarchicalConfiguration<T> config, final String key, final boolean optional) {
    322        this(config, key, optional, null);
    323    }
    324
    325    /**
    326     * Constructs a new instance of {@code XMLBeanDeclaration} and initializes it from the given configuration supporting
    327     * optional declarations and a default bean class name. The passed in key points to the bean declaration. If the key
    328     * does not exist and the boolean argument is <strong>true</strong>, the declaration is initialized with an empty configuration.
    329     * It is possible to create objects from such an empty declaration if a default class is provided. If the key on the
    330     * other hand has multiple values or is undefined and the boolean argument is <strong>false</strong>, a
    331     * {@code IllegalArgumentException} exception will be thrown. It is possible to set a default bean class name; this name
    332     * is used if the configuration does not contain a bean class.
    333     *
    334     * @param config the configuration (must not be <strong>null</strong>)
    335     * @param key the key to the bean declaration
    336     * @param optional a flag whether this declaration is optional; if set to <strong>true</strong>, no exception will be thrown if
    337     *        the passed in key is undefined
    338     * @param defBeanClsName a default bean class name
    339     * @param <T> the node type of the configuration
    340     * @throws IllegalArgumentException if required information is missing to construct the bean declaration
    341     * @since 2.0
    342     */
    343    public <T> XMLBeanDeclaration(final HierarchicalConfiguration<T> config, final String key, final boolean optional, final String defBeanClsName) {
    344        if (config == null) {
    345            throw new IllegalArgumentException("Configuration must not be null.");
    346        }
    347
    348        HierarchicalConfiguration<?> tmpconfiguration;
    349        try {
    350            tmpconfiguration = config.configurationAt(key);
    351        } catch (final ConfigurationRuntimeException iex) {
    352            // If we reach this block, the key does not have exactly one value
    353            if (!optional || config.getMaxIndex(key) > 0) {
    354                throw iex;
    355            }
    356            tmpconfiguration = new BaseHierarchicalConfiguration();
    357        }
    358        this.nodeData = createNodeDataFromConfiguration(tmpconfiguration);
    359        this.configuration = tmpconfiguration;
    360        defaultBeanClassName = defBeanClsName;
    361        initSubnodeConfiguration(getConfiguration());
    362    }
    363
    364    /**
    365     * Creates a new {@code BeanDeclaration} for a child node of the current configuration node. This method is called by
    366     * {@code getNestedBeanDeclarations()} for all complex sub properties detected by this method. Derived classes can hook
    367     * in if they need a specific initialization. This base implementation creates a {@code XMLBeanDeclaration} that is
    368     * properly initialized from the passed in node.
    369     *
    370     * @param nodeData the child node, for which a {@code BeanDeclaration} is to be created
    371     * @return the {@code BeanDeclaration} for this child node
    372     */
    373    BeanDeclaration createBeanDeclaration(final NodeData<?> nodeData) {
    374        for (final HierarchicalConfiguration<?> config : getConfiguration().configurationsAt(nodeData.escapedNodeName(getConfiguration()))) {
    375            if (nodeData.matchesConfigRootNode(config)) {
    376                return new XMLBeanDeclaration(config, nodeData);
    377            }
    378        }
    379        throw new ConfigurationRuntimeException("Unable to match node for " + nodeData.nodeName());
    380    }
    381
    382    /**
    383     * Creates a {@code ConstructorArg} object for the specified configuration node.
    384     *
    385     * @param child the configuration node
    386     * @return the corresponding {@code ConstructorArg} object
    387     */
    388    private ConstructorArg createConstructorArg(final NodeData<?> child) {
    389        final String type = getAttribute(child, ATTR_CTOR_TYPE);
    390        if (isBeanDeclarationArgument(child)) {
    391            return ConstructorArg.forValue(getAttribute(child, ATTR_CTOR_VALUE), type);
    392        }
    393        return ConstructorArg.forBeanDeclaration(createBeanDeclaration(child), type);
    394    }
    395
    396    /**
    397     * Gets an attribute of a configuration node. This method also takes interpolation into account.
    398     *
    399     * @param nodeData the node
    400     * @param attribute the name of the attribute
    401     * @return the string value of this attribute (can be <strong>null</strong>)
    402     */
    403    private String getAttribute(final NodeData<?> nodeData, final String attribute) {
    404        final Object value = nodeData.getAttribute(attribute);
    405        return value == null ? null : String.valueOf(interpolate(value));
    406    }
    407
    408    /**
    409     * Gets a set with the names of the attributes of the configuration node holding the data of this bean declaration.
    410     *
    411     * @return the attribute names of the underlying configuration node
    412     */
    413    protected Set<String> getAttributeNames() {
    414        return getNode().getAttributes();
    415    }
    416
    417    /**
    418     * Gets the name of the class of the bean to be created. This information is obtained from the {@code config-class}
    419     * attribute.
    420     *
    421     * @return the name of the bean's class
    422     */
    423    @Override
    424    public String getBeanClassName() {
    425        return getConfiguration().getString(ATTR_BEAN_CLASS, getDefaultBeanClassName());
    426    }
    427
    428    /**
    429     * Gets the name of the bean factory. This information is fetched from the {@code config-factory} attribute.
    430     *
    431     * @return the name of the bean factory
    432     */
    433    @Override
    434    public String getBeanFactoryName() {
    435        return getConfiguration().getString(ATTR_BEAN_FACTORY, null);
    436    }
    437
    438    /**
    439     * Gets a parameter for the bean factory. This information is fetched from the {@code config-factoryParam} attribute.
    440     *
    441     * @return the parameter for the bean factory
    442     */
    443    @Override
    444    public Object getBeanFactoryParameter() {
    445        return getConfiguration().getProperty(ATTR_FACTORY_PARAM);
    446    }
    447
    448    /**
    449     * Gets a map with the bean's (simple) properties. The properties are collected from all attribute nodes, which are
    450     * not reserved.
    451     *
    452     * @return a map with the bean's properties
    453     */
    454    @Override
    455    public Map<String, Object> getBeanProperties() {
    456        return getAttributeNames().stream().filter(e -> !isReservedAttributeName(e))
    457            .collect(Collectors.toMap(Function.identity(), e -> interpolate(getNode().getAttribute(e))));
    458    }
    459
    460    /**
    461     * Gets the configuration object this bean declaration is based on.
    462     *
    463     * @return the associated configuration
    464     */
    465    public HierarchicalConfiguration<?> getConfiguration() {
    466        return configuration;
    467    }
    468
    469    /**
    470     * {@inheritDoc} This implementation processes all child nodes with the name {@code config-constrarg}. If such a node
    471     * has a {@code config-class} attribute, it is considered a nested bean declaration; otherwise it is interpreted as a
    472     * simple value. If no nested constructor argument declarations are found, result is an empty collection.
    473     */
    474    @Override
    475    public Collection<ConstructorArg> getConstructorArgs() {
    476        return getNode().getChildren(ELEM_CTOR_ARG).stream().map(this::createConstructorArg).collect(Collectors.toCollection(LinkedList::new));
    477    }
    478
    479    /**
    480     * Gets the name of the default bean class. This class is used if no bean class is specified in the configuration. It
    481     * may be <strong>null</strong> if no default class was set.
    482     *
    483     * @return the default bean class name
    484     * @since 2.0
    485     */
    486    public String getDefaultBeanClassName() {
    487        return defaultBeanClassName;
    488    }
    489
    490    /**
    491     * Gets a map with bean declarations for the complex properties of the bean to be created. These declarations are
    492     * obtained from the child nodes of this declaration's root node.
    493     *
    494     * @return a map with bean declarations for complex properties
    495     */
    496    @Override
    497    public Map<String, Object> getNestedBeanDeclarations() {
    498        final Map<String, Object> nested = new HashMap<>();
    499        getNode().getChildren().forEach(child -> {
    500            if (!isReservedChildName(child.nodeName())) {
    501                final Object obj = nested.get(child.nodeName());
    502                if (obj != null) {
    503                    final List<BeanDeclaration> list;
    504                    if (obj instanceof List) {
    505                        // Safe because we created the lists ourselves.
    506                        @SuppressWarnings("unchecked")
    507                        final List<BeanDeclaration> tmpList = (List<BeanDeclaration>) obj;
    508                        list = tmpList;
    509                    } else {
    510                        list = new ArrayList<>();
    511                        list.add((BeanDeclaration) obj);
    512                        nested.put(child.nodeName(), list);
    513                    }
    514                    list.add(createBeanDeclaration(child));
    515                } else {
    516                    nested.put(child.nodeName(), createBeanDeclaration(child));
    517                }
    518            }
    519        });
    520        return nested;
    521    }
    522
    523    /**
    524     * Gets the data about the associated node.
    525     *
    526     * @return the node with the bean declaration
    527     */
    528    NodeData<?> getNode() {
    529        return nodeData;
    530    }
    531
    532    /**
    533     * Initializes the internally managed sub configuration. This method will set some default values for some properties.
    534     *
    535     * @param conf the configuration to initialize
    536     */
    537    private void initSubnodeConfiguration(final HierarchicalConfiguration<?> conf) {
    538        conf.setExpressionEngine(null);
    539    }
    540
    541    /**
    542     * Performs interpolation for the specified value. This implementation will interpolate against the current subnode
    543     * configuration's parent. If sub classes need a different interpolation mechanism, they should override this method.
    544     *
    545     * @param value the value that is to be interpolated
    546     * @return the interpolated value
    547     */
    548    protected Object interpolate(final Object value) {
    549        final ConfigurationInterpolator interpolator = getConfiguration().getInterpolator();
    550        return interpolator != null ? interpolator.interpolate(value) : value;
    551    }
    552
    553    /**
    554     * Tests if the specified attribute name is reserved and thus does not point to a property of the bean to be created.
    555     * This method is called when processing the attributes of this bean declaration. It is then possible to ignore some
    556     * attributes with a specific meaning. This implementation delegates to {@link #isReservedName(String)}.
    557     *
    558     * @param name the name of the attribute to be checked
    559     * @return a flag whether this name is reserved
    560     * @since 2.0
    561     */
    562    protected boolean isReservedAttributeName(final String name) {
    563        return isReservedName(name);
    564    }
    565
    566    /**
    567     * Tests if the specified child node name is reserved and thus should be ignored. This method is called when processing
    568     * child nodes of this bean declaration. It is then possible to ignore some nodes with a specific meaning. This
    569     * implementation delegates to {@link #isReservedName(String)}.
    570     *
    571     * @param name the name of the child node to be checked
    572     * @return a flag whether this name is reserved
    573     * @since 2.0
    574     */
    575    protected boolean isReservedChildName(final String name) {
    576        return isReservedName(name);
    577    }
    578
    579    /**
    580     * Tests if the specified name of a node or attribute is reserved and thus should be ignored. This method is called per
    581     * default by the methods for checking attribute and child node names. It checks whether the passed in name starts with
    582     * the reserved prefix.
    583     *
    584     * @param name the name to be checked
    585     * @return a flag whether this name is reserved
    586     */
    587    protected boolean isReservedName(final String name) {
    588        return name == null || name.startsWith(RESERVED_PREFIX);
    589    }
    590}