001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.commons.configuration.beanutils; 018 019import java.util.ArrayList; 020import java.util.HashMap; 021import java.util.Iterator; 022import java.util.List; 023import java.util.Map; 024 025import org.apache.commons.configuration.ConfigurationRuntimeException; 026import org.apache.commons.configuration.HierarchicalConfiguration; 027import org.apache.commons.configuration.PropertyConverter; 028import org.apache.commons.configuration.SubnodeConfiguration; 029import org.apache.commons.configuration.tree.ConfigurationNode; 030import org.apache.commons.configuration.tree.DefaultConfigurationNode; 031 032/** 033 * <p> 034 * An implementation of the {@code BeanDeclaration} interface that is 035 * suitable for XML configuration files. 036 * </p> 037 * <p> 038 * This class defines the standard layout of a bean declaration in an XML 039 * configuration file. Such a declaration must look like the following example 040 * fragment: 041 * </p> 042 * <p> 043 * 044 * <pre> 045 * ... 046 * <personBean config-class="my.model.PersonBean" 047 * lastName="Doe" firstName="John"> 048 * <address config-class="my.model.AddressBean" 049 * street="21st street 11" zip="1234" 050 * city="TestCity"/> 051 * </personBean> 052 * </pre> 053 * 054 * </p> 055 * <p> 056 * The bean declaration can be contained in an arbitrary element. Here it is the 057 * {@code personBean} element. In the attributes of this element 058 * there can occur some reserved attributes, which have the following meaning: 059 * <dl> 060 * <dt>{@code config-class}</dt> 061 * <dd>Here the full qualified name of the bean's class can be specified. An 062 * instance of this class will be created. If this attribute is not specified, 063 * the bean class must be provided in another way, e.g. as the 064 * {@code defaultClass} passed to the {@code BeanHelper} class.</dd> 065 * <dt>{@code config-factory}</dt> 066 * <dd>This attribute can contain the name of the 067 * {@link BeanFactory} that should be used for creating the bean. 068 * If it is defined, a factory with this name must have been registered at the 069 * {@code BeanHelper} class. If this attribute is missing, the default 070 * bean factory will be used.</dd> 071 * <dt>{@code config-factoryParam}</dt> 072 * <dd>With this attribute a parameter can be specified that will be passed to 073 * the bean factory. This may be useful for custom bean factories.</dd> 074 * </dl> 075 * </p> 076 * <p> 077 * All further attributes starting with the {@code config-} prefix are 078 * considered as meta data and will be ignored. All other attributes are treated 079 * as properties of the bean to be created, i.e. corresponding setter methods of 080 * the bean will be invoked with the values specified here. 081 * </p> 082 * <p> 083 * If the bean to be created has also some complex properties (which are itself 084 * beans), their values cannot be initialized from attributes. For this purpose 085 * nested elements can be used. The example listing shows how an address bean 086 * can be initialized. This is done in a nested element whose name must match 087 * the name of a property of the enclosing bean declaration. The format of this 088 * nested element is exactly the same as for the bean declaration itself, i.e. 089 * it can have attributes defining meta data or bean properties and even further 090 * nested elements for complex bean properties. 091 * </p> 092 * <p> 093 * A {@code XMLBeanDeclaration} object is usually created from a 094 * {@code HierarchicalConfiguration}. From this it will derive a 095 * {@code SubnodeConfiguration}, which is used to access the needed 096 * properties. This subnode configuration can be obtained using the 097 * {@link #getConfiguration()} method. All of its properties can 098 * be accessed in the usual way. To ensure that the property keys used by this 099 * 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 */ 109public 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 this(config, key, false); 146 } 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 { 167 if (config == null) 168 { 169 throw new IllegalArgumentException( 170 "Configuration must not be null!"); 171 } 172 173 SubnodeConfiguration tmpconfiguration = null; 174 ConfigurationNode tmpnode = null; 175 try 176 { 177 tmpconfiguration = config.configurationAt(key); 178 tmpnode = tmpconfiguration.getRootNode(); 179 } 180 catch (IllegalArgumentException iex) 181 { 182 // If we reach this block, the key does not have exactly one value 183 if (!optional || config.getMaxIndex(key) > 0) 184 { 185 throw iex; 186 } 187 tmpconfiguration = config.configurationAt(null); 188 tmpnode = new DefaultConfigurationNode(); 189 } 190 this.node = tmpnode; 191 this.configuration = tmpconfiguration; 192 initSubnodeConfiguration(getConfiguration()); 193 } 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 this(config, (String) null); 205 } 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 { 218 if (config == null) 219 { 220 throw new IllegalArgumentException( 221 "Configuration must not be null!"); 222 } 223 if (node == null) 224 { 225 throw new IllegalArgumentException("Node must not be null!"); 226 } 227 228 this.node = node; 229 configuration = config; 230 initSubnodeConfiguration(config); 231 } 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 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 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 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 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 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 Map<String, Object> props = new HashMap<String, Object>(); 295 for (ConfigurationNode attr : getNode().getAttributes()) 296 { 297 if (!isReservedNode(attr)) 298 { 299 props.put(attr.getName(), interpolate(attr .getValue())); 300 } 301 } 302 303 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 Map<String, Object> nested = new HashMap<String, Object>(); 316 for (ConfigurationNode child : getNode().getChildren()) 317 { 318 if (!isReservedNode(child)) 319 { 320 if (nested.containsKey(child.getName())) 321 { 322 Object obj = nested.get(child.getName()); 323 List<BeanDeclaration> list; 324 if (obj instanceof List) 325 { 326 // Safe because we created the lists ourselves. 327 @SuppressWarnings("unchecked") 328 List<BeanDeclaration> tmpList = (List<BeanDeclaration>) obj; 329 list = tmpList; 330 } 331 else 332 { 333 list = new ArrayList<BeanDeclaration>(); 334 list.add((BeanDeclaration) obj); 335 nested.put(child.getName(), list); 336 } 337 list.add(createBeanDeclaration(child)); 338 } 339 else 340 { 341 nested.put(child.getName(), createBeanDeclaration(child)); 342 } 343 } 344 } 345 346 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 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 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 List<HierarchicalConfiguration> list = getConfiguration().configurationsAt(node.getName()); 398 if (list.size() == 1) 399 { 400 return new XMLBeanDeclaration((SubnodeConfiguration) list.get(0), node); 401 } 402 else 403 { 404 Iterator<HierarchicalConfiguration> iter = list.iterator(); 405 while (iter.hasNext()) 406 { 407 SubnodeConfiguration config = (SubnodeConfiguration) iter.next(); 408 if (config.getRootNode().equals(node)) 409 { 410 return new XMLBeanDeclaration(config, node); 411 } 412 } 413 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 conf.setThrowExceptionOnMissing(false); 426 conf.setExpressionEngine(null); 427 } 428}