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 670739 2008-06-23 20:36:37Z oheger $
103 */
104 public class XMLBeanDeclaration implements BeanDeclaration
105 {
106 /** Constant for the prefix of reserved attributes. */
107 public static final String RESERVED_PREFIX = "config-";
108
109 /** Constant for the prefix for reserved attributes.*/
110 public static final String ATTR_PREFIX = "[@" + RESERVED_PREFIX;
111
112 /** Constant for the bean class attribute. */
113 public static final String ATTR_BEAN_CLASS = ATTR_PREFIX + "class]";
114
115 /** Constant for the bean factory attribute. */
116 public static final String ATTR_BEAN_FACTORY = ATTR_PREFIX + "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, String key)
139 {
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, String key,
160 boolean optional)
161 {
162 if (config == null)
163 {
164 throw new IllegalArgumentException(
165 "Configuration must not be null!");
166 }
167
168 try
169 {
170 configuration = config.configurationAt(key);
171 node = configuration.getRootNode();
172 }
173 catch (IllegalArgumentException iex)
174 {
175 // If we reach this block, the key does not have exactly one value
176 if (!optional || config.getMaxIndex(key) > 0)
177 {
178 throw iex;
179 }
180 configuration = config.configurationAt(null);
181 node = new DefaultConfigurationNode();
182 }
183 initSubnodeConfiguration(getConfiguration());
184 }
185
186 /**
187 * Creates a new instance of <code>XMLBeanDeclaration</code> and
188 * initializes it from the given configuration. The configuration's root
189 * node must contain the bean declaration.
190 *
191 * @param config the configuration with the bean declaration
192 */
193 public XMLBeanDeclaration(HierarchicalConfiguration config)
194 {
195 this(config, (String) null);
196 }
197
198 /**
199 * Creates a new instance of <code>XMLBeanDeclaration</code> and
200 * initializes it with the configuration node that contains the bean
201 * declaration.
202 *
203 * @param config the configuration
204 * @param node the node with the bean declaration.
205 */
206 public XMLBeanDeclaration(SubnodeConfiguration config,
207 ConfigurationNode node)
208 {
209 if (config == null)
210 {
211 throw new IllegalArgumentException(
212 "Configuration must not be null!");
213 }
214 if (node == null)
215 {
216 throw new IllegalArgumentException("Node must not be null!");
217 }
218
219 this.node = node;
220 configuration = config;
221 initSubnodeConfiguration(config);
222 }
223
224 /**
225 * Returns the configuration object this bean declaration is based on.
226 *
227 * @return the associated configuration
228 */
229 public SubnodeConfiguration getConfiguration()
230 {
231 return configuration;
232 }
233
234 /**
235 * Returns the node that contains the bean declaration.
236 *
237 * @return the configuration node this bean declaration is based on
238 */
239 public ConfigurationNode getNode()
240 {
241 return node;
242 }
243
244 /**
245 * Returns the name of the bean factory. This information is fetched from
246 * the <code>config-factory</code> attribute.
247 *
248 * @return the name of the bean factory
249 */
250 public String getBeanFactoryName()
251 {
252 return getConfiguration().getString(ATTR_BEAN_FACTORY);
253 }
254
255 /**
256 * Returns a parameter for the bean factory. This information is fetched
257 * from the <code>config-factoryParam</code> attribute.
258 *
259 * @return the parameter for the bean factory
260 */
261 public Object getBeanFactoryParameter()
262 {
263 return getConfiguration().getProperty(ATTR_FACTORY_PARAM);
264 }
265
266 /**
267 * Returns the name of the class of the bean to be created. This information
268 * is obtained from the <code>config-class</code> attribute.
269 *
270 * @return the name of the bean's class
271 */
272 public String getBeanClassName()
273 {
274 return getConfiguration().getString(ATTR_BEAN_CLASS);
275 }
276
277 /**
278 * Returns a map with the bean's (simple) properties. The properties are
279 * collected from all attribute nodes, which are not reserved.
280 *
281 * @return a map with the bean's properties
282 */
283 public Map getBeanProperties()
284 {
285 Map props = new HashMap();
286 for (Iterator it = getNode().getAttributes().iterator(); it.hasNext();)
287 {
288 ConfigurationNode attr = (ConfigurationNode) it.next();
289 if (!isReservedNode(attr))
290 {
291 props.put(attr.getName(), interpolate(attr .getValue()));
292 }
293 }
294
295 return props;
296 }
297
298 /**
299 * Returns a map with bean declarations for the complex properties of the
300 * bean to be created. These declarations are obtained from the child nodes
301 * of this declaration's root node.
302 *
303 * @return a map with bean declarations for complex properties
304 */
305 public Map getNestedBeanDeclarations()
306 {
307 Map nested = new HashMap();
308 for (Iterator it = getNode().getChildren().iterator(); it.hasNext();)
309 {
310 ConfigurationNode child = (ConfigurationNode) it.next();
311 if (!isReservedNode(child))
312 {
313 nested.put(child.getName(), createBeanDeclaration(child));
314 }
315 }
316
317 return nested;
318 }
319
320 /**
321 * Performs interpolation for the specified value. This implementation will
322 * interpolate against the current subnode configuration's parent. If sub
323 * classes need a different interpolation mechanism, they should override
324 * this method.
325 *
326 * @param value the value that is to be interpolated
327 * @return the interpolated value
328 */
329 protected Object interpolate(Object value)
330 {
331 return PropertyConverter.interpolate(value, getConfiguration()
332 .getParent());
333 }
334
335 /**
336 * Checks if the specified node is reserved and thus should be ignored. This
337 * method is called when the maps for the bean's properties and complex
338 * properties are collected. It checks whether the given node is an
339 * attribute node and if its name starts with the reserved prefix.
340 *
341 * @param nd the node to be checked
342 * @return a flag whether this node is reserved (and does not point to a
343 * property)
344 */
345 protected boolean isReservedNode(ConfigurationNode nd)
346 {
347 return nd.isAttribute()
348 && (nd.getName() == null || nd.getName().startsWith(
349 RESERVED_PREFIX));
350 }
351
352 /**
353 * Creates a new <code>BeanDeclaration</code> for a child node of the
354 * current configuration node. This method is called by
355 * <code>getNestedBeanDeclarations()</code> for all complex sub properties
356 * detected by this method. Derived classes can hook in if they need a
357 * specific initialization. This base implementation creates a
358 * <code>XMLBeanDeclaration</code> that is properly initialized from the
359 * passed in node.
360 *
361 * @param node the child node, for which a <code>BeanDeclaration</code> is
362 * to be created
363 * @return the <code>BeanDeclaration</code> for this child node
364 * @since 1.6
365 */
366 protected BeanDeclaration createBeanDeclaration(ConfigurationNode node)
367 {
368 return new XMLBeanDeclaration(getConfiguration().configurationAt(
369 node.getName()), node);
370 }
371
372 /**
373 * Initializes the internally managed subnode configuration. This method
374 * will set some default values for some properties.
375 *
376 * @param conf the configuration to initialize
377 */
378 private void initSubnodeConfiguration(SubnodeConfiguration conf)
379 {
380 conf.setThrowExceptionOnMissing(false);
381 conf.setExpressionEngine(null);
382 }
383 }