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.Collections; 021 import java.util.HashMap; 022 import java.util.Iterator; 023 import java.util.Map; 024 import java.util.Set; 025 026 import org.apache.commons.beanutils.BeanUtils; 027 import org.apache.commons.beanutils.PropertyUtils; 028 import org.apache.commons.configuration.ConfigurationRuntimeException; 029 import org.apache.commons.lang.ClassUtils; 030 031 /** 032 * <p> 033 * A helper class for creating bean instances that are defined in configuration 034 * files. 035 * </p> 036 * <p> 037 * This class provides static utility methods related to bean creation 038 * operations. These methods simplify such operations because a client need not 039 * deal with all involved interfaces. Usually, if a bean declaration has already 040 * been obtained, a single method call is necessary to create a new bean 041 * instance. 042 * </p> 043 * <p> 044 * This class also supports the registration of custom bean factories. 045 * Implementations of the <code>{@link BeanFactory}</code> interface can be 046 * registered under a symbolic name using the <code>registerBeanFactory()</code> 047 * method. In the configuration file the name of the bean factory can be 048 * specified in the bean declaration. Then this factory will be used to create 049 * the bean. 050 * </p> 051 * 052 * @since 1.3 053 * @author Oliver Heger 054 * @version $Id: BeanHelper.java 727168 2008-12-16 21:44:29Z oheger $ 055 */ 056 public class BeanHelper 057 { 058 /** Stores a map with the registered bean factories. */ 059 private static Map beanFactories = Collections.synchronizedMap(new HashMap()); 060 061 /** 062 * Stores the default bean factory, which will be used if no other factory 063 * is provided. 064 */ 065 private static BeanFactory defaultBeanFactory = DefaultBeanFactory.INSTANCE; 066 067 /** 068 * Private constructor, so no instances can be created. 069 */ 070 private BeanHelper() 071 { 072 } 073 074 /** 075 * Register a bean factory under a symbolic name. This factory object can 076 * then be specified in bean declarations with the effect that this factory 077 * will be used to obtain an instance for the corresponding bean 078 * declaration. 079 * 080 * @param name the name of the factory 081 * @param factory the factory to be registered 082 */ 083 public static void registerBeanFactory(String name, BeanFactory factory) 084 { 085 if (name == null) 086 { 087 throw new IllegalArgumentException( 088 "Name for bean factory must not be null!"); 089 } 090 if (factory == null) 091 { 092 throw new IllegalArgumentException("Bean factory must not be null!"); 093 } 094 095 beanFactories.put(name, factory); 096 } 097 098 /** 099 * 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 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 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 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 if (factory == null) 141 { 142 throw new IllegalArgumentException( 143 "Default bean factory must not be null!"); 144 } 145 defaultBeanFactory = factory; 146 } 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 Map properties = data.getBeanProperties(); 162 if (properties != null) 163 { 164 for (Iterator it = properties.entrySet().iterator(); it.hasNext();) 165 { 166 Map.Entry e = (Map.Entry) it.next(); 167 String propName = (String) e.getKey(); 168 initProperty(bean, propName, e.getValue()); 169 } 170 } 171 172 Map nestedBeans = data.getNestedBeanDeclarations(); 173 if (nestedBeans != null) 174 { 175 for (Iterator it = nestedBeans.entrySet().iterator(); it.hasNext();) 176 { 177 Map.Entry e = (Map.Entry) it.next(); 178 String propName = (String) e.getKey(); 179 initProperty(bean, propName, createBean( 180 (BeanDeclaration) e.getValue(), null)); 181 } 182 } 183 } 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 if (!PropertyUtils.isWriteable(bean, propName)) 198 { 199 throw new ConfigurationRuntimeException("Property " + propName 200 + " cannot be set!"); 201 } 202 203 try 204 { 205 BeanUtils.setProperty(bean, propName, value); 206 } 207 catch (IllegalAccessException iaex) 208 { 209 throw new ConfigurationRuntimeException(iaex); 210 } 211 catch (InvocationTargetException itex) 212 { 213 throw new ConfigurationRuntimeException(itex); 214 } 215 } 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 if (data == null) 239 { 240 throw new IllegalArgumentException( 241 "Bean declaration must not be null!"); 242 } 243 244 BeanFactory factory = fetchBeanFactory(data); 245 try 246 { 247 return factory.createBean(fetchBeanClass(data, defaultClass, 248 factory), data, param); 249 } 250 catch (Exception ex) 251 { 252 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 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 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 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 String clsName = data.getBeanClassName(); 321 if (clsName != null) 322 { 323 try 324 { 325 return loadClass(clsName, factory.getClass()); 326 } 327 catch (ClassNotFoundException cex) 328 { 329 throw new ConfigurationRuntimeException(cex); 330 } 331 } 332 333 if (defaultClass != null) 334 { 335 return defaultClass; 336 } 337 338 Class clazz = factory.getDefaultBeanClass(); 339 if (clazz == null) 340 { 341 throw new ConfigurationRuntimeException( 342 "Bean class is not specified!"); 343 } 344 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 String factoryName = data.getBeanFactoryName(); 360 if (factoryName != null) 361 { 362 BeanFactory factory = (BeanFactory) beanFactories.get(factoryName); 363 if (factory == null) 364 { 365 throw new ConfigurationRuntimeException( 366 "Unknown bean factory: " + factoryName); 367 } 368 else 369 { 370 return factory; 371 } 372 } 373 else 374 { 375 return getDefaultBeanFactory(); 376 } 377 } 378 }