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