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,v 1.16 2004/09/22 17:17:30 ebourg Exp $
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 name of the load method.*/
62 private static final String METH_LOAD = "load";
63
64 /*** Constant for the default base path (points to actual directory).*/
65 private static final String DEF_BASE_PATH = ".";
66
67 /*** static logger */
68 private static Log log = LogFactory.getLog(ConfigurationFactory.class);
69
70 /*** The XML file with the details about the configuration to load */
71 private String configurationFileName;
72
73 /*** The URL to the XML file with the details about the configuration to load. */
74 private URL configurationURL;
75
76 /***
77 * The implicit base path for included files. This path is determined by
78 * the configuration to load and used unless no other base path was
79 * explicitely specified.
80 */
81 private String implicitBasePath;
82
83 /*** The basePath to prefix file paths for file based property files. */
84 private String basePath;
85
86 /*** URL for xml digester rules file */
87 private URL digesterRules;
88
89 /*** The digester namespace to parse */
90 private String digesterRuleNamespaceURI;
91
92 /***
93 * Constructor
94 */
95 public ConfigurationFactory()
96 {
97 setBasePath(DEF_BASE_PATH);
98 }
99 /***
100 * Constructor with ConfigurationFile Name passed
101 *
102 * @param configurationFileName The path to the configuration file
103 */
104 public ConfigurationFactory(String configurationFileName)
105 {
106 this.configurationFileName = configurationFileName;
107 }
108
109 /***
110 * Return the configuration provided by this factory. It loads the
111 * configuration file which is a XML description of the actual
112 * configurations to load. It can contain various different types of
113 * configuration, currently Properties, XML and JNDI.
114 *
115 * @return A Configuration object
116 * @throws ConfigurationException A generic exception that we had trouble during the
117 * loading of the configuration data.
118 */
119 public Configuration getConfiguration() throws ConfigurationException
120 {
121 Digester digester;
122 InputStream input = null;
123 ConfigurationBuilder builder = new ConfigurationBuilder();
124 URL url = getConfigurationURL();
125 try
126 {
127 if (url == null)
128 {
129 url = ConfigurationUtils.locate(implicitBasePath, getConfigurationFileName());
130 }
131 input = url.openStream();
132 }
133 catch (Exception e)
134 {
135 log.error("Exception caught opening stream to URL", e);
136 throw new ConfigurationException("Exception caught opening stream to URL", e);
137 }
138
139 if (getDigesterRules() == null)
140 {
141 digester = new Digester();
142 configureNamespace(digester);
143 initDefaultDigesterRules(digester);
144 }
145 else
146 {
147 digester = DigesterLoader.createDigester(getDigesterRules());
148
149
150 configureNamespace(digester);
151 }
152
153 digester.push(builder);
154
155 try
156 {
157 digester.parse(input);
158 input.close();
159 }
160 catch (SAXException saxe)
161 {
162 log.error("SAX Exception caught", saxe);
163 throw new ConfigurationException("SAX Exception caught", saxe);
164 }
165 catch (IOException ioe)
166 {
167 log.error("IO Exception caught", ioe);
168 throw new ConfigurationException("IO Exception caught", ioe);
169 }
170 return builder.getConfiguration();
171 }
172
173 /***
174 * Returns the configurationFile.
175 *
176 * @return The name of the configuration file. Can be null.
177 */
178 public String getConfigurationFileName()
179 {
180 return configurationFileName;
181 }
182
183 /***
184 * Sets the configurationFile.
185 *
186 * @param configurationFileName The name of the configurationFile to use.
187 */
188 public void setConfigurationFileName(String configurationFileName)
189 {
190 File file = new File(configurationFileName).getAbsoluteFile();
191 this.configurationFileName = file.getName();
192 implicitBasePath = file.getParent();
193 }
194
195 /***
196 * Returns the URL of the configuration file to be loaded.
197 *
198 * @return the URL of the configuration to load
199 */
200 public URL getConfigurationURL()
201 {
202 return configurationURL;
203 }
204
205 /***
206 * Sets the URL of the configuration to load. This configuration can be
207 * either specified by a file name or by a URL.
208 *
209 * @param url the URL of the configuration to load
210 */
211 public void setConfigurationURL(URL url)
212 {
213 configurationURL = url;
214 implicitBasePath = url.toString();
215
216
217
218
219
220 if (DEF_BASE_PATH.equals(getBasePath()))
221 {
222 setBasePath(null);
223 }
224 }
225
226 /***
227 * Returns the digesterRules.
228 *
229 * @return URL
230 */
231 public URL getDigesterRules()
232 {
233 return digesterRules;
234 }
235
236 /***
237 * Sets the digesterRules.
238 *
239 * @param digesterRules The digesterRules to set
240 */
241 public void setDigesterRules(URL digesterRules)
242 {
243 this.digesterRules = digesterRules;
244 }
245
246 /***
247 * Initializes the parsing rules for the default digester
248 *
249 * This allows the Configuration Factory to understand the
250 * default types: Properties, XML and JNDI. Two special sections are
251 * introduced: <code><override></code> and
252 * <code><additional></code>.
253 *
254 * @param digester The digester to configure
255 */
256 protected void initDefaultDigesterRules(Digester digester)
257 {
258 initDigesterSectionRules(digester, SEC_ROOT, false);
259 initDigesterSectionRules(digester, SEC_OVERRIDE, false);
260 initDigesterSectionRules(digester, SEC_ADDITIONAL, true);
261 }
262
263 /***
264 * Sets up digester rules for a specified section of the configuration
265 * info file.
266 *
267 * @param digester the current digester instance
268 * @param matchString specifies the section
269 * @param additional a flag if rules for the additional section are to be
270 * added
271 */
272 protected void initDigesterSectionRules(Digester digester, String matchString, boolean additional)
273 {
274 setupDigesterInstance(
275 digester,
276 matchString + "properties",
277 new FileConfigurationFactory(PropertiesConfiguration.class),
278 METH_LOAD,
279 additional);
280
281 setupDigesterInstance(
282 digester,
283 matchString + "xml",
284 new FileConfigurationFactory(XMLConfiguration.class),
285 METH_LOAD,
286 additional);
287
288 setupDigesterInstance(
289 digester,
290 matchString + "hierarchicalXml",
291 new FileConfigurationFactory(HierarchicalXMLConfiguration.class),
292 METH_LOAD,
293 additional);
294
295 setupDigesterInstance(
296 digester,
297 matchString + "jndi",
298 new JNDIConfigurationFactory(),
299 null,
300 additional);
301 }
302
303 /***
304 * Sets up digester rules for a configuration to be loaded.
305 *
306 * @param digester the current digester
307 * @param matchString the pattern to match with this rule
308 * @param factory an ObjectCreationFactory instance to use for creating new
309 * objects
310 * @param method the name of a method to be called or <b>null</b> for none
311 * @param additional a flag if rules for the additional section are to be
312 * added
313 */
314 protected void setupDigesterInstance(
315 Digester digester,
316 String matchString,
317 ObjectCreationFactory factory,
318 String method,
319 boolean additional)
320 {
321 if (additional)
322 {
323 setupUnionRules(digester, matchString);
324 }
325 digester.addFactoryCreate(matchString, factory);
326 digester.addSetProperties(matchString);
327 if (method != null)
328 {
329 digester.addCallMethod(matchString, method);
330 }
331 digester.addSetNext(
332 matchString,
333 "addConfiguration",
334 Configuration.class.getName());
335 }
336
337 /***
338 * Sets up rules for configurations in the additional section.
339 *
340 * @param digester the current digester
341 * @param matchString the pattern to match with this rule
342 */
343 protected void setupUnionRules(Digester digester, String matchString)
344 {
345 digester.addObjectCreate(matchString,
346 AdditionalConfigurationData.class);
347 digester.addSetProperties(matchString);
348 digester.addSetNext(matchString, "addAdditionalConfig",
349 AdditionalConfigurationData.class.getName());
350 }
351
352 /***
353 * Returns the digesterRuleNamespaceURI.
354 *
355 * @return A String with the digesterRuleNamespaceURI.
356 */
357 public String getDigesterRuleNamespaceURI()
358 {
359 return digesterRuleNamespaceURI;
360 }
361
362 /***
363 * Sets the digesterRuleNamespaceURI.
364 *
365 * @param digesterRuleNamespaceURI The new digesterRuleNamespaceURI to use
366 */
367 public void setDigesterRuleNamespaceURI(String digesterRuleNamespaceURI)
368 {
369 this.digesterRuleNamespaceURI = digesterRuleNamespaceURI;
370 }
371
372 /***
373 * Configure the current digester to be namespace aware and to have
374 * a Configuration object to which all of the other configurations
375 * should be added
376 *
377 * @param digester The Digester to configure
378 */
379 private void configureNamespace(Digester digester)
380 {
381 if (getDigesterRuleNamespaceURI() != null)
382 {
383 digester.setNamespaceAware(true);
384 digester.setRuleNamespaceURI(getDigesterRuleNamespaceURI());
385 }
386 else
387 {
388 digester.setNamespaceAware(false);
389 }
390 digester.setValidating(false);
391 }
392
393 /***
394 * Returns the Base path from which this Configuration Factory operates.
395 * This is never null. If you set the BasePath to null, then a base path
396 * according to the configuration to load is returned.
397 *
398 * @return The base Path of this configuration factory.
399 */
400 public String getBasePath()
401 {
402 String path = StringUtils.isEmpty(basePath) ? implicitBasePath : basePath;
403 return StringUtils.isEmpty(path) ? "." : path;
404 }
405
406 /***
407 * Sets the basePath for all file references from this Configuration Factory.
408 * Normally a base path need not to be set because it is determined by
409 * the location of the configuration file to load. All relative pathes in
410 * this file are resolved relative to this file. Setting a base path makes
411 * sense if such relative pathes should be otherwise resolved, e.g. if
412 * the configuration file is loaded from the class path and all sub
413 * configurations it refers to are stored in a special config directory.
414 *
415 * @param basePath The new basePath to set.
416 */
417 public void setBasePath(String basePath)
418 {
419 this.basePath = basePath;
420 }
421
422 /***
423 * A base class for digester factory classes. This base class maintains
424 * a default class for the objects to be created.
425 * There will be sub classes for specific configuration implementations.
426 */
427 public class DigesterConfigurationFactory extends AbstractObjectCreationFactory
428 {
429 /*** Actual class to use. */
430 private Class clazz;
431
432 /***
433 * Creates a new instance of <code>DigesterConfigurationFactory</code>.
434 *
435 * @param clazz the class which we should instantiate
436 */
437 public DigesterConfigurationFactory(Class clazz)
438 {
439 this.clazz = clazz;
440 }
441
442 /***
443 * Creates an instance of the specified class.
444 *
445 * @param attribs the attributes (ignored)
446 * @return the new object
447 * @throws Exception if object creation fails
448 */
449 public Object createObject(Attributes attribs) throws Exception
450 {
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 DigesterConfigurationFactory
462 {
463 /***
464 * C'tor
465 *
466 * @param clazz The class which we should instantiate.
467 */
468 public FileConfigurationFactory(Class clazz)
469 {
470 super(clazz);
471 }
472
473 /***
474 * Gets called by the digester.
475 *
476 * @param attributes the actual attributes
477 * @return the new object
478 * @throws Exception Couldn't instantiate the requested object.
479 */
480 public Object createObject(Attributes attributes) throws Exception
481 {
482 FileConfiguration conf = (FileConfiguration) super.createObject(attributes);
483 conf.setBasePath(getBasePath());
484 return conf;
485 }
486 }
487
488 /***
489 * A tiny inner class that allows the Configuration Factory to
490 * let the digester construct JNDIPathConfiguration objects.
491 */
492 private class JNDIConfigurationFactory extends DigesterConfigurationFactory
493 {
494 /***
495 * C'tor
496 */
497 public JNDIConfigurationFactory()
498 {
499 super(JNDIConfiguration.class);
500 }
501 }
502
503 /***
504 * A simple data class that holds all information about a configuration
505 * from the <code><additional></code> section.
506 */
507 public static class AdditionalConfigurationData
508 {
509 /*** Stores the configuration object.*/
510 private Configuration configuration;
511
512 /*** Stores the location of this configuration in the global tree.*/
513 private String at;
514
515 /***
516 * Returns the value of the <code>at</code> attribute.
517 *
518 * @return the at attribute
519 */
520 public String getAt()
521 {
522 return at;
523 }
524
525 /***
526 * Sets the value of the <code>at</code> attribute.
527 *
528 * @param string the attribute value
529 */
530 public void setAt(String string)
531 {
532 at = string;
533 }
534
535 /***
536 * Returns the configuration object.
537 *
538 * @return the configuration
539 */
540 public Configuration getConfiguration()
541 {
542 return configuration;
543 }
544
545 /***
546 * Sets the configuration object. Note: Normally this method should be
547 * named <code>setConfiguration()</code>, but the name
548 * <code>addConfiguration()</code> is required by some of the digester
549 * rules.
550 *
551 * @param config the configuration to set
552 */
553 public void addConfiguration(Configuration config)
554 {
555 configuration = config;
556 }
557 }
558
559 /***
560 * An internally used helper class for constructing the composite
561 * configuration object.
562 */
563 public static class ConfigurationBuilder
564 {
565 /*** Stores the composite configuration.*/
566 private CompositeConfiguration config;
567
568 /*** Stores a collection with the configs from the additional section.*/
569 private Collection additionalConfigs;
570
571 /***
572 * Creates a new instance of <code>ConfigurationBuilder</code>.
573 */
574 public ConfigurationBuilder()
575 {
576 config = new CompositeConfiguration();
577 additionalConfigs = new LinkedList();
578 }
579
580 /***
581 * Adds a new configuration to this object. This method is called by
582 * Digester.
583 *
584 * @param conf the configuration to be added
585 */
586 public void addConfiguration(Configuration conf)
587 {
588 config.addConfiguration(conf);
589 }
590
591 /***
592 * Adds information about an additional configuration. This method is
593 * called by Digester.
594 *
595 * @param data the data about the additional configuration
596 */
597 public void addAdditionalConfig(AdditionalConfigurationData data)
598 {
599 additionalConfigs.add(data);
600 }
601
602 /***
603 * Returns the final composite configuration.
604 *
605 * @return the final configuration object
606 */
607 public CompositeConfiguration getConfiguration()
608 {
609 if (!additionalConfigs.isEmpty())
610 {
611 Configuration unionConfig = createAdditionalConfiguration(additionalConfigs);
612 if (unionConfig != null)
613 {
614 addConfiguration(unionConfig);
615 }
616 additionalConfigs.clear();
617 }
618
619 return config;
620 }
621
622 /***
623 * Creates a configuration object with the union of all properties
624 * defined in the <code><additional></code> section. This
625 * implementation returns a <code>HierarchicalConfiguration</code>
626 * object.
627 *
628 * @param configs a collection with
629 * <code>AdditionalConfigurationData</code> objects
630 * @return the union configuration (can be <b>null</b>)
631 */
632 protected Configuration createAdditionalConfiguration(Collection configs)
633 {
634 HierarchicalConfiguration result = new HierarchicalConfiguration();
635
636 for (Iterator it = configs.iterator(); it.hasNext();)
637 {
638 AdditionalConfigurationData cdata =
639 (AdditionalConfigurationData) it.next();
640 result.addNodes(cdata.getAt(),
641 createRootNode(cdata).getChildren());
642 }
643
644 return result.isEmpty() ? null : result;
645 }
646
647 /***
648 * Creates a configuration root node for the specified configuration.
649 *
650 * @param cdata the configuration data object
651 * @return a root node for this configuration
652 */
653 private HierarchicalConfiguration.Node createRootNode(AdditionalConfigurationData cdata)
654 {
655 if (cdata.getConfiguration() instanceof HierarchicalConfiguration)
656 {
657
658 return ((HierarchicalConfiguration) cdata.getConfiguration()).getRoot();
659 }
660 else
661 {
662
663 HierarchicalConfigurationNodeConverter conv =
664 new HierarchicalConfigurationNodeConverter();
665 conv.process(cdata.getConfiguration());
666 return conv.getRootNode();
667 }
668 }
669 }
670
671 /***
672 * A specialized <code>HierarchicalConfigurationConverter</code> class
673 * that creates a <code>HierarchicalConfiguration</code> root node from
674 * an arbitrary <code>Configuration</code> object. This class is used to
675 * add additional configuration objects to the hierarchical configuration
676 * managed by the <code>ConfigurationBuilder</code>.
677 */
678 static class HierarchicalConfigurationNodeConverter extends HierarchicalConfigurationConverter
679 {
680 /*** A stack for constructing the hierarchy.*/
681 private Stack nodes;
682
683 /*** Stores the root node.*/
684 private HierarchicalConfiguration.Node root;
685
686 /***
687 * Default constructor.
688 */
689 public HierarchicalConfigurationNodeConverter()
690 {
691 nodes = new Stack();
692 root = new HierarchicalConfiguration.Node();
693 nodes.push(root);
694 }
695
696 /***
697 * Callback for an element start event. Creates a new node and adds
698 * it to the actual parent.
699 *
700 * @param name the name of the new node
701 * @param value the node's value
702 */
703 protected void elementStart(String name, Object value)
704 {
705 HierarchicalConfiguration.Node parent = (HierarchicalConfiguration.Node) nodes.peek();
706 HierarchicalConfiguration.Node child = new HierarchicalConfiguration.Node(name);
707 if (value != null)
708 {
709 child.setValue(value);
710 }
711 parent.addChild(child);
712 nodes.push(child);
713 }
714
715 /***
716 * Callback for an element end event. Clears the stack.
717 *
718 * @param name the name of the element
719 */
720 protected void elementEnd(String name)
721 {
722 nodes.pop();
723 }
724
725 /***
726 * Returns the constructed root node.
727 *
728 * @return the root node
729 */
730 public HierarchicalConfiguration.Node getRootNode()
731 {
732 return root;
733 }
734 }
735 }