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.lang.reflect.InvocationTargetException;
20  import java.util.HashMap;
21  import java.util.Iterator;
22  import java.util.Map;
23  import java.util.Set;
24  
25  import org.apache.commons.beanutils.BeanUtils;
26  import org.apache.commons.beanutils.PropertyUtils;
27  import org.apache.commons.configuration.ConfigurationRuntimeException;
28  import org.apache.commons.lang.ClassUtils;
29  
30  /***
31   * <p>
32   * A helper class for creating bean instances that are defined in configuration
33   * files.
34   * </p>
35   * <p>
36   * This class provides static utility methods related to bean creation
37   * operations. These methods simplify such operations because a client need not
38   * deal with all involved interfaces. Usually, if a bean declaration has already
39   * been obtained, a single method call is necessary to create a new bean
40   * instance.
41   * </p>
42   * <p>
43   * This class also supports the registration of custom bean factories.
44   * Implementations of the <code>{@link BeanFactory}</code> interface can be
45   * registered under a symbolic name using the <code>registerBeanFactory()</code>
46   * method. In the configuration file the name of the bean factory can be
47   * specified in the bean declaration. Then this factory will be used to create
48   * the bean.
49   * </p>
50   *
51   * @since 1.3
52   * @author Oliver Heger
53   * @version $Id: BeanHelper.java 508152 2007-02-15 21:16:37Z oheger $
54   */
55  public class BeanHelper
56  {
57      /*** Stores a map with the registered bean factories. */
58      private static Map beanFactories = new HashMap();
59  
60      /***
61       * Stores the default bean factory, which will be used if no other factory
62       * is provided.
63       */
64      private static BeanFactory defaultBeanFactory = DefaultBeanFactory.INSTANCE;
65  
66      /***
67       * Private constructor, so no instances can be created.
68       */
69      private BeanHelper()
70      {
71      }
72  
73      /***
74       * Register a bean factory under a symbolic name. This factory object can
75       * then be specified in bean declarations with the effect that this factory
76       * will be used to obtain an instance for the corresponding bean
77       * declaration.
78       *
79       * @param name the name of the factory
80       * @param factory the factory to be registered
81       */
82      public static void registerBeanFactory(String name, BeanFactory factory)
83      {
84          if (name == null)
85          {
86              throw new IllegalArgumentException(
87                      "Name for bean factory must not be null!");
88          }
89          if (factory == null)
90          {
91              throw new IllegalArgumentException("Bean factory must not be null!");
92          }
93  
94          beanFactories.put(name, factory);
95      }
96  
97      /***
98       * Deregisters the bean factory with the given name. After that this factory
99       * cannot be used any longer.
100      *
101      * @param name the name of the factory to be deregistered
102      * @return the factory that was registered under this name; <b>null</b> if
103      * there was no such factory
104      */
105     public static BeanFactory deregisterBeanFactory(String name)
106     {
107         return (BeanFactory) beanFactories.remove(name);
108     }
109 
110     /***
111      * Returns a set with the names of all currently registered bean factories.
112      *
113      * @return a set with the names of the registered bean factories
114      */
115     public static Set registeredFactoryNames()
116     {
117         return beanFactories.keySet();
118     }
119 
120     /***
121      * Returns the default bean factory.
122      *
123      * @return the default bean factory
124      */
125     public static BeanFactory getDefaultBeanFactory()
126     {
127         return defaultBeanFactory;
128     }
129 
130     /***
131      * Sets the default bean factory. This factory will be used for all create
132      * operations, for which no special factory is provided in the bean
133      * declaration.
134      *
135      * @param factory the default bean factory (must not be <b>null</b>)
136      */
137     public static void setDefaultBeanFactory(BeanFactory factory)
138     {
139         if (factory == null)
140         {
141             throw new IllegalArgumentException(
142                     "Default bean factory must not be null!");
143         }
144         defaultBeanFactory = factory;
145     }
146 
147     /***
148      * Initializes the passed in bean. This method will obtain all the bean's
149      * properties that are defined in the passed in bean declaration. These
150      * properties will be set on the bean. If necessary, further beans will be
151      * created recursively.
152      *
153      * @param bean the bean to be initialized
154      * @param data the bean declaration
155      * @throws ConfigurationRuntimeException if a property cannot be set
156      */
157     public static void initBean(Object bean, BeanDeclaration data)
158             throws ConfigurationRuntimeException
159     {
160         Map properties = data.getBeanProperties();
161         if (properties != null)
162         {
163             for (Iterator it = properties.keySet().iterator(); it.hasNext();)
164             {
165                 String propName = (String) it.next();
166                 initProperty(bean, propName, properties.get(propName));
167             }
168         }
169 
170         Map nestedBeans = data.getNestedBeanDeclarations();
171         if (nestedBeans != null)
172         {
173             for (Iterator it = nestedBeans.keySet().iterator(); it.hasNext();)
174             {
175                 String propName = (String) it.next();
176                 initProperty(bean, propName, createBean(
177                         (BeanDeclaration) nestedBeans.get(propName), null));
178             }
179         }
180     }
181 
182     /***
183      * Sets a property on the given bean using Common Beanutils.
184      *
185      * @param bean the bean
186      * @param propName the name of the property
187      * @param value the property's value
188      * @throws ConfigurationRuntimeException if the property is not writeable or
189      * an error occurred
190      */
191     private static void initProperty(Object bean, String propName, Object value)
192             throws ConfigurationRuntimeException
193     {
194         if (!PropertyUtils.isWriteable(bean, propName))
195         {
196             throw new ConfigurationRuntimeException("Property " + propName
197                     + " cannot be set!");
198         }
199 
200         try
201         {
202             BeanUtils.setProperty(bean, propName, value);
203         }
204         catch (IllegalAccessException iaex)
205         {
206             throw new ConfigurationRuntimeException(iaex);
207         }
208         catch (InvocationTargetException itex)
209         {
210             throw new ConfigurationRuntimeException(itex);
211         }
212     }
213 
214     /***
215      * The main method for creating and initializing beans from a configuration.
216      * This method will return an initialized instance of the bean class
217      * specified in the passed in bean declaration. If this declaration does not
218      * contain the class of the bean, the passed in default class will be used.
219      * From the bean declaration the factory to be used for creating the bean is
220      * queried. The declaration may here return <b>null</b>, then a default
221      * factory is used. This factory is then invoked to perform the create
222      * operation.
223      *
224      * @param data the bean declaration
225      * @param defaultClass the default class to use
226      * @param param an additional parameter that will be passed to the bean
227      * factory; some factories may support parameters and behave different
228      * depending on the value passed in here
229      * @return the new bean
230      * @throws ConfigurationRuntimeException if an error occurs
231      */
232     public static Object createBean(BeanDeclaration data, Class defaultClass,
233             Object param) throws ConfigurationRuntimeException
234     {
235         if (data == null)
236         {
237             throw new IllegalArgumentException(
238                     "Bean declaration must not be null!");
239         }
240 
241         BeanFactory factory = fetchBeanFactory(data);
242         try
243         {
244             return factory.createBean(fetchBeanClass(data, defaultClass,
245                     factory), data, param);
246         }
247         catch (Exception ex)
248         {
249             throw new ConfigurationRuntimeException(ex);
250         }
251     }
252 
253     /***
254      * Returns a bean instance for the specified declaration. This method is a
255      * short cut for <code>createBean(data, null, null);</code>.
256      *
257      * @param data the bean declaration
258      * @param defaultClass the class to be used when in the declation no class
259      * is specified
260      * @return the new bean
261      * @throws ConfigurationRuntimeException if an error occurs
262      */
263     public static Object createBean(BeanDeclaration data, Class defaultClass)
264             throws ConfigurationRuntimeException
265     {
266         return createBean(data, defaultClass, null);
267     }
268 
269     /***
270      * Returns a bean instance for the specified declaration. This method is a
271      * short cut for <code>createBean(data, null);</code>.
272      *
273      * @param data the bean declaration
274      * @return the new bean
275      * @throws ConfigurationRuntimeException if an error occurs
276      */
277     public static Object createBean(BeanDeclaration data)
278             throws ConfigurationRuntimeException
279     {
280         return createBean(data, null);
281     }
282 
283     /***
284      * Returns a <code>java.lang.Class</code> object for the specified name.
285      * This method and the helper method it invokes are very similar to code
286      * extracted from the <code>ClassLoaderUtils</code> class of Commons
287      * Jelly. It should be replaced if Commons Lang provides a generic version.
288      *
289      * @param name the name of the class to be loaded
290      * @param callingClass the calling class
291      * @return the class object for the specified name
292      * @throws ClassNotFoundException if the class cannot be loaded
293      */
294     static Class loadClass(String name, Class callingClass)
295             throws ClassNotFoundException
296     {
297         return ClassUtils.getClass(name);
298     }
299 
300     /***
301      * Determines the class of the bean to be created. If the bean declaration
302      * contains a class name, this class is used. Otherwise it is checked
303      * whether a default class is provided. If this is not the case, the
304      * factory's default class is used. If this class is undefined, too, an
305      * exception is thrown.
306      *
307      * @param data the bean declaration
308      * @param defaultClass the default class
309      * @param factory the bean factory to use
310      * @return the class of the bean to be created
311      * @throws ConfigurationRuntimeException if the class cannot be determined
312      */
313     private static Class fetchBeanClass(BeanDeclaration data,
314             Class defaultClass, BeanFactory factory)
315             throws ConfigurationRuntimeException
316     {
317         String clsName = data.getBeanClassName();
318         if (clsName != null)
319         {
320             try
321             {
322                 return loadClass(clsName, factory.getClass());
323             }
324             catch (ClassNotFoundException cex)
325             {
326                 throw new ConfigurationRuntimeException(cex);
327             }
328         }
329 
330         if (defaultClass != null)
331         {
332             return defaultClass;
333         }
334 
335         Class clazz = factory.getDefaultBeanClass();
336         if (clazz == null)
337         {
338             throw new ConfigurationRuntimeException(
339                     "Bean class is not specified!");
340         }
341         return clazz;
342     }
343 
344     /***
345      * Obtains the bean factory to use for creating the specified bean. This
346      * method will check whether a factory is specified in the bean declaration.
347      * If this is not the case, the default bean factory will be used.
348      *
349      * @param data the bean declaration
350      * @return the bean factory to use
351      * @throws ConfigurationRuntimeException if the factory cannot be determined
352      */
353     private static BeanFactory fetchBeanFactory(BeanDeclaration data)
354             throws ConfigurationRuntimeException
355     {
356         String factoryName = data.getBeanFactoryName();
357         if (factoryName != null)
358         {
359             BeanFactory factory = (BeanFactory) beanFactories.get(factoryName);
360             if (factory == null)
361             {
362                 throw new ConfigurationRuntimeException(
363                         "Unknown bean factory: " + factoryName);
364             }
365             else
366             {
367                 return factory;
368             }
369         }
370         else
371         {
372             return getDefaultBeanFactory();
373         }
374     }
375 }