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 1842268 2018-09-28 16:25:25Z 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(final HierarchicalConfiguration<T> config, final 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(final HierarchicalConfiguration<T> config, final String key, 192 final 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(final HierarchicalConfiguration<T> config, final String key, 222 final boolean optional, final 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 (final 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(final 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(final HierarchicalConfiguration<?> config, 272 final 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 final Map<String, Object> props = new HashMap<>(); 349 for (final 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 final Map<String, Object> nested = new HashMap<>(); 371 for (final NodeData<?> child : getNode().getChildren()) 372 { 373 if (!isReservedChildName(child.nodeName())) 374 { 375 if (nested.containsKey(child.nodeName())) 376 { 377 final 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 final 384 List<BeanDeclaration> tmpList = (List<BeanDeclaration>) obj; 385 list = tmpList; 386 } 387 else 388 { 389 list = new ArrayList<>(); 390 list.add((BeanDeclaration) obj); 391 nested.put(child.nodeName(), list); 392 } 393 list.add(createBeanDeclaration(child)); 394 } 395 else 396 { 397 nested.put(child.nodeName(), createBeanDeclaration(child)); 398 } 399 } 400 } 401 402 return nested; 403 } 404 405 /** 406 * {@inheritDoc} This implementation processes all child nodes with the name 407 * {@code config-constrarg}. If such a node has a {@code config-class} 408 * attribute, it is considered a nested bean declaration; otherwise it is 409 * interpreted as a simple value. If no nested constructor argument 410 * declarations are found, result is an empty collection. 411 */ 412 @Override 413 public Collection<ConstructorArg> getConstructorArgs() 414 { 415 final Collection<ConstructorArg> args = new LinkedList<>(); 416 for (final NodeData<?> child : getNode().getChildren(ELEM_CTOR_ARG)) 417 { 418 args.add(createConstructorArg(child)); 419 } 420 return args; 421 } 422 423 /** 424 * Performs interpolation for the specified value. This implementation will 425 * interpolate against the current subnode configuration's parent. If sub 426 * classes need a different interpolation mechanism, they should override 427 * this method. 428 * 429 * @param value the value that is to be interpolated 430 * @return the interpolated value 431 */ 432 protected Object interpolate(final Object value) 433 { 434 final ConfigurationInterpolator interpolator = 435 getConfiguration().getInterpolator(); 436 return interpolator != null ? interpolator.interpolate(value) : value; 437 } 438 439 /** 440 * Checks if the specified child node name is reserved and thus should be 441 * ignored. This method is called when processing child nodes of this bean 442 * declaration. It is then possible to ignore some nodes with a specific 443 * meaning. This implementation delegates to {@link #isReservedName(String)} 444 * . 445 * 446 * @param name the name of the child node to be checked 447 * @return a flag whether this name is reserved 448 * @since 2.0 449 */ 450 protected boolean isReservedChildName(final String name) 451 { 452 return isReservedName(name); 453 } 454 455 /** 456 * Checks if the specified attribute name is reserved and thus does not 457 * point to a property of the bean to be created. This method is called when 458 * processing the attributes of this bean declaration. It is then possible 459 * to ignore some attributes with a specific meaning. This implementation 460 * delegates to {@link #isReservedName(String)}. 461 * 462 * @param name the name of the attribute to be checked 463 * @return a flag whether this name is reserved 464 * @since 2.0 465 */ 466 protected boolean isReservedAttributeName(final String name) 467 { 468 return isReservedName(name); 469 } 470 471 /** 472 * Checks if the specified name of a node or attribute is reserved and thus 473 * should be ignored. This method is called per default by the methods for 474 * checking attribute and child node names. It checks whether the passed in 475 * name starts with the reserved prefix. 476 * 477 * @param name the name to be checked 478 * @return a flag whether this name is reserved 479 */ 480 protected boolean isReservedName(final String name) 481 { 482 return name == null || name.startsWith(RESERVED_PREFIX); 483 } 484 485 /** 486 * Returns a set with the names of the attributes of the configuration node 487 * holding the data of this bean declaration. 488 * 489 * @return the attribute names of the underlying configuration node 490 */ 491 protected Set<String> getAttributeNames() 492 { 493 return getNode().getAttributes(); 494 } 495 496 /** 497 * Returns the data about the associated node. 498 * 499 * @return the node with the bean declaration 500 */ 501 NodeData<?> getNode() 502 { 503 return node; 504 } 505 506 /** 507 * Creates a new {@code BeanDeclaration} for a child node of the 508 * current configuration node. This method is called by 509 * {@code getNestedBeanDeclarations()} for all complex sub properties 510 * detected by this method. Derived classes can hook in if they need a 511 * specific initialization. This base implementation creates a 512 * {@code XMLBeanDeclaration} that is properly initialized from the 513 * passed in node. 514 * 515 * @param node the child node, for which a {@code BeanDeclaration} is 516 * to be created 517 * @return the {@code BeanDeclaration} for this child node 518 */ 519 BeanDeclaration createBeanDeclaration(final NodeData<?> node) 520 { 521 for (final HierarchicalConfiguration<?> config : getConfiguration() 522 .configurationsAt(node.escapedNodeName(getConfiguration()))) 523 { 524 if (node.matchesConfigRootNode(config)) 525 { 526 return new XMLBeanDeclaration(config, node); 527 } 528 } 529 throw new ConfigurationRuntimeException("Unable to match node for " 530 + node.nodeName()); 531 } 532 533 /** 534 * Initializes the internally managed sub configuration. This method 535 * will set some default values for some properties. 536 * 537 * @param conf the configuration to initialize 538 */ 539 private void initSubnodeConfiguration(final HierarchicalConfiguration<?> conf) 540 { 541 conf.setExpressionEngine(null); 542 } 543 544 /** 545 * Creates a {@code ConstructorArg} object for the specified configuration 546 * node. 547 * 548 * @param child the configuration node 549 * @return the corresponding {@code ConstructorArg} object 550 */ 551 private ConstructorArg createConstructorArg(final NodeData<?> child) 552 { 553 final String type = getAttribute(child, ATTR_CTOR_TYPE); 554 if (isBeanDeclarationArgument(child)) 555 { 556 return ConstructorArg.forValue( 557 getAttribute(child, ATTR_CTOR_VALUE), type); 558 } 559 return ConstructorArg.forBeanDeclaration( 560 createBeanDeclaration(child), type); 561 } 562 563 /** 564 * Helper method for obtaining an attribute of a configuration node. 565 * This method also takes interpolation into account. 566 * 567 * @param nd the node 568 * @param attr the name of the attribute 569 * @return the string value of this attribute (can be <b>null</b>) 570 */ 571 private String getAttribute(final NodeData<?> nd, final String attr) 572 { 573 final Object value = nd.getAttribute(attr); 574 return value == null ? null : String.valueOf(interpolate(value)); 575 } 576 577 /** 578 * Checks whether the constructor argument represented by the given 579 * configuration node is a bean declaration. 580 * 581 * @param nd the configuration node in question 582 * @return a flag whether this constructor argument is a bean declaration 583 */ 584 private static boolean isBeanDeclarationArgument(final NodeData<?> nd) 585 { 586 return !nd.getAttributes().contains(ATTR_BEAN_CLASS_NAME); 587 } 588 589 /** 590 * Creates a {@code NodeData} object from the root node of the given 591 * configuration. 592 * 593 * @param config the configuration 594 * @param <T> the type of the nodes 595 * @return the {@code NodeData} object 596 */ 597 private static <T> NodeData<T> createNodeDataFromConfiguration( 598 final HierarchicalConfiguration<T> config) 599 { 600 final NodeHandler<T> handler = config.getNodeModel().getNodeHandler(); 601 return new NodeData<>(handler.getRootNode(), handler); 602 } 603 604 /** 605 * An internally used helper class which wraps the node with the bean 606 * declaration and the corresponding node handler. 607 * 608 * @param <T> the type of the node 609 */ 610 static class NodeData<T> 611 { 612 /** The wrapped node. */ 613 private final T node; 614 615 /** The node handler for interacting with this node. */ 616 private final NodeHandler<T> handler; 617 618 /** 619 * Creates a new instance of {@code NodeData}. 620 * 621 * @param nd the node 622 * @param hndlr the handler 623 */ 624 public NodeData(final T nd, final NodeHandler<T> hndlr) 625 { 626 node = nd; 627 handler = hndlr; 628 } 629 630 /** 631 * Returns the name of the wrapped node. 632 * 633 * @return the node name 634 */ 635 public String nodeName() 636 { 637 return handler.nodeName(node); 638 } 639 640 /** 641 * Returns the unescaped name of the node stored in this data object. 642 * This method handles the case that the node name may contain reserved 643 * characters with a special meaning for the current expression engine. 644 * In this case, the characters affected have to be escaped accordingly. 645 * 646 * @param config the configuration 647 * @return the escaped node name 648 */ 649 public String escapedNodeName(final HierarchicalConfiguration<?> config) 650 { 651 return config.getExpressionEngine().nodeKey(node, 652 StringUtils.EMPTY, handler); 653 } 654 655 /** 656 * Returns a list with the children of the wrapped node, again wrapped 657 * into {@code NodeData} objects. 658 * 659 * @return a list with the children 660 */ 661 public List<NodeData<T>> getChildren() 662 { 663 return wrapInNodeData(handler.getChildren(node)); 664 } 665 666 /** 667 * Returns a list with the children of the wrapped node with the given 668 * name, again wrapped into {@code NodeData} objects. 669 * 670 * @param name the name of the desired child nodes 671 * @return a list with the children with this name 672 */ 673 public List<NodeData<T>> getChildren(final String name) 674 { 675 return wrapInNodeData(handler.getChildren(node, name)); 676 } 677 678 /** 679 * Returns a set with the names of the attributes of the wrapped node. 680 * 681 * @return the attribute names of this node 682 */ 683 public Set<String> getAttributes() 684 { 685 return handler.getAttributes(node); 686 } 687 688 /** 689 * Returns the value of the attribute with the given name of the wrapped 690 * node. 691 * 692 * @param key the key of the attribute 693 * @return the value of this attribute 694 */ 695 public Object getAttribute(final String key) 696 { 697 return handler.getAttributeValue(node, key); 698 } 699 700 /** 701 * Returns a flag whether the wrapped node is the root node of the 702 * passed in configuration. 703 * 704 * @param config the configuration 705 * @return a flag whether this node is the configuration's root node 706 */ 707 public boolean matchesConfigRootNode(final HierarchicalConfiguration<?> config) 708 { 709 return config.getNodeModel().getNodeHandler().getRootNode() 710 .equals(node); 711 } 712 713 /** 714 * Wraps the passed in list of nodes in {@code NodeData} objects. 715 * 716 * @param nodes the list with nodes 717 * @return the wrapped nodes 718 */ 719 private List<NodeData<T>> wrapInNodeData(final List<T> nodes) 720 { 721 final List<NodeData<T>> result = new ArrayList<>(nodes.size()); 722 for (final T node : nodes) 723 { 724 result.add(new NodeData<>(node, handler)); 725 } 726 return result; 727 } 728 } 729}