Coverage Report - org.apache.commons.configuration.beanutils.XMLBeanDeclaration
 
Classes in this File Line Coverage Branch Coverage Complexity
XMLBeanDeclaration
100%
47/47
100%
9/9
1,929
 
 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.util.HashMap;
 20  
 import java.util.Iterator;
 21  
 import java.util.Map;
 22  
 
 23  
 import org.apache.commons.configuration.HierarchicalConfiguration;
 24  
 import org.apache.commons.configuration.PropertyConverter;
 25  
 import org.apache.commons.configuration.SubnodeConfiguration;
 26  
 import org.apache.commons.configuration.tree.ConfigurationNode;
 27  
 import org.apache.commons.configuration.tree.DefaultConfigurationNode;
 28  
 
 29  
 /**
 30  
  * <p>
 31  
  * An implementation of the <code>BeanDeclaration</code> interface that is
 32  
  * suitable for XML configuration files.
 33  
  * </p>
 34  
  * <p>
 35  
  * This class defines the standard layout of a bean declaration in an XML
 36  
  * configuration file. Such a declaration must look like the following example
 37  
  * fragement:
 38  
  * </p>
 39  
  * <p>
 40  
  *
 41  
  * <pre>
 42  
  *   ...
 43  
  *   &lt;personBean config-class=&quot;my.model.PersonBean&quot;
 44  
  *       lastName=&quot;Doe&quot; firstName=&quot;John&quot;&gt;
 45  
  *       &lt;address config-class=&quot;my.model.AddressBean&quot;
 46  
  *           street=&quot;21st street 11&quot; zip=&quot;1234&quot;
 47  
  *           city=&quot;TestCity&quot;/&gt;
 48  
  *   &lt;/personBean&gt;
 49  
  * </pre>
 50  
  *
 51  
  * </p>
 52  
  * <p>
 53  
  * The bean declaration can be contained in an arbitrary element. Here it is the
 54  
  * <code>&lt;personBean&gt;</code> element. In the attributes of this element
 55  
  * there can occur some reserved attributes, which have the following meaning:
 56  
  * <dl>
 57  
  * <dt><code>config-class</code></dt>
 58  
  * <dd>Here the full qualified name of the bean's class can be specified. An
 59  
  * instance of this class will be created. If this attribute is not specified,
 60  
  * the bean class must be provided in another way, e.g. as the
 61  
  * <code>defaultClass</code> passed to the <code>BeanHelper</code> class.</dd>
 62  
  * <dt><code>config-factory</code></dt>
 63  
  * <dd>This attribute can contain the name of the
 64  
  * <code>{@link BeanFactory}</code> that should be used for creating the bean.
 65  
  * If it is defined, a factory with this name must have been registered at the
 66  
  * <code>BeanHelper</code> class. If this attribute is missing, the default
 67  
  * bean factory will be used.</dd>
 68  
  * <dt><code>config-factoryParam</code></dt>
 69  
  * <dd>With this attribute a parameter can be specified that will be passed to
 70  
  * the bean factory. This may be useful for custom bean factories.</dd>
 71  
  * </dl>
 72  
  * </p>
 73  
  * <p>
 74  
  * All further attributes starting with the <code>config-</code> prefix are
 75  
  * considered as meta data and will be ignored. All other attributes are treated
 76  
  * as properties of the bean to be created, i.e. corresponding setter methods of
 77  
  * the bean will be invoked with the values specified here.
 78  
  * </p>
 79  
  * <p>
 80  
  * If the bean to be created has also some complex properties (which are itself
 81  
  * beans), their values cannot be initialized from attributes. For this purpose
 82  
  * nested elements can be used. The example listing shows how an address bean
 83  
  * can be initialized. This is done in a nested element whose name must match
 84  
  * the name of a property of the enclosing bean declaration. The format of this
 85  
  * nested element is exactly the same as for the bean declaration itself, i.e.
 86  
  * it can have attributes defining meta data or bean properties and even further
 87  
  * nested elements for complex bean properties.
 88  
  * </p>
 89  
  * <p>
 90  
  * A <code>XMLBeanDeclaration</code> object is usually created from a
 91  
  * <code>HierarchicalConfiguration</code>. From this it will derive a
 92  
  * <code>SubnodeConfiguration</code>, which is used to access the needed
 93  
  * properties. This subnode configuration can be obtained using the
 94  
  * <code>{@link #getConfiguration()}</code> method. All of its properties can
 95  
  * be accessed in the usual way. To ensure that the property keys used by this
 96  
  * class are understood by the configuration, the default expression engine will
 97  
  * be set.
 98  
  * </p>
 99  
  *
 100  
  * @since 1.3
 101  
  * @author Oliver Heger
 102  
  * @version $Id: XMLBeanDeclaration.java 439648 2006-09-02 20:42:10Z oheger $
 103  
  */
 104  
 public class XMLBeanDeclaration implements BeanDeclaration
 105  
 {
 106  
     /** Constant for the prefix of reserved attributes. */
 107  
     public static final String RESERVED_PREFIX = "config-";
 108  
 
 109  
     /** Constant for the prefix for reserved attributes.*/
 110  
     public static final String ATTR_PREFIX = "[@" + RESERVED_PREFIX;
 111  
 
 112  
     /** Constant for the bean class attribute. */
 113  
     public static final String ATTR_BEAN_CLASS = ATTR_PREFIX + "class]";
 114  
 
 115  
     /** Constant for the bean factory attribute. */
 116  
     public static final String ATTR_BEAN_FACTORY = ATTR_PREFIX + "factory]";
 117  
 
 118  
     /** Constant for the bean factory parameter attribute. */
 119  
     public static final String ATTR_FACTORY_PARAM = ATTR_PREFIX
 120  
             + "factoryParam]";
 121  
 
 122  
     /** Stores the associated configuration. */
 123  
     private SubnodeConfiguration configuration;
 124  
 
 125  
     /** Stores the configuration node that contains the bean declaration. */
 126  
     private ConfigurationNode node;
 127  
 
 128  
     /**
 129  
      * Creates a new instance of <code>XMLBeanDeclaration</code> and
 130  
      * initializes it from the given configuration. The passed in key points to
 131  
      * the bean declaration.
 132  
      *
 133  
      * @param config the configuration
 134  
      * @param key the key to the bean declaration (this key must point to
 135  
      * exactly one bean declaration or a <code>IllegalArgumentException</code>
 136  
      * exception will be thrown)
 137  
      */
 138  
     public XMLBeanDeclaration(HierarchicalConfiguration config, String key)
 139  
     {
 140  79
         this(config, key, false);
 141  75
     }
 142  
 
 143  
     /**
 144  
      * Creates a new instance of <code>XMLBeanDeclaration</code> and
 145  
      * initializes it from the given configuration. The passed in key points to
 146  
      * the bean declaration. If the key does not exist and the boolean argument
 147  
      * is <b>true</b>, the declaration is initialized with an empty
 148  
      * configuration. It is possible to create objects from such an empty
 149  
      * declaration if a default class is provided. If the key on the other hand
 150  
      * has multiple values or is undefined and the boolean argument is <b>false</b>,
 151  
      * a <code>IllegalArgumentException</code> exception will be thrown.
 152  
      *
 153  
      * @param config the configuration
 154  
      * @param key the key to the bean declaration
 155  
      * @param optional a flag whether this declaration is optional; if set to
 156  
      * <b>true</b>, no exception will be thrown if the passed in key is
 157  
      * undefined
 158  
      */
 159  
     public XMLBeanDeclaration(HierarchicalConfiguration config, String key,
 160  
             boolean optional)
 161  97
     {
 162  97
         if (config == null)
 163  
         {
 164  2
             throw new IllegalArgumentException(
 165  
                     "Configuration must not be null!");
 166  
         }
 167  
 
 168  
         try
 169  
         {
 170  95
             configuration = config.configurationAt(key);
 171  78
             node = configuration.getRootNode();
 172  78
         }
 173  
         catch (IllegalArgumentException iex)
 174  
         {
 175  
             // If we reach this block, the key does not have exactly one value
 176  17
             if (!optional || config.getMaxIndex(key) > 0)
 177  
             {
 178  2
                 throw iex;
 179  
             }
 180  15
             configuration = config.configurationAt(null);
 181  15
             node = new DefaultConfigurationNode();
 182  
         }
 183  93
         initSubnodeConfiguration(getConfiguration());
 184  93
     }
 185  
 
 186  
     /**
 187  
      * Creates a new instance of <code>XMLBeanDeclaration</code> and
 188  
      * initializes it from the given configuration. The configuration's root
 189  
      * node must contain the bean declaration.
 190  
      *
 191  
      * @param config the configuration with the bean declaration
 192  
      */
 193  
     public XMLBeanDeclaration(HierarchicalConfiguration config)
 194  
     {
 195  68
         this(config, (String) null);
 196  67
     }
 197  
 
 198  
     /**
 199  
      * Creates a new instance of <code>XMLBeanDeclaration</code> and
 200  
      * initializes it with the configuration node that contains the bean
 201  
      * declaration.
 202  
      *
 203  
      * @param config the configuration
 204  
      * @param node the node with the bean declaration.
 205  
      */
 206  
     public XMLBeanDeclaration(SubnodeConfiguration config,
 207  
             ConfigurationNode node)
 208  16
     {
 209  16
         if (config == null)
 210  
         {
 211  1
             throw new IllegalArgumentException(
 212  
                     "Configuration must not be null!");
 213  
         }
 214  15
         if (node == null)
 215  
         {
 216  1
             throw new IllegalArgumentException("Node must not be null!");
 217  
         }
 218  
 
 219  14
         this.node = node;
 220  14
         configuration = config;
 221  14
         initSubnodeConfiguration(config);
 222  14
     }
 223  
 
 224  
     /**
 225  
      * Returns the configuration object this bean declaration is based on.
 226  
      *
 227  
      * @return the associated configuration
 228  
      */
 229  
     public SubnodeConfiguration getConfiguration()
 230  
     {
 231  416
         return configuration;
 232  
     }
 233  
 
 234  
     /**
 235  
      * Returns the node that contains the bean declaration.
 236  
      *
 237  
      * @return the configuration node this bean declaration is based on
 238  
      */
 239  
     public ConfigurationNode getNode()
 240  
     {
 241  242
         return node;
 242  
     }
 243  
 
 244  
     /**
 245  
      * Returns the name of the bean factory. This information is fetched from
 246  
      * the <code>config-factory</code> attribute.
 247  
      *
 248  
      * @return the name of the bean factory
 249  
      */
 250  
     public String getBeanFactoryName()
 251  
     {
 252  31
         return getConfiguration().getString(ATTR_BEAN_FACTORY);
 253  
     }
 254  
 
 255  
     /**
 256  
      * Returns a parameter for the bean factory. This information is fetched
 257  
      * from the <code>config-factoryParam</code> attribute.
 258  
      *
 259  
      * @return the parameter for the bean factory
 260  
      */
 261  
     public Object getBeanFactoryParameter()
 262  
     {
 263  2
         return getConfiguration().getProperty(ATTR_FACTORY_PARAM);
 264  
     }
 265  
 
 266  
     /**
 267  
      * Returns the name of the class of the bean to be created. This information
 268  
      * is obtained from the <code>config-class</code> attribute.
 269  
      *
 270  
      * @return the name of the bean's class
 271  
      */
 272  
     public String getBeanClassName()
 273  
     {
 274  34
         return getConfiguration().getString(ATTR_BEAN_CLASS);
 275  
     }
 276  
 
 277  
     /**
 278  
      * Returns a map with the bean's (simple) properties. The properties are
 279  
      * collected from all attribute nodes, which are not reserved.
 280  
      *
 281  
      * @return a map with the bean's properties
 282  
      */
 283  
     public Map getBeanProperties()
 284  
     {
 285  94
         Map props = new HashMap();
 286  331
         for (Iterator it = getNode().getAttributes().iterator(); it.hasNext();)
 287  
         {
 288  143
             ConfigurationNode attr = (ConfigurationNode) it.next();
 289  143
             if (!isReservedNode(attr))
 290  
             {
 291  98
                 props.put(attr.getName(), interpolate(attr .getValue()));
 292  
             }
 293  
         }
 294  
 
 295  94
         return props;
 296  
     }
 297  
 
 298  
     /**
 299  
      * Returns a map with bean declarations for the complex properties of the
 300  
      * bean to be created. These declarations are obtained from the child nodes
 301  
      * of this declaration's root node.
 302  
      *
 303  
      * @return a map with bean declarations for complex properties
 304  
      */
 305  
     public Map getNestedBeanDeclarations()
 306  
     {
 307  89
         Map nested = new HashMap();
 308  192
         for (Iterator it = getNode().getChildren().iterator(); it.hasNext();)
 309  
         {
 310  14
             ConfigurationNode child = (ConfigurationNode) it.next();
 311  14
             if (!isReservedNode(child))
 312  
             {
 313  14
                 nested.put(child.getName(), new XMLBeanDeclaration(
 314  
                         getConfiguration().configurationAt(child.getName()), child));
 315  
             }
 316  
         }
 317  
 
 318  89
         return nested;
 319  
     }
 320  
 
 321  
     /**
 322  
      * Performs interpolation for the specified value. This implementation will
 323  
      * interpolate against the current subnode configuration's parent. If sub
 324  
      * classes need a different interpolation mechanism, they should override
 325  
      * this method.
 326  
      *
 327  
      * @param value the value that is to be interpolated
 328  
      * @return the interpolated value
 329  
      */
 330  
     protected Object interpolate(Object value)
 331  
     {
 332  41
         return PropertyConverter.interpolate(value, getConfiguration()
 333  
                 .getParent());
 334  
     }
 335  
 
 336  
     /**
 337  
      * Checks if the specified node is reserved and thus should be ignored. This
 338  
      * method is called when the maps for the bean's properties and complex
 339  
      * properties are collected. It checks whether the given node is an
 340  
      * attribute node and if its name starts with the reserved prefix.
 341  
      *
 342  
      * @param nd the node to be checked
 343  
      * @return a flag whether this node is reserved (and does not point to a
 344  
      * property)
 345  
      */
 346  
     protected boolean isReservedNode(ConfigurationNode nd)
 347  
     {
 348  168
         return nd.isAttribute()
 349  
                 && (nd.getName() == null || nd.getName().startsWith(
 350  
                         RESERVED_PREFIX));
 351  
     }
 352  
 
 353  
     /**
 354  
      * Initializes the internally managed subnode configuration. This method
 355  
      * will set some default values for some properties.
 356  
      *
 357  
      * @param conf the configuration to initialize
 358  
      */
 359  
     private void initSubnodeConfiguration(SubnodeConfiguration conf)
 360  
     {
 361  107
         conf.setThrowExceptionOnMissing(false);
 362  107
         conf.setExpressionEngine(null);
 363  107
     }
 364  
 }