All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.commons.configuration2.beanutils.XMLBeanDeclaration Maven / Gradle / Ivy

Go to download

Tools to assist in the reading of configuration/preferences files in various formats

The newest version!
/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.commons.configuration2.beanutils;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;

import org.apache.commons.configuration2.BaseHierarchicalConfiguration;
import org.apache.commons.configuration2.HierarchicalConfiguration;
import org.apache.commons.configuration2.ex.ConfigurationRuntimeException;
import org.apache.commons.configuration2.interpol.ConfigurationInterpolator;
import org.apache.commons.configuration2.tree.NodeHandler;
import org.apache.commons.lang3.StringUtils;

/**
 * 

* An implementation of the {@code BeanDeclaration} interface that is suitable for XML configuration files. *

*

* This class defines the standard layout of a bean declaration in an XML configuration file. Such a declaration must * look like the following example fragment: *

* *
 *   ...
 *   <personBean config-class="my.model.PersonBean"
 *       lastName="Doe" firstName="John">
 *       <config-constrarg config-value="ID03493" config-type="java.lang.String"/>
 *       <address config-class="my.model.AddressBean"
 *           street="21st street 11" zip="1234"
 *           city="TestCity"/>
 *   </personBean>
 * 
* *

* The bean declaration can be contained in an arbitrary element. Here it is the {@code personBean} element. In the * attributes of this element there can occur some reserved attributes, which have the following meaning: *

*
*
{@code config-class}
*
Here the full qualified name of the bean's class can be specified. An instance of this class will be created. If * this attribute is not specified, the bean class must be provided in another way, e.g. as the {@code defaultClass} * passed to the {@code BeanHelper} class.
*
{@code config-factory}
*
This attribute can contain the name of the {@link BeanFactory} that should be used for creating the bean. If it * is defined, a factory with this name must have been registered at the {@code BeanHelper} class. If this attribute is * missing, the default bean factory will be used.
*
{@code config-factoryParam}
*
With this attribute a parameter can be specified that will be passed to the bean factory. This may be useful for * custom bean factories.
*
*

* All further attributes starting with the {@code config-} prefix are considered as meta data and will be ignored. All * other attributes are treated as properties of the bean to be created, i.e. corresponding setter methods of the bean * will be invoked with the values specified here. *

*

* If the bean to be created has also some complex properties (which are itself beans), their values cannot be * initialized from attributes. For this purpose nested elements can be used. The example listing shows how an address * bean can be initialized. This is done in a nested element whose name must match the name of a property of the * enclosing bean declaration. The format of this nested element is exactly the same as for the bean declaration itself, * i.e. it can have attributes defining meta data or bean properties and even further nested elements for complex bean * properties. *

*

* If the bean should be created using a specific constructor, the constructor arguments have to be specified. This is * done by an arbitrary number of nested {@code } elements. Each element can either have the * {@code config-value} attribute - then it defines a simple value - or must be again a bean declaration (conforming to * the format defined here) defining the complex value of this constructor argument. *

*

* A {@code XMLBeanDeclaration} object is usually created from a {@code HierarchicalConfiguration}. From this it will * derive a {@code SubnodeConfiguration}, which is used to access the needed properties. This subnode configuration can * be obtained using the {@link #getConfiguration()} method. All of its properties can be accessed in the usual way. To * ensure that the property keys used by this class are understood by the configuration, the default expression engine * will be set. *

* * @since 1.3 */ public class XMLBeanDeclaration implements BeanDeclaration { /** * An internal helper class which wraps the node with the bean declaration and the corresponding node handler. * * @param the type of the node */ static class NodeData { /** The wrapped node. */ private final T node; /** The node handler for interacting with this node. */ private final NodeHandler nodeHandler; /** * Constructs a new instance of {@code NodeData}. * * @param node the node * @param nodeHandler the node handler */ NodeData(final T node, final NodeHandler nodeHandler) { this.node = node; this.nodeHandler = nodeHandler; } /** * Returns the unescaped name of the node stored in this data object. This method handles the case that the node name * may contain reserved characters with a special meaning for the current expression engine. In this case, the * characters affected have to be escaped accordingly. * * @param config the configuration * @return the escaped node name */ String escapedNodeName(final HierarchicalConfiguration config) { return config.getExpressionEngine().nodeKey(node, StringUtils.EMPTY, nodeHandler); } /** * Gets the value of the attribute with the given name of the wrapped node. * * @param key the key of the attribute * @return the value of this attribute */ Object getAttribute(final String key) { return nodeHandler.getAttributeValue(node, key); } /** * Gets a set with the names of the attributes of the wrapped node. * * @return the attribute names of this node */ Set getAttributes() { return nodeHandler.getAttributes(node); } /** * Gets a list with the children of the wrapped node, again wrapped into {@code NodeData} objects. * * @return a list with the children */ List> getChildren() { return wrapInNodeData(nodeHandler.getChildren(node)); } /** * Gets a list with the children of the wrapped node with the given name, again wrapped into {@code NodeData} * objects. * * @param name the name of the desired child nodes * @return a list with the children with this name */ List> getChildren(final String name) { return wrapInNodeData(nodeHandler.getChildren(node, name)); } /** * Returns a flag whether the wrapped node is the root node of the passed in configuration. * * @param config the configuration * @return a flag whether this node is the configuration's root node */ boolean matchesConfigRootNode(final HierarchicalConfiguration config) { return config.getNodeModel().getNodeHandler().getRootNode().equals(node); } /** * Returns the name of the wrapped node. * * @return the node name */ String nodeName() { return nodeHandler.nodeName(node); } /** * Wraps the passed in list of nodes in {@code NodeData} objects. * * @param nodes the list with nodes * @return the wrapped nodes */ List> wrapInNodeData(final List nodes) { return nodes.stream().map(n -> new NodeData<>(n, nodeHandler)).collect(Collectors.toList()); } } /** Constant for the prefix of reserved attributes. */ public static final String RESERVED_PREFIX = "config-"; /** Constant for the prefix for reserved attributes. */ public static final String ATTR_PREFIX = "[@" + RESERVED_PREFIX; /** Constant for the bean class attribute. */ public static final String ATTR_BEAN_CLASS = ATTR_PREFIX + "class]"; /** Constant for the bean factory attribute. */ public static final String ATTR_BEAN_FACTORY = ATTR_PREFIX + "factory]"; /** Constant for the bean factory parameter attribute. */ public static final String ATTR_FACTORY_PARAM = ATTR_PREFIX + "factoryParam]"; /** Constant for the name of the bean class attribute. */ private static final String ATTR_BEAN_CLASS_NAME = RESERVED_PREFIX + "class"; /** Constant for the name of the element for constructor arguments. */ private static final String ELEM_CTOR_ARG = RESERVED_PREFIX + "constrarg"; /** * Constant for the name of the attribute with the value of a constructor argument. */ private static final String ATTR_CTOR_VALUE = RESERVED_PREFIX + "value"; /** * Constant for the name of the attribute with the data type of a constructor argument. */ private static final String ATTR_CTOR_TYPE = RESERVED_PREFIX + "type"; /** * Creates a {@code NodeData} object from the root node of the given configuration. * * @param config the configuration * @param the type of the nodes * @return the {@code NodeData} object */ private static NodeData createNodeDataFromConfiguration(final HierarchicalConfiguration config) { final NodeHandler handler = config.getNodeModel().getNodeHandler(); return new NodeData<>(handler.getRootNode(), handler); } /** * Tests whether the constructor argument represented by the given configuration node is a bean declaration. * * @param nodeData the configuration node in question * @return a flag whether this constructor argument is a bean declaration */ private static boolean isBeanDeclarationArgument(final NodeData nodeData) { return !nodeData.getAttributes().contains(ATTR_BEAN_CLASS_NAME); } /** Stores the associated configuration. */ private final HierarchicalConfiguration configuration; /** Stores the configuration node that contains the bean declaration. */ private final NodeData nodeData; /** The name of the default bean class. */ private final String defaultBeanClassName; /** * Constructs a new instance of {@code XMLBeanDeclaration} and initializes it with the configuration node that contains the * bean declaration. This constructor is used internally. * * @param config the configuration * @param node the node with the bean declaration. */ XMLBeanDeclaration(final HierarchicalConfiguration config, final NodeData node) { this.nodeData = node; configuration = config; defaultBeanClassName = null; initSubnodeConfiguration(config); } /** * Constructs a new instance of {@code XMLBeanDeclaration} and initializes it from the given configuration. The * configuration's root node must contain the bean declaration. * * @param config the configuration with the bean declaration * @param the node type of the configuration */ public XMLBeanDeclaration(final HierarchicalConfiguration config) { this(config, (String) null); } /** * Constructs a new instance of {@code XMLBeanDeclaration} and initializes it from the given configuration. The passed in * key points to the bean declaration. * * @param config the configuration (must not be null) * @param key the key to the bean declaration (this key must point to exactly one bean declaration or a * {@code IllegalArgumentException} exception will be thrown) * @param the node type of the configuration * @throws IllegalArgumentException if required information is missing to construct the bean declaration */ public XMLBeanDeclaration(final HierarchicalConfiguration config, final String key) { this(config, key, false); } /** * Constructs a new instance of {@code XMLBeanDeclaration} and initializes it from the given configuration supporting * optional declarations. * * @param config the configuration (must not be null) * @param key the key to the bean declaration * @param optional a flag whether this declaration is optional; if set to true, no exception will be thrown if * the passed in key is undefined * @param the node type of the configuration * @throws IllegalArgumentException if required information is missing to construct the bean declaration */ public XMLBeanDeclaration(final HierarchicalConfiguration config, final String key, final boolean optional) { this(config, key, optional, null); } /** * Constructs a new instance of {@code XMLBeanDeclaration} and initializes it from the given configuration supporting * optional declarations and a default bean class name. The passed in key points to the bean declaration. If the key * does not exist and the boolean argument is true, the declaration is initialized with an empty configuration. * It is possible to create objects from such an empty declaration if a default class is provided. If the key on the * other hand has multiple values or is undefined and the boolean argument is false, a * {@code IllegalArgumentException} exception will be thrown. It is possible to set a default bean class name; this name * is used if the configuration does not contain a bean class. * * @param config the configuration (must not be null) * @param key the key to the bean declaration * @param optional a flag whether this declaration is optional; if set to true, no exception will be thrown if * the passed in key is undefined * @param defBeanClsName a default bean class name * @param the node type of the configuration * @throws IllegalArgumentException if required information is missing to construct the bean declaration * @since 2.0 */ public XMLBeanDeclaration(final HierarchicalConfiguration config, final String key, final boolean optional, final String defBeanClsName) { if (config == null) { throw new IllegalArgumentException("Configuration must not be null!"); } HierarchicalConfiguration tmpconfiguration; try { tmpconfiguration = config.configurationAt(key); } catch (final ConfigurationRuntimeException iex) { // If we reach this block, the key does not have exactly one value if (!optional || config.getMaxIndex(key) > 0) { throw iex; } tmpconfiguration = new BaseHierarchicalConfiguration(); } this.nodeData = createNodeDataFromConfiguration(tmpconfiguration); this.configuration = tmpconfiguration; defaultBeanClassName = defBeanClsName; initSubnodeConfiguration(getConfiguration()); } /** * Creates a new {@code BeanDeclaration} for a child node of the current configuration node. This method is called by * {@code getNestedBeanDeclarations()} for all complex sub properties detected by this method. Derived classes can hook * in if they need a specific initialization. This base implementation creates a {@code XMLBeanDeclaration} that is * properly initialized from the passed in node. * * @param nodeData the child node, for which a {@code BeanDeclaration} is to be created * @return the {@code BeanDeclaration} for this child node */ BeanDeclaration createBeanDeclaration(final NodeData nodeData) { for (final HierarchicalConfiguration config : getConfiguration().configurationsAt(nodeData.escapedNodeName(getConfiguration()))) { if (nodeData.matchesConfigRootNode(config)) { return new XMLBeanDeclaration(config, nodeData); } } throw new ConfigurationRuntimeException("Unable to match node for " + nodeData.nodeName()); } /** * Creates a {@code ConstructorArg} object for the specified configuration node. * * @param child the configuration node * @return the corresponding {@code ConstructorArg} object */ private ConstructorArg createConstructorArg(final NodeData child) { final String type = getAttribute(child, ATTR_CTOR_TYPE); if (isBeanDeclarationArgument(child)) { return ConstructorArg.forValue(getAttribute(child, ATTR_CTOR_VALUE), type); } return ConstructorArg.forBeanDeclaration(createBeanDeclaration(child), type); } /** * Gets an attribute of a configuration node. This method also takes interpolation into account. * * @param nodeData the node * @param attribute the name of the attribute * @return the string value of this attribute (can be null) */ private String getAttribute(final NodeData nodeData, final String attribute) { final Object value = nodeData.getAttribute(attribute); return value == null ? null : String.valueOf(interpolate(value)); } /** * Gets a set with the names of the attributes of the configuration node holding the data of this bean declaration. * * @return the attribute names of the underlying configuration node */ protected Set getAttributeNames() { return getNode().getAttributes(); } /** * Gets the name of the class of the bean to be created. This information is obtained from the {@code config-class} * attribute. * * @return the name of the bean's class */ @Override public String getBeanClassName() { return getConfiguration().getString(ATTR_BEAN_CLASS, getDefaultBeanClassName()); } /** * Gets the name of the bean factory. This information is fetched from the {@code config-factory} attribute. * * @return the name of the bean factory */ @Override public String getBeanFactoryName() { return getConfiguration().getString(ATTR_BEAN_FACTORY, null); } /** * Gets a parameter for the bean factory. This information is fetched from the {@code config-factoryParam} attribute. * * @return the parameter for the bean factory */ @Override public Object getBeanFactoryParameter() { return getConfiguration().getProperty(ATTR_FACTORY_PARAM); } /** * Gets a map with the bean's (simple) properties. The properties are collected from all attribute nodes, which are * not reserved. * * @return a map with the bean's properties */ @Override public Map getBeanProperties() { return getAttributeNames().stream().filter(e -> !isReservedAttributeName(e)) .collect(Collectors.toMap(Function.identity(), e -> interpolate(getNode().getAttribute(e)))); } /** * Gets the configuration object this bean declaration is based on. * * @return the associated configuration */ public HierarchicalConfiguration getConfiguration() { return configuration; } /** * {@inheritDoc} This implementation processes all child nodes with the name {@code config-constrarg}. If such a node * has a {@code config-class} attribute, it is considered a nested bean declaration; otherwise it is interpreted as a * simple value. If no nested constructor argument declarations are found, result is an empty collection. */ @Override public Collection getConstructorArgs() { return getNode().getChildren(ELEM_CTOR_ARG).stream().map(this::createConstructorArg).collect(Collectors.toCollection(LinkedList::new)); } /** * Gets the name of the default bean class. This class is used if no bean class is specified in the configuration. It * may be null if no default class was set. * * @return the default bean class name * @since 2.0 */ public String getDefaultBeanClassName() { return defaultBeanClassName; } /** * Gets a map with bean declarations for the complex properties of the bean to be created. These declarations are * obtained from the child nodes of this declaration's root node. * * @return a map with bean declarations for complex properties */ @Override public Map getNestedBeanDeclarations() { final Map nested = new HashMap<>(); getNode().getChildren().forEach(child -> { if (!isReservedChildName(child.nodeName())) { final Object obj = nested.get(child.nodeName()); if (obj != null) { final List list; if (obj instanceof List) { // Safe because we created the lists ourselves. @SuppressWarnings("unchecked") final List tmpList = (List) obj; list = tmpList; } else { list = new ArrayList<>(); list.add((BeanDeclaration) obj); nested.put(child.nodeName(), list); } list.add(createBeanDeclaration(child)); } else { nested.put(child.nodeName(), createBeanDeclaration(child)); } } }); return nested; } /** * Gets the data about the associated node. * * @return the node with the bean declaration */ NodeData getNode() { return nodeData; } /** * Initializes the internally managed sub configuration. This method will set some default values for some properties. * * @param conf the configuration to initialize */ private void initSubnodeConfiguration(final HierarchicalConfiguration conf) { conf.setExpressionEngine(null); } /** * Performs interpolation for the specified value. This implementation will interpolate against the current subnode * configuration's parent. If sub classes need a different interpolation mechanism, they should override this method. * * @param value the value that is to be interpolated * @return the interpolated value */ protected Object interpolate(final Object value) { final ConfigurationInterpolator interpolator = getConfiguration().getInterpolator(); return interpolator != null ? interpolator.interpolate(value) : value; } /** * Tests if the specified attribute name is reserved and thus does not point to a property of the bean to be created. * This method is called when processing the attributes of this bean declaration. It is then possible to ignore some * attributes with a specific meaning. This implementation delegates to {@link #isReservedName(String)}. * * @param name the name of the attribute to be checked * @return a flag whether this name is reserved * @since 2.0 */ protected boolean isReservedAttributeName(final String name) { return isReservedName(name); } /** * Tests if the specified child node name is reserved and thus should be ignored. This method is called when processing * child nodes of this bean declaration. It is then possible to ignore some nodes with a specific meaning. This * implementation delegates to {@link #isReservedName(String)} . * * @param name the name of the child node to be checked * @return a flag whether this name is reserved * @since 2.0 */ protected boolean isReservedChildName(final String name) { return isReservedName(name); } /** * Tests if the specified name of a node or attribute is reserved and thus should be ignored. This method is called per * default by the methods for checking attribute and child node names. It checks whether the passed in name starts with * the reserved prefix. * * @param name the name to be checked * @return a flag whether this name is reserved */ protected boolean isReservedName(final String name) { return name == null || name.startsWith(RESERVED_PREFIX); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy