1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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 }