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.Iterator;
026    import java.util.LinkedList;
027    import java.util.Map;
028    
029    import org.apache.commons.configuration.plist.PropertyListConfiguration;
030    import org.apache.commons.configuration.plist.XMLPropertyListConfiguration;
031    import org.apache.commons.digester.AbstractObjectCreationFactory;
032    import org.apache.commons.digester.CallMethodRule;
033    import org.apache.commons.digester.Digester;
034    import org.apache.commons.digester.ObjectCreationFactory;
035    import org.apache.commons.digester.Substitutor;
036    import org.apache.commons.digester.substitution.MultiVariableExpander;
037    import org.apache.commons.digester.substitution.VariableSubstitutor;
038    import org.apache.commons.digester.xmlrules.DigesterLoader;
039    import org.apache.commons.lang.StringUtils;
040    import org.apache.commons.logging.Log;
041    import org.apache.commons.logging.LogFactory;
042    import org.xml.sax.Attributes;
043    import org.xml.sax.SAXException;
044    
045    /**
046     * <p>
047     * Factory class to create a CompositeConfiguration from a .xml file using
048     * Digester.  By default it can handle the Configurations from commons-
049     * configuration.  If you need to add your own, then you can pass in your own
050     * digester rules to use.  It is also namespace aware, by providing a
051     * digesterRuleNamespaceURI.
052     * </p>
053     * <p>
054     * <em>Note:</em> Almost all of the features provided by this class and many
055     * more are also available for the <code>{@link DefaultConfigurationBuilder}</code>
056     * class. <code>DefaultConfigurationBuilder</code> also has a more robust
057     * merge algorithm for constructing combined configurations. So it is
058     * recommended to use this class instead of <code>ConfigurationFactory</code>.
059     * </p>
060     *
061     * @author <a href="mailto:epugh@upstate.com">Eric Pugh</a>
062     * @author <a href="mailto:hps@intermeta.de">Henning P. Schmiedehausen</a>
063     * @version $Id: ConfigurationFactory.java 1161254 2011-08-24 19:54:07Z oheger $
064     * @deprecated Use {@link DefaultConfigurationBuilder} instead; this class
065     * provides the same features as ConfigurationFactory plus some more; it can
066     * also process the same configuration definition files.
067     */
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         * explicitely 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            Map systemProperties = System.getProperties();
274            MultiVariableExpander expander = new MultiVariableExpander();
275            expander.addSource("$", systemProperties);
276    
277            // allow expansion in both xml attributes and element text
278            Substitutor substitutor = new VariableSubstitutor(expander);
279            digester.setSubstitutor(substitutor);
280        }
281    
282        /**
283         * Initializes the parsing rules for the default digester
284         *
285         * This allows the Configuration Factory to understand the default types:
286         * Properties, XML and JNDI. Two special sections are introduced:
287         * <code>&lt;override&gt;</code> and <code>&lt;additional&gt;</code>.
288         *
289         * @param digester The digester to configure
290         */
291        protected void initDefaultDigesterRules(Digester digester)
292        {
293            initDigesterSectionRules(digester, SEC_ROOT, false);
294            initDigesterSectionRules(digester, SEC_OVERRIDE, false);
295            initDigesterSectionRules(digester, SEC_ADDITIONAL, true);
296        }
297    
298        /**
299         * Sets up digester rules for a specified section of the configuration
300         * info file.
301         *
302         * @param digester the current digester instance
303         * @param matchString specifies the section
304         * @param additional a flag if rules for the additional section are to be
305         * added
306         */
307        protected void initDigesterSectionRules(Digester digester, String matchString, boolean additional)
308        {
309            setupDigesterInstance(
310                digester,
311                matchString + "properties",
312                new PropertiesConfigurationFactory(),
313                METH_LOAD,
314                additional);
315    
316            setupDigesterInstance(
317                digester,
318                matchString + "plist",
319                new PropertyListConfigurationFactory(),
320                METH_LOAD,
321                additional);
322    
323            setupDigesterInstance(
324                digester,
325                matchString + "xml",
326                new FileConfigurationFactory(XMLConfiguration.class),
327                METH_LOAD,
328                additional);
329    
330            setupDigesterInstance(
331                digester,
332                matchString + "hierarchicalXml",
333                new FileConfigurationFactory(XMLConfiguration.class),
334                METH_LOAD,
335                additional);
336    
337            setupDigesterInstance(
338                digester,
339                matchString + "jndi",
340                new JNDIConfigurationFactory(),
341                null,
342                additional);
343    
344            setupDigesterInstance(
345                digester,
346                matchString + "system",
347                new SystemConfigurationFactory(),
348                null,
349                additional);
350        }
351    
352        /**
353         * Sets up digester rules for a configuration to be loaded.
354         *
355         * @param digester the current digester
356         * @param matchString the pattern to match with this rule
357         * @param factory an ObjectCreationFactory instance to use for creating new
358         * objects
359         * @param method the name of a method to be called or <b>null</b> for none
360         * @param additional a flag if rules for the additional section are to be
361         * added
362         */
363        protected void setupDigesterInstance(
364                Digester digester,
365                String matchString,
366                ObjectCreationFactory factory,
367                String method,
368                boolean additional)
369        {
370            if (additional)
371            {
372                setupUnionRules(digester, matchString);
373            }
374    
375            digester.addFactoryCreate(matchString, factory);
376            digester.addSetProperties(matchString);
377    
378            if (method != null)
379            {
380                digester.addRule(matchString, new CallOptionalMethodRule(method));
381            }
382    
383            digester.addSetNext(matchString, "addConfiguration", Configuration.class.getName());
384        }
385    
386        /**
387         * Sets up rules for configurations in the additional section.
388         *
389         * @param digester the current digester
390         * @param matchString the pattern to match with this rule
391         */
392        protected void setupUnionRules(Digester digester, String matchString)
393        {
394            digester.addObjectCreate(matchString,
395            AdditionalConfigurationData.class);
396            digester.addSetProperties(matchString);
397            digester.addSetNext(matchString, "addAdditionalConfig",
398            AdditionalConfigurationData.class.getName());
399        }
400    
401        /**
402         * Returns the digesterRuleNamespaceURI.
403         *
404         * @return A String with the digesterRuleNamespaceURI.
405         */
406        public String getDigesterRuleNamespaceURI()
407        {
408            return digesterRuleNamespaceURI;
409        }
410    
411        /**
412         * Sets the digesterRuleNamespaceURI.
413         *
414         * @param digesterRuleNamespaceURI The new digesterRuleNamespaceURI to use
415         */
416        public void setDigesterRuleNamespaceURI(String digesterRuleNamespaceURI)
417        {
418            this.digesterRuleNamespaceURI = digesterRuleNamespaceURI;
419        }
420    
421        /**
422         * Configure the current digester to be namespace aware and to have
423         * a Configuration object to which all of the other configurations
424         * should be added
425         *
426         * @param digester The Digester to configure
427         */
428        private void configureNamespace(Digester digester)
429        {
430            if (getDigesterRuleNamespaceURI() != null)
431            {
432                digester.setNamespaceAware(true);
433                digester.setRuleNamespaceURI(getDigesterRuleNamespaceURI());
434            }
435            else
436            {
437                digester.setNamespaceAware(false);
438            }
439            digester.setValidating(false);
440        }
441    
442        /**
443         * Returns the Base path from which this Configuration Factory operates.
444         * This is never null. If you set the BasePath to null, then a base path
445         * according to the configuration to load is returned.
446         *
447         * @return The base Path of this configuration factory.
448         */
449        public String getBasePath()
450        {
451            String path = StringUtils.isEmpty(basePath)
452                    || DEF_BASE_PATH.equals(basePath) ? implicitBasePath : basePath;
453            return StringUtils.isEmpty(path) ? DEF_BASE_PATH : path;
454        }
455    
456        /**
457         * Sets the basePath for all file references from this Configuration Factory.
458         * Normally a base path need not to be set because it is determined by
459         * the location of the configuration file to load. All relative pathes in
460         * this file are resolved relative to this file. Setting a base path makes
461         * sense if such relative pathes should be otherwise resolved, e.g. if
462         * the configuration file is loaded from the class path and all sub
463         * configurations it refers to are stored in a special config directory.
464         *
465         * @param basePath The new basePath to set.
466         */
467        public void setBasePath(String basePath)
468        {
469            this.basePath = basePath;
470        }
471    
472        /**
473         * A base class for digester factory classes. This base class maintains
474         * a default class for the objects to be created.
475         * There will be sub classes for specific configuration implementations.
476         */
477        public class DigesterConfigurationFactory extends AbstractObjectCreationFactory
478        {
479            /** Actual class to use. */
480            private Class clazz;
481    
482            /**
483             * Creates a new instance of <code>DigesterConfigurationFactory</code>.
484             *
485             * @param clazz the class which we should instantiate
486             */
487            public DigesterConfigurationFactory(Class clazz)
488            {
489                this.clazz = clazz;
490            }
491    
492            /**
493             * Creates an instance of the specified class.
494             *
495             * @param attribs the attributes (ignored)
496             * @return the new object
497             * @throws Exception if object creation fails
498             */
499            public Object createObject(Attributes attribs) throws Exception
500            {
501                return clazz.newInstance();
502            }
503        }
504    
505        /**
506         * A tiny inner class that allows the Configuration Factory to
507         * let the digester construct FileConfiguration objects
508         * that already have the correct base Path set.
509         *
510         */
511        public class FileConfigurationFactory extends DigesterConfigurationFactory
512        {
513            /**
514             * C'tor
515             *
516             * @param clazz The class which we should instantiate.
517             */
518            public FileConfigurationFactory(Class clazz)
519            {
520                super(clazz);
521            }
522    
523            /**
524             * Gets called by the digester.
525             *
526             * @param attributes the actual attributes
527             * @return the new object
528             * @throws Exception Couldn't instantiate the requested object.
529             */
530            public Object createObject(Attributes attributes) throws Exception
531            {
532                FileConfiguration conf = createConfiguration(attributes);
533                conf.setBasePath(getBasePath());
534                return conf;
535            }
536    
537            /**
538             * Creates the object, a <code>FileConfiguration</code>.
539             *
540             * @param attributes the actual attributes
541             * @return the file configuration
542             * @throws Exception if the object could not be created
543             */
544            protected FileConfiguration createConfiguration(Attributes attributes) throws Exception
545            {
546                return (FileConfiguration) super.createObject(attributes);
547            }
548        }
549    
550        /**
551         * A factory that returns an XMLPropertiesConfiguration for .xml files
552         * and a PropertiesConfiguration for the others.
553         *
554         * @since 1.2
555         */
556        public class PropertiesConfigurationFactory extends FileConfigurationFactory
557        {
558            /**
559             * Creates a new instance of <code>PropertiesConfigurationFactory</code>.
560             */
561            public PropertiesConfigurationFactory()
562            {
563                super(null);
564            }
565    
566            /**
567             * Creates the new configuration object. Based on the file name
568             * provided in the attributes either a <code>PropertiesConfiguration</code>
569             * or a <code>XMLPropertiesConfiguration</code> object will be
570             * returned.
571             *
572             * @param attributes the attributes
573             * @return the new configuration object
574             * @throws Exception if an error occurs
575             */
576            protected FileConfiguration createConfiguration(Attributes attributes) throws Exception
577            {
578                String filename = attributes.getValue(ATTR_FILENAME);
579    
580                if (filename != null && filename.toLowerCase().trim().endsWith(".xml"))
581                {
582                    return new XMLPropertiesConfiguration();
583                }
584                else
585                {
586                    return new PropertiesConfiguration();
587                }
588            }
589        }
590    
591        /**
592         * A factory that returns an XMLPropertyListConfiguration for .xml files
593         * and a PropertyListConfiguration for the others.
594         *
595         * @since 1.2
596         */
597        public class PropertyListConfigurationFactory extends FileConfigurationFactory
598        {
599            /**
600             * Creates a new instance of <code>PropertyListConfigurationFactory</code>.
601             */
602            public PropertyListConfigurationFactory()
603            {
604                super(null);
605            }
606    
607            /**
608             * Creates the new configuration object. Based on the file name
609             * provided in the attributes either a <code>XMLPropertyListConfiguration</code>
610             * or a <code>PropertyListConfiguration</code> object will be
611             * returned.
612             *
613             * @param attributes the attributes
614             * @return the new configuration object
615             * @throws Exception if an error occurs
616             */
617            protected FileConfiguration createConfiguration(Attributes attributes) throws Exception
618            {
619                String filename = attributes.getValue(ATTR_FILENAME);
620    
621                if (filename != null && filename.toLowerCase().trim().endsWith(".xml"))
622                {
623                    return new XMLPropertyListConfiguration();
624                }
625                else
626                {
627                    return new PropertyListConfiguration();
628                }
629            }
630        }
631    
632        /**
633         * A tiny inner class that allows the Configuration Factory to
634         * let the digester construct JNDIConfiguration objects.
635         */
636        private class JNDIConfigurationFactory extends DigesterConfigurationFactory
637        {
638            /**
639             * Creates a new instance of <code>JNDIConfigurationFactory</code>.
640             */
641            public JNDIConfigurationFactory()
642            {
643                super(JNDIConfiguration.class);
644            }
645        }
646    
647        /**
648         * A tiny inner class that allows the Configuration Factory to
649         * let the digester construct SystemConfiguration objects.
650         */
651        private class SystemConfigurationFactory extends DigesterConfigurationFactory
652        {
653            /**
654             * Creates a new instance of <code>SystemConfigurationFactory</code>.
655             */
656            public SystemConfigurationFactory()
657            {
658                super(SystemConfiguration.class);
659            }
660        }
661    
662        /**
663         * A simple data class that holds all information about a configuration
664         * from the <code>&lt;additional&gt;</code> section.
665         */
666        public static class AdditionalConfigurationData
667        {
668            /** Stores the configuration object.*/
669            private Configuration configuration;
670    
671            /** Stores the location of this configuration in the global tree.*/
672            private String at;
673    
674            /**
675             * Returns the value of the <code>at</code> attribute.
676             *
677             * @return the at attribute
678             */
679            public String getAt()
680            {
681                return at;
682            }
683    
684            /**
685             * Sets the value of the <code>at</code> attribute.
686             *
687             * @param string the attribute value
688             */
689            public void setAt(String string)
690            {
691                at = string;
692            }
693    
694            /**
695             * Returns the configuration object.
696             *
697             * @return the configuration
698             */
699            public Configuration getConfiguration()
700            {
701                return configuration;
702            }
703    
704            /**
705             * Sets the configuration object. Note: Normally this method should be
706             * named <code>setConfiguration()</code>, but the name
707             * <code>addConfiguration()</code> is required by some of the digester
708             * rules.
709             *
710             * @param config the configuration to set
711             */
712            public void addConfiguration(Configuration config)
713            {
714                configuration = config;
715            }
716        }
717    
718        /**
719         * An internally used helper class for constructing the composite
720         * configuration object.
721         */
722        public static class ConfigurationBuilder
723        {
724            /** Stores the composite configuration.*/
725            private CompositeConfiguration config;
726    
727            /** Stores a collection with the configs from the additional section.*/
728            private Collection additionalConfigs;
729    
730            /**
731             * Creates a new instance of <code>ConfigurationBuilder</code>.
732             */
733            public ConfigurationBuilder()
734            {
735                config = new CompositeConfiguration();
736                additionalConfigs = new LinkedList();
737            }
738    
739            /**
740             * Adds a new configuration to this object. This method is called by
741             * Digester.
742             *
743             * @param conf the configuration to be added
744             */
745            public void addConfiguration(Configuration conf)
746            {
747                config.addConfiguration(conf);
748            }
749    
750            /**
751             * Adds information about an additional configuration. This method is
752             * called by Digester.
753             *
754             * @param data the data about the additional configuration
755             */
756            public void addAdditionalConfig(AdditionalConfigurationData data)
757            {
758                additionalConfigs.add(data);
759            }
760    
761            /**
762             * Returns the final composite configuration.
763             *
764             * @return the final configuration object
765             */
766            public CompositeConfiguration getConfiguration()
767            {
768                if (!additionalConfigs.isEmpty())
769                {
770                    Configuration unionConfig = createAdditionalConfiguration(additionalConfigs);
771                    if (unionConfig != null)
772                    {
773                        addConfiguration(unionConfig);
774                    }
775                    additionalConfigs.clear();
776                }
777    
778                return config;
779            }
780    
781            /**
782             * Creates a configuration object with the union of all properties
783             * defined in the <code>&lt;additional&gt;</code> section. This
784             * implementation returns a <code>HierarchicalConfiguration</code>
785             * object.
786             *
787             * @param configs a collection with
788             * <code>AdditionalConfigurationData</code> objects
789             * @return the union configuration (can be <b>null</b>)
790             */
791            protected Configuration createAdditionalConfiguration(Collection configs)
792            {
793                HierarchicalConfiguration result = new HierarchicalConfiguration();
794    
795                for (Iterator it = configs.iterator(); it.hasNext();)
796                {
797                    AdditionalConfigurationData cdata =
798                    (AdditionalConfigurationData) it.next();
799                    result.addNodes(cdata.getAt(),
800                    createRootNode(cdata).getChildren());
801                }
802    
803                return result.isEmpty() ? null : result;
804            }
805    
806            /**
807             * Creates a configuration root node for the specified configuration.
808             *
809             * @param cdata the configuration data object
810             * @return a root node for this configuration
811             */
812            private HierarchicalConfiguration.Node createRootNode(AdditionalConfigurationData cdata)
813            {
814                if (cdata.getConfiguration() instanceof HierarchicalConfiguration)
815                {
816                    // we can directly use this configuration's root node
817                    return ((HierarchicalConfiguration) cdata.getConfiguration()).getRoot();
818                }
819                else
820                {
821                    // transform configuration to a hierarchical root node
822                    HierarchicalConfiguration hc = new HierarchicalConfiguration();
823                    ConfigurationUtils.copy(cdata.getConfiguration(), hc);
824                    return hc.getRoot();
825                }
826            }
827        }
828    
829        /**
830         * A special implementation of Digester's <code>CallMethodRule</code> that
831         * is internally used for calling a file configuration's <code>load()</code>
832         * method. This class difers from its ancestor that it catches all occuring
833         * exceptions when the specified method is called. It then checks whether
834         * for the corresponding configuration the optional attribute is set. If
835         * this is the case, the exception will simply be ignored.
836         *
837         * @since 1.4
838         */
839        private static class CallOptionalMethodRule extends CallMethodRule
840        {
841            /** A flag whether the optional attribute is set for this node. */
842            private boolean optional;
843    
844            /**
845             * Creates a new instance of <code>CallOptionalMethodRule</code> and
846             * sets the name of the method to invoke.
847             *
848             * @param methodName the name of the method
849             */
850            public CallOptionalMethodRule(String methodName)
851            {
852                super(methodName);
853            }
854    
855            /**
856             * Checks if the optional attribute is set.
857             *
858             * @param attrs the attributes
859             * @throws Exception if an error occurs
860             */
861            public void begin(Attributes attrs) throws Exception
862            {
863                optional = attrs.getValue(ATTR_OPTIONAL) != null
864                        && PropertyConverter.toBoolean(
865                                attrs.getValue(ATTR_OPTIONAL)).booleanValue();
866                super.begin(attrs);
867            }
868    
869            /**
870             * Calls the method. If the optional attribute was set, occurring
871             * exceptions will be ignored.
872             *
873             * @throws Exception if an error occurs
874             */
875            public void end() throws Exception
876            {
877                try
878                {
879                    super.end();
880                }
881                catch (Exception ex)
882                {
883                    if (optional)
884                    {
885                        log.warn("Could not create optional configuration!", ex);
886                    }
887                    else
888                    {
889                        throw ex;
890                    }
891                }
892            }
893        }
894    }