View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *     http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.configuration.beanutils;
18  
19  import java.util.ArrayList;
20  import java.util.HashMap;
21  import java.util.Iterator;
22  import java.util.List;
23  import java.util.Map;
24  
25  import org.apache.commons.configuration.ConfigurationRuntimeException;
26  import org.apache.commons.configuration.HierarchicalConfiguration;
27  import org.apache.commons.configuration.PropertyConverter;
28  import org.apache.commons.configuration.SubnodeConfiguration;
29  import org.apache.commons.configuration.tree.ConfigurationNode;
30  import org.apache.commons.configuration.tree.DefaultConfigurationNode;
31  
32  /**
33   * <p>
34   * An implementation of the {@code BeanDeclaration} interface that is
35   * suitable for XML configuration files.
36   * </p>
37   * <p>
38   * This class defines the standard layout of a bean declaration in an XML
39   * configuration file. Such a declaration must look like the following example
40   * fragment:
41   * </p>
42   * <p>
43   *
44   * <pre>
45   *   ...
46   *   &lt;personBean config-class=&quot;my.model.PersonBean&quot;
47   *       lastName=&quot;Doe&quot; firstName=&quot;John&quot;&gt;
48   *       &lt;address config-class=&quot;my.model.AddressBean&quot;
49   *           street=&quot;21st street 11&quot; zip=&quot;1234&quot;
50   *           city=&quot;TestCity&quot;/&gt;
51   *   &lt;/personBean&gt;
52   * </pre>
53   *
54   * </p>
55   * <p>
56   * The bean declaration can be contained in an arbitrary element. Here it is the
57   * {@code personBean} element. In the attributes of this element
58   * there can occur some reserved attributes, which have the following meaning:
59   * <dl>
60   * <dt>{@code config-class}</dt>
61   * <dd>Here the full qualified name of the bean's class can be specified. An
62   * instance of this class will be created. If this attribute is not specified,
63   * the bean class must be provided in another way, e.g. as the
64   * {@code defaultClass} passed to the {@code BeanHelper} class.</dd>
65   * <dt>{@code config-factory}</dt>
66   * <dd>This attribute can contain the name of the
67   * {@link BeanFactory} that should be used for creating the bean.
68   * If it is defined, a factory with this name must have been registered at the
69   * {@code BeanHelper} class. If this attribute is missing, the default
70   * bean factory will be used.</dd>
71   * <dt>{@code config-factoryParam}</dt>
72   * <dd>With this attribute a parameter can be specified that will be passed to
73   * the bean factory. This may be useful for custom bean factories.</dd>
74   * </dl>
75   * </p>
76   * <p>
77   * All further attributes starting with the {@code config-} prefix are
78   * considered as meta data and will be ignored. All other attributes are treated
79   * as properties of the bean to be created, i.e. corresponding setter methods of
80   * the bean will be invoked with the values specified here.
81   * </p>
82   * <p>
83   * If the bean to be created has also some complex properties (which are itself
84   * beans), their values cannot be initialized from attributes. For this purpose
85   * nested elements can be used. The example listing shows how an address bean
86   * can be initialized. This is done in a nested element whose name must match
87   * the name of a property of the enclosing bean declaration. The format of this
88   * nested element is exactly the same as for the bean declaration itself, i.e.
89   * it can have attributes defining meta data or bean properties and even further
90   * nested elements for complex bean properties.
91   * </p>
92   * <p>
93   * A {@code XMLBeanDeclaration} object is usually created from a
94   * {@code HierarchicalConfiguration}. From this it will derive a
95   * {@code SubnodeConfiguration}, which is used to access the needed
96   * properties. This subnode configuration can be obtained using the
97   * {@link #getConfiguration()} method. All of its properties can
98   * be accessed in the usual way. To ensure that the property keys used by this
99   * 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 1301959 2012-03-17 16:43:18Z 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 final SubnodeConfiguration configuration;
129 
130     /** Stores the configuration node that contains the bean declaration. */
131     private final 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         SubnodeConfiguration tmpconfiguration = null;
174         ConfigurationNode tmpnode = null;
175         try
176         {
177             tmpconfiguration = config.configurationAt(key);
178             tmpnode = tmpconfiguration.getRootNode();
179         }
180         catch (IllegalArgumentException iex)
181         {
182             // If we reach this block, the key does not have exactly one value
183             if (!optional || config.getMaxIndex(key) > 0)
184             {
185                 throw iex;
186             }
187             tmpconfiguration = config.configurationAt(null);
188             tmpnode = new DefaultConfigurationNode();
189         }
190         this.node = tmpnode;
191         this.configuration = tmpconfiguration;
192         initSubnodeConfiguration(getConfiguration());
193     }
194 
195     /**
196      * Creates a new instance of {@code XMLBeanDeclaration} and
197      * initializes it from the given configuration. The configuration's root
198      * node must contain the bean declaration.
199      *
200      * @param config the configuration with the bean declaration
201      */
202     public XMLBeanDeclaration(HierarchicalConfiguration config)
203     {
204         this(config, (String) null);
205     }
206 
207     /**
208      * Creates a new instance of {@code XMLBeanDeclaration} and
209      * initializes it with the configuration node that contains the bean
210      * declaration.
211      *
212      * @param config the configuration
213      * @param node the node with the bean declaration.
214      */
215     public XMLBeanDeclaration(SubnodeConfiguration config,
216             ConfigurationNode node)
217     {
218         if (config == null)
219         {
220             throw new IllegalArgumentException(
221                     "Configuration must not be null!");
222         }
223         if (node == null)
224         {
225             throw new IllegalArgumentException("Node must not be null!");
226         }
227 
228         this.node = node;
229         configuration = config;
230         initSubnodeConfiguration(config);
231     }
232 
233     /**
234      * Returns the configuration object this bean declaration is based on.
235      *
236      * @return the associated configuration
237      */
238     public SubnodeConfiguration getConfiguration()
239     {
240         return configuration;
241     }
242 
243     /**
244      * Returns the node that contains the bean declaration.
245      *
246      * @return the configuration node this bean declaration is based on
247      */
248     public ConfigurationNode getNode()
249     {
250         return node;
251     }
252 
253     /**
254      * Returns the name of the bean factory. This information is fetched from
255      * the {@code config-factory} attribute.
256      *
257      * @return the name of the bean factory
258      */
259     public String getBeanFactoryName()
260     {
261         return getConfiguration().getString(ATTR_BEAN_FACTORY);
262     }
263 
264     /**
265      * Returns a parameter for the bean factory. This information is fetched
266      * from the {@code config-factoryParam} attribute.
267      *
268      * @return the parameter for the bean factory
269      */
270     public Object getBeanFactoryParameter()
271     {
272         return getConfiguration().getProperty(ATTR_FACTORY_PARAM);
273     }
274 
275     /**
276      * Returns the name of the class of the bean to be created. This information
277      * is obtained from the {@code config-class} attribute.
278      *
279      * @return the name of the bean's class
280      */
281     public String getBeanClassName()
282     {
283         return getConfiguration().getString(ATTR_BEAN_CLASS);
284     }
285 
286     /**
287      * Returns a map with the bean's (simple) properties. The properties are
288      * collected from all attribute nodes, which are not reserved.
289      *
290      * @return a map with the bean's properties
291      */
292     public Map<String, Object> getBeanProperties()
293     {
294         Map<String, Object> props = new HashMap<String, Object>();
295         for (ConfigurationNode attr : getNode().getAttributes())
296         {
297             if (!isReservedNode(attr))
298             {
299                 props.put(attr.getName(), interpolate(attr .getValue()));
300             }
301         }
302 
303         return props;
304     }
305 
306     /**
307      * Returns a map with bean declarations for the complex properties of the
308      * bean to be created. These declarations are obtained from the child nodes
309      * of this declaration's root node.
310      *
311      * @return a map with bean declarations for complex properties
312      */
313     public Map<String, Object> getNestedBeanDeclarations()
314     {
315         Map<String, Object> nested = new HashMap<String, Object>();
316         for (ConfigurationNode child : getNode().getChildren())
317         {
318             if (!isReservedNode(child))
319             {
320                 if (nested.containsKey(child.getName()))
321                 {
322                     Object obj = nested.get(child.getName());
323                     List<BeanDeclaration> list;
324                     if (obj instanceof List)
325                     {
326                         // Safe because we created the lists ourselves.
327                         @SuppressWarnings("unchecked")
328                         List<BeanDeclaration> tmpList = (List<BeanDeclaration>) obj;
329                         list = tmpList;
330                     }
331                     else
332                     {
333                         list = new ArrayList<BeanDeclaration>();
334                         list.add((BeanDeclaration) obj);
335                         nested.put(child.getName(), list);
336                     }
337                     list.add(createBeanDeclaration(child));
338                 }
339                 else
340                 {
341                     nested.put(child.getName(), createBeanDeclaration(child));
342                 }
343             }
344         }
345 
346         return nested;
347     }
348 
349     /**
350      * Performs interpolation for the specified value. This implementation will
351      * interpolate against the current subnode configuration's parent. If sub
352      * classes need a different interpolation mechanism, they should override
353      * this method.
354      *
355      * @param value the value that is to be interpolated
356      * @return the interpolated value
357      */
358     protected Object interpolate(Object value)
359     {
360         return PropertyConverter.interpolate(value, getConfiguration()
361                 .getParent());
362     }
363 
364     /**
365      * Checks if the specified node is reserved and thus should be ignored. This
366      * method is called when the maps for the bean's properties and complex
367      * properties are collected. It checks whether the given node is an
368      * attribute node and if its name starts with the reserved prefix.
369      *
370      * @param nd the node to be checked
371      * @return a flag whether this node is reserved (and does not point to a
372      * property)
373      */
374     protected boolean isReservedNode(ConfigurationNode nd)
375     {
376         return nd.isAttribute()
377                 && (nd.getName() == null || nd.getName().startsWith(
378                         RESERVED_PREFIX));
379     }
380 
381     /**
382      * Creates a new {@code BeanDeclaration} for a child node of the
383      * current configuration node. This method is called by
384      * {@code getNestedBeanDeclarations()} for all complex sub properties
385      * detected by this method. Derived classes can hook in if they need a
386      * specific initialization. This base implementation creates a
387      * {@code XMLBeanDeclaration} that is properly initialized from the
388      * passed in node.
389      *
390      * @param node the child node, for which a {@code BeanDeclaration} is
391      *        to be created
392      * @return the {@code BeanDeclaration} for this child node
393      * @since 1.6
394      */
395     protected BeanDeclaration createBeanDeclaration(ConfigurationNode node)
396     {
397         List<HierarchicalConfiguration> list = getConfiguration().configurationsAt(node.getName());
398         if (list.size() == 1)
399         {
400             return new XMLBeanDeclaration((SubnodeConfiguration) list.get(0), node);
401         }
402         else
403         {
404             Iterator<HierarchicalConfiguration> iter = list.iterator();
405             while (iter.hasNext())
406             {
407                 SubnodeConfiguration config = (SubnodeConfiguration) iter.next();
408                 if (config.getRootNode().equals(node))
409                 {
410                     return new XMLBeanDeclaration(config, node);
411                 }
412             }
413             throw new ConfigurationRuntimeException("Unable to match node for " + node.getName());
414         }
415     }
416 
417     /**
418      * Initializes the internally managed subnode configuration. This method
419      * will set some default values for some properties.
420      *
421      * @param conf the configuration to initialize
422      */
423     private void initSubnodeConfiguration(SubnodeConfiguration conf)
424     {
425         conf.setThrowExceptionOnMissing(false);
426         conf.setExpressionEngine(null);
427     }
428 }