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: */
017: package org.apache.commons.configuration.beanutils;
018:
019: import java.util.HashMap;
020: import java.util.Iterator;
021: import java.util.Map;
022:
023: import org.apache.commons.configuration.HierarchicalConfiguration;
024: import org.apache.commons.configuration.PropertyConverter;
025: import org.apache.commons.configuration.SubnodeConfiguration;
026: import org.apache.commons.configuration.tree.ConfigurationNode;
027: import org.apache.commons.configuration.tree.DefaultConfigurationNode;
028:
029: /**
030: * <p>
031: * An implementation of the <code>BeanDeclaration</code> interface that is
032: * suitable for XML configuration files.
033: * </p>
034: * <p>
035: * This class defines the standard layout of a bean declaration in an XML
036: * configuration file. Such a declaration must look like the following example
037: * fragement:
038: * </p>
039: * <p>
040: *
041: * <pre>
042: * ...
043: * <personBean config-class="my.model.PersonBean"
044: * lastName="Doe" firstName="John">
045: * <address config-class="my.model.AddressBean"
046: * street="21st street 11" zip="1234"
047: * city="TestCity"/>
048: * </personBean>
049: * </pre>
050: *
051: * </p>
052: * <p>
053: * The bean declaration can be contained in an arbitrary element. Here it is the
054: * <code><personBean></code> element. In the attributes of this element
055: * there can occur some reserved attributes, which have the following meaning:
056: * <dl>
057: * <dt><code>config-class</code></dt>
058: * <dd>Here the full qualified name of the bean's class can be specified. An
059: * instance of this class will be created. If this attribute is not specified,
060: * the bean class must be provided in another way, e.g. as the
061: * <code>defaultClass</code> passed to the <code>BeanHelper</code> class.</dd>
062: * <dt><code>config-factory</code></dt>
063: * <dd>This attribute can contain the name of the
064: * <code>{@link BeanFactory}</code> that should be used for creating the bean.
065: * If it is defined, a factory with this name must have been registered at the
066: * <code>BeanHelper</code> class. If this attribute is missing, the default
067: * bean factory will be used.</dd>
068: * <dt><code>config-factoryParam</code></dt>
069: * <dd>With this attribute a parameter can be specified that will be passed to
070: * the bean factory. This may be useful for custom bean factories.</dd>
071: * </dl>
072: * </p>
073: * <p>
074: * All further attributes starting with the <code>config-</code> prefix are
075: * considered as meta data and will be ignored. All other attributes are treated
076: * as properties of the bean to be created, i.e. corresponding setter methods of
077: * the bean will be invoked with the values specified here.
078: * </p>
079: * <p>
080: * If the bean to be created has also some complex properties (which are itself
081: * beans), their values cannot be initialized from attributes. For this purpose
082: * nested elements can be used. The example listing shows how an address bean
083: * can be initialized. This is done in a nested element whose name must match
084: * the name of a property of the enclosing bean declaration. The format of this
085: * nested element is exactly the same as for the bean declaration itself, i.e.
086: * it can have attributes defining meta data or bean properties and even further
087: * nested elements for complex bean properties.
088: * </p>
089: * <p>
090: * A <code>XMLBeanDeclaration</code> object is usually created from a
091: * <code>HierarchicalConfiguration</code>. From this it will derive a
092: * <code>SubnodeConfiguration</code>, which is used to access the needed
093: * properties. This subnode configuration can be obtained using the
094: * <code>{@link #getConfiguration()}</code> method. All of its properties can
095: * be accessed in the usual way. To ensure that the property keys used by this
096: * class are understood by the configuration, the default expression engine will
097: * be set.
098: * </p>
099: *
100: * @since 1.3
101: * @author Oliver Heger
102: * @version $Id: XMLBeanDeclaration.java 439648 2006-09-02 20:42:10Z oheger $
103: */
104: public class XMLBeanDeclaration implements BeanDeclaration {
105: /** Constant for the prefix of reserved attributes. */
106: public static final String RESERVED_PREFIX = "config-";
107:
108: /** Constant for the prefix for reserved attributes.*/
109: public static final String ATTR_PREFIX = "[@" + RESERVED_PREFIX;
110:
111: /** Constant for the bean class attribute. */
112: public static final String ATTR_BEAN_CLASS = ATTR_PREFIX + "class]";
113:
114: /** Constant for the bean factory attribute. */
115: public static final String ATTR_BEAN_FACTORY = ATTR_PREFIX
116: + "factory]";
117:
118: /** Constant for the bean factory parameter attribute. */
119: public static final String ATTR_FACTORY_PARAM = ATTR_PREFIX
120: + "factoryParam]";
121:
122: /** Stores the associated configuration. */
123: private SubnodeConfiguration configuration;
124:
125: /** Stores the configuration node that contains the bean declaration. */
126: private ConfigurationNode node;
127:
128: /**
129: * Creates a new instance of <code>XMLBeanDeclaration</code> and
130: * initializes it from the given configuration. The passed in key points to
131: * the bean declaration.
132: *
133: * @param config the configuration
134: * @param key the key to the bean declaration (this key must point to
135: * exactly one bean declaration or a <code>IllegalArgumentException</code>
136: * exception will be thrown)
137: */
138: public XMLBeanDeclaration(HierarchicalConfiguration config,
139: String key) {
140: this (config, key, false);
141: }
142:
143: /**
144: * Creates a new instance of <code>XMLBeanDeclaration</code> and
145: * initializes it from the given configuration. The passed in key points to
146: * the bean declaration. If the key does not exist and the boolean argument
147: * is <b>true</b>, the declaration is initialized with an empty
148: * configuration. It is possible to create objects from such an empty
149: * declaration if a default class is provided. If the key on the other hand
150: * has multiple values or is undefined and the boolean argument is <b>false</b>,
151: * a <code>IllegalArgumentException</code> exception will be thrown.
152: *
153: * @param config the configuration
154: * @param key the key to the bean declaration
155: * @param optional a flag whether this declaration is optional; if set to
156: * <b>true</b>, no exception will be thrown if the passed in key is
157: * undefined
158: */
159: public XMLBeanDeclaration(HierarchicalConfiguration config,
160: String key, boolean optional) {
161: if (config == null) {
162: throw new IllegalArgumentException(
163: "Configuration must not be null!");
164: }
165:
166: try {
167: configuration = config.configurationAt(key);
168: node = configuration.getRootNode();
169: } catch (IllegalArgumentException iex) {
170: // If we reach this block, the key does not have exactly one value
171: if (!optional || config.getMaxIndex(key) > 0) {
172: throw iex;
173: }
174: configuration = config.configurationAt(null);
175: node = new DefaultConfigurationNode();
176: }
177: initSubnodeConfiguration(getConfiguration());
178: }
179:
180: /**
181: * Creates a new instance of <code>XMLBeanDeclaration</code> and
182: * initializes it from the given configuration. The configuration's root
183: * node must contain the bean declaration.
184: *
185: * @param config the configuration with the bean declaration
186: */
187: public XMLBeanDeclaration(HierarchicalConfiguration config) {
188: this (config, (String) null);
189: }
190:
191: /**
192: * Creates a new instance of <code>XMLBeanDeclaration</code> and
193: * initializes it with the configuration node that contains the bean
194: * declaration.
195: *
196: * @param config the configuration
197: * @param node the node with the bean declaration.
198: */
199: public XMLBeanDeclaration(SubnodeConfiguration config,
200: ConfigurationNode node) {
201: if (config == null) {
202: throw new IllegalArgumentException(
203: "Configuration must not be null!");
204: }
205: if (node == null) {
206: throw new IllegalArgumentException("Node must not be null!");
207: }
208:
209: this .node = node;
210: configuration = config;
211: initSubnodeConfiguration(config);
212: }
213:
214: /**
215: * Returns the configuration object this bean declaration is based on.
216: *
217: * @return the associated configuration
218: */
219: public SubnodeConfiguration getConfiguration() {
220: return configuration;
221: }
222:
223: /**
224: * Returns the node that contains the bean declaration.
225: *
226: * @return the configuration node this bean declaration is based on
227: */
228: public ConfigurationNode getNode() {
229: return node;
230: }
231:
232: /**
233: * Returns the name of the bean factory. This information is fetched from
234: * the <code>config-factory</code> attribute.
235: *
236: * @return the name of the bean factory
237: */
238: public String getBeanFactoryName() {
239: return getConfiguration().getString(ATTR_BEAN_FACTORY);
240: }
241:
242: /**
243: * Returns a parameter for the bean factory. This information is fetched
244: * from the <code>config-factoryParam</code> attribute.
245: *
246: * @return the parameter for the bean factory
247: */
248: public Object getBeanFactoryParameter() {
249: return getConfiguration().getProperty(ATTR_FACTORY_PARAM);
250: }
251:
252: /**
253: * Returns the name of the class of the bean to be created. This information
254: * is obtained from the <code>config-class</code> attribute.
255: *
256: * @return the name of the bean's class
257: */
258: public String getBeanClassName() {
259: return getConfiguration().getString(ATTR_BEAN_CLASS);
260: }
261:
262: /**
263: * Returns a map with the bean's (simple) properties. The properties are
264: * collected from all attribute nodes, which are not reserved.
265: *
266: * @return a map with the bean's properties
267: */
268: public Map getBeanProperties() {
269: Map props = new HashMap();
270: for (Iterator it = getNode().getAttributes().iterator(); it
271: .hasNext();) {
272: ConfigurationNode attr = (ConfigurationNode) it.next();
273: if (!isReservedNode(attr)) {
274: props.put(attr.getName(), interpolate(attr.getValue()));
275: }
276: }
277:
278: return props;
279: }
280:
281: /**
282: * Returns a map with bean declarations for the complex properties of the
283: * bean to be created. These declarations are obtained from the child nodes
284: * of this declaration's root node.
285: *
286: * @return a map with bean declarations for complex properties
287: */
288: public Map getNestedBeanDeclarations() {
289: Map nested = new HashMap();
290: for (Iterator it = getNode().getChildren().iterator(); it
291: .hasNext();) {
292: ConfigurationNode child = (ConfigurationNode) it.next();
293: if (!isReservedNode(child)) {
294: nested.put(child.getName(), new XMLBeanDeclaration(
295: getConfiguration().configurationAt(
296: child.getName()), child));
297: }
298: }
299:
300: return nested;
301: }
302:
303: /**
304: * Performs interpolation for the specified value. This implementation will
305: * interpolate against the current subnode configuration's parent. If sub
306: * classes need a different interpolation mechanism, they should override
307: * this method.
308: *
309: * @param value the value that is to be interpolated
310: * @return the interpolated value
311: */
312: protected Object interpolate(Object value) {
313: return PropertyConverter.interpolate(value, getConfiguration()
314: .getParent());
315: }
316:
317: /**
318: * Checks if the specified node is reserved and thus should be ignored. This
319: * method is called when the maps for the bean's properties and complex
320: * properties are collected. It checks whether the given node is an
321: * attribute node and if its name starts with the reserved prefix.
322: *
323: * @param nd the node to be checked
324: * @return a flag whether this node is reserved (and does not point to a
325: * property)
326: */
327: protected boolean isReservedNode(ConfigurationNode nd) {
328: return nd.isAttribute()
329: && (nd.getName() == null || nd.getName().startsWith(
330: RESERVED_PREFIX));
331: }
332:
333: /**
334: * Initializes the internally managed subnode configuration. This method
335: * will set some default values for some properties.
336: *
337: * @param conf the configuration to initialize
338: */
339: private void initSubnodeConfiguration(SubnodeConfiguration conf) {
340: conf.setThrowExceptionOnMissing(false);
341: conf.setExpressionEngine(null);
342: }
343: }
|