1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.commons.configuration.beanutils;
18
19 import java.util.HashMap;
20 import java.util.Iterator;
21 import java.util.Map;
22
23 import org.apache.commons.configuration.HierarchicalConfiguration;
24 import org.apache.commons.configuration.PropertyConverter;
25 import org.apache.commons.configuration.SubnodeConfiguration;
26 import org.apache.commons.configuration.tree.ConfigurationNode;
27 import org.apache.commons.configuration.tree.DefaultConfigurationNode;
28
29 /***
30 * <p>
31 * An implementation of the <code>BeanDeclaration</code> interface that is
32 * suitable for XML configuration files.
33 * </p>
34 * <p>
35 * This class defines the standard layout of a bean declaration in an XML
36 * configuration file. Such a declaration must look like the following example
37 * fragement:
38 * </p>
39 * <p>
40 *
41 * <pre>
42 * ...
43 * <personBean config-class="my.model.PersonBean"
44 * lastName="Doe" firstName="John">
45 * <address config-class="my.model.AddressBean"
46 * street="21st street 11" zip="1234"
47 * city="TestCity"/>
48 * </personBean>
49 * </pre>
50 *
51 * </p>
52 * <p>
53 * The bean declaration can be contained in an arbitrary element. Here it is the
54 * <code><personBean></code> element. In the attributes of this element
55 * there can occur some reserved attributes, which have the following meaning:
56 * <dl>
57 * <dt><code>config-class</code></dt>
58 * <dd>Here the full qualified name of the bean's class can be specified. An
59 * instance of this class will be created. If this attribute is not specified,
60 * the bean class must be provided in another way, e.g. as the
61 * <code>defaultClass</code> passed to the <code>BeanHelper</code> class.</dd>
62 * <dt><code>config-factory</code></dt>
63 * <dd>This attribute can contain the name of the
64 * <code>{@link BeanFactory}</code> that should be used for creating the bean.
65 * If it is defined, a factory with this name must have been registered at the
66 * <code>BeanHelper</code> class. If this attribute is missing, the default
67 * bean factory will be used.</dd>
68 * <dt><code>config-factoryParam</code></dt>
69 * <dd>With this attribute a parameter can be specified that will be passed to
70 * the bean factory. This may be useful for custom bean factories.</dd>
71 * </dl>
72 * </p>
73 * <p>
74 * All further attributes starting with the <code>config-</code> prefix are
75 * considered as meta data and will be ignored. All other attributes are treated
76 * as properties of the bean to be created, i.e. corresponding setter methods of
77 * the bean will be invoked with the values specified here.
78 * </p>
79 * <p>
80 * If the bean to be created has also some complex properties (which are itself
81 * beans), their values cannot be initialized from attributes. For this purpose
82 * nested elements can be used. The example listing shows how an address bean
83 * can be initialized. This is done in a nested element whose name must match
84 * the name of a property of the enclosing bean declaration. The format of this
85 * nested element is exactly the same as for the bean declaration itself, i.e.
86 * it can have attributes defining meta data or bean properties and even further
87 * nested elements for complex bean properties.
88 * </p>
89 * <p>
90 * A <code>XMLBeanDeclaration</code> object is usually created from a
91 * <code>HierarchicalConfiguration</code>. From this it will derive a
92 * <code>SubnodeConfiguration</code>, which is used to access the needed
93 * properties. This subnode configuration can be obtained using the
94 * <code>{@link #getConfiguration()}</code> method. All of its properties can
95 * be accessed in the usual way. To ensure that the property keys used by this
96 * class are understood by the configuration, the default expression engine will
97 * be set.
98 * </p>
99 *
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 {
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
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(), new XMLBeanDeclaration(
314 getConfiguration().configurationAt(child.getName()), child));
315 }
316 }
317
318 return nested;
319 }
320
321 /***
322 * Performs interpolation for the specified value. This implementation will
323 * interpolate against the current subnode configuration's parent. If sub
324 * classes need a different interpolation mechanism, they should override
325 * this method.
326 *
327 * @param value the value that is to be interpolated
328 * @return the interpolated value
329 */
330 protected Object interpolate(Object value)
331 {
332 return PropertyConverter.interpolate(value, getConfiguration()
333 .getParent());
334 }
335
336 /***
337 * Checks if the specified node is reserved and thus should be ignored. This
338 * method is called when the maps for the bean's properties and complex
339 * properties are collected. It checks whether the given node is an
340 * attribute node and if its name starts with the reserved prefix.
341 *
342 * @param nd the node to be checked
343 * @return a flag whether this node is reserved (and does not point to a
344 * property)
345 */
346 protected boolean isReservedNode(ConfigurationNode nd)
347 {
348 return nd.isAttribute()
349 && (nd.getName() == null || nd.getName().startsWith(
350 RESERVED_PREFIX));
351 }
352
353 /***
354 * Initializes the internally managed subnode configuration. This method
355 * will set some default values for some properties.
356 *
357 * @param conf the configuration to initialize
358 */
359 private void initSubnodeConfiguration(SubnodeConfiguration conf)
360 {
361 conf.setThrowExceptionOnMissing(false);
362 conf.setExpressionEngine(null);
363 }
364 }