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