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