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