Classes in this File | Line Coverage | Branch Coverage | Complexity | ||||
BeanHelper |
|
| 3.357142857142857;3,357 |
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.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 570462 2007-08-28 15:56:49Z oheger $ | |
55 | */ | |
56 | public class BeanHelper | |
57 | { | |
58 | /** Stores a map with the registered bean factories. */ | |
59 | 4 | 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 | 4 | private static BeanFactory defaultBeanFactory = DefaultBeanFactory.INSTANCE; |
66 | ||
67 | /** | |
68 | * Private constructor, so no instances can be created. | |
69 | */ | |
70 | private BeanHelper() | |
71 | 0 | { |
72 | 0 | } |
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 | 13 | if (name == null) |
86 | { | |
87 | 1 | throw new IllegalArgumentException( |
88 | "Name for bean factory must not be null!"); | |
89 | } | |
90 | 12 | if (factory == null) |
91 | { | |
92 | 1 | throw new IllegalArgumentException("Bean factory must not be null!"); |
93 | } | |
94 | ||
95 | 11 | beanFactories.put(name, factory); |
96 | 11 | } |
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 | 11 | 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 | 122 | 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 | 71 | 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 | 22 | if (factory == null) |
141 | { | |
142 | 1 | throw new IllegalArgumentException( |
143 | "Default bean factory must not be null!"); | |
144 | } | |
145 | 21 | defaultBeanFactory = factory; |
146 | 21 | } |
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 | 114 | Map properties = data.getBeanProperties(); |
162 | 114 | if (properties != null) |
163 | { | |
164 | 113 | for (Iterator it = properties.keySet().iterator(); it.hasNext();) |
165 | { | |
166 | 107 | String propName = (String) it.next(); |
167 | 107 | initProperty(bean, propName, properties.get(propName)); |
168 | 106 | } |
169 | } | |
170 | ||
171 | 113 | Map nestedBeans = data.getNestedBeanDeclarations(); |
172 | 113 | if (nestedBeans != null) |
173 | { | |
174 | 105 | for (Iterator it = nestedBeans.keySet().iterator(); it.hasNext();) |
175 | { | |
176 | 18 | String propName = (String) it.next(); |
177 | 18 | initProperty(bean, propName, createBean( |
178 | (BeanDeclaration) nestedBeans.get(propName), null)); | |
179 | 18 | } |
180 | } | |
181 | 113 | } |
182 | ||
183 | /** | |
184 | * Sets a property on the given bean using Common Beanutils. | |
185 | * | |
186 | * @param bean the bean | |
187 | * @param propName the name of the property | |
188 | * @param value the property's value | |
189 | * @throws ConfigurationRuntimeException if the property is not writeable or | |
190 | * an error occurred | |
191 | */ | |
192 | private static void initProperty(Object bean, String propName, Object value) | |
193 | throws ConfigurationRuntimeException | |
194 | { | |
195 | 125 | if (!PropertyUtils.isWriteable(bean, propName)) |
196 | { | |
197 | 1 | throw new ConfigurationRuntimeException("Property " + propName |
198 | + " cannot be set!"); | |
199 | } | |
200 | ||
201 | try | |
202 | { | |
203 | 124 | BeanUtils.setProperty(bean, propName, value); |
204 | } | |
205 | 0 | catch (IllegalAccessException iaex) |
206 | { | |
207 | 0 | throw new ConfigurationRuntimeException(iaex); |
208 | } | |
209 | 0 | catch (InvocationTargetException itex) |
210 | { | |
211 | 0 | throw new ConfigurationRuntimeException(itex); |
212 | 124 | } |
213 | 124 | } |
214 | ||
215 | /** | |
216 | * The main method for creating and initializing beans from a configuration. | |
217 | * This method will return an initialized instance of the bean class | |
218 | * specified in the passed in bean declaration. If this declaration does not | |
219 | * contain the class of the bean, the passed in default class will be used. | |
220 | * From the bean declaration the factory to be used for creating the bean is | |
221 | * queried. The declaration may here return <b>null</b>, then a default | |
222 | * factory is used. This factory is then invoked to perform the create | |
223 | * operation. | |
224 | * | |
225 | * @param data the bean declaration | |
226 | * @param defaultClass the default class to use | |
227 | * @param param an additional parameter that will be passed to the bean | |
228 | * factory; some factories may support parameters and behave different | |
229 | * depending on the value passed in here | |
230 | * @return the new bean | |
231 | * @throws ConfigurationRuntimeException if an error occurs | |
232 | */ | |
233 | public static Object createBean(BeanDeclaration data, Class defaultClass, | |
234 | Object param) throws ConfigurationRuntimeException | |
235 | { | |
236 | 115 | if (data == null) |
237 | { | |
238 | 1 | throw new IllegalArgumentException( |
239 | "Bean declaration must not be null!"); | |
240 | } | |
241 | ||
242 | 114 | BeanFactory factory = fetchBeanFactory(data); |
243 | try | |
244 | { | |
245 | 113 | return factory.createBean(fetchBeanClass(data, defaultClass, |
246 | factory), data, param); | |
247 | } | |
248 | 5 | catch (Exception ex) |
249 | { | |
250 | 5 | throw new ConfigurationRuntimeException(ex); |
251 | } | |
252 | } | |
253 | ||
254 | /** | |
255 | * Returns a bean instance for the specified declaration. This method is a | |
256 | * short cut for <code>createBean(data, null, null);</code>. | |
257 | * | |
258 | * @param data the bean declaration | |
259 | * @param defaultClass the class to be used when in the declation no class | |
260 | * is specified | |
261 | * @return the new bean | |
262 | * @throws ConfigurationRuntimeException if an error occurs | |
263 | */ | |
264 | public static Object createBean(BeanDeclaration data, Class defaultClass) | |
265 | throws ConfigurationRuntimeException | |
266 | { | |
267 | 114 | return createBean(data, defaultClass, null); |
268 | } | |
269 | ||
270 | /** | |
271 | * Returns a bean instance for the specified declaration. This method is a | |
272 | * short cut for <code>createBean(data, null);</code>. | |
273 | * | |
274 | * @param data the bean declaration | |
275 | * @return the new bean | |
276 | * @throws ConfigurationRuntimeException if an error occurs | |
277 | */ | |
278 | public static Object createBean(BeanDeclaration data) | |
279 | throws ConfigurationRuntimeException | |
280 | { | |
281 | 66 | return createBean(data, null); |
282 | } | |
283 | ||
284 | /** | |
285 | * Returns a <code>java.lang.Class</code> object for the specified name. | |
286 | * This method and the helper method it invokes are very similar to code | |
287 | * extracted from the <code>ClassLoaderUtils</code> class of Commons | |
288 | * Jelly. It should be replaced if Commons Lang provides a generic version. | |
289 | * | |
290 | * @param name the name of the class to be loaded | |
291 | * @param callingClass the calling class | |
292 | * @return the class object for the specified name | |
293 | * @throws ClassNotFoundException if the class cannot be loaded | |
294 | */ | |
295 | static Class loadClass(String name, Class callingClass) | |
296 | throws ClassNotFoundException | |
297 | { | |
298 | 23 | return ClassUtils.getClass(name); |
299 | } | |
300 | ||
301 | /** | |
302 | * Determines the class of the bean to be created. If the bean declaration | |
303 | * contains a class name, this class is used. Otherwise it is checked | |
304 | * whether a default class is provided. If this is not the case, the | |
305 | * factory's default class is used. If this class is undefined, too, an | |
306 | * exception is thrown. | |
307 | * | |
308 | * @param data the bean declaration | |
309 | * @param defaultClass the default class | |
310 | * @param factory the bean factory to use | |
311 | * @return the class of the bean to be created | |
312 | * @throws ConfigurationRuntimeException if the class cannot be determined | |
313 | */ | |
314 | private static Class fetchBeanClass(BeanDeclaration data, | |
315 | Class defaultClass, BeanFactory factory) | |
316 | throws ConfigurationRuntimeException | |
317 | { | |
318 | 113 | String clsName = data.getBeanClassName(); |
319 | 113 | if (clsName != null) |
320 | { | |
321 | try | |
322 | { | |
323 | 23 | return loadClass(clsName, factory.getClass()); |
324 | } | |
325 | 1 | catch (ClassNotFoundException cex) |
326 | { | |
327 | 1 | throw new ConfigurationRuntimeException(cex); |
328 | } | |
329 | } | |
330 | ||
331 | 90 | if (defaultClass != null) |
332 | { | |
333 | 23 | return defaultClass; |
334 | } | |
335 | ||
336 | 67 | Class clazz = factory.getDefaultBeanClass(); |
337 | 67 | if (clazz == null) |
338 | { | |
339 | 1 | throw new ConfigurationRuntimeException( |
340 | "Bean class is not specified!"); | |
341 | } | |
342 | 66 | return clazz; |
343 | } | |
344 | ||
345 | /** | |
346 | * Obtains the bean factory to use for creating the specified bean. This | |
347 | * method will check whether a factory is specified in the bean declaration. | |
348 | * If this is not the case, the default bean factory will be used. | |
349 | * | |
350 | * @param data the bean declaration | |
351 | * @return the bean factory to use | |
352 | * @throws ConfigurationRuntimeException if the factory cannot be determined | |
353 | */ | |
354 | private static BeanFactory fetchBeanFactory(BeanDeclaration data) | |
355 | throws ConfigurationRuntimeException | |
356 | { | |
357 | 114 | String factoryName = data.getBeanFactoryName(); |
358 | 114 | if (factoryName != null) |
359 | { | |
360 | 73 | BeanFactory factory = (BeanFactory) beanFactories.get(factoryName); |
361 | 73 | if (factory == null) |
362 | { | |
363 | 1 | throw new ConfigurationRuntimeException( |
364 | "Unknown bean factory: " + factoryName); | |
365 | } | |
366 | else | |
367 | { | |
368 | 72 | return factory; |
369 | } | |
370 | } | |
371 | else | |
372 | { | |
373 | 41 | return getDefaultBeanFactory(); |
374 | } | |
375 | } | |
376 | } |