Coverage Report - org.apache.commons.configuration.beanutils.BeanHelper
 
Classes in this File Line Coverage Branch Coverage Complexity
BeanHelper
90%
60/67
94%
15/16
3,333
 
 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  
 
 29  
 /**
 30  
  * <p>
 31  
  * A helper class for creating bean instances that are defined in configuration
 32  
  * files.
 33  
  * </p>
 34  
  * <p>
 35  
  * This class provides static utility methods related to bean creation
 36  
  * operations. These methods simplify such operations because a client need not
 37  
  * deal with all involved interfaces. Usually, if a bean declaration has already
 38  
  * been obtained, a single method call is necessary to create a new bean
 39  
  * instance.
 40  
  * </p>
 41  
  * <p>
 42  
  * This class also supports the registration of custom bean factories.
 43  
  * Implementations of the <code>{@link BeanFactory}</code> interface can be
 44  
  * registered under a symbolic name using the <code>registerBeanFactory()</code>
 45  
  * method. In the configuration file the name of the bean factory can be
 46  
  * specified in the bean declaration. Then this factory will be used to create
 47  
  * the bean.
 48  
  * </p>
 49  
  *
 50  
  * @since 1.3
 51  
  * @author Oliver Heger
 52  
  * @version $Id: BeanHelper.java 439648 2006-09-02 20:42:10Z oheger $
 53  
  */
 54  
 public class BeanHelper
 55  
 {
 56  
     /** Stores a map with the registered bean factories. */
 57  8
     private static Map beanFactories = new HashMap();
 58  
 
 59  
     /**
 60  
      * Stores the default bean factory, which will be used if no other factory
 61  
      * is provided.
 62  
      */
 63  4
     private static BeanFactory defaultBeanFactory = DefaultBeanFactory.INSTANCE;
 64  
 
 65  
     /**
 66  
      * Private constructor, so no instances can be created.
 67  
      */
 68  
     private BeanHelper()
 69  0
     {
 70  0
     }
 71  
 
 72  
     /**
 73  
      * Register a bean factory under a symbolic name. This factory object can
 74  
      * then be specified in bean declarations with the effect that this factory
 75  
      * will be used to obtain an instance for the corresponding bean
 76  
      * declaration.
 77  
      *
 78  
      * @param name the name of the factory
 79  
      * @param factory the factory to be registered
 80  
      */
 81  
     public static void registerBeanFactory(String name, BeanFactory factory)
 82  
     {
 83  13
         if (name == null)
 84  
         {
 85  1
             throw new IllegalArgumentException(
 86  
                     "Name for bean factory must not be null!");
 87  
         }
 88  12
         if (factory == null)
 89  
         {
 90  1
             throw new IllegalArgumentException("Bean factory must not be null!");
 91  
         }
 92  
 
 93  11
         beanFactories.put(name, factory);
 94  11
     }
 95  
 
 96  
     /**
 97  
      * Deregisters the bean factory with the given name. After that this factory
 98  
      * cannot be used any longer.
 99  
      *
 100  
      * @param name the name of the factory to be deregistered
 101  
      * @return the factory that was registered under this name; <b>null</b> if
 102  
      * there was no such factory
 103  
      */
 104  
     public static BeanFactory deregisterBeanFactory(String name)
 105  
     {
 106  11
         return (BeanFactory) beanFactories.remove(name);
 107  
     }
 108  
 
 109  
     /**
 110  
      * Returns a set with the names of all currently registered bean factories.
 111  
      *
 112  
      * @return a set with the names of the registered bean factories
 113  
      */
 114  
     public static Set registeredFactoryNames()
 115  
     {
 116  42
         return beanFactories.keySet();
 117  
     }
 118  
 
 119  
     /**
 120  
      * Returns the default bean factory.
 121  
      *
 122  
      * @return the default bean factory
 123  
      */
 124  
     public static BeanFactory getDefaultBeanFactory()
 125  
     {
 126  66
         return defaultBeanFactory;
 127  
     }
 128  
 
 129  
     /**
 130  
      * Sets the default bean factory. This factory will be used for all create
 131  
      * operations, for which no special factory is provided in the bean
 132  
      * declaration.
 133  
      *
 134  
      * @param factory the default bean factory (must not be <b>null</b>)
 135  
      */
 136  
     public static void setDefaultBeanFactory(BeanFactory factory)
 137  
     {
 138  22
         if (factory == null)
 139  
         {
 140  1
             throw new IllegalArgumentException(
 141  
                     "Default bean factory must not be null!");
 142  
         }
 143  21
         defaultBeanFactory = factory;
 144  21
     }
 145  
 
 146  
     /**
 147  
      * Initializes the passed in bean. This method will obtain all the bean's
 148  
      * properties that are defined in the passed in bean declaration. These
 149  
      * properties will be set on the bean. If necessary, further beans will be
 150  
      * created recursively.
 151  
      *
 152  
      * @param bean the bean to be initialized
 153  
      * @param data the bean declaration
 154  
      * @throws ConfigurationRuntimeException if a property cannot be set
 155  
      */
 156  
     public static void initBean(Object bean, BeanDeclaration data)
 157  
             throws ConfigurationRuntimeException
 158  
     {
 159  102
         Map properties = data.getBeanProperties();
 160  102
         if (properties != null)
 161  
         {
 162  299
             for (Iterator it = properties.keySet().iterator(); it.hasNext();)
 163  
             {
 164  98
                 String propName = (String) it.next();
 165  98
                 initProperty(bean, propName, properties.get(propName));
 166  
             }
 167  
         }
 168  
 
 169  101
         Map nestedBeans = data.getNestedBeanDeclarations();
 170  101
         if (nestedBeans != null)
 171  
         {
 172  204
             for (Iterator it = nestedBeans.keySet().iterator(); it.hasNext();)
 173  
             {
 174  18
                 String propName = (String) it.next();
 175  18
                 initProperty(bean, propName, createBean(
 176  
                         (BeanDeclaration) nestedBeans.get(propName), null));
 177  
             }
 178  
         }
 179  101
     }
 180  
 
 181  
     /**
 182  
      * Sets a property on the given bean using Common Beanutils.
 183  
      *
 184  
      * @param bean the bean
 185  
      * @param propName the name of the property
 186  
      * @param value the property's value
 187  
      * @throws ConfigurationRuntimeException if the property is not writeable or
 188  
      * an error occurred
 189  
      */
 190  
     private static void initProperty(Object bean, String propName, Object value)
 191  
             throws ConfigurationRuntimeException
 192  
     {
 193  116
         if (!PropertyUtils.isWriteable(bean, propName))
 194  
         {
 195  1
             throw new ConfigurationRuntimeException("Property " + propName
 196  
                     + " cannot be set!");
 197  
         }
 198  
 
 199  
         try
 200  
         {
 201  115
             BeanUtils.setProperty(bean, propName, value);
 202  115
         }
 203  
         catch (IllegalAccessException iaex)
 204  
         {
 205  0
             throw new ConfigurationRuntimeException(iaex);
 206  
         }
 207  
         catch (InvocationTargetException itex)
 208  
         {
 209  0
             throw new ConfigurationRuntimeException(itex);
 210  
         }
 211  115
     }
 212  
 
 213  
     /**
 214  
      * The main method for creating and initializing beans from a configuration.
 215  
      * This method will return an initialized instance of the bean class
 216  
      * specified in the passed in bean declaration. If this declaration does not
 217  
      * contain the class of the bean, the passed in default class will be used.
 218  
      * From the bean declaration the factory to be used for creating the bean is
 219  
      * queried. The declaration may here return <b>null</b>, then a default
 220  
      * factory is used. This factory is then invoked to perform the create
 221  
      * operation.
 222  
      *
 223  
      * @param data the bean declaration
 224  
      * @param defaultClass the default class to use
 225  
      * @param param an additional parameter that will be passed to the bean
 226  
      * factory; some factories may support parameters and behave different
 227  
      * depending on the value passed in here
 228  
      * @return the new bean
 229  
      * @throws ConfigurationRuntimeException if an error occurs
 230  
      */
 231  
     public static Object createBean(BeanDeclaration data, Class defaultClass,
 232  
             Object param) throws ConfigurationRuntimeException
 233  
     {
 234  104
         if (data == null)
 235  
         {
 236  1
             throw new IllegalArgumentException(
 237  
                     "Bean declaration must not be null!");
 238  
         }
 239  
 
 240  103
         BeanFactory factory = fetchBeanFactory(data);
 241  
         try
 242  
         {
 243  102
             return factory.createBean(fetchBeanClass(data, defaultClass,
 244  
                     factory), data, param);
 245  
         }
 246  
         catch (Exception ex)
 247  
         {
 248  5
             throw new ConfigurationRuntimeException(ex);
 249  
         }
 250  
     }
 251  
 
 252  
     /**
 253  
      * Returns a bean instance for the specified declaration. This method is a
 254  
      * short cut for <code>createBean(data, null, null);</code>.
 255  
      *
 256  
      * @param data the bean declaration
 257  
      * @param defaultClass the class to be used when in the declation no class
 258  
      * is specified
 259  
      * @return the new bean
 260  
      * @throws ConfigurationRuntimeException if an error occurs
 261  
      */
 262  
     public static Object createBean(BeanDeclaration data, Class defaultClass)
 263  
             throws ConfigurationRuntimeException
 264  
     {
 265  103
         return createBean(data, defaultClass, null);
 266  
     }
 267  
 
 268  
     /**
 269  
      * Returns a bean instance for the specified declaration. This method is a
 270  
      * short cut for <code>createBean(data, null);</code>.
 271  
      *
 272  
      * @param data the bean declaration
 273  
      * @return the new bean
 274  
      * @throws ConfigurationRuntimeException if an error occurs
 275  
      */
 276  
     public static Object createBean(BeanDeclaration data)
 277  
             throws ConfigurationRuntimeException
 278  
     {
 279  60
         return createBean(data, null);
 280  
     }
 281  
 
 282  
     /**
 283  
      * Returns a <code>java.lang.Class</code> object for the specified name.
 284  
      * This method and the helper method it invokes are very similar to code
 285  
      * extracted from the <code>ClassLoaderUtils</code> class of Commons
 286  
      * Jelly. It should be replaced if Commons Lang provides a generic version.
 287  
      *
 288  
      * @param name the name of the class to be loaded
 289  
      * @param callingClass the calling class
 290  
      * @return the class object for the specified name
 291  
      * @throws ClassNotFoundException if the class cannot be loaded
 292  
      */
 293  
     static Class loadClass(String name, Class callingClass)
 294  
             throws ClassNotFoundException
 295  
     {
 296  23
         ClassLoader loader = findClassLoader(callingClass);
 297  23
         return Class.forName(name, true, loader);
 298  
     }
 299  
 
 300  
     /**
 301  
      * Determines which class loader should be used in the context of the given
 302  
      * class.
 303  
      *
 304  
      * @param callingClass the calling class
 305  
      * @return the class loader to be used
 306  
      */
 307  
     private static ClassLoader findClassLoader(Class callingClass)
 308  
     {
 309  23
         ClassLoader loader = Thread.currentThread().getContextClassLoader();
 310  23
         if (loader == null)
 311  
         {
 312  0
             loader = callingClass.getClassLoader();
 313  0
             if (loader == null)
 314  
             {
 315  0
                 loader = ClassLoader.getSystemClassLoader();
 316  
             }
 317  
         }
 318  23
         return loader;
 319  
     }
 320  
 
 321  
     /**
 322  
      * Determines the class of the bean to be created. If the bean declaration
 323  
      * contains a class name, this class is used. Otherwise it is checked
 324  
      * whether a default class is provided. If this is not the case, the
 325  
      * factory's default class is used. If this class is undefined, too, an
 326  
      * exception is thrown.
 327  
      *
 328  
      * @param data the bean declaration
 329  
      * @param defaultClass the default class
 330  
      * @param factory the bean factory to use
 331  
      * @return the class of the bean to be created
 332  
      * @throws ConfigurationRuntimeException if the class cannot be determined
 333  
      */
 334  
     private static Class fetchBeanClass(BeanDeclaration data,
 335  
             Class defaultClass, BeanFactory factory)
 336  
             throws ConfigurationRuntimeException
 337  
     {
 338  102
         String clsName = data.getBeanClassName();
 339  102
         if (clsName != null)
 340  
         {
 341  
             try
 342  
             {
 343  23
                 return loadClass(clsName, factory.getClass());
 344  
             }
 345  
             catch (ClassNotFoundException cex)
 346  
             {
 347  1
                 throw new ConfigurationRuntimeException(cex);
 348  
             }
 349  
         }
 350  
 
 351  79
         if (defaultClass != null)
 352  
         {
 353  18
             return defaultClass;
 354  
         }
 355  
 
 356  61
         Class clazz = factory.getDefaultBeanClass();
 357  61
         if (clazz == null)
 358  
         {
 359  1
             throw new ConfigurationRuntimeException(
 360  
                     "Bean class is not specified!");
 361  
         }
 362  60
         return clazz;
 363  
     }
 364  
 
 365  
     /**
 366  
      * Obtains the bean factory to use for creating the specified bean. This
 367  
      * method will check whether a factory is specified in the bean declaration.
 368  
      * If this is not the case, the default bean factory will be used.
 369  
      *
 370  
      * @param data the bean declaration
 371  
      * @return the bean factory to use
 372  
      * @throws ConfigurationRuntimeException if the factory cannot be determined
 373  
      */
 374  
     private static BeanFactory fetchBeanFactory(BeanDeclaration data)
 375  
             throws ConfigurationRuntimeException
 376  
     {
 377  103
         String factoryName = data.getBeanFactoryName();
 378  103
         if (factoryName != null)
 379  
         {
 380  67
             BeanFactory factory = (BeanFactory) beanFactories.get(factoryName);
 381  67
             if (factory == null)
 382  
             {
 383  1
                 throw new ConfigurationRuntimeException(
 384  
                         "Unknown bean factory: " + factoryName);
 385  
             }
 386  
             else
 387  
             {
 388  66
                 return factory;
 389  
             }
 390  
         }
 391  
         else
 392  
         {
 393  36
             return getDefaultBeanFactory();
 394  
         }
 395  
     }
 396  
 }