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