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.configuration2.beanutils; 018 019import java.util.ArrayList; 020import java.util.Collection; 021import java.util.HashMap; 022import java.util.LinkedList; 023import java.util.List; 024import java.util.Map; 025import java.util.Set; 026import java.util.stream.Collectors; 027 028import org.apache.commons.configuration2.BaseHierarchicalConfiguration; 029import org.apache.commons.configuration2.HierarchicalConfiguration; 030import org.apache.commons.configuration2.ex.ConfigurationRuntimeException; 031import org.apache.commons.configuration2.interpol.ConfigurationInterpolator; 032import org.apache.commons.configuration2.tree.NodeHandler; 033import org.apache.commons.lang3.StringUtils; 034 035/** 036 * <p> 037 * An implementation of the {@code BeanDeclaration} interface that is suitable for XML configuration files. 038 * </p> 039 * <p> 040 * This class defines the standard layout of a bean declaration in an XML configuration file. Such a declaration must 041 * look like the following example fragment: 042 * </p> 043 * 044 * <pre> 045 * ... 046 * <personBean config-class="my.model.PersonBean" 047 * lastName="Doe" firstName="John"> 048 * <config-constrarg config-value="ID03493" config-type="java.lang.String"/> 049 * <address config-class="my.model.AddressBean" 050 * street="21st street 11" zip="1234" 051 * city="TestCity"/> 052 * </personBean> 053 * </pre> 054 * 055 * <p> 056 * The bean declaration can be contained in an arbitrary element. Here it is the {@code personBean} element. In the 057 * attributes of this element there can occur some reserved attributes, which have the following meaning: 058 * </p> 059 * <dl> 060 * <dt>{@code config-class}</dt> 061 * <dd>Here the full qualified name of the bean's class can be specified. An instance of this class will be created. If 062 * this attribute is not specified, the bean class must be provided in another way, e.g. as the {@code defaultClass} 063 * passed to the {@code BeanHelper} class.</dd> 064 * <dt>{@code config-factory}</dt> 065 * <dd>This attribute can contain the name of the {@link BeanFactory} that should be used for creating the bean. If it 066 * is defined, a factory with this name must have been registered at the {@code BeanHelper} class. If this attribute is 067 * missing, the default bean factory will be used.</dd> 068 * <dt>{@code config-factoryParam}</dt> 069 * <dd>With this attribute a parameter can be specified that will be passed to the bean factory. This may be useful for 070 * custom bean factories.</dd> 071 * </dl> 072 * <p> 073 * All further attributes starting with the {@code config-} prefix are considered as meta data and will be ignored. All 074 * other attributes are treated as properties of the bean to be created, i.e. corresponding setter methods of the bean 075 * will be invoked with the values specified here. 076 * </p> 077 * <p> 078 * If the bean to be created has also some complex properties (which are itself beans), their values cannot be 079 * initialized from attributes. For this purpose nested elements can be used. The example listing shows how an address 080 * bean can be initialized. This is done in a nested element whose name must match the name of a property of the 081 * enclosing bean declaration. The format of this nested element is exactly the same as for the bean declaration itself, 082 * i.e. it can have attributes defining meta data or bean properties and even further nested elements for complex bean 083 * properties. 084 * </p> 085 * <p> 086 * If the bean should be created using a specific constructor, the constructor arguments have to be specified. This is 087 * done by an arbitrary number of nested {@code <config-constrarg>} elements. Each element can either have the 088 * {@code config-value} attribute - then it defines a simple value - or must be again a bean declaration (conforming to 089 * the format defined here) defining the complex value of this constructor argument. 090 * </p> 091 * <p> 092 * A {@code XMLBeanDeclaration} object is usually created from a {@code HierarchicalConfiguration}. From this it will 093 * derive a {@code SubnodeConfiguration}, which is used to access the needed properties. This subnode configuration can 094 * be obtained using the {@link #getConfiguration()} method. All of its properties can be accessed in the usual way. To 095 * ensure that the property keys used by this class are understood by the configuration, the default expression engine 096 * will be set. 097 * </p> 098 * 099 * @since 1.3 100 */ 101public class XMLBeanDeclaration implements BeanDeclaration { 102 103 /** Constant for the prefix of reserved attributes. */ 104 public static final String RESERVED_PREFIX = "config-"; 105 106 /** Constant for the prefix for reserved attributes. */ 107 public static final String ATTR_PREFIX = "[@" + RESERVED_PREFIX; 108 109 /** Constant for the bean class attribute. */ 110 public static final String ATTR_BEAN_CLASS = ATTR_PREFIX + "class]"; 111 112 /** Constant for the bean factory attribute. */ 113 public static final String ATTR_BEAN_FACTORY = ATTR_PREFIX + "factory]"; 114 115 /** Constant for the bean factory parameter attribute. */ 116 public static final String ATTR_FACTORY_PARAM = ATTR_PREFIX + "factoryParam]"; 117 118 /** Constant for the name of the bean class attribute. */ 119 private static final String ATTR_BEAN_CLASS_NAME = RESERVED_PREFIX + "class"; 120 121 /** Constant for the name of the element for constructor arguments. */ 122 private static final String ELEM_CTOR_ARG = RESERVED_PREFIX + "constrarg"; 123 124 /** 125 * Constant for the name of the attribute with the value of a constructor argument. 126 */ 127 private static final String ATTR_CTOR_VALUE = RESERVED_PREFIX + "value"; 128 129 /** 130 * Constant for the name of the attribute with the data type of a constructor argument. 131 */ 132 private static final String ATTR_CTOR_TYPE = RESERVED_PREFIX + "type"; 133 134 /** Stores the associated configuration. */ 135 private final HierarchicalConfiguration<?> configuration; 136 137 /** Stores the configuration node that contains the bean declaration. */ 138 private final NodeData<?> nodeData; 139 140 /** The name of the default bean class. */ 141 private final String defaultBeanClassName; 142 143 /** 144 * Constructs a new instance of {@code XMLBeanDeclaration} and initializes it from the given configuration. The passed in 145 * key points to the bean declaration. 146 * 147 * @param config the configuration (must not be <b>null</b>) 148 * @param key the key to the bean declaration (this key must point to exactly one bean declaration or a 149 * {@code IllegalArgumentException} exception will be thrown) 150 * @param <T> the node type of the configuration 151 * @throws IllegalArgumentException if required information is missing to construct the bean declaration 152 */ 153 public <T> XMLBeanDeclaration(final HierarchicalConfiguration<T> config, final String key) { 154 this(config, key, false); 155 } 156 157 /** 158 * Constructs a new instance of {@code XMLBeanDeclaration} and initializes it from the given configuration supporting 159 * optional declarations. 160 * 161 * @param config the configuration (must not be <b>null</b>) 162 * @param key the key to the bean declaration 163 * @param optional a flag whether this declaration is optional; if set to <b>true</b>, no exception will be thrown if 164 * the passed in key is undefined 165 * @param <T> the node type of the configuration 166 * @throws IllegalArgumentException if required information is missing to construct the bean declaration 167 */ 168 public <T> XMLBeanDeclaration(final HierarchicalConfiguration<T> config, final String key, final boolean optional) { 169 this(config, key, optional, null); 170 } 171 172 /** 173 * Constructs a new instance of {@code XMLBeanDeclaration} and initializes it from the given configuration supporting 174 * optional declarations and a default bean class name. The passed in key points to the bean declaration. If the key 175 * does not exist and the boolean argument is <b>true</b>, the declaration is initialized with an empty configuration. 176 * It is possible to create objects from such an empty declaration if a default class is provided. If the key on the 177 * other hand has multiple values or is undefined and the boolean argument is <b>false</b>, a 178 * {@code IllegalArgumentException} exception will be thrown. It is possible to set a default bean class name; this name 179 * is used if the configuration does not contain a bean class. 180 * 181 * @param config the configuration (must not be <b>null</b>) 182 * @param key the key to the bean declaration 183 * @param optional a flag whether this declaration is optional; if set to <b>true</b>, no exception will be thrown if 184 * the passed in key is undefined 185 * @param defBeanClsName a default bean class name 186 * @param <T> the node type of the configuration 187 * @throws IllegalArgumentException if required information is missing to construct the bean declaration 188 * @since 2.0 189 */ 190 public <T> XMLBeanDeclaration(final HierarchicalConfiguration<T> config, final String key, final boolean optional, final String defBeanClsName) { 191 if (config == null) { 192 throw new IllegalArgumentException("Configuration must not be null!"); 193 } 194 195 HierarchicalConfiguration<?> tmpconfiguration; 196 try { 197 tmpconfiguration = config.configurationAt(key); 198 } catch (final ConfigurationRuntimeException iex) { 199 // If we reach this block, the key does not have exactly one value 200 if (!optional || config.getMaxIndex(key) > 0) { 201 throw iex; 202 } 203 tmpconfiguration = new BaseHierarchicalConfiguration(); 204 } 205 this.nodeData = createNodeDataFromConfiguration(tmpconfiguration); 206 this.configuration = tmpconfiguration; 207 defaultBeanClassName = defBeanClsName; 208 initSubnodeConfiguration(getConfiguration()); 209 } 210 211 /** 212 * Constructs a new instance of {@code XMLBeanDeclaration} and initializes it from the given configuration. The 213 * configuration's root node must contain the bean declaration. 214 * 215 * @param config the configuration with the bean declaration 216 * @param <T> the node type of the configuration 217 */ 218 public <T> XMLBeanDeclaration(final HierarchicalConfiguration<T> config) { 219 this(config, (String) null); 220 } 221 222 /** 223 * Constructs a new instance of {@code XMLBeanDeclaration} and initializes it with the configuration node that contains the 224 * bean declaration. This constructor is used internally. 225 * 226 * @param config the configuration 227 * @param node the node with the bean declaration. 228 */ 229 XMLBeanDeclaration(final HierarchicalConfiguration<?> config, final NodeData<?> node) { 230 this.nodeData = node; 231 configuration = config; 232 defaultBeanClassName = null; 233 initSubnodeConfiguration(config); 234 } 235 236 /** 237 * Gets the configuration object this bean declaration is based on. 238 * 239 * @return the associated configuration 240 */ 241 public HierarchicalConfiguration<?> getConfiguration() { 242 return configuration; 243 } 244 245 /** 246 * Gets the name of the default bean class. This class is used if no bean class is specified in the configuration. It 247 * may be <b>null</b> if no default class was set. 248 * 249 * @return the default bean class name 250 * @since 2.0 251 */ 252 public String getDefaultBeanClassName() { 253 return defaultBeanClassName; 254 } 255 256 /** 257 * Gets the name of the bean factory. This information is fetched from the {@code config-factory} attribute. 258 * 259 * @return the name of the bean factory 260 */ 261 @Override 262 public String getBeanFactoryName() { 263 return getConfiguration().getString(ATTR_BEAN_FACTORY, null); 264 } 265 266 /** 267 * Gets a parameter for the bean factory. This information is fetched from the {@code config-factoryParam} attribute. 268 * 269 * @return the parameter for the bean factory 270 */ 271 @Override 272 public Object getBeanFactoryParameter() { 273 return getConfiguration().getProperty(ATTR_FACTORY_PARAM); 274 } 275 276 /** 277 * Gets the name of the class of the bean to be created. This information is obtained from the {@code config-class} 278 * attribute. 279 * 280 * @return the name of the bean's class 281 */ 282 @Override 283 public String getBeanClassName() { 284 return getConfiguration().getString(ATTR_BEAN_CLASS, getDefaultBeanClassName()); 285 } 286 287 /** 288 * Gets a map with the bean's (simple) properties. The properties are collected from all attribute nodes, which are 289 * not reserved. 290 * 291 * @return a map with the bean's properties 292 */ 293 @Override 294 public Map<String, Object> getBeanProperties() { 295 final Map<String, Object> props = new HashMap<>(); 296 for (final String key : getAttributeNames()) { 297 if (!isReservedAttributeName(key)) { 298 props.put(key, interpolate(getNode().getAttribute(key))); 299 } 300 } 301 302 return props; 303 } 304 305 /** 306 * Gets a map with bean declarations for the complex properties of the bean to be created. These declarations are 307 * obtained from the child nodes of this declaration's root node. 308 * 309 * @return a map with bean declarations for complex properties 310 */ 311 @Override 312 public Map<String, Object> getNestedBeanDeclarations() { 313 final Map<String, Object> nested = new HashMap<>(); 314 for (final NodeData<?> child : getNode().getChildren()) { 315 if (!isReservedChildName(child.nodeName())) { 316 if (nested.containsKey(child.nodeName())) { 317 final Object obj = nested.get(child.nodeName()); 318 final List<BeanDeclaration> list; 319 if (obj instanceof List) { 320 // Safe because we created the lists ourselves. 321 @SuppressWarnings("unchecked") 322 final List<BeanDeclaration> tmpList = (List<BeanDeclaration>) obj; 323 list = tmpList; 324 } else { 325 list = new ArrayList<>(); 326 list.add((BeanDeclaration) obj); 327 nested.put(child.nodeName(), list); 328 } 329 list.add(createBeanDeclaration(child)); 330 } else { 331 nested.put(child.nodeName(), createBeanDeclaration(child)); 332 } 333 } 334 } 335 336 return nested; 337 } 338 339 /** 340 * {@inheritDoc} This implementation processes all child nodes with the name {@code config-constrarg}. If such a node 341 * has a {@code config-class} attribute, it is considered a nested bean declaration; otherwise it is interpreted as a 342 * simple value. If no nested constructor argument declarations are found, result is an empty collection. 343 */ 344 @Override 345 public Collection<ConstructorArg> getConstructorArgs() { 346 final Collection<ConstructorArg> args = new LinkedList<>(); 347 for (final NodeData<?> child : getNode().getChildren(ELEM_CTOR_ARG)) { 348 args.add(createConstructorArg(child)); 349 } 350 return args; 351 } 352 353 /** 354 * Performs interpolation for the specified value. This implementation will interpolate against the current subnode 355 * configuration's parent. If sub classes need a different interpolation mechanism, they should override this method. 356 * 357 * @param value the value that is to be interpolated 358 * @return the interpolated value 359 */ 360 protected Object interpolate(final Object value) { 361 final ConfigurationInterpolator interpolator = getConfiguration().getInterpolator(); 362 return interpolator != null ? interpolator.interpolate(value) : value; 363 } 364 365 /** 366 * Tests if the specified child node name is reserved and thus should be ignored. This method is called when processing 367 * child nodes of this bean declaration. It is then possible to ignore some nodes with a specific meaning. This 368 * implementation delegates to {@link #isReservedName(String)} . 369 * 370 * @param name the name of the child node to be checked 371 * @return a flag whether this name is reserved 372 * @since 2.0 373 */ 374 protected boolean isReservedChildName(final String name) { 375 return isReservedName(name); 376 } 377 378 /** 379 * Tests if the specified attribute name is reserved and thus does not point to a property of the bean to be created. 380 * This method is called when processing the attributes of this bean declaration. It is then possible to ignore some 381 * attributes with a specific meaning. This implementation delegates to {@link #isReservedName(String)}. 382 * 383 * @param name the name of the attribute to be checked 384 * @return a flag whether this name is reserved 385 * @since 2.0 386 */ 387 protected boolean isReservedAttributeName(final String name) { 388 return isReservedName(name); 389 } 390 391 /** 392 * Tests if the specified name of a node or attribute is reserved and thus should be ignored. This method is called per 393 * default by the methods for checking attribute and child node names. It checks whether the passed in name starts with 394 * the reserved prefix. 395 * 396 * @param name the name to be checked 397 * @return a flag whether this name is reserved 398 */ 399 protected boolean isReservedName(final String name) { 400 return name == null || name.startsWith(RESERVED_PREFIX); 401 } 402 403 /** 404 * Gets a set with the names of the attributes of the configuration node holding the data of this bean declaration. 405 * 406 * @return the attribute names of the underlying configuration node 407 */ 408 protected Set<String> getAttributeNames() { 409 return getNode().getAttributes(); 410 } 411 412 /** 413 * Gets the data about the associated node. 414 * 415 * @return the node with the bean declaration 416 */ 417 NodeData<?> getNode() { 418 return nodeData; 419 } 420 421 /** 422 * Creates a new {@code BeanDeclaration} for a child node of the current configuration node. This method is called by 423 * {@code getNestedBeanDeclarations()} for all complex sub properties detected by this method. Derived classes can hook 424 * in if they need a specific initialization. This base implementation creates a {@code XMLBeanDeclaration} that is 425 * properly initialized from the passed in node. 426 * 427 * @param nodeData the child node, for which a {@code BeanDeclaration} is to be created 428 * @return the {@code BeanDeclaration} for this child node 429 */ 430 BeanDeclaration createBeanDeclaration(final NodeData<?> nodeData) { 431 for (final HierarchicalConfiguration<?> config : getConfiguration().configurationsAt(nodeData.escapedNodeName(getConfiguration()))) { 432 if (nodeData.matchesConfigRootNode(config)) { 433 return new XMLBeanDeclaration(config, nodeData); 434 } 435 } 436 throw new ConfigurationRuntimeException("Unable to match node for " + nodeData.nodeName()); 437 } 438 439 /** 440 * Initializes the internally managed sub configuration. This method will set some default values for some properties. 441 * 442 * @param conf the configuration to initialize 443 */ 444 private void initSubnodeConfiguration(final HierarchicalConfiguration<?> conf) { 445 conf.setExpressionEngine(null); 446 } 447 448 /** 449 * Creates a {@code ConstructorArg} object for the specified configuration node. 450 * 451 * @param child the configuration node 452 * @return the corresponding {@code ConstructorArg} object 453 */ 454 private ConstructorArg createConstructorArg(final NodeData<?> child) { 455 final String type = getAttribute(child, ATTR_CTOR_TYPE); 456 if (isBeanDeclarationArgument(child)) { 457 return ConstructorArg.forValue(getAttribute(child, ATTR_CTOR_VALUE), type); 458 } 459 return ConstructorArg.forBeanDeclaration(createBeanDeclaration(child), type); 460 } 461 462 /** 463 * Gets an attribute of a configuration node. This method also takes interpolation into account. 464 * 465 * @param nodeData the node 466 * @param attribute the name of the attribute 467 * @return the string value of this attribute (can be <b>null</b>) 468 */ 469 private String getAttribute(final NodeData<?> nodeData, final String attribute) { 470 final Object value = nodeData.getAttribute(attribute); 471 return value == null ? null : String.valueOf(interpolate(value)); 472 } 473 474 /** 475 * Tests whether the constructor argument represented by the given configuration node is a bean declaration. 476 * 477 * @param nodeData the configuration node in question 478 * @return a flag whether this constructor argument is a bean declaration 479 */ 480 private static boolean isBeanDeclarationArgument(final NodeData<?> nodeData) { 481 return !nodeData.getAttributes().contains(ATTR_BEAN_CLASS_NAME); 482 } 483 484 /** 485 * Creates a {@code NodeData} object from the root node of the given configuration. 486 * 487 * @param config the configuration 488 * @param <T> the type of the nodes 489 * @return the {@code NodeData} object 490 */ 491 private static <T> NodeData<T> createNodeDataFromConfiguration(final HierarchicalConfiguration<T> config) { 492 final NodeHandler<T> handler = config.getNodeModel().getNodeHandler(); 493 return new NodeData<>(handler.getRootNode(), handler); 494 } 495 496 /** 497 * An internal helper class which wraps the node with the bean declaration and the corresponding node handler. 498 * 499 * @param <T> the type of the node 500 */ 501 static class NodeData<T> { 502 503 /** The wrapped node. */ 504 private final T node; 505 506 /** The node handler for interacting with this node. */ 507 private final NodeHandler<T> nodeHandler; 508 509 /** 510 * Constructs a new instance of {@code NodeData}. 511 * 512 * @param node the node 513 * @param nodeHandler the node handler 514 */ 515 NodeData(final T node, final NodeHandler<T> nodeHandler) { 516 this.node = node; 517 this.nodeHandler = nodeHandler; 518 } 519 520 /** 521 * Returns the name of the wrapped node. 522 * 523 * @return the node name 524 */ 525 String nodeName() { 526 return nodeHandler.nodeName(node); 527 } 528 529 /** 530 * Returns the unescaped name of the node stored in this data object. This method handles the case that the node name 531 * may contain reserved characters with a special meaning for the current expression engine. In this case, the 532 * characters affected have to be escaped accordingly. 533 * 534 * @param config the configuration 535 * @return the escaped node name 536 */ 537 String escapedNodeName(final HierarchicalConfiguration<?> config) { 538 return config.getExpressionEngine().nodeKey(node, StringUtils.EMPTY, nodeHandler); 539 } 540 541 /** 542 * Gets a list with the children of the wrapped node, again wrapped into {@code NodeData} objects. 543 * 544 * @return a list with the children 545 */ 546 List<NodeData<T>> getChildren() { 547 return wrapInNodeData(nodeHandler.getChildren(node)); 548 } 549 550 /** 551 * Gets a list with the children of the wrapped node with the given name, again wrapped into {@code NodeData} 552 * objects. 553 * 554 * @param name the name of the desired child nodes 555 * @return a list with the children with this name 556 */ 557 List<NodeData<T>> getChildren(final String name) { 558 return wrapInNodeData(nodeHandler.getChildren(node, name)); 559 } 560 561 /** 562 * Gets a set with the names of the attributes of the wrapped node. 563 * 564 * @return the attribute names of this node 565 */ 566 Set<String> getAttributes() { 567 return nodeHandler.getAttributes(node); 568 } 569 570 /** 571 * Gets the value of the attribute with the given name of the wrapped node. 572 * 573 * @param key the key of the attribute 574 * @return the value of this attribute 575 */ 576 Object getAttribute(final String key) { 577 return nodeHandler.getAttributeValue(node, key); 578 } 579 580 /** 581 * Returns a flag whether the wrapped node is the root node of the passed in configuration. 582 * 583 * @param config the configuration 584 * @return a flag whether this node is the configuration's root node 585 */ 586 boolean matchesConfigRootNode(final HierarchicalConfiguration<?> config) { 587 return config.getNodeModel().getNodeHandler().getRootNode().equals(node); 588 } 589 590 /** 591 * Wraps the passed in list of nodes in {@code NodeData} objects. 592 * 593 * @param nodes the list with nodes 594 * @return the wrapped nodes 595 */ 596 List<NodeData<T>> wrapInNodeData(final List<T> nodes) { 597 return nodes.stream().map(n -> new NodeData<>(n, nodeHandler)).collect(Collectors.toList()); 598 } 599 } 600}