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