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