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 023 import org.apache.commons.configuration.HierarchicalConfiguration; 024 import org.apache.commons.configuration.PropertyConverter; 025 import org.apache.commons.configuration.SubnodeConfiguration; 026 import org.apache.commons.configuration.tree.ConfigurationNode; 027 import org.apache.commons.configuration.tree.DefaultConfigurationNode; 028 029 /** 030 * <p> 031 * An implementation of the <code>BeanDeclaration</code> interface that is 032 * suitable for XML configuration files. 033 * </p> 034 * <p> 035 * This class defines the standard layout of a bean declaration in an XML 036 * configuration file. Such a declaration must look like the following example 037 * fragement: 038 * </p> 039 * <p> 040 * 041 * <pre> 042 * ... 043 * <personBean config-class="my.model.PersonBean" 044 * lastName="Doe" firstName="John"> 045 * <address config-class="my.model.AddressBean" 046 * street="21st street 11" zip="1234" 047 * city="TestCity"/> 048 * </personBean> 049 * </pre> 050 * 051 * </p> 052 * <p> 053 * The bean declaration can be contained in an arbitrary element. Here it is the 054 * <code><personBean></code> element. In the attributes of this element 055 * there can occur some reserved attributes, which have the following meaning: 056 * <dl> 057 * <dt><code>config-class</code></dt> 058 * <dd>Here the full qualified name of the bean's class can be specified. An 059 * instance of this class will be created. If this attribute is not specified, 060 * the bean class must be provided in another way, e.g. as the 061 * <code>defaultClass</code> passed to the <code>BeanHelper</code> class.</dd> 062 * <dt><code>config-factory</code></dt> 063 * <dd>This attribute can contain the name of the 064 * <code>{@link BeanFactory}</code> that should be used for creating the bean. 065 * If it is defined, a factory with this name must have been registered at the 066 * <code>BeanHelper</code> class. If this attribute is missing, the default 067 * bean factory will be used.</dd> 068 * <dt><code>config-factoryParam</code></dt> 069 * <dd>With this attribute a parameter can be specified that will be passed to 070 * the bean factory. This may be useful for custom bean factories.</dd> 071 * </dl> 072 * </p> 073 * <p> 074 * All further attributes starting with the <code>config-</code> prefix are 075 * considered as meta data and will be ignored. All other attributes are treated 076 * as properties of the bean to be created, i.e. corresponding setter methods of 077 * the bean will be invoked with the values specified here. 078 * </p> 079 * <p> 080 * If the bean to be created has also some complex properties (which are itself 081 * beans), their values cannot be initialized from attributes. For this purpose 082 * nested elements can be used. The example listing shows how an address bean 083 * can be initialized. This is done in a nested element whose name must match 084 * the name of a property of the enclosing bean declaration. The format of this 085 * nested element is exactly the same as for the bean declaration itself, i.e. 086 * it can have attributes defining meta data or bean properties and even further 087 * nested elements for complex bean properties. 088 * </p> 089 * <p> 090 * A <code>XMLBeanDeclaration</code> object is usually created from a 091 * <code>HierarchicalConfiguration</code>. From this it will derive a 092 * <code>SubnodeConfiguration</code>, which is used to access the needed 093 * properties. This subnode configuration can be obtained using the 094 * <code>{@link #getConfiguration()}</code> method. All of its properties can 095 * be accessed in the usual way. To ensure that the property keys used by this 096 * class are understood by the configuration, the default expression engine will 097 * be set. 098 * </p> 099 * 100 * @since 1.3 101 * @author Oliver Heger 102 * @version $Id: XMLBeanDeclaration.java 670739 2008-06-23 20:36:37Z 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 this(config, key, false); 141 } 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 { 162 if (config == null) 163 { 164 throw new IllegalArgumentException( 165 "Configuration must not be null!"); 166 } 167 168 try 169 { 170 configuration = config.configurationAt(key); 171 node = configuration.getRootNode(); 172 } 173 catch (IllegalArgumentException iex) 174 { 175 // If we reach this block, the key does not have exactly one value 176 if (!optional || config.getMaxIndex(key) > 0) 177 { 178 throw iex; 179 } 180 configuration = config.configurationAt(null); 181 node = new DefaultConfigurationNode(); 182 } 183 initSubnodeConfiguration(getConfiguration()); 184 } 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 this(config, (String) null); 196 } 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 { 209 if (config == null) 210 { 211 throw new IllegalArgumentException( 212 "Configuration must not be null!"); 213 } 214 if (node == null) 215 { 216 throw new IllegalArgumentException("Node must not be null!"); 217 } 218 219 this.node = node; 220 configuration = config; 221 initSubnodeConfiguration(config); 222 } 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 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 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 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 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 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 Map props = new HashMap(); 286 for (Iterator it = getNode().getAttributes().iterator(); it.hasNext();) 287 { 288 ConfigurationNode attr = (ConfigurationNode) it.next(); 289 if (!isReservedNode(attr)) 290 { 291 props.put(attr.getName(), interpolate(attr .getValue())); 292 } 293 } 294 295 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 Map nested = new HashMap(); 308 for (Iterator it = getNode().getChildren().iterator(); it.hasNext();) 309 { 310 ConfigurationNode child = (ConfigurationNode) it.next(); 311 if (!isReservedNode(child)) 312 { 313 nested.put(child.getName(), createBeanDeclaration(child)); 314 } 315 } 316 317 return nested; 318 } 319 320 /** 321 * Performs interpolation for the specified value. This implementation will 322 * interpolate against the current subnode configuration's parent. If sub 323 * classes need a different interpolation mechanism, they should override 324 * this method. 325 * 326 * @param value the value that is to be interpolated 327 * @return the interpolated value 328 */ 329 protected Object interpolate(Object value) 330 { 331 return PropertyConverter.interpolate(value, getConfiguration() 332 .getParent()); 333 } 334 335 /** 336 * Checks if the specified node is reserved and thus should be ignored. This 337 * method is called when the maps for the bean's properties and complex 338 * properties are collected. It checks whether the given node is an 339 * attribute node and if its name starts with the reserved prefix. 340 * 341 * @param nd the node to be checked 342 * @return a flag whether this node is reserved (and does not point to a 343 * property) 344 */ 345 protected boolean isReservedNode(ConfigurationNode nd) 346 { 347 return nd.isAttribute() 348 && (nd.getName() == null || nd.getName().startsWith( 349 RESERVED_PREFIX)); 350 } 351 352 /** 353 * Creates a new <code>BeanDeclaration</code> for a child node of the 354 * current configuration node. This method is called by 355 * <code>getNestedBeanDeclarations()</code> for all complex sub properties 356 * detected by this method. Derived classes can hook in if they need a 357 * specific initialization. This base implementation creates a 358 * <code>XMLBeanDeclaration</code> that is properly initialized from the 359 * passed in node. 360 * 361 * @param node the child node, for which a <code>BeanDeclaration</code> is 362 * to be created 363 * @return the <code>BeanDeclaration</code> for this child node 364 * @since 1.6 365 */ 366 protected BeanDeclaration createBeanDeclaration(ConfigurationNode node) 367 { 368 return new XMLBeanDeclaration(getConfiguration().configurationAt( 369 node.getName()), node); 370 } 371 372 /** 373 * Initializes the internally managed subnode configuration. This method 374 * will set some default values for some properties. 375 * 376 * @param conf the configuration to initialize 377 */ 378 private void initSubnodeConfiguration(SubnodeConfiguration conf) 379 { 380 conf.setThrowExceptionOnMissing(false); 381 conf.setExpressionEngine(null); 382 } 383 }