001 /* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017 018 package org.apache.commons.configuration; 019 020 import java.io.File; 021 import java.io.IOException; 022 import java.io.InputStream; 023 import java.net.URL; 024 import java.util.Collection; 025 import java.util.LinkedList; 026 import java.util.Map; 027 028 import org.apache.commons.configuration.plist.PropertyListConfiguration; 029 import org.apache.commons.configuration.plist.XMLPropertyListConfiguration; 030 import org.apache.commons.digester.AbstractObjectCreationFactory; 031 import org.apache.commons.digester.CallMethodRule; 032 import org.apache.commons.digester.Digester; 033 import org.apache.commons.digester.ObjectCreationFactory; 034 import org.apache.commons.digester.Substitutor; 035 import org.apache.commons.digester.substitution.MultiVariableExpander; 036 import org.apache.commons.digester.substitution.VariableSubstitutor; 037 import org.apache.commons.digester.xmlrules.DigesterLoader; 038 import org.apache.commons.lang.StringUtils; 039 import org.apache.commons.logging.Log; 040 import org.apache.commons.logging.LogFactory; 041 import org.xml.sax.Attributes; 042 import org.xml.sax.SAXException; 043 044 /** 045 * <p> 046 * Factory class to create a CompositeConfiguration from a .xml file using 047 * Digester. By default it can handle the Configurations from commons- 048 * configuration. If you need to add your own, then you can pass in your own 049 * digester rules to use. It is also namespace aware, by providing a 050 * digesterRuleNamespaceURI. 051 * </p> 052 * <p> 053 * <em>Note:</em> Almost all of the features provided by this class and many 054 * more are also available for the {@link DefaultConfigurationBuilder} 055 * class. {@code DefaultConfigurationBuilder} also has a more robust 056 * merge algorithm for constructing combined configurations. So it is 057 * recommended to use this class instead of {@code ConfigurationFactory}. 058 * </p> 059 * 060 * @author <a href="mailto:epugh@upstate.com">Eric Pugh</a> 061 * @author <a href="mailto:hps@intermeta.de">Henning P. Schmiedehausen</a> 062 * @version $Id: ConfigurationFactory.java 1209685 2011-12-02 20:47:44Z oheger $ 063 * @deprecated Use {@link DefaultConfigurationBuilder} instead; this class 064 * provides the same features as ConfigurationFactory plus some more; it can 065 * also process the same configuration definition files. 066 */ 067 @Deprecated 068 public class ConfigurationFactory 069 { 070 /** Constant for the root element in the info file.*/ 071 private static final String SEC_ROOT = "configuration/"; 072 073 /** Constant for the override section.*/ 074 private static final String SEC_OVERRIDE = SEC_ROOT + "override/"; 075 076 /** Constant for the additional section.*/ 077 private static final String SEC_ADDITIONAL = SEC_ROOT + "additional/"; 078 079 /** Constant for the optional attribute.*/ 080 private static final String ATTR_OPTIONAL = "optional"; 081 082 /** Constant for the fileName attribute.*/ 083 private static final String ATTR_FILENAME = "fileName"; 084 085 /** Constant for the load method.*/ 086 private static final String METH_LOAD = "load"; 087 088 /** Constant for the default base path (points to actual directory).*/ 089 private static final String DEF_BASE_PATH = "."; 090 091 /** static logger */ 092 private static Log log = LogFactory.getLog(ConfigurationFactory.class); 093 094 /** The XML file with the details about the configuration to load */ 095 private String configurationFileName; 096 097 /** The URL to the XML file with the details about the configuration to load. */ 098 private URL configurationURL; 099 100 /** 101 * The implicit base path for included files. This path is determined by 102 * the configuration to load and used unless no other base path was 103 * explicitly specified. 104 */ 105 private String implicitBasePath; 106 107 /** The basePath to prefix file paths for file based property files. */ 108 private String basePath; 109 110 /** URL for xml digester rules file */ 111 private URL digesterRules; 112 113 /** The digester namespace to parse */ 114 private String digesterRuleNamespaceURI; 115 116 /** 117 * Constructor 118 */ 119 public ConfigurationFactory() 120 { 121 setBasePath(DEF_BASE_PATH); 122 } 123 /** 124 * Constructor with ConfigurationFile Name passed 125 * 126 * @param configurationFileName The path to the configuration file 127 */ 128 public ConfigurationFactory(String configurationFileName) 129 { 130 setConfigurationFileName(configurationFileName); 131 } 132 133 /** 134 * Return the configuration provided by this factory. It loads the 135 * configuration file which is a XML description of the actual 136 * configurations to load. It can contain various different types of 137 * configuration, e.g. Properties, XML and JNDI. 138 * 139 * @return A Configuration object 140 * @throws ConfigurationException A generic exception that we had trouble during the 141 * loading of the configuration data. 142 */ 143 public Configuration getConfiguration() throws ConfigurationException 144 { 145 Digester digester; 146 InputStream input = null; 147 ConfigurationBuilder builder = new ConfigurationBuilder(); 148 URL url = getConfigurationURL(); 149 try 150 { 151 if (url == null) 152 { 153 url = ConfigurationUtils.locate(implicitBasePath, getConfigurationFileName()); 154 } 155 input = url.openStream(); 156 } 157 catch (Exception e) 158 { 159 log.error("Exception caught opening stream to URL", e); 160 throw new ConfigurationException("Exception caught opening stream to URL", e); 161 } 162 163 if (getDigesterRules() == null) 164 { 165 digester = new Digester(); 166 configureNamespace(digester); 167 initDefaultDigesterRules(digester); 168 } 169 else 170 { 171 digester = DigesterLoader.createDigester(getDigesterRules()); 172 // This might already be too late. As far as I can see, the namespace 173 // awareness must be configured before the digester rules are loaded. 174 configureNamespace(digester); 175 } 176 177 // Configure digester to always enable the context class loader 178 digester.setUseContextClassLoader(true); 179 // Add a substitutor to resolve system properties 180 enableDigesterSubstitutor(digester); 181 // Put the composite builder object below all of the other objects. 182 digester.push(builder); 183 // Parse the input stream to configure our mappings 184 try 185 { 186 digester.parse(input); 187 input.close(); 188 } 189 catch (SAXException saxe) 190 { 191 log.error("SAX Exception caught", saxe); 192 throw new ConfigurationException("SAX Exception caught", saxe); 193 } 194 catch (IOException ioe) 195 { 196 log.error("IO Exception caught", ioe); 197 throw new ConfigurationException("IO Exception caught", ioe); 198 } 199 return builder.getConfiguration(); 200 } 201 202 /** 203 * Returns the configurationFile. 204 * 205 * @return The name of the configuration file. Can be null. 206 */ 207 public String getConfigurationFileName() 208 { 209 return configurationFileName; 210 } 211 212 /** 213 * Sets the configurationFile. 214 * 215 * @param configurationFileName The name of the configurationFile to use. 216 */ 217 public void setConfigurationFileName(String configurationFileName) 218 { 219 File file = new File(configurationFileName).getAbsoluteFile(); 220 this.configurationFileName = file.getName(); 221 implicitBasePath = file.getParent(); 222 } 223 224 /** 225 * Returns the URL of the configuration file to be loaded. 226 * 227 * @return the URL of the configuration to load 228 */ 229 public URL getConfigurationURL() 230 { 231 return configurationURL; 232 } 233 234 /** 235 * Sets the URL of the configuration to load. This configuration can be 236 * either specified by a file name or by a URL. 237 * 238 * @param url the URL of the configuration to load 239 */ 240 public void setConfigurationURL(URL url) 241 { 242 configurationURL = url; 243 implicitBasePath = url.toString(); 244 } 245 246 /** 247 * Returns the digesterRules. 248 * 249 * @return URL 250 */ 251 public URL getDigesterRules() 252 { 253 return digesterRules; 254 } 255 256 /** 257 * Sets the digesterRules. 258 * 259 * @param digesterRules The digesterRules to set 260 */ 261 public void setDigesterRules(URL digesterRules) 262 { 263 this.digesterRules = digesterRules; 264 } 265 266 /** 267 * Adds a substitutor to interpolate system properties 268 * 269 * @param digester The digester to which we add the substitutor 270 */ 271 protected void enableDigesterSubstitutor(Digester digester) 272 { 273 // This is ugly, but it is safe because the Properties object returned 274 // by System.getProperties() (which is actually a Map<Object, Object>) 275 // contains only String keys. 276 @SuppressWarnings("unchecked") 277 Map<String, Object> systemProperties = 278 (Map<String, Object>) (Object) System.getProperties(); 279 MultiVariableExpander expander = new MultiVariableExpander(); 280 expander.addSource("$", systemProperties); 281 282 // allow expansion in both xml attributes and element text 283 Substitutor substitutor = new VariableSubstitutor(expander); 284 digester.setSubstitutor(substitutor); 285 } 286 287 /** 288 * Initializes the parsing rules for the default digester 289 * 290 * This allows the Configuration Factory to understand the default types: 291 * Properties, XML and JNDI. Two special sections are introduced: 292 * <code><override></code> and <code><additional></code>. 293 * 294 * @param digester The digester to configure 295 */ 296 protected void initDefaultDigesterRules(Digester digester) 297 { 298 initDigesterSectionRules(digester, SEC_ROOT, false); 299 initDigesterSectionRules(digester, SEC_OVERRIDE, false); 300 initDigesterSectionRules(digester, SEC_ADDITIONAL, true); 301 } 302 303 /** 304 * Sets up digester rules for a specified section of the configuration 305 * info file. 306 * 307 * @param digester the current digester instance 308 * @param matchString specifies the section 309 * @param additional a flag if rules for the additional section are to be 310 * added 311 */ 312 protected void initDigesterSectionRules(Digester digester, String matchString, boolean additional) 313 { 314 setupDigesterInstance( 315 digester, 316 matchString + "properties", 317 new PropertiesConfigurationFactory(), 318 METH_LOAD, 319 additional); 320 321 setupDigesterInstance( 322 digester, 323 matchString + "plist", 324 new PropertyListConfigurationFactory(), 325 METH_LOAD, 326 additional); 327 328 setupDigesterInstance( 329 digester, 330 matchString + "xml", 331 new FileConfigurationFactory(XMLConfiguration.class), 332 METH_LOAD, 333 additional); 334 335 setupDigesterInstance( 336 digester, 337 matchString + "hierarchicalXml", 338 new FileConfigurationFactory(XMLConfiguration.class), 339 METH_LOAD, 340 additional); 341 342 setupDigesterInstance( 343 digester, 344 matchString + "jndi", 345 new JNDIConfigurationFactory(), 346 null, 347 additional); 348 349 setupDigesterInstance( 350 digester, 351 matchString + "system", 352 new SystemConfigurationFactory(), 353 null, 354 additional); 355 } 356 357 /** 358 * Sets up digester rules for a configuration to be loaded. 359 * 360 * @param digester the current digester 361 * @param matchString the pattern to match with this rule 362 * @param factory an ObjectCreationFactory instance to use for creating new 363 * objects 364 * @param method the name of a method to be called or <b>null</b> for none 365 * @param additional a flag if rules for the additional section are to be 366 * added 367 */ 368 protected void setupDigesterInstance( 369 Digester digester, 370 String matchString, 371 ObjectCreationFactory factory, 372 String method, 373 boolean additional) 374 { 375 if (additional) 376 { 377 setupUnionRules(digester, matchString); 378 } 379 380 digester.addFactoryCreate(matchString, factory); 381 digester.addSetProperties(matchString); 382 383 if (method != null) 384 { 385 digester.addRule(matchString, new CallOptionalMethodRule(method)); 386 } 387 388 digester.addSetNext(matchString, "addConfiguration", Configuration.class.getName()); 389 } 390 391 /** 392 * Sets up rules for configurations in the additional section. 393 * 394 * @param digester the current digester 395 * @param matchString the pattern to match with this rule 396 */ 397 protected void setupUnionRules(Digester digester, String matchString) 398 { 399 digester.addObjectCreate(matchString, 400 AdditionalConfigurationData.class); 401 digester.addSetProperties(matchString); 402 digester.addSetNext(matchString, "addAdditionalConfig", 403 AdditionalConfigurationData.class.getName()); 404 } 405 406 /** 407 * Returns the digesterRuleNamespaceURI. 408 * 409 * @return A String with the digesterRuleNamespaceURI. 410 */ 411 public String getDigesterRuleNamespaceURI() 412 { 413 return digesterRuleNamespaceURI; 414 } 415 416 /** 417 * Sets the digesterRuleNamespaceURI. 418 * 419 * @param digesterRuleNamespaceURI The new digesterRuleNamespaceURI to use 420 */ 421 public void setDigesterRuleNamespaceURI(String digesterRuleNamespaceURI) 422 { 423 this.digesterRuleNamespaceURI = digesterRuleNamespaceURI; 424 } 425 426 /** 427 * Configure the current digester to be namespace aware and to have 428 * a Configuration object to which all of the other configurations 429 * should be added 430 * 431 * @param digester The Digester to configure 432 */ 433 private void configureNamespace(Digester digester) 434 { 435 if (getDigesterRuleNamespaceURI() != null) 436 { 437 digester.setNamespaceAware(true); 438 digester.setRuleNamespaceURI(getDigesterRuleNamespaceURI()); 439 } 440 else 441 { 442 digester.setNamespaceAware(false); 443 } 444 digester.setValidating(false); 445 } 446 447 /** 448 * Returns the Base path from which this Configuration Factory operates. 449 * This is never null. If you set the BasePath to null, then a base path 450 * according to the configuration to load is returned. 451 * 452 * @return The base Path of this configuration factory. 453 */ 454 public String getBasePath() 455 { 456 String path = StringUtils.isEmpty(basePath) 457 || DEF_BASE_PATH.equals(basePath) ? implicitBasePath : basePath; 458 return StringUtils.isEmpty(path) ? DEF_BASE_PATH : path; 459 } 460 461 /** 462 * Sets the basePath for all file references from this Configuration Factory. 463 * Normally a base path need not to be set because it is determined by 464 * the location of the configuration file to load. All relative pathes in 465 * this file are resolved relative to this file. Setting a base path makes 466 * sense if such relative pathes should be otherwise resolved, e.g. if 467 * the configuration file is loaded from the class path and all sub 468 * configurations it refers to are stored in a special config directory. 469 * 470 * @param basePath The new basePath to set. 471 */ 472 public void setBasePath(String basePath) 473 { 474 this.basePath = basePath; 475 } 476 477 /** 478 * A base class for digester factory classes. This base class maintains 479 * a default class for the objects to be created. 480 * There will be sub classes for specific configuration implementations. 481 */ 482 public class DigesterConfigurationFactory extends AbstractObjectCreationFactory 483 { 484 /** Actual class to use. */ 485 private Class<?> clazz; 486 487 /** 488 * Creates a new instance of {@code DigesterConfigurationFactory}. 489 * 490 * @param clazz the class which we should instantiate 491 */ 492 public DigesterConfigurationFactory(Class<?> clazz) 493 { 494 this.clazz = clazz; 495 } 496 497 /** 498 * Creates an instance of the specified class. 499 * 500 * @param attribs the attributes (ignored) 501 * @return the new object 502 * @throws Exception if object creation fails 503 */ 504 @Override 505 public Object createObject(Attributes attribs) throws Exception 506 { 507 return clazz.newInstance(); 508 } 509 } 510 511 /** 512 * A tiny inner class that allows the Configuration Factory to 513 * let the digester construct FileConfiguration objects 514 * that already have the correct base Path set. 515 * 516 */ 517 public class FileConfigurationFactory extends DigesterConfigurationFactory 518 { 519 /** 520 * C'tor 521 * 522 * @param clazz The class which we should instantiate. 523 */ 524 public FileConfigurationFactory(Class<?> clazz) 525 { 526 super(clazz); 527 } 528 529 /** 530 * Gets called by the digester. 531 * 532 * @param attributes the actual attributes 533 * @return the new object 534 * @throws Exception Couldn't instantiate the requested object. 535 */ 536 @Override 537 public Object createObject(Attributes attributes) throws Exception 538 { 539 FileConfiguration conf = createConfiguration(attributes); 540 conf.setBasePath(getBasePath()); 541 return conf; 542 } 543 544 /** 545 * Creates the object, a {@code FileConfiguration}. 546 * 547 * @param attributes the actual attributes 548 * @return the file configuration 549 * @throws Exception if the object could not be created 550 */ 551 protected FileConfiguration createConfiguration(Attributes attributes) throws Exception 552 { 553 return (FileConfiguration) super.createObject(attributes); 554 } 555 } 556 557 /** 558 * A factory that returns an XMLPropertiesConfiguration for .xml files 559 * and a PropertiesConfiguration for the others. 560 * 561 * @since 1.2 562 */ 563 public class PropertiesConfigurationFactory extends FileConfigurationFactory 564 { 565 /** 566 * Creates a new instance of {@code PropertiesConfigurationFactory}. 567 */ 568 public PropertiesConfigurationFactory() 569 { 570 super(null); 571 } 572 573 /** 574 * Creates the new configuration object. Based on the file name 575 * provided in the attributes either a {@code PropertiesConfiguration} 576 * or a {@code XMLPropertiesConfiguration} object will be 577 * returned. 578 * 579 * @param attributes the attributes 580 * @return the new configuration object 581 * @throws Exception if an error occurs 582 */ 583 @Override 584 protected FileConfiguration createConfiguration(Attributes attributes) throws Exception 585 { 586 String filename = attributes.getValue(ATTR_FILENAME); 587 588 if (filename != null && filename.toLowerCase().trim().endsWith(".xml")) 589 { 590 return new XMLPropertiesConfiguration(); 591 } 592 else 593 { 594 return new PropertiesConfiguration(); 595 } 596 } 597 } 598 599 /** 600 * A factory that returns an XMLPropertyListConfiguration for .xml files 601 * and a PropertyListConfiguration for the others. 602 * 603 * @since 1.2 604 */ 605 public class PropertyListConfigurationFactory extends FileConfigurationFactory 606 { 607 /** 608 * Creates a new instance of PropertyListConfigurationFactory</code>. 609 */ 610 public PropertyListConfigurationFactory() 611 { 612 super(null); 613 } 614 615 /** 616 * Creates the new configuration object. Based on the file name 617 * provided in the attributes either a {@code XMLPropertyListConfiguration} 618 * or a {@code PropertyListConfiguration} object will be 619 * returned. 620 * 621 * @param attributes the attributes 622 * @return the new configuration object 623 * @throws Exception if an error occurs 624 */ 625 @Override 626 protected FileConfiguration createConfiguration(Attributes attributes) throws Exception 627 { 628 String filename = attributes.getValue(ATTR_FILENAME); 629 630 if (filename != null && filename.toLowerCase().trim().endsWith(".xml")) 631 { 632 return new XMLPropertyListConfiguration(); 633 } 634 else 635 { 636 return new PropertyListConfiguration(); 637 } 638 } 639 } 640 641 /** 642 * A tiny inner class that allows the Configuration Factory to 643 * let the digester construct JNDIConfiguration objects. 644 */ 645 private class JNDIConfigurationFactory extends DigesterConfigurationFactory 646 { 647 /** 648 * Creates a new instance of {@code JNDIConfigurationFactory}. 649 */ 650 public JNDIConfigurationFactory() 651 { 652 super(JNDIConfiguration.class); 653 } 654 } 655 656 /** 657 * A tiny inner class that allows the Configuration Factory to 658 * let the digester construct SystemConfiguration objects. 659 */ 660 private class SystemConfigurationFactory extends DigesterConfigurationFactory 661 { 662 /** 663 * Creates a new instance of {@code SystemConfigurationFactory}. 664 */ 665 public SystemConfigurationFactory() 666 { 667 super(SystemConfiguration.class); 668 } 669 } 670 671 /** 672 * A simple data class that holds all information about a configuration 673 * from the <code><additional></code> section. 674 */ 675 public static class AdditionalConfigurationData 676 { 677 /** Stores the configuration object.*/ 678 private Configuration configuration; 679 680 /** Stores the location of this configuration in the global tree.*/ 681 private String at; 682 683 /** 684 * Returns the value of the {@code at} attribute. 685 * 686 * @return the at attribute 687 */ 688 public String getAt() 689 { 690 return at; 691 } 692 693 /** 694 * Sets the value of the {@code at} attribute. 695 * 696 * @param string the attribute value 697 */ 698 public void setAt(String string) 699 { 700 at = string; 701 } 702 703 /** 704 * Returns the configuration object. 705 * 706 * @return the configuration 707 */ 708 public Configuration getConfiguration() 709 { 710 return configuration; 711 } 712 713 /** 714 * Sets the configuration object. Note: Normally this method should be 715 * named {@code setConfiguration()}, but the name 716 * {@code addConfiguration()} is required by some of the digester 717 * rules. 718 * 719 * @param config the configuration to set 720 */ 721 public void addConfiguration(Configuration config) 722 { 723 configuration = config; 724 } 725 } 726 727 /** 728 * An internally used helper class for constructing the composite 729 * configuration object. 730 */ 731 public static class ConfigurationBuilder 732 { 733 /** Stores the composite configuration.*/ 734 private CompositeConfiguration config; 735 736 /** Stores a collection with the configs from the additional section.*/ 737 private Collection<AdditionalConfigurationData> additionalConfigs; 738 739 /** 740 * Creates a new instance of {@code ConfigurationBuilder}. 741 */ 742 public ConfigurationBuilder() 743 { 744 config = new CompositeConfiguration(); 745 additionalConfigs = new LinkedList<AdditionalConfigurationData>(); 746 } 747 748 /** 749 * Adds a new configuration to this object. This method is called by 750 * Digester. 751 * 752 * @param conf the configuration to be added 753 */ 754 public void addConfiguration(Configuration conf) 755 { 756 config.addConfiguration(conf); 757 } 758 759 /** 760 * Adds information about an additional configuration. This method is 761 * called by Digester. 762 * 763 * @param data the data about the additional configuration 764 */ 765 public void addAdditionalConfig(AdditionalConfigurationData data) 766 { 767 additionalConfigs.add(data); 768 } 769 770 /** 771 * Returns the final composite configuration. 772 * 773 * @return the final configuration object 774 */ 775 public CompositeConfiguration getConfiguration() 776 { 777 if (!additionalConfigs.isEmpty()) 778 { 779 Configuration unionConfig = createAdditionalConfiguration(additionalConfigs); 780 if (unionConfig != null) 781 { 782 addConfiguration(unionConfig); 783 } 784 additionalConfigs.clear(); 785 } 786 787 return config; 788 } 789 790 /** 791 * Creates a configuration object with the union of all properties 792 * defined in the <code><additional></code> section. This 793 * implementation returns a {@code HierarchicalConfiguration} 794 * object. 795 * 796 * @param configs a collection with 797 * {@code AdditionalConfigurationData} objects 798 * @return the union configuration (can be <b>null</b>) 799 */ 800 protected Configuration createAdditionalConfiguration(Collection<AdditionalConfigurationData> configs) 801 { 802 HierarchicalConfiguration result = new HierarchicalConfiguration(); 803 804 for (AdditionalConfigurationData cdata : configs) 805 { 806 result.addNodes(cdata.getAt(), 807 createRootNode(cdata).getChildren()); 808 } 809 810 return result.isEmpty() ? null : result; 811 } 812 813 /** 814 * Creates a configuration root node for the specified configuration. 815 * 816 * @param cdata the configuration data object 817 * @return a root node for this configuration 818 */ 819 private HierarchicalConfiguration.Node createRootNode(AdditionalConfigurationData cdata) 820 { 821 if (cdata.getConfiguration() instanceof HierarchicalConfiguration) 822 { 823 // we can directly use this configuration's root node 824 return ((HierarchicalConfiguration) cdata.getConfiguration()).getRoot(); 825 } 826 else 827 { 828 // transform configuration to a hierarchical root node 829 HierarchicalConfiguration hc = new HierarchicalConfiguration(); 830 ConfigurationUtils.copy(cdata.getConfiguration(), hc); 831 return hc.getRoot(); 832 } 833 } 834 } 835 836 /** 837 * A special implementation of Digester's {@code CallMethodRule} that 838 * is internally used for calling a file configuration's {@code load()} 839 * method. This class differs from its ancestor that it catches all occurring 840 * exceptions when the specified method is called. It then checks whether 841 * for the corresponding configuration the optional attribute is set. If 842 * this is the case, the exception will simply be ignored. 843 * 844 * @since 1.4 845 */ 846 private static class CallOptionalMethodRule extends CallMethodRule 847 { 848 /** A flag whether the optional attribute is set for this node. */ 849 private boolean optional; 850 851 /** 852 * Creates a new instance of {@code CallOptionalMethodRule} and 853 * sets the name of the method to invoke. 854 * 855 * @param methodName the name of the method 856 */ 857 public CallOptionalMethodRule(String methodName) 858 { 859 super(methodName); 860 } 861 862 /** 863 * Checks if the optional attribute is set. 864 * 865 * @param attrs the attributes 866 * @throws Exception if an error occurs 867 */ 868 @Override 869 public void begin(Attributes attrs) throws Exception 870 { 871 optional = attrs.getValue(ATTR_OPTIONAL) != null 872 && PropertyConverter.toBoolean( 873 attrs.getValue(ATTR_OPTIONAL)).booleanValue(); 874 super.begin(attrs); 875 } 876 877 /** 878 * Calls the method. If the optional attribute was set, occurring 879 * exceptions will be ignored. 880 * 881 * @throws Exception if an error occurs 882 */ 883 @Override 884 public void end() throws Exception 885 { 886 try 887 { 888 super.end(); 889 } 890 catch (Exception ex) 891 { 892 if (optional) 893 { 894 log.warn("Could not create optional configuration!", ex); 895 } 896 else 897 { 898 throw ex; 899 } 900 } 901 } 902 } 903 }