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 727168 2008-12-16 21:44:29Z 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 | 127 | 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 | 98 | 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 | 163 | Map properties = data.getBeanProperties(); |
162 | 163 | if (properties != null) |
163 | { | |
164 | 162 | for (Iterator it = properties.entrySet().iterator(); it.hasNext();) |
165 | { | |
166 | 152 | Map.Entry e = (Map.Entry) it.next(); |
167 | 152 | String propName = (String) e.getKey(); |
168 | 152 | initProperty(bean, propName, e.getValue()); |
169 | } | |
170 | } | |
171 | ||
172 | 162 | Map nestedBeans = data.getNestedBeanDeclarations(); |
173 | 162 | if (nestedBeans != null) |
174 | { | |
175 | 154 | for (Iterator it = nestedBeans.entrySet().iterator(); it.hasNext();) |
176 | { | |
177 | 37 | Map.Entry e = (Map.Entry) it.next(); |
178 | 37 | String propName = (String) e.getKey(); |
179 | 37 | initProperty(bean, propName, createBean( |
180 | (BeanDeclaration) e.getValue(), null)); | |
181 | } | |
182 | } | |
183 | 162 | } |
184 | ||
185 | /** | |
186 | * Sets a property on the given bean using Common Beanutils. | |
187 | * | |
188 | * @param bean the bean | |
189 | * @param propName the name of the property | |
190 | * @param value the property's value | |
191 | * @throws ConfigurationRuntimeException if the property is not writeable or | |
192 | * an error occurred | |
193 | */ | |
194 | private static void initProperty(Object bean, String propName, Object value) | |
195 | throws ConfigurationRuntimeException | |
196 | { | |
197 | 189 | if (!PropertyUtils.isWriteable(bean, propName)) |
198 | { | |
199 | 1 | throw new ConfigurationRuntimeException("Property " + propName |
200 | + " cannot be set!"); | |
201 | } | |
202 | ||
203 | try | |
204 | { | |
205 | 188 | BeanUtils.setProperty(bean, propName, value); |
206 | } | |
207 | 0 | catch (IllegalAccessException iaex) |
208 | { | |
209 | 0 | throw new ConfigurationRuntimeException(iaex); |
210 | } | |
211 | 0 | catch (InvocationTargetException itex) |
212 | { | |
213 | 0 | throw new ConfigurationRuntimeException(itex); |
214 | 188 | } |
215 | 188 | } |
216 | ||
217 | /** | |
218 | * The main method for creating and initializing beans from a configuration. | |
219 | * This method will return an initialized instance of the bean class | |
220 | * specified in the passed in bean declaration. If this declaration does not | |
221 | * contain the class of the bean, the passed in default class will be used. | |
222 | * From the bean declaration the factory to be used for creating the bean is | |
223 | * queried. The declaration may here return <b>null</b>, then a default | |
224 | * factory is used. This factory is then invoked to perform the create | |
225 | * operation. | |
226 | * | |
227 | * @param data the bean declaration | |
228 | * @param defaultClass the default class to use | |
229 | * @param param an additional parameter that will be passed to the bean | |
230 | * factory; some factories may support parameters and behave different | |
231 | * depending on the value passed in here | |
232 | * @return the new bean | |
233 | * @throws ConfigurationRuntimeException if an error occurs | |
234 | */ | |
235 | public static Object createBean(BeanDeclaration data, Class defaultClass, | |
236 | Object param) throws ConfigurationRuntimeException | |
237 | { | |
238 | 164 | if (data == null) |
239 | { | |
240 | 1 | throw new IllegalArgumentException( |
241 | "Bean declaration must not be null!"); | |
242 | } | |
243 | ||
244 | 163 | BeanFactory factory = fetchBeanFactory(data); |
245 | try | |
246 | { | |
247 | 162 | return factory.createBean(fetchBeanClass(data, defaultClass, |
248 | factory), data, param); | |
249 | } | |
250 | 5 | catch (Exception ex) |
251 | { | |
252 | 5 | throw new ConfigurationRuntimeException(ex); |
253 | } | |
254 | } | |
255 | ||
256 | /** | |
257 | * Returns a bean instance for the specified declaration. This method is a | |
258 | * short cut for <code>createBean(data, null, null);</code>. | |
259 | * | |
260 | * @param data the bean declaration | |
261 | * @param defaultClass the class to be used when in the declation no class | |
262 | * is specified | |
263 | * @return the new bean | |
264 | * @throws ConfigurationRuntimeException if an error occurs | |
265 | */ | |
266 | public static Object createBean(BeanDeclaration data, Class defaultClass) | |
267 | throws ConfigurationRuntimeException | |
268 | { | |
269 | 163 | return createBean(data, defaultClass, null); |
270 | } | |
271 | ||
272 | /** | |
273 | * Returns a bean instance for the specified declaration. This method is a | |
274 | * short cut for <code>createBean(data, null);</code>. | |
275 | * | |
276 | * @param data the bean declaration | |
277 | * @return the new bean | |
278 | * @throws ConfigurationRuntimeException if an error occurs | |
279 | */ | |
280 | public static Object createBean(BeanDeclaration data) | |
281 | throws ConfigurationRuntimeException | |
282 | { | |
283 | 91 | return createBean(data, null); |
284 | } | |
285 | ||
286 | /** | |
287 | * Returns a <code>java.lang.Class</code> object for the specified name. | |
288 | * Because class loading can be tricky in some environments the code for | |
289 | * retrieving a class by its name was extracted into this helper method. So | |
290 | * if changes are necessary, they can be made at a single place. | |
291 | * | |
292 | * @param name the name of the class to be loaded | |
293 | * @param callingClass the calling class | |
294 | * @return the class object for the specified name | |
295 | * @throws ClassNotFoundException if the class cannot be loaded | |
296 | */ | |
297 | static Class loadClass(String name, Class callingClass) | |
298 | throws ClassNotFoundException | |
299 | { | |
300 | 46 | return ClassUtils.getClass(name); |
301 | } | |
302 | ||
303 | /** | |
304 | * Determines the class of the bean to be created. If the bean declaration | |
305 | * contains a class name, this class is used. Otherwise it is checked | |
306 | * whether a default class is provided. If this is not the case, the | |
307 | * factory's default class is used. If this class is undefined, too, an | |
308 | * exception is thrown. | |
309 | * | |
310 | * @param data the bean declaration | |
311 | * @param defaultClass the default class | |
312 | * @param factory the bean factory to use | |
313 | * @return the class of the bean to be created | |
314 | * @throws ConfigurationRuntimeException if the class cannot be determined | |
315 | */ | |
316 | private static Class fetchBeanClass(BeanDeclaration data, | |
317 | Class defaultClass, BeanFactory factory) | |
318 | throws ConfigurationRuntimeException | |
319 | { | |
320 | 162 | String clsName = data.getBeanClassName(); |
321 | 162 | if (clsName != null) |
322 | { | |
323 | try | |
324 | { | |
325 | 46 | return loadClass(clsName, factory.getClass()); |
326 | } | |
327 | 1 | catch (ClassNotFoundException cex) |
328 | { | |
329 | 1 | throw new ConfigurationRuntimeException(cex); |
330 | } | |
331 | } | |
332 | ||
333 | 116 | if (defaultClass != null) |
334 | { | |
335 | 27 | return defaultClass; |
336 | } | |
337 | ||
338 | 89 | Class clazz = factory.getDefaultBeanClass(); |
339 | 89 | if (clazz == null) |
340 | { | |
341 | 1 | throw new ConfigurationRuntimeException( |
342 | "Bean class is not specified!"); | |
343 | } | |
344 | 88 | return clazz; |
345 | } | |
346 | ||
347 | /** | |
348 | * Obtains the bean factory to use for creating the specified bean. This | |
349 | * method will check whether a factory is specified in the bean declaration. | |
350 | * If this is not the case, the default bean factory will be used. | |
351 | * | |
352 | * @param data the bean declaration | |
353 | * @return the bean factory to use | |
354 | * @throws ConfigurationRuntimeException if the factory cannot be determined | |
355 | */ | |
356 | private static BeanFactory fetchBeanFactory(BeanDeclaration data) | |
357 | throws ConfigurationRuntimeException | |
358 | { | |
359 | 163 | String factoryName = data.getBeanFactoryName(); |
360 | 163 | if (factoryName != null) |
361 | { | |
362 | 95 | BeanFactory factory = (BeanFactory) beanFactories.get(factoryName); |
363 | 95 | if (factory == null) |
364 | { | |
365 | 1 | throw new ConfigurationRuntimeException( |
366 | "Unknown bean factory: " + factoryName); | |
367 | } | |
368 | else | |
369 | { | |
370 | 94 | return factory; |
371 | } | |
372 | } | |
373 | else | |
374 | { | |
375 | 68 | return getDefaultBeanFactory(); |
376 | } | |
377 | } | |
378 | } |