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.digester.AbstractObjectCreationFactory;
29 import org.apache.commons.digester.Digester;
30 import org.apache.commons.digester.ObjectCreationFactory;
31 import org.apache.commons.digester.xmlrules.DigesterLoader;
32 import org.apache.commons.lang.StringUtils;
33 import org.apache.commons.logging.Log;
34 import org.apache.commons.logging.LogFactory;
35 import org.xml.sax.Attributes;
36 import org.xml.sax.SAXException;
37
38 /***
39 * Factory class to create a CompositeConfiguration from a .xml file using
40 * Digester. By default it can handle the Configurations from commons-
41 * configuration. If you need to add your own, then you can pass in your own
42 * digester rules to use. It is also namespace aware, by providing a
43 * digesterRuleNamespaceURI.
44 *
45 * @author <a href="mailto:epugh@upstate.com">Eric Pugh</a>
46 * @author <a href="mailto:hps@intermeta.de">Henning P. Schmiedehausen</a>
47 * @author <a href="mailto:oliver.heger@t-online.de">Oliver Heger</a>
48 * @version $Id: ConfigurationFactory.java 155408 2005-02-26 12:56:39Z dirkv $
49 */
50 public class ConfigurationFactory
51 {
52 /*** Constant for the root element in the info file.*/
53 private static final String SEC_ROOT = "configuration/";
54
55 /*** Constant for the override section.*/
56 private static final String SEC_OVERRIDE = SEC_ROOT + "override/";
57
58 /*** Constant for the additional section.*/
59 private static final String SEC_ADDITIONAL = SEC_ROOT + "additional/";
60
61 /*** Constant for the optional attribute.*/
62 private static final String ATTR_OPTIONAL = "optional";
63
64 /*** Constant for the fileName attribute.*/
65 private static final String ATTR_FILENAME = "fileName";
66
67 /*** Constant for the default base path (points to actual directory).*/
68 private static final String DEF_BASE_PATH = ".";
69
70 /*** static logger */
71 private static Log log = LogFactory.getLog(ConfigurationFactory.class);
72
73 /*** The XML file with the details about the configuration to load */
74 private String configurationFileName;
75
76 /*** The URL to the XML file with the details about the configuration to load. */
77 private URL configurationURL;
78
79 /***
80 * The implicit base path for included files. This path is determined by
81 * the configuration to load and used unless no other base path was
82 * explicitely specified.
83 */
84 private String implicitBasePath;
85
86 /*** The basePath to prefix file paths for file based property files. */
87 private String basePath;
88
89 /*** URL for xml digester rules file */
90 private URL digesterRules;
91
92 /*** The digester namespace to parse */
93 private String digesterRuleNamespaceURI;
94
95 /***
96 * Constructor
97 */
98 public ConfigurationFactory()
99 {
100 setBasePath(DEF_BASE_PATH);
101 }
102 /***
103 * Constructor with ConfigurationFile Name passed
104 *
105 * @param configurationFileName The path to the configuration file
106 */
107 public ConfigurationFactory(String configurationFileName)
108 {
109 this.configurationFileName = configurationFileName;
110 }
111
112 /***
113 * Return the configuration provided by this factory. It loads the
114 * configuration file which is a XML description of the actual
115 * configurations to load. It can contain various different types of
116 * configuration, currently Properties, XML and JNDI.
117 *
118 * @return A Configuration object
119 * @throws ConfigurationException A generic exception that we had trouble during the
120 * loading of the configuration data.
121 */
122 public Configuration getConfiguration() throws ConfigurationException
123 {
124 Digester digester;
125 InputStream input = null;
126 ConfigurationBuilder builder = new ConfigurationBuilder();
127 URL url = getConfigurationURL();
128 try
129 {
130 if (url == null)
131 {
132 url = ConfigurationUtils.locate(implicitBasePath, getConfigurationFileName());
133 }
134 input = url.openStream();
135 }
136 catch (Exception e)
137 {
138 log.error("Exception caught opening stream to URL", e);
139 throw new ConfigurationException("Exception caught opening stream to URL", e);
140 }
141
142 if (getDigesterRules() == null)
143 {
144 digester = new Digester();
145 configureNamespace(digester);
146 initDefaultDigesterRules(digester);
147 }
148 else
149 {
150 digester = DigesterLoader.createDigester(getDigesterRules());
151
152
153 configureNamespace(digester);
154 }
155
156
157 digester.setUseContextClassLoader(true);
158
159 digester.push(builder);
160
161 try
162 {
163 digester.parse(input);
164 input.close();
165 }
166 catch (SAXException saxe)
167 {
168 log.error("SAX Exception caught", saxe);
169 throw new ConfigurationException("SAX Exception caught", saxe);
170 }
171 catch (IOException ioe)
172 {
173 log.error("IO Exception caught", ioe);
174 throw new ConfigurationException("IO Exception caught", ioe);
175 }
176 return builder.getConfiguration();
177 }
178
179 /***
180 * Returns the configurationFile.
181 *
182 * @return The name of the configuration file. Can be null.
183 */
184 public String getConfigurationFileName()
185 {
186 return configurationFileName;
187 }
188
189 /***
190 * Sets the configurationFile.
191 *
192 * @param configurationFileName The name of the configurationFile to use.
193 */
194 public void setConfigurationFileName(String configurationFileName)
195 {
196 File file = new File(configurationFileName).getAbsoluteFile();
197 this.configurationFileName = file.getName();
198 implicitBasePath = file.getParent();
199 }
200
201 /***
202 * Returns the URL of the configuration file to be loaded.
203 *
204 * @return the URL of the configuration to load
205 */
206 public URL getConfigurationURL()
207 {
208 return configurationURL;
209 }
210
211 /***
212 * Sets the URL of the configuration to load. This configuration can be
213 * either specified by a file name or by a URL.
214 *
215 * @param url the URL of the configuration to load
216 */
217 public void setConfigurationURL(URL url)
218 {
219 configurationURL = url;
220 implicitBasePath = url.toString();
221
222
223
224
225
226 if (DEF_BASE_PATH.equals(getBasePath()))
227 {
228 setBasePath(null);
229 }
230 }
231
232 /***
233 * Returns the digesterRules.
234 *
235 * @return URL
236 */
237 public URL getDigesterRules()
238 {
239 return digesterRules;
240 }
241
242 /***
243 * Sets the digesterRules.
244 *
245 * @param digesterRules The digesterRules to set
246 */
247 public void setDigesterRules(URL digesterRules)
248 {
249 this.digesterRules = digesterRules;
250 }
251
252 /***
253 * Initializes the parsing rules for the default digester
254 *
255 * This allows the Configuration Factory to understand the
256 * default types: Properties, XML and JNDI. Two special sections are
257 * introduced: <code><override></code> and
258 * <code><additional></code>.
259 *
260 * @param digester The digester to configure
261 */
262 protected void initDefaultDigesterRules(Digester digester)
263 {
264 initDigesterSectionRules(digester, SEC_ROOT, false);
265 initDigesterSectionRules(digester, SEC_OVERRIDE, false);
266 initDigesterSectionRules(digester, SEC_ADDITIONAL, true);
267 }
268
269 /***
270 * Sets up digester rules for a specified section of the configuration
271 * info file.
272 *
273 * @param digester the current digester instance
274 * @param matchString specifies the section
275 * @param additional a flag if rules for the additional section are to be
276 * added
277 */
278 protected void initDigesterSectionRules(Digester digester, String matchString, boolean additional)
279 {
280 setupDigesterInstance(
281 digester,
282 matchString + "properties",
283 new FileConfigurationFactory(PropertiesConfiguration.class),
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 digester.addFactoryCreate(matchString, factory);
339 digester.addSetProperties(matchString);
340 if (method != null)
341 {
342 digester.addCallMethod(matchString, method);
343 }
344 digester.addSetNext(
345 matchString,
346 "addConfiguration",
347 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) ? implicitBasePath : basePath;
416 return StringUtils.isEmpty(path) ? "." : path;
417 }
418
419 /***
420 * Sets the basePath for all file references from this Configuration Factory.
421 * Normally a base path need not to be set because it is determined by
422 * the location of the configuration file to load. All relative pathes in
423 * this file are resolved relative to this file. Setting a base path makes
424 * sense if such relative pathes should be otherwise resolved, e.g. if
425 * the configuration file is loaded from the class path and all sub
426 * configurations it refers to are stored in a special config directory.
427 *
428 * @param basePath The new basePath to set.
429 */
430 public void setBasePath(String basePath)
431 {
432 this.basePath = basePath;
433 }
434
435 /***
436 * A base class for digester factory classes. This base class maintains
437 * a default class for the objects to be created.
438 * There will be sub classes for specific configuration implementations.
439 */
440 public class DigesterConfigurationFactory extends AbstractObjectCreationFactory
441 {
442 /*** Actual class to use. */
443 private Class clazz;
444
445 /***
446 * Creates a new instance of <code>DigesterConfigurationFactory</code>.
447 *
448 * @param clazz the class which we should instantiate
449 */
450 public DigesterConfigurationFactory(Class clazz)
451 {
452 this.clazz = clazz;
453 }
454
455 /***
456 * Creates an instance of the specified class.
457 *
458 * @param attribs the attributes (ignored)
459 * @return the new object
460 * @throws Exception if object creation fails
461 */
462 public Object createObject(Attributes attribs) throws Exception
463 {
464 return clazz.newInstance();
465 }
466 }
467
468 /***
469 * A tiny inner class that allows the Configuration Factory to
470 * let the digester construct FileConfiguration objects
471 * that already have the correct base Path set.
472 *
473 */
474 public class FileConfigurationFactory extends DigesterConfigurationFactory
475 {
476 /***
477 * C'tor
478 *
479 * @param clazz The class which we should instantiate.
480 */
481 public FileConfigurationFactory(Class clazz)
482 {
483 super(clazz);
484 }
485
486 /***
487 * Gets called by the digester.
488 *
489 * @param attributes the actual attributes
490 * @return the new object
491 * @throws Exception Couldn't instantiate the requested object.
492 */
493 public Object createObject(Attributes attributes) throws Exception
494 {
495 FileConfiguration conf = (FileConfiguration) super.createObject(attributes);
496 conf.setBasePath(getBasePath());
497 conf.setFileName(attributes.getValue(ATTR_FILENAME));
498 try
499 {
500 log.info("Trying to load configuration " + conf.getFileName());
501 conf.load();
502 }
503 catch(ConfigurationException cex)
504 {
505 if(attributes.getValue(ATTR_OPTIONAL) != null
506 && PropertyConverter.toBoolean(attributes.getValue(ATTR_OPTIONAL)).booleanValue())
507 {
508 log.warn("Could not load optional configuration " + conf.getFileName());
509 }
510 else
511 {
512 throw cex;
513 }
514 }
515 return conf;
516 }
517 }
518
519 /***
520 * A tiny inner class that allows the Configuration Factory to
521 * let the digester construct JNDIConfiguration objects.
522 */
523 private class JNDIConfigurationFactory extends DigesterConfigurationFactory
524 {
525 public JNDIConfigurationFactory()
526 {
527 super(JNDIConfiguration.class);
528 }
529 }
530
531 /***
532 * A tiny inner class that allows the Configuration Factory to
533 * let the digester construct SystemConfiguration objects.
534 */
535 private class SystemConfigurationFactory extends DigesterConfigurationFactory
536 {
537 public SystemConfigurationFactory()
538 {
539 super(SystemConfiguration.class);
540 }
541 }
542
543 /***
544 * A simple data class that holds all information about a configuration
545 * from the <code><additional></code> section.
546 */
547 public static class AdditionalConfigurationData
548 {
549 /*** Stores the configuration object.*/
550 private Configuration configuration;
551
552 /*** Stores the location of this configuration in the global tree.*/
553 private String at;
554
555 /***
556 * Returns the value of the <code>at</code> attribute.
557 *
558 * @return the at attribute
559 */
560 public String getAt()
561 {
562 return at;
563 }
564
565 /***
566 * Sets the value of the <code>at</code> attribute.
567 *
568 * @param string the attribute value
569 */
570 public void setAt(String string)
571 {
572 at = string;
573 }
574
575 /***
576 * Returns the configuration object.
577 *
578 * @return the configuration
579 */
580 public Configuration getConfiguration()
581 {
582 return configuration;
583 }
584
585 /***
586 * Sets the configuration object. Note: Normally this method should be
587 * named <code>setConfiguration()</code>, but the name
588 * <code>addConfiguration()</code> is required by some of the digester
589 * rules.
590 *
591 * @param config the configuration to set
592 */
593 public void addConfiguration(Configuration config)
594 {
595 configuration = config;
596 }
597 }
598
599 /***
600 * An internally used helper class for constructing the composite
601 * configuration object.
602 */
603 public static class ConfigurationBuilder
604 {
605 /*** Stores the composite configuration.*/
606 private CompositeConfiguration config;
607
608 /*** Stores a collection with the configs from the additional section.*/
609 private Collection additionalConfigs;
610
611 /***
612 * Creates a new instance of <code>ConfigurationBuilder</code>.
613 */
614 public ConfigurationBuilder()
615 {
616 config = new CompositeConfiguration();
617 additionalConfigs = new LinkedList();
618 }
619
620 /***
621 * Adds a new configuration to this object. This method is called by
622 * Digester.
623 *
624 * @param conf the configuration to be added
625 */
626 public void addConfiguration(Configuration conf)
627 {
628 config.addConfiguration(conf);
629 }
630
631 /***
632 * Adds information about an additional configuration. This method is
633 * called by Digester.
634 *
635 * @param data the data about the additional configuration
636 */
637 public void addAdditionalConfig(AdditionalConfigurationData data)
638 {
639 additionalConfigs.add(data);
640 }
641
642 /***
643 * Returns the final composite configuration.
644 *
645 * @return the final configuration object
646 */
647 public CompositeConfiguration getConfiguration()
648 {
649 if (!additionalConfigs.isEmpty())
650 {
651 Configuration unionConfig = createAdditionalConfiguration(additionalConfigs);
652 if (unionConfig != null)
653 {
654 addConfiguration(unionConfig);
655 }
656 additionalConfigs.clear();
657 }
658
659 return config;
660 }
661
662 /***
663 * Creates a configuration object with the union of all properties
664 * defined in the <code><additional></code> section. This
665 * implementation returns a <code>HierarchicalConfiguration</code>
666 * object.
667 *
668 * @param configs a collection with
669 * <code>AdditionalConfigurationData</code> objects
670 * @return the union configuration (can be <b>null</b>)
671 */
672 protected Configuration createAdditionalConfiguration(Collection configs)
673 {
674 HierarchicalConfiguration result = new HierarchicalConfiguration();
675
676 for (Iterator it = configs.iterator(); it.hasNext();)
677 {
678 AdditionalConfigurationData cdata =
679 (AdditionalConfigurationData) it.next();
680 result.addNodes(cdata.getAt(),
681 createRootNode(cdata).getChildren());
682 }
683
684 return result.isEmpty() ? null : result;
685 }
686
687 /***
688 * Creates a configuration root node for the specified configuration.
689 *
690 * @param cdata the configuration data object
691 * @return a root node for this configuration
692 */
693 private HierarchicalConfiguration.Node createRootNode(AdditionalConfigurationData cdata)
694 {
695 if (cdata.getConfiguration() instanceof HierarchicalConfiguration)
696 {
697
698 return ((HierarchicalConfiguration) cdata.getConfiguration()).getRoot();
699 }
700 else
701 {
702
703 HierarchicalConfigurationNodeConverter conv =
704 new HierarchicalConfigurationNodeConverter();
705 conv.process(cdata.getConfiguration());
706 return conv.getRootNode();
707 }
708 }
709 }
710
711 /***
712 * A specialized <code>HierarchicalConfigurationConverter</code> class
713 * that creates a <code>HierarchicalConfiguration</code> root node from
714 * an arbitrary <code>Configuration</code> object. This class is used to
715 * add additional configuration objects to the hierarchical configuration
716 * managed by the <code>ConfigurationBuilder</code>.
717 */
718 static class HierarchicalConfigurationNodeConverter extends HierarchicalConfigurationConverter
719 {
720 /*** A stack for constructing the hierarchy.*/
721 private Stack nodes;
722
723 /*** Stores the root node.*/
724 private HierarchicalConfiguration.Node root;
725
726 /***
727 * Default constructor.
728 */
729 public HierarchicalConfigurationNodeConverter()
730 {
731 nodes = new Stack();
732 root = new HierarchicalConfiguration.Node();
733 nodes.push(root);
734 }
735
736 /***
737 * Callback for an element start event. Creates a new node and adds
738 * it to the actual parent.
739 *
740 * @param name the name of the new node
741 * @param value the node's value
742 */
743 protected void elementStart(String name, Object value)
744 {
745 HierarchicalConfiguration.Node parent = (HierarchicalConfiguration.Node) nodes.peek();
746 HierarchicalConfiguration.Node child = new HierarchicalConfiguration.Node(name);
747 if (value != null)
748 {
749 child.setValue(value);
750 }
751 parent.addChild(child);
752 nodes.push(child);
753 }
754
755 /***
756 * Callback for an element end event. Clears the stack.
757 *
758 * @param name the name of the element
759 */
760 protected void elementEnd(String name)
761 {
762 nodes.pop();
763 }
764
765 /***
766 * Returns the constructed root node.
767 *
768 * @return the root node
769 */
770 public HierarchicalConfiguration.Node getRootNode()
771 {
772 return root;
773 }
774 }
775 }