View Javadoc

    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      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      private static BeanFactory defaultBeanFactory = DefaultBeanFactory.INSTANCE;
    64
    65      /***
    66       * Private constructor, so no instances can be created.
    67       */
    68      private BeanHelper()
    69      {
    70      }
    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          if (name == null)
    84          {
    85              throw new IllegalArgumentException(
    86                      "Name for bean factory must not be null!");
    87          }
    88          if (factory == null)
    89          {
    90              throw new IllegalArgumentException("Bean factory must not be null!");
    91          }
    92
    93          beanFactories.put(name, factory);
    94      }
    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         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         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         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         if (factory == null)
    139         {
    140             throw new IllegalArgumentException(
    141                     "Default bean factory must not be null!");
    142         }
    143         defaultBeanFactory = factory;
    144     }
    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         Map properties = data.getBeanProperties();
    160         if (properties != null)
    161         {
    162             for (Iterator it = properties.keySet().iterator(); it.hasNext();)
    163             {
    164                 String propName = (String) it.next();
    165                 initProperty(bean, propName, properties.get(propName));
    166             }
    167         }
    168
    169         Map nestedBeans = data.getNestedBeanDeclarations();
    170         if (nestedBeans != null)
    171         {
    172             for (Iterator it = nestedBeans.keySet().iterator(); it.hasNext();)
    173             {
    174                 String propName = (String) it.next();
    175                 initProperty(bean, propName, createBean(
    176                         (BeanDeclaration) nestedBeans.get(propName), null));
    177             }
    178         }
    179     }
    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         if (!PropertyUtils.isWriteable(bean, propName))
    194         {
    195             throw new ConfigurationRuntimeException("Property " + propName
    196                     + " cannot be set!");
    197         }
    198
    199         try
    200         {
    201             BeanUtils.setProperty(bean, propName, value);
    202         }
    203         catch (IllegalAccessException iaex)
    204         {
    205             throw new ConfigurationRuntimeException(iaex);
    206         }
    207         catch (InvocationTargetException itex)
    208         {
    209             throw new ConfigurationRuntimeException(itex);
    210         }
    211     }
    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         if (data == null)
    235         {
    236             throw new IllegalArgumentException(
    237                     "Bean declaration must not be null!");
    238         }
    239
    240         BeanFactory factory = fetchBeanFactory(data);
    241         try
    242         {
    243             return factory.createBean(fetchBeanClass(data, defaultClass,
    244                     factory), data, param);
    245         }
    246         catch (Exception ex)
    247         {
    248             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         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         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         ClassLoader loader = findClassLoader(callingClass);
    297         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         ClassLoader loader = Thread.currentThread().getContextClassLoader();
    310         if (loader == null)
    311         {
    312             loader = callingClass.getClassLoader();
    313             if (loader == null)
    314             {
    315                 loader = ClassLoader.getSystemClassLoader();
    316             }
    317         }
    318         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         String clsName = data.getBeanClassName();
    339         if (clsName != null)
    340         {
    341             try
    342             {
    343                 return loadClass(clsName, factory.getClass());
    344             }
    345             catch (ClassNotFoundException cex)
    346             {
    347                 throw new ConfigurationRuntimeException(cex);
    348             }
    349         }
    350
    351         if (defaultClass != null)
    352         {
    353             return defaultClass;
    354         }
    355
    356         Class clazz = factory.getDefaultBeanClass();
    357         if (clazz == null)
    358         {
    359             throw new ConfigurationRuntimeException(
    360                     "Bean class is not specified!");
    361         }
    362         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         String factoryName = data.getBeanFactoryName();
    378         if (factoryName != null)
    379         {
    380             BeanFactory factory = (BeanFactory) beanFactories.get(factoryName);
    381             if (factory == null)
    382             {
    383                 throw new ConfigurationRuntimeException(
    384                         "Unknown bean factory: " + factoryName);
    385             }
    386             else
    387             {
    388                 return factory;
    389             }
    390         }
    391         else
    392         {
    393             return getDefaultBeanFactory();
    394         }
    395     }
    396 }