View Javadoc

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