Classes in this File | Line Coverage | Branch Coverage | Complexity | ||||
BeanHelper |
|
| 3.3333333333333335;3,333 |
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 | 8 | 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 | 4 | private static BeanFactory defaultBeanFactory = DefaultBeanFactory.INSTANCE; |
64 | ||
65 | /** | |
66 | * Private constructor, so no instances can be created. | |
67 | */ | |
68 | private BeanHelper() | |
69 | 0 | { |
70 | 0 | } |
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 | 13 | if (name == null) |
84 | { | |
85 | 1 | throw new IllegalArgumentException( |
86 | "Name for bean factory must not be null!"); | |
87 | } | |
88 | 12 | if (factory == null) |
89 | { | |
90 | 1 | throw new IllegalArgumentException("Bean factory must not be null!"); |
91 | } | |
92 | ||
93 | 11 | beanFactories.put(name, factory); |
94 | 11 | } |
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 | 11 | 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 | 42 | 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 | 66 | 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 | 22 | if (factory == null) |
139 | { | |
140 | 1 | throw new IllegalArgumentException( |
141 | "Default bean factory must not be null!"); | |
142 | } | |
143 | 21 | defaultBeanFactory = factory; |
144 | 21 | } |
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 | 102 | Map properties = data.getBeanProperties(); |
160 | 102 | if (properties != null) |
161 | { | |
162 | 299 | for (Iterator it = properties.keySet().iterator(); it.hasNext();) |
163 | { | |
164 | 98 | String propName = (String) it.next(); |
165 | 98 | initProperty(bean, propName, properties.get(propName)); |
166 | } | |
167 | } | |
168 | ||
169 | 101 | Map nestedBeans = data.getNestedBeanDeclarations(); |
170 | 101 | if (nestedBeans != null) |
171 | { | |
172 | 204 | for (Iterator it = nestedBeans.keySet().iterator(); it.hasNext();) |
173 | { | |
174 | 18 | String propName = (String) it.next(); |
175 | 18 | initProperty(bean, propName, createBean( |
176 | (BeanDeclaration) nestedBeans.get(propName), null)); | |
177 | } | |
178 | } | |
179 | 101 | } |
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 | 116 | if (!PropertyUtils.isWriteable(bean, propName)) |
194 | { | |
195 | 1 | throw new ConfigurationRuntimeException("Property " + propName |
196 | + " cannot be set!"); | |
197 | } | |
198 | ||
199 | try | |
200 | { | |
201 | 115 | BeanUtils.setProperty(bean, propName, value); |
202 | 115 | } |
203 | catch (IllegalAccessException iaex) | |
204 | { | |
205 | 0 | throw new ConfigurationRuntimeException(iaex); |
206 | } | |
207 | catch (InvocationTargetException itex) | |
208 | { | |
209 | 0 | throw new ConfigurationRuntimeException(itex); |
210 | } | |
211 | 115 | } |
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 | 104 | if (data == null) |
235 | { | |
236 | 1 | throw new IllegalArgumentException( |
237 | "Bean declaration must not be null!"); | |
238 | } | |
239 | ||
240 | 103 | BeanFactory factory = fetchBeanFactory(data); |
241 | try | |
242 | { | |
243 | 102 | return factory.createBean(fetchBeanClass(data, defaultClass, |
244 | factory), data, param); | |
245 | } | |
246 | catch (Exception ex) | |
247 | { | |
248 | 5 | 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 | 103 | 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 | 60 | 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 | 23 | ClassLoader loader = findClassLoader(callingClass); |
297 | 23 | 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 | 23 | ClassLoader loader = Thread.currentThread().getContextClassLoader(); |
310 | 23 | if (loader == null) |
311 | { | |
312 | 0 | loader = callingClass.getClassLoader(); |
313 | 0 | if (loader == null) |
314 | { | |
315 | 0 | loader = ClassLoader.getSystemClassLoader(); |
316 | } | |
317 | } | |
318 | 23 | 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 | 102 | String clsName = data.getBeanClassName(); |
339 | 102 | if (clsName != null) |
340 | { | |
341 | try | |
342 | { | |
343 | 23 | return loadClass(clsName, factory.getClass()); |
344 | } | |
345 | catch (ClassNotFoundException cex) | |
346 | { | |
347 | 1 | throw new ConfigurationRuntimeException(cex); |
348 | } | |
349 | } | |
350 | ||
351 | 79 | if (defaultClass != null) |
352 | { | |
353 | 18 | return defaultClass; |
354 | } | |
355 | ||
356 | 61 | Class clazz = factory.getDefaultBeanClass(); |
357 | 61 | if (clazz == null) |
358 | { | |
359 | 1 | throw new ConfigurationRuntimeException( |
360 | "Bean class is not specified!"); | |
361 | } | |
362 | 60 | 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 | 103 | String factoryName = data.getBeanFactoryName(); |
378 | 103 | if (factoryName != null) |
379 | { | |
380 | 67 | BeanFactory factory = (BeanFactory) beanFactories.get(factoryName); |
381 | 67 | if (factory == null) |
382 | { | |
383 | 1 | throw new ConfigurationRuntimeException( |
384 | "Unknown bean factory: " + factoryName); | |
385 | } | |
386 | else | |
387 | { | |
388 | 66 | return factory; |
389 | } | |
390 | } | |
391 | else | |
392 | { | |
393 | 36 | return getDefaultBeanFactory(); |
394 | } | |
395 | } | |
396 | } |