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