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 */
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, e.g. 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 <b>null</b>)
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 <b>null</b>)
315     * @param key the key to the bean declaration
316     * @param optional a flag whether this declaration is optional; if set to <b>true</b>, 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 <b>true</b>, 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 <b>false</b>, 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 <b>null</b>)
335     * @param key the key to the bean declaration
336     * @param optional a flag whether this declaration is optional; if set to <b>true</b>, 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 <b>null</b>)
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 <b>null</b> 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}