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 * @version $Id: XMLBeanDeclaration.java 1790899 2017-04-10 21:56:46Z ggregory $ 114 */ 115public class XMLBeanDeclaration implements BeanDeclaration 116{ 117 /** Constant for the prefix of reserved attributes. */ 118 public static final String RESERVED_PREFIX = "config-"; 119 120 /** Constant for the prefix for reserved attributes.*/ 121 public static final String ATTR_PREFIX = "[@" + RESERVED_PREFIX; 122 123 /** Constant for the bean class attribute. */ 124 public static final String ATTR_BEAN_CLASS = ATTR_PREFIX + "class]"; 125 126 /** Constant for the bean factory attribute. */ 127 public static final String ATTR_BEAN_FACTORY = ATTR_PREFIX + "factory]"; 128 129 /** Constant for the bean factory parameter attribute. */ 130 public static final String ATTR_FACTORY_PARAM = ATTR_PREFIX 131 + "factoryParam]"; 132 133 /** Constant for the name of the bean class attribute. */ 134 private static final String ATTR_BEAN_CLASS_NAME = RESERVED_PREFIX + "class"; 135 136 /** Constant for the name of the element for constructor arguments. */ 137 private static final String ELEM_CTOR_ARG = RESERVED_PREFIX + "constrarg"; 138 139 /** 140 * Constant for the name of the attribute with the value of a constructor 141 * argument. 142 */ 143 private static final String ATTR_CTOR_VALUE = RESERVED_PREFIX + "value"; 144 145 /** 146 * Constant for the name of the attribute with the data type of a 147 * constructor argument. 148 */ 149 private static final String ATTR_CTOR_TYPE = RESERVED_PREFIX + "type"; 150 151 /** Stores the associated configuration. */ 152 private final HierarchicalConfiguration<?> configuration; 153 154 /** Stores the configuration node that contains the bean declaration. */ 155 private final NodeData<?> node; 156 157 /** The name of the default bean class. */ 158 private final String defaultBeanClassName; 159 160 /** 161 * Creates a new instance of {@code XMLBeanDeclaration} and initializes it 162 * from the given configuration. The passed in key points to the bean 163 * declaration. 164 * 165 * @param config the configuration (must not be <b>null</b>) 166 * @param key the key to the bean declaration (this key must point to 167 * exactly one bean declaration or a {@code IllegalArgumentException} 168 * exception will be thrown) 169 * @param <T> the node type of the configuration 170 * @throws IllegalArgumentException if required information is missing to 171 * construct the bean declaration 172 */ 173 public <T> XMLBeanDeclaration(HierarchicalConfiguration<T> config, String key) 174 { 175 this(config, key, false); 176 } 177 178 /** 179 * Creates a new instance of {@code XMLBeanDeclaration} and initializes it 180 * from the given configuration supporting optional declarations. 181 * 182 * @param config the configuration (must not be <b>null</b>) 183 * @param key the key to the bean declaration 184 * @param optional a flag whether this declaration is optional; if set to 185 * <b>true</b>, no exception will be thrown if the passed in key is 186 * undefined 187 * @param <T> the node type of the configuration 188 * @throws IllegalArgumentException if required information is missing to 189 * construct the bean declaration 190 */ 191 public <T> XMLBeanDeclaration(HierarchicalConfiguration<T> config, String key, 192 boolean optional) 193 { 194 this(config, key, optional, null); 195 } 196 197 /** 198 * Creates a new instance of {@code XMLBeanDeclaration} and initializes it 199 * from the given configuration supporting optional declarations and a 200 * default bean class name. The passed in key points to the bean 201 * declaration. If the key does not exist and the boolean argument is 202 * <b>true</b>, the declaration is initialized with an empty configuration. 203 * It is possible to create objects from such an empty declaration if a 204 * default class is provided. If the key on the other hand has multiple 205 * values or is undefined and the boolean argument is <b>false</b>, a 206 * {@code IllegalArgumentException} exception will be thrown. It is possible 207 * to set a default bean class name; this name is used if the configuration 208 * does not contain a bean class. 209 * 210 * @param config the configuration (must not be <b>null</b>) 211 * @param key the key to the bean declaration 212 * @param optional a flag whether this declaration is optional; if set to 213 * <b>true</b>, no exception will be thrown if the passed in key is 214 * undefined 215 * @param defBeanClsName a default bean class name 216 * @param <T> the node type of the configuration 217 * @throws IllegalArgumentException if required information is missing to 218 * construct the bean declaration 219 * @since 2.0 220 */ 221 public <T> XMLBeanDeclaration(HierarchicalConfiguration<T> config, String key, 222 boolean optional, String defBeanClsName) 223 { 224 if (config == null) 225 { 226 throw new IllegalArgumentException( 227 "Configuration must not be null!"); 228 } 229 230 HierarchicalConfiguration<?> tmpconfiguration; 231 try 232 { 233 tmpconfiguration = config.configurationAt(key); 234 } 235 catch (ConfigurationRuntimeException iex) 236 { 237 // If we reach this block, the key does not have exactly one value 238 if (!optional || config.getMaxIndex(key) > 0) 239 { 240 throw iex; 241 } 242 tmpconfiguration = new BaseHierarchicalConfiguration(); 243 } 244 this.node = createNodeDataFromConfiguration(tmpconfiguration); 245 this.configuration = tmpconfiguration; 246 defaultBeanClassName = defBeanClsName; 247 initSubnodeConfiguration(getConfiguration()); 248 } 249 250 /** 251 * Creates a new instance of {@code XMLBeanDeclaration} and 252 * initializes it from the given configuration. The configuration's root 253 * node must contain the bean declaration. 254 * 255 * @param config the configuration with the bean declaration 256 * @param <T> the node type of the configuration 257 */ 258 public <T> XMLBeanDeclaration(HierarchicalConfiguration<T> config) 259 { 260 this(config, (String) null); 261 } 262 263 /** 264 * Creates a new instance of {@code XMLBeanDeclaration} and 265 * initializes it with the configuration node that contains the bean 266 * declaration. This constructor is used internally. 267 * 268 * @param config the configuration 269 * @param node the node with the bean declaration. 270 */ 271 XMLBeanDeclaration(HierarchicalConfiguration<?> config, 272 NodeData<?> node) 273 { 274 this.node = node; 275 configuration = config; 276 defaultBeanClassName = null; 277 initSubnodeConfiguration(config); 278 } 279 280 /** 281 * Returns the configuration object this bean declaration is based on. 282 * 283 * @return the associated configuration 284 */ 285 public HierarchicalConfiguration<?> getConfiguration() 286 { 287 return configuration; 288 } 289 290 /** 291 * Returns the name of the default bean class. This class is used if no bean 292 * class is specified in the configuration. It may be <b>null</b> if no 293 * default class was set. 294 * 295 * @return the default bean class name 296 * @since 2.0 297 */ 298 public String getDefaultBeanClassName() 299 { 300 return defaultBeanClassName; 301 } 302 303 /** 304 * Returns the name of the bean factory. This information is fetched from 305 * the {@code config-factory} attribute. 306 * 307 * @return the name of the bean factory 308 */ 309 @Override 310 public String getBeanFactoryName() 311 { 312 return getConfiguration().getString(ATTR_BEAN_FACTORY, null); 313 } 314 315 /** 316 * Returns a parameter for the bean factory. This information is fetched 317 * from the {@code config-factoryParam} attribute. 318 * 319 * @return the parameter for the bean factory 320 */ 321 @Override 322 public Object getBeanFactoryParameter() 323 { 324 return getConfiguration().getProperty(ATTR_FACTORY_PARAM); 325 } 326 327 /** 328 * Returns the name of the class of the bean to be created. This information 329 * is obtained from the {@code config-class} attribute. 330 * 331 * @return the name of the bean's class 332 */ 333 @Override 334 public String getBeanClassName() 335 { 336 return getConfiguration().getString(ATTR_BEAN_CLASS, getDefaultBeanClassName()); 337 } 338 339 /** 340 * Returns a map with the bean's (simple) properties. The properties are 341 * collected from all attribute nodes, which are not reserved. 342 * 343 * @return a map with the bean's properties 344 */ 345 @Override 346 public Map<String, Object> getBeanProperties() 347 { 348 Map<String, Object> props = new HashMap<>(); 349 for (String key : getAttributeNames()) 350 { 351 if (!isReservedAttributeName(key)) 352 { 353 props.put(key, interpolate(getNode().getAttribute(key))); 354 } 355 } 356 357 return props; 358 } 359 360 /** 361 * Returns a map with bean declarations for the complex properties of the 362 * bean to be created. These declarations are obtained from the child nodes 363 * of this declaration's root node. 364 * 365 * @return a map with bean declarations for complex properties 366 */ 367 @Override 368 public Map<String, Object> getNestedBeanDeclarations() 369 { 370 Map<String, Object> nested = new HashMap<>(); 371 for (NodeData<?> child : getNode().getChildren()) 372 { 373 if (!isReservedChildName(child.nodeName())) 374 { 375 if (nested.containsKey(child.nodeName())) 376 { 377 Object obj = nested.get(child.nodeName()); 378 List<BeanDeclaration> list; 379 if (obj instanceof List) 380 { 381 // Safe because we created the lists ourselves. 382 @SuppressWarnings("unchecked") 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 Collection<ConstructorArg> args = new LinkedList<>(); 415 for (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(Object value) 432 { 433 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(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(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(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(NodeData<?> node) 519 { 520 for (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(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(NodeData<?> child) 551 { 552 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 else 559 { 560 return ConstructorArg.forBeanDeclaration( 561 createBeanDeclaration(child), type); 562 } 563 } 564 565 /** 566 * Helper method for obtaining an attribute of a configuration node. 567 * This method also takes interpolation into account. 568 * 569 * @param nd the node 570 * @param attr the name of the attribute 571 * @return the string value of this attribute (can be <b>null</b>) 572 */ 573 private String getAttribute(NodeData<?> nd, String attr) 574 { 575 Object value = nd.getAttribute(attr); 576 return (value == null) ? null : String.valueOf(interpolate(value)); 577 } 578 579 /** 580 * Checks whether the constructor argument represented by the given 581 * configuration node is a bean declaration. 582 * 583 * @param nd the configuration node in question 584 * @return a flag whether this constructor argument is a bean declaration 585 */ 586 private static boolean isBeanDeclarationArgument(NodeData<?> nd) 587 { 588 return !nd.getAttributes().contains(ATTR_BEAN_CLASS_NAME); 589 } 590 591 /** 592 * Creates a {@code NodeData} object from the root node of the given 593 * configuration. 594 * 595 * @param config the configuration 596 * @param <T> the type of the nodes 597 * @return the {@code NodeData} object 598 */ 599 private static <T> NodeData<T> createNodeDataFromConfiguration( 600 HierarchicalConfiguration<T> config) 601 { 602 NodeHandler<T> handler = config.getNodeModel().getNodeHandler(); 603 return new NodeData<>(handler.getRootNode(), handler); 604 } 605 606 /** 607 * An internally used helper class which wraps the node with the bean 608 * declaration and the corresponding node handler. 609 * 610 * @param <T> the type of the node 611 */ 612 static class NodeData<T> 613 { 614 /** The wrapped node. */ 615 private final T node; 616 617 /** The node handler for interacting with this node. */ 618 private final NodeHandler<T> handler; 619 620 /** 621 * Creates a new instance of {@code NodeData}. 622 * 623 * @param nd the node 624 * @param hndlr the handler 625 */ 626 public NodeData(T nd, NodeHandler<T> hndlr) 627 { 628 node = nd; 629 handler = hndlr; 630 } 631 632 /** 633 * Returns the name of the wrapped node. 634 * 635 * @return the node name 636 */ 637 public String nodeName() 638 { 639 return handler.nodeName(node); 640 } 641 642 /** 643 * Returns the unescaped name of the node stored in this data object. 644 * This method handles the case that the node name may contain reserved 645 * characters with a special meaning for the current expression engine. 646 * In this case, the characters affected have to be escaped accordingly. 647 * 648 * @param config the configuration 649 * @return the escaped node name 650 */ 651 public String escapedNodeName(HierarchicalConfiguration<?> config) 652 { 653 return config.getExpressionEngine().nodeKey(node, 654 StringUtils.EMPTY, handler); 655 } 656 657 /** 658 * Returns a list with the children of the wrapped node, again wrapped 659 * into {@code NodeData} objects. 660 * 661 * @return a list with the children 662 */ 663 public List<NodeData<T>> getChildren() 664 { 665 return wrapInNodeData(handler.getChildren(node)); 666 } 667 668 /** 669 * Returns a list with the children of the wrapped node with the given 670 * name, again wrapped into {@code NodeData} objects. 671 * 672 * @param name the name of the desired child nodes 673 * @return a list with the children with this name 674 */ 675 public List<NodeData<T>> getChildren(String name) 676 { 677 return wrapInNodeData(handler.getChildren(node, name)); 678 } 679 680 /** 681 * Returns a set with the names of the attributes of the wrapped node. 682 * 683 * @return the attribute names of this node 684 */ 685 public Set<String> getAttributes() 686 { 687 return handler.getAttributes(node); 688 } 689 690 /** 691 * Returns the value of the attribute with the given name of the wrapped 692 * node. 693 * 694 * @param key the key of the attribute 695 * @return the value of this attribute 696 */ 697 public Object getAttribute(String key) 698 { 699 return handler.getAttributeValue(node, key); 700 } 701 702 /** 703 * Returns a flag whether the wrapped node is the root node of the 704 * passed in configuration. 705 * 706 * @param config the configuration 707 * @return a flag whether this node is the configuration's root node 708 */ 709 public boolean matchesConfigRootNode(HierarchicalConfiguration<?> config) 710 { 711 return config.getNodeModel().getNodeHandler().getRootNode() 712 .equals(node); 713 } 714 715 /** 716 * Wraps the passed in list of nodes in {@code NodeData} objects. 717 * 718 * @param nodes the list with nodes 719 * @return the wrapped nodes 720 */ 721 private List<NodeData<T>> wrapInNodeData(List<T> nodes) 722 { 723 List<NodeData<T>> result = new ArrayList<>(nodes.size()); 724 for (T node : nodes) 725 { 726 result.add(new NodeData<>(node, handler)); 727 } 728 return result; 729 } 730 } 731}