001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017: package org.apache.commons.configuration.beanutils;
018:
019: import java.lang.reflect.InvocationTargetException;
020: import java.util.HashMap;
021: import java.util.Iterator;
022: import java.util.Map;
023: import java.util.Set;
024:
025: import org.apache.commons.beanutils.BeanUtils;
026: import org.apache.commons.beanutils.PropertyUtils;
027: import org.apache.commons.configuration.ConfigurationRuntimeException;
028: import org.apache.commons.lang.ClassUtils;
029:
030: /**
031: * <p>
032: * A helper class for creating bean instances that are defined in configuration
033: * files.
034: * </p>
035: * <p>
036: * This class provides static utility methods related to bean creation
037: * operations. These methods simplify such operations because a client need not
038: * deal with all involved interfaces. Usually, if a bean declaration has already
039: * been obtained, a single method call is necessary to create a new bean
040: * instance.
041: * </p>
042: * <p>
043: * This class also supports the registration of custom bean factories.
044: * Implementations of the <code>{@link BeanFactory}</code> interface can be
045: * registered under a symbolic name using the <code>registerBeanFactory()</code>
046: * method. In the configuration file the name of the bean factory can be
047: * specified in the bean declaration. Then this factory will be used to create
048: * the bean.
049: * </p>
050: *
051: * @since 1.3
052: * @author Oliver Heger
053: * @version $Id: BeanHelper.java 508152 2007-02-15 21:16:37Z oheger $
054: */
055: public class BeanHelper {
056: /** Stores a map with the registered bean factories. */
057: private static Map beanFactories = new HashMap();
058:
059: /**
060: * Stores the default bean factory, which will be used if no other factory
061: * is provided.
062: */
063: private static BeanFactory defaultBeanFactory = DefaultBeanFactory.INSTANCE;
064:
065: /**
066: * Private constructor, so no instances can be created.
067: */
068: private BeanHelper() {
069: }
070:
071: /**
072: * Register a bean factory under a symbolic name. This factory object can
073: * then be specified in bean declarations with the effect that this factory
074: * will be used to obtain an instance for the corresponding bean
075: * declaration.
076: *
077: * @param name the name of the factory
078: * @param factory the factory to be registered
079: */
080: public static void registerBeanFactory(String name,
081: BeanFactory factory) {
082: if (name == null) {
083: throw new IllegalArgumentException(
084: "Name for bean factory must not be null!");
085: }
086: if (factory == null) {
087: throw new IllegalArgumentException(
088: "Bean factory must not be null!");
089: }
090:
091: beanFactories.put(name, factory);
092: }
093:
094: /**
095: * Deregisters the bean factory with the given name. After that this factory
096: * cannot be used any longer.
097: *
098: * @param name the name of the factory to be deregistered
099: * @return the factory that was registered under this name; <b>null</b> if
100: * there was no such factory
101: */
102: public static BeanFactory deregisterBeanFactory(String name) {
103: return (BeanFactory) beanFactories.remove(name);
104: }
105:
106: /**
107: * Returns a set with the names of all currently registered bean factories.
108: *
109: * @return a set with the names of the registered bean factories
110: */
111: public static Set registeredFactoryNames() {
112: return beanFactories.keySet();
113: }
114:
115: /**
116: * Returns the default bean factory.
117: *
118: * @return the default bean factory
119: */
120: public static BeanFactory getDefaultBeanFactory() {
121: return defaultBeanFactory;
122: }
123:
124: /**
125: * Sets the default bean factory. This factory will be used for all create
126: * operations, for which no special factory is provided in the bean
127: * declaration.
128: *
129: * @param factory the default bean factory (must not be <b>null</b>)
130: */
131: public static void setDefaultBeanFactory(BeanFactory factory) {
132: if (factory == null) {
133: throw new IllegalArgumentException(
134: "Default bean factory must not be null!");
135: }
136: defaultBeanFactory = factory;
137: }
138:
139: /**
140: * Initializes the passed in bean. This method will obtain all the bean's
141: * properties that are defined in the passed in bean declaration. These
142: * properties will be set on the bean. If necessary, further beans will be
143: * created recursively.
144: *
145: * @param bean the bean to be initialized
146: * @param data the bean declaration
147: * @throws ConfigurationRuntimeException if a property cannot be set
148: */
149: public static void initBean(Object bean, BeanDeclaration data)
150: throws ConfigurationRuntimeException {
151: Map properties = data.getBeanProperties();
152: if (properties != null) {
153: for (Iterator it = properties.keySet().iterator(); it
154: .hasNext();) {
155: String propName = (String) it.next();
156: initProperty(bean, propName, properties.get(propName));
157: }
158: }
159:
160: Map nestedBeans = data.getNestedBeanDeclarations();
161: if (nestedBeans != null) {
162: for (Iterator it = nestedBeans.keySet().iterator(); it
163: .hasNext();) {
164: String propName = (String) it.next();
165: initProperty(bean, propName, createBean(
166: (BeanDeclaration) nestedBeans.get(propName),
167: null));
168: }
169: }
170: }
171:
172: /**
173: * Sets a property on the given bean using Common Beanutils.
174: *
175: * @param bean the bean
176: * @param propName the name of the property
177: * @param value the property's value
178: * @throws ConfigurationRuntimeException if the property is not writeable or
179: * an error occurred
180: */
181: private static void initProperty(Object bean, String propName,
182: Object value) throws ConfigurationRuntimeException {
183: if (!PropertyUtils.isWriteable(bean, propName)) {
184: throw new ConfigurationRuntimeException("Property "
185: + propName + " cannot be set!");
186: }
187:
188: try {
189: BeanUtils.setProperty(bean, propName, value);
190: } catch (IllegalAccessException iaex) {
191: throw new ConfigurationRuntimeException(iaex);
192: } catch (InvocationTargetException itex) {
193: throw new ConfigurationRuntimeException(itex);
194: }
195: }
196:
197: /**
198: * The main method for creating and initializing beans from a configuration.
199: * This method will return an initialized instance of the bean class
200: * specified in the passed in bean declaration. If this declaration does not
201: * contain the class of the bean, the passed in default class will be used.
202: * From the bean declaration the factory to be used for creating the bean is
203: * queried. The declaration may here return <b>null</b>, then a default
204: * factory is used. This factory is then invoked to perform the create
205: * operation.
206: *
207: * @param data the bean declaration
208: * @param defaultClass the default class to use
209: * @param param an additional parameter that will be passed to the bean
210: * factory; some factories may support parameters and behave different
211: * depending on the value passed in here
212: * @return the new bean
213: * @throws ConfigurationRuntimeException if an error occurs
214: */
215: public static Object createBean(BeanDeclaration data,
216: Class defaultClass, Object param)
217: throws ConfigurationRuntimeException {
218: if (data == null) {
219: throw new IllegalArgumentException(
220: "Bean declaration must not be null!");
221: }
222:
223: BeanFactory factory = fetchBeanFactory(data);
224: try {
225: return factory.createBean(fetchBeanClass(data,
226: defaultClass, factory), data, param);
227: } catch (Exception ex) {
228: throw new ConfigurationRuntimeException(ex);
229: }
230: }
231:
232: /**
233: * Returns a bean instance for the specified declaration. This method is a
234: * short cut for <code>createBean(data, null, null);</code>.
235: *
236: * @param data the bean declaration
237: * @param defaultClass the class to be used when in the declation no class
238: * is specified
239: * @return the new bean
240: * @throws ConfigurationRuntimeException if an error occurs
241: */
242: public static Object createBean(BeanDeclaration data,
243: Class defaultClass) throws ConfigurationRuntimeException {
244: return createBean(data, defaultClass, null);
245: }
246:
247: /**
248: * Returns a bean instance for the specified declaration. This method is a
249: * short cut for <code>createBean(data, null);</code>.
250: *
251: * @param data the bean declaration
252: * @return the new bean
253: * @throws ConfigurationRuntimeException if an error occurs
254: */
255: public static Object createBean(BeanDeclaration data)
256: throws ConfigurationRuntimeException {
257: return createBean(data, null);
258: }
259:
260: /**
261: * Returns a <code>java.lang.Class</code> object for the specified name.
262: * This method and the helper method it invokes are very similar to code
263: * extracted from the <code>ClassLoaderUtils</code> class of Commons
264: * Jelly. It should be replaced if Commons Lang provides a generic version.
265: *
266: * @param name the name of the class to be loaded
267: * @param callingClass the calling class
268: * @return the class object for the specified name
269: * @throws ClassNotFoundException if the class cannot be loaded
270: */
271: static Class loadClass(String name, Class callingClass)
272: throws ClassNotFoundException {
273: return ClassUtils.getClass(name);
274: }
275:
276: /**
277: * Determines the class of the bean to be created. If the bean declaration
278: * contains a class name, this class is used. Otherwise it is checked
279: * whether a default class is provided. If this is not the case, the
280: * factory's default class is used. If this class is undefined, too, an
281: * exception is thrown.
282: *
283: * @param data the bean declaration
284: * @param defaultClass the default class
285: * @param factory the bean factory to use
286: * @return the class of the bean to be created
287: * @throws ConfigurationRuntimeException if the class cannot be determined
288: */
289: private static Class fetchBeanClass(BeanDeclaration data,
290: Class defaultClass, BeanFactory factory)
291: throws ConfigurationRuntimeException {
292: String clsName = data.getBeanClassName();
293: if (clsName != null) {
294: try {
295: return loadClass(clsName, factory.getClass());
296: } catch (ClassNotFoundException cex) {
297: throw new ConfigurationRuntimeException(cex);
298: }
299: }
300:
301: if (defaultClass != null) {
302: return defaultClass;
303: }
304:
305: Class clazz = factory.getDefaultBeanClass();
306: if (clazz == null) {
307: throw new ConfigurationRuntimeException(
308: "Bean class is not specified!");
309: }
310: return clazz;
311: }
312:
313: /**
314: * Obtains the bean factory to use for creating the specified bean. This
315: * method will check whether a factory is specified in the bean declaration.
316: * If this is not the case, the default bean factory will be used.
317: *
318: * @param data the bean declaration
319: * @return the bean factory to use
320: * @throws ConfigurationRuntimeException if the factory cannot be determined
321: */
322: private static BeanFactory fetchBeanFactory(BeanDeclaration data)
323: throws ConfigurationRuntimeException {
324: String factoryName = data.getBeanFactoryName();
325: if (factoryName != null) {
326: BeanFactory factory = (BeanFactory) beanFactories
327: .get(factoryName);
328: if (factory == null) {
329: throw new ConfigurationRuntimeException(
330: "Unknown bean factory: " + factoryName);
331: } else {
332: return factory;
333: }
334: } else {
335: return getDefaultBeanFactory();
336: }
337: }
338: }
|