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     * @author <a href="mailto:oliver.heger@t-online.de">Oliver Heger</a>
064     * @version $Id: ConfigurationFactory.java 524006 2007-03-30 09:33:17Z oheger $
065     */
066    public class ConfigurationFactory
067    {
068        /** Constant for the root element in the info file.*/
069        private static final String SEC_ROOT = "configuration/";
070    
071        /** Constant for the override section.*/
072        private static final String SEC_OVERRIDE = SEC_ROOT + "override/";
073    
074        /** Constant for the additional section.*/
075        private static final String SEC_ADDITIONAL = SEC_ROOT + "additional/";
076    
077        /** Constant for the optional attribute.*/
078        private static final String ATTR_OPTIONAL = "optional";
079    
080        /** Constant for the fileName attribute.*/
081        private static final String ATTR_FILENAME = "fileName";
082    
083        /** Constant for the load method.*/
084        private static final String METH_LOAD = "load";
085    
086        /** Constant for the default base path (points to actual directory).*/
087        private static final String DEF_BASE_PATH = ".";
088    
089        /** static logger */
090        private static Log log = LogFactory.getLog(ConfigurationFactory.class);
091    
092        /** The XML file with the details about the configuration to load */
093        private String configurationFileName;
094    
095        /** The URL to the XML file with the details about the configuration to load. */
096        private URL configurationURL;
097    
098        /**
099         * 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    }