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.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 }