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 727168 2008-12-16 21:44:29Z 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.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 }