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