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 */ 017 package org.apache.commons.configuration.beanutils; 018 019 import java.util.HashMap; 020 import java.util.Iterator; 021 import java.util.Map; 022 import java.util.List; 023 import java.util.ArrayList; 024 025 import org.apache.commons.configuration.HierarchicalConfiguration; 026 import org.apache.commons.configuration.PropertyConverter; 027 import org.apache.commons.configuration.SubnodeConfiguration; 028 import org.apache.commons.configuration.ConfigurationRuntimeException; 029 import org.apache.commons.configuration.tree.ConfigurationNode; 030 import org.apache.commons.configuration.tree.DefaultConfigurationNode; 031 032 /** 033 * <p> 034 * An implementation of the <code>BeanDeclaration</code> 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 * fragement: 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></code> 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</code></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</code> passed to the <code>BeanHelper</code> class.</dd> 065 * <dt><code>config-factory</code></dt> 066 * <dd>This attribute can contain the name of the 067 * <code>{@link BeanFactory}</code> 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</code> class. If this attribute is missing, the default 070 * bean factory will be used.</dd> 071 * <dt><code>config-factoryParam</code></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-</code> 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</code> object is usually created from a 094 * <code>HierarchicalConfiguration</code>. From this it will derive a 095 * <code>SubnodeConfiguration</code>, which is used to access the needed 096 * properties. This subnode configuration can be obtained using the 097 * <code>{@link #getConfiguration()}</code> 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 Oliver Heger 105 * @version $Id: XMLBeanDeclaration.java 766914 2009-04-20 23:38:49Z rgoers $ 106 */ 107 public class XMLBeanDeclaration implements BeanDeclaration 108 { 109 /** Constant for the prefix of reserved attributes. */ 110 public static final String RESERVED_PREFIX = "config-"; 111 112 /** Constant for the prefix for reserved attributes.*/ 113 public static final String ATTR_PREFIX = "[@" + RESERVED_PREFIX; 114 115 /** Constant for the bean class attribute. */ 116 public static final String ATTR_BEAN_CLASS = ATTR_PREFIX + "class]"; 117 118 /** Constant for the bean factory attribute. */ 119 public static final String ATTR_BEAN_FACTORY = ATTR_PREFIX + "factory]"; 120 121 /** Constant for the bean factory parameter attribute. */ 122 public static final String ATTR_FACTORY_PARAM = ATTR_PREFIX 123 + "factoryParam]"; 124 125 /** Stores the associated configuration. */ 126 private SubnodeConfiguration configuration; 127 128 /** Stores the configuration node that contains the bean declaration. */ 129 private ConfigurationNode node; 130 131 /** 132 * Creates a new instance of <code>XMLBeanDeclaration</code> and 133 * initializes it from the given configuration. The passed in key points to 134 * the bean declaration. 135 * 136 * @param config the configuration 137 * @param key the key to the bean declaration (this key must point to 138 * exactly one bean declaration or a <code>IllegalArgumentException</code> 139 * exception will be thrown) 140 */ 141 public XMLBeanDeclaration(HierarchicalConfiguration config, String key) 142 { 143 this(config, key, false); 144 } 145 146 /** 147 * Creates a new instance of <code>XMLBeanDeclaration</code> and 148 * initializes it from the given configuration. The passed in key points to 149 * the bean declaration. If the key does not exist and the boolean argument 150 * is <b>true</b>, the declaration is initialized with an empty 151 * configuration. It is possible to create objects from such an empty 152 * declaration if a default class is provided. If the key on the other hand 153 * has multiple values or is undefined and the boolean argument is <b>false</b>, 154 * a <code>IllegalArgumentException</code> exception will be thrown. 155 * 156 * @param config the configuration 157 * @param key the key to the bean declaration 158 * @param optional a flag whether this declaration is optional; if set to 159 * <b>true</b>, no exception will be thrown if the passed in key is 160 * undefined 161 */ 162 public XMLBeanDeclaration(HierarchicalConfiguration config, String key, 163 boolean optional) 164 { 165 if (config == null) 166 { 167 throw new IllegalArgumentException( 168 "Configuration must not be null!"); 169 } 170 171 try 172 { 173 configuration = config.configurationAt(key); 174 node = configuration.getRootNode(); 175 } 176 catch (IllegalArgumentException iex) 177 { 178 // If we reach this block, the key does not have exactly one value 179 if (!optional || config.getMaxIndex(key) > 0) 180 { 181 throw iex; 182 } 183 configuration = config.configurationAt(null); 184 node = new DefaultConfigurationNode(); 185 } 186 initSubnodeConfiguration(getConfiguration()); 187 } 188 189 /** 190 * Creates a new instance of <code>XMLBeanDeclaration</code> and 191 * initializes it from the given configuration. The configuration's root 192 * node must contain the bean declaration. 193 * 194 * @param config the configuration with the bean declaration 195 */ 196 public XMLBeanDeclaration(HierarchicalConfiguration config) 197 { 198 this(config, (String) null); 199 } 200 201 /** 202 * Creates a new instance of <code>XMLBeanDeclaration</code> and 203 * initializes it with the configuration node that contains the bean 204 * declaration. 205 * 206 * @param config the configuration 207 * @param node the node with the bean declaration. 208 */ 209 public XMLBeanDeclaration(SubnodeConfiguration config, 210 ConfigurationNode node) 211 { 212 if (config == null) 213 { 214 throw new IllegalArgumentException( 215 "Configuration must not be null!"); 216 } 217 if (node == null) 218 { 219 throw new IllegalArgumentException("Node must not be null!"); 220 } 221 222 this.node = node; 223 configuration = config; 224 initSubnodeConfiguration(config); 225 } 226 227 /** 228 * Returns the configuration object this bean declaration is based on. 229 * 230 * @return the associated configuration 231 */ 232 public SubnodeConfiguration getConfiguration() 233 { 234 return configuration; 235 } 236 237 /** 238 * Returns the node that contains the bean declaration. 239 * 240 * @return the configuration node this bean declaration is based on 241 */ 242 public ConfigurationNode getNode() 243 { 244 return node; 245 } 246 247 /** 248 * Returns the name of the bean factory. This information is fetched from 249 * the <code>config-factory</code> attribute. 250 * 251 * @return the name of the bean factory 252 */ 253 public String getBeanFactoryName() 254 { 255 return getConfiguration().getString(ATTR_BEAN_FACTORY); 256 } 257 258 /** 259 * Returns a parameter for the bean factory. This information is fetched 260 * from the <code>config-factoryParam</code> attribute. 261 * 262 * @return the parameter for the bean factory 263 */ 264 public Object getBeanFactoryParameter() 265 { 266 return getConfiguration().getProperty(ATTR_FACTORY_PARAM); 267 } 268 269 /** 270 * Returns the name of the class of the bean to be created. This information 271 * is obtained from the <code>config-class</code> attribute. 272 * 273 * @return the name of the bean's class 274 */ 275 public String getBeanClassName() 276 { 277 return getConfiguration().getString(ATTR_BEAN_CLASS); 278 } 279 280 /** 281 * Returns a map with the bean's (simple) properties. The properties are 282 * collected from all attribute nodes, which are not reserved. 283 * 284 * @return a map with the bean's properties 285 */ 286 public Map getBeanProperties() 287 { 288 Map props = new HashMap(); 289 for (Iterator it = getNode().getAttributes().iterator(); it.hasNext();) 290 { 291 ConfigurationNode attr = (ConfigurationNode) it.next(); 292 if (!isReservedNode(attr)) 293 { 294 props.put(attr.getName(), interpolate(attr .getValue())); 295 } 296 } 297 298 return props; 299 } 300 301 /** 302 * Returns a map with bean declarations for the complex properties of the 303 * bean to be created. These declarations are obtained from the child nodes 304 * of this declaration's root node. 305 * 306 * @return a map with bean declarations for complex properties 307 */ 308 public Map getNestedBeanDeclarations() 309 { 310 Map nested = new HashMap(); 311 for (Iterator it = getNode().getChildren().iterator(); it.hasNext();) 312 { 313 ConfigurationNode child = (ConfigurationNode) it.next(); 314 if (!isReservedNode(child)) 315 { 316 if (nested.containsKey(child.getName())) 317 { 318 Object obj = nested.get(child.getName()); 319 List list; 320 if (obj instanceof List) 321 { 322 list = (List) obj; 323 } 324 else 325 { 326 list = new ArrayList(); 327 list.add(obj); 328 nested.put(child.getName(), list); 329 } 330 list.add(createBeanDeclaration(child)); 331 } 332 else 333 { 334 nested.put(child.getName(), createBeanDeclaration(child)); 335 } 336 } 337 } 338 339 return nested; 340 } 341 342 /** 343 * Performs interpolation for the specified value. This implementation will 344 * interpolate against the current subnode configuration's parent. If sub 345 * classes need a different interpolation mechanism, they should override 346 * this method. 347 * 348 * @param value the value that is to be interpolated 349 * @return the interpolated value 350 */ 351 protected Object interpolate(Object value) 352 { 353 return PropertyConverter.interpolate(value, getConfiguration() 354 .getParent()); 355 } 356 357 /** 358 * Checks if the specified node is reserved and thus should be ignored. This 359 * method is called when the maps for the bean's properties and complex 360 * properties are collected. It checks whether the given node is an 361 * attribute node and if its name starts with the reserved prefix. 362 * 363 * @param nd the node to be checked 364 * @return a flag whether this node is reserved (and does not point to a 365 * property) 366 */ 367 protected boolean isReservedNode(ConfigurationNode nd) 368 { 369 return nd.isAttribute() 370 && (nd.getName() == null || nd.getName().startsWith( 371 RESERVED_PREFIX)); 372 } 373 374 /** 375 * Creates a new <code>BeanDeclaration</code> for a child node of the 376 * current configuration node. This method is called by 377 * <code>getNestedBeanDeclarations()</code> for all complex sub properties 378 * detected by this method. Derived classes can hook in if they need a 379 * specific initialization. This base implementation creates a 380 * <code>XMLBeanDeclaration</code> that is properly initialized from the 381 * passed in node. 382 * 383 * @param node the child node, for which a <code>BeanDeclaration</code> is 384 * to be created 385 * @return the <code>BeanDeclaration</code> for this child node 386 * @since 1.6 387 */ 388 protected BeanDeclaration createBeanDeclaration(ConfigurationNode node) 389 { 390 List list = getConfiguration().configurationsAt(node.getName()); 391 if (list.size() == 1) 392 { 393 return new XMLBeanDeclaration((SubnodeConfiguration) list.get(0), node); 394 } 395 else 396 { 397 Iterator iter = list.iterator(); 398 while (iter.hasNext()) 399 { 400 SubnodeConfiguration config = (SubnodeConfiguration) iter.next(); 401 if (config.getRootNode().equals(node)) 402 { 403 return new XMLBeanDeclaration(config, node); 404 } 405 } 406 throw new ConfigurationRuntimeException("Unable to match node for " + node.getName()); 407 } 408 } 409 410 /** 411 * Initializes the internally managed subnode configuration. This method 412 * will set some default values for some properties. 413 * 414 * @param conf the configuration to initialize 415 */ 416 private void initSubnodeConfiguration(SubnodeConfiguration conf) 417 { 418 conf.setThrowExceptionOnMissing(false); 419 conf.setExpressionEngine(null); 420 } 421 }