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