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