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.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 | 8 | 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 | 4 | private static BeanFactory defaultBeanFactory = DefaultBeanFactory.INSTANCE; |
65 | ||
66 | /** | |
67 | * Private constructor, so no instances can be created. | |
68 | */ | |
69 | private BeanHelper() | |
70 | 0 | { |
71 | 0 | } |
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 | 13 | if (name == null) |
85 | { | |
86 | 1 | throw new IllegalArgumentException( |
87 | "Name for bean factory must not be null!"); | |
88 | } | |
89 | 12 | if (factory == null) |
90 | { | |
91 | 1 | throw new IllegalArgumentException("Bean factory must not be null!"); |
92 | } | |
93 | ||
94 | 11 | beanFactories.put(name, factory); |
95 | 11 | } |
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 | 11 | 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 | 42 | 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 | 69 | 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 | 22 | if (factory == null) |
140 | { | |
141 | 1 | throw new IllegalArgumentException( |
142 | "Default bean factory must not be null!"); | |
143 | } | |
144 | 21 | defaultBeanFactory = factory; |
145 | 21 | } |
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 | 109 | Map properties = data.getBeanProperties(); |
161 | 109 | if (properties != null) |
162 | { | |
163 | 317 | for (Iterator it = properties.keySet().iterator(); it.hasNext();) |
164 | { | |
165 | 102 | String propName = (String) it.next(); |
166 | 102 | initProperty(bean, propName, properties.get(propName)); |
167 | } | |
168 | } | |
169 | ||
170 | 108 | Map nestedBeans = data.getNestedBeanDeclarations(); |
171 | 108 | if (nestedBeans != null) |
172 | { | |
173 | 218 | for (Iterator it = nestedBeans.keySet().iterator(); it.hasNext();) |
174 | { | |
175 | 18 | String propName = (String) it.next(); |
176 | 18 | initProperty(bean, propName, createBean( |
177 | (BeanDeclaration) nestedBeans.get(propName), null)); | |
178 | } | |
179 | } | |
180 | 108 | } |
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 | 120 | if (!PropertyUtils.isWriteable(bean, propName)) |
195 | { | |
196 | 1 | throw new ConfigurationRuntimeException("Property " + propName |
197 | + " cannot be set!"); | |
198 | } | |
199 | ||
200 | try | |
201 | { | |
202 | 119 | BeanUtils.setProperty(bean, propName, value); |
203 | 119 | } |
204 | catch (IllegalAccessException iaex) | |
205 | { | |
206 | 0 | throw new ConfigurationRuntimeException(iaex); |
207 | } | |
208 | catch (InvocationTargetException itex) | |
209 | { | |
210 | 0 | throw new ConfigurationRuntimeException(itex); |
211 | } | |
212 | 119 | } |
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 | 110 | if (data == null) |
236 | { | |
237 | 1 | throw new IllegalArgumentException( |
238 | "Bean declaration must not be null!"); | |
239 | } | |
240 | ||
241 | 109 | BeanFactory factory = fetchBeanFactory(data); |
242 | try | |
243 | { | |
244 | 108 | return factory.createBean(fetchBeanClass(data, defaultClass, |
245 | factory), data, param); | |
246 | } | |
247 | catch (Exception ex) | |
248 | { | |
249 | 5 | 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 | 109 | 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 | 63 | 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 | 23 | 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 | 108 | String clsName = data.getBeanClassName(); |
318 | 108 | if (clsName != null) |
319 | { | |
320 | try | |
321 | { | |
322 | 23 | return loadClass(clsName, factory.getClass()); |
323 | } | |
324 | catch (ClassNotFoundException cex) | |
325 | { | |
326 | 1 | throw new ConfigurationRuntimeException(cex); |
327 | } | |
328 | } | |
329 | ||
330 | 85 | if (defaultClass != null) |
331 | { | |
332 | 21 | return defaultClass; |
333 | } | |
334 | ||
335 | 64 | Class clazz = factory.getDefaultBeanClass(); |
336 | 64 | if (clazz == null) |
337 | { | |
338 | 1 | throw new ConfigurationRuntimeException( |
339 | "Bean class is not specified!"); | |
340 | } | |
341 | 63 | 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 | 109 | String factoryName = data.getBeanFactoryName(); |
357 | 109 | if (factoryName != null) |
358 | { | |
359 | 70 | BeanFactory factory = (BeanFactory) beanFactories.get(factoryName); |
360 | 70 | if (factory == null) |
361 | { | |
362 | 1 | throw new ConfigurationRuntimeException( |
363 | "Unknown bean factory: " + factoryName); | |
364 | } | |
365 | else | |
366 | { | |
367 | 69 | return factory; |
368 | } | |
369 | } | |
370 | else | |
371 | { | |
372 | 39 | return getDefaultBeanFactory(); |
373 | } | |
374 | } | |
375 | } |