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