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

There is a newer version: 2.11.0
Show 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 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 * @version $Id: XMLBeanDeclaration.java 1790899 2017-04-10 21:56:46Z ggregory $ */ public class XMLBeanDeclaration implements BeanDeclaration { /** 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"; /** Stores the associated configuration. */ private final HierarchicalConfiguration configuration; /** Stores the configuration node that contains the bean declaration. */ private final NodeData node; /** The name of the default bean class. */ private final String defaultBeanClassName; /** * Creates 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(HierarchicalConfiguration config, String key) { this(config, key, false); } /** * Creates 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(HierarchicalConfiguration config, String key, boolean optional) { this(config, key, optional, null); } /** * Creates 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(HierarchicalConfiguration config, String key, boolean optional, String defBeanClsName) { if (config == null) { throw new IllegalArgumentException( "Configuration must not be null!"); } HierarchicalConfiguration tmpconfiguration; try { tmpconfiguration = config.configurationAt(key); } catch (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.node = createNodeDataFromConfiguration(tmpconfiguration); this.configuration = tmpconfiguration; defaultBeanClassName = defBeanClsName; initSubnodeConfiguration(getConfiguration()); } /** * Creates 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(HierarchicalConfiguration config) { this(config, (String) null); } /** * Creates 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(HierarchicalConfiguration config, NodeData node) { this.node = node; configuration = config; defaultBeanClassName = null; initSubnodeConfiguration(config); } /** * Returns the configuration object this bean declaration is based on. * * @return the associated configuration */ public HierarchicalConfiguration getConfiguration() { return configuration; } /** * Returns 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; } /** * Returns 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); } /** * Returns 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); } /** * Returns 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()); } /** * Returns 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() { Map props = new HashMap<>(); for (String key : getAttributeNames()) { if (!isReservedAttributeName(key)) { props.put(key, interpolate(getNode().getAttribute(key))); } } return props; } /** * Returns 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() { Map nested = new HashMap<>(); for (NodeData child : getNode().getChildren()) { if (!isReservedChildName(child.nodeName())) { if (nested.containsKey(child.nodeName())) { Object obj = nested.get(child.nodeName()); List list; if (obj instanceof List) { // Safe because we created the lists ourselves. @SuppressWarnings("unchecked") 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; } /** * {@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() { Collection args = new LinkedList<>(); for (NodeData child : getNode().getChildren(ELEM_CTOR_ARG)) { args.add(createConstructorArg(child)); } return args; } /** * 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(Object value) { ConfigurationInterpolator interpolator = getConfiguration().getInterpolator(); return (interpolator != null) ? interpolator.interpolate(value) : value; } /** * Checks 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(String name) { return isReservedName(name); } /** * Checks 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(String name) { return isReservedName(name); } /** * Checks 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(String name) { return name == null || name.startsWith(RESERVED_PREFIX); } /** * Returns 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(); } /** * Returns the data about the associated node. * * @return the node with the bean declaration */ NodeData getNode() { return node; } /** * 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 node the child node, for which a {@code BeanDeclaration} is * to be created * @return the {@code BeanDeclaration} for this child node */ BeanDeclaration createBeanDeclaration(NodeData node) { for (HierarchicalConfiguration config : getConfiguration() .configurationsAt(node.escapedNodeName(getConfiguration()))) { if (node.matchesConfigRootNode(config)) { return new XMLBeanDeclaration(config, node); } } throw new ConfigurationRuntimeException("Unable to match node for " + node.nodeName()); } /** * 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(HierarchicalConfiguration conf) { conf.setExpressionEngine(null); } /** * Creates a {@code ConstructorArg} object for the specified configuration * node. * * @param child the configuration node * @return the corresponding {@code ConstructorArg} object */ private ConstructorArg createConstructorArg(NodeData child) { String type = getAttribute(child, ATTR_CTOR_TYPE); if (isBeanDeclarationArgument(child)) { return ConstructorArg.forValue( getAttribute(child, ATTR_CTOR_VALUE), type); } else { return ConstructorArg.forBeanDeclaration( createBeanDeclaration(child), type); } } /** * Helper method for obtaining an attribute of a configuration node. * This method also takes interpolation into account. * * @param nd the node * @param attr the name of the attribute * @return the string value of this attribute (can be null) */ private String getAttribute(NodeData nd, String attr) { Object value = nd.getAttribute(attr); return (value == null) ? null : String.valueOf(interpolate(value)); } /** * Checks whether the constructor argument represented by the given * configuration node is a bean declaration. * * @param nd the configuration node in question * @return a flag whether this constructor argument is a bean declaration */ private static boolean isBeanDeclarationArgument(NodeData nd) { return !nd.getAttributes().contains(ATTR_BEAN_CLASS_NAME); } /** * 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( HierarchicalConfiguration config) { NodeHandler handler = config.getNodeModel().getNodeHandler(); return new NodeData<>(handler.getRootNode(), handler); } /** * An internally used 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 handler; /** * Creates a new instance of {@code NodeData}. * * @param nd the node * @param hndlr the handler */ public NodeData(T nd, NodeHandler hndlr) { node = nd; handler = hndlr; } /** * Returns the name of the wrapped node. * * @return the node name */ public String nodeName() { return handler.nodeName(node); } /** * 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 */ public String escapedNodeName(HierarchicalConfiguration config) { return config.getExpressionEngine().nodeKey(node, StringUtils.EMPTY, handler); } /** * Returns a list with the children of the wrapped node, again wrapped * into {@code NodeData} objects. * * @return a list with the children */ public List> getChildren() { return wrapInNodeData(handler.getChildren(node)); } /** * Returns 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 */ public List> getChildren(String name) { return wrapInNodeData(handler.getChildren(node, name)); } /** * Returns a set with the names of the attributes of the wrapped node. * * @return the attribute names of this node */ public Set getAttributes() { return handler.getAttributes(node); } /** * Returns 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 */ public Object getAttribute(String key) { return handler.getAttributeValue(node, key); } /** * 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 */ public boolean matchesConfigRootNode(HierarchicalConfiguration config) { return config.getNodeModel().getNodeHandler().getRootNode() .equals(node); } /** * Wraps the passed in list of nodes in {@code NodeData} objects. * * @param nodes the list with nodes * @return the wrapped nodes */ private List> wrapInNodeData(List nodes) { List> result = new ArrayList<>(nodes.size()); for (T node : nodes) { result.add(new NodeData<>(node, handler)); } return result; } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy