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.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   *   &lt;personBean config-class=&quot;my.model.PersonBean&quot;
44   *       lastName=&quot;Doe&quot; firstName=&quot;John&quot;&gt;
45   *       &lt;address config-class=&quot;my.model.AddressBean&quot;
46   *           street=&quot;21st street 11&quot; zip=&quot;1234&quot;
47   *           city=&quot;TestCity&quot;/&gt;
48   *   &lt;/personBean&gt;
49   * </pre>
50   *
51   * </p>
52   * <p>
53   * The bean declaration can be contained in an arbitrary element. Here it is the
54   * <code>&lt;personBean&gt;</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             // 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(), 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 }