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