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