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