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.util.ArrayList;
20 import java.util.Collection;
21 import java.util.HashMap;
22 import java.util.Iterator;
23 import java.util.List;
24 import java.util.Map;
25 import java.util.Set;
26
27 import org.apache.commons.configuration.event.ConfigurationEvent;
28 import org.apache.commons.configuration.event.ConfigurationListener;
29 import org.apache.commons.configuration.tree.ConfigurationNode;
30 import org.apache.commons.configuration.tree.DefaultConfigurationKey;
31 import org.apache.commons.configuration.tree.DefaultConfigurationNode;
32 import org.apache.commons.configuration.tree.DefaultExpressionEngine;
33 import org.apache.commons.configuration.tree.NodeCombiner;
34 import org.apache.commons.configuration.tree.UnionCombiner;
35 import org.apache.commons.configuration.tree.ViewNode;
36
37 /***
38 * <p>
39 * A hierarchical composite configuration class.
40 * </p>
41 * <p>
42 * This class maintains a list of configuration objects, which can be added
43 * using the divers <code>addConfiguration()</code> methods. After that the
44 * configurations can be accessed either by name (if one was provided when the
45 * configuration was added) or by index. For the whole set of managed
46 * configurations a logical node structure is constructed. For this purpose a
47 * <code>{@link org.apache.commons.configuration.tree.NodeCombiner NodeCombiner}</code>
48 * object can be set. This makes it possible to specify different algorithms for
49 * the combination process.
50 * </p>
51 * <p>
52 * The big advantage of this class is that it creates a truely hierarchical
53 * structure of all the properties stored in the contained configurations - even
54 * if some of them are no hierarchical configurations per se. So all enhanced
55 * features provided by a hierarchical configuration (e.g. choosing an
56 * expression engine) are applicable.
57 * </p>
58 * <p>
59 * The class works by registering itself as an event listener add all added
60 * configurations. So it gets notified whenever one of these configurations is
61 * changed and can invalidate its internal node structure. The next time a
62 * property is accessed the node structure will be re-constructed using the
63 * current state of the managed configurations. Node that, depending on the used
64 * <code>NodeCombiner</code>, this may be a complex operation.
65 * </p>
66 * <p>
67 * It is not strictly forbidden to manipulate a
68 * <code>CombinedConfiguration</code> directly, but the results may be
69 * unpredictable. For instance some node combiners use special view nodes for
70 * linking parts of the original configurations' data together. If new
71 * properties are added to such a special node, they do not belong to any of the
72 * managed configurations and thus hang in the air. It is also possible that
73 * direct updates on a <code>CombinedConfiguration</code> are incompatible
74 * with the used node combiner (e.g. if the
75 * <code>{@link org.apache.commons.configuration.tree.OverrideCombiner OverrideCombiner}</code>
76 * is used and properties are removed the resulting node structure may be
77 * incorrect because some properties that were hidden by the removed properties
78 * are not visible). So it is recommended to perform updates only on the managed
79 * configurations.
80 * </p>
81 * <p>
82 * Whenever the node structure of a <code>CombinedConfiguration</code> becomes
83 * invalid (either because one of the contained configurations was modified or
84 * because the <code>invalidate()</code> method was directly called) an event
85 * is generated. So this can be detected by interested event listeners. This
86 * also makes it possible to add a combined configuration into another one.
87 * </p>
88 * <p>
89 * Implementation note: Adding and removing configurations to and from a
90 * combined configuration is not thread-safe. If a combined configuration is
91 * manipulated by multiple threads, the developer has to take care about
92 * properly synchronization.
93 * </p>
94 *
95 * @author <a
96 * href="http://jakarta.apache.org/commons/configuration/team-list.html">Commons
97 * Configuration team</a>
98 * @since 1.3
99 * @version $Id: CombinedConfiguration.java 484692 2006-12-08 18:30:15Z oheger $
100 */
101 public class CombinedConfiguration extends HierarchicalConfiguration implements
102 ConfigurationListener, Cloneable
103 {
104 /***
105 * Constant for the invalidate event that is fired when the internal node
106 * structure becomes invalid.
107 */
108 public static final int EVENT_COMBINED_INVALIDATE = 40;
109
110 /***
111 * The serial version ID.
112 */
113 private static final long serialVersionUID = 8338574525528692307L;
114
115 /*** Constant for the expression engine for parsing the at path. */
116 private static final DefaultExpressionEngine AT_ENGINE = new DefaultExpressionEngine();
117
118 /*** Constant for the default node combiner. */
119 private static final NodeCombiner DEFAULT_COMBINER = new UnionCombiner();
120
121 /*** Constant for the name of the property used for the reload check.*/
122 private static final String PROP_RELOAD_CHECK = "CombinedConfigurationReloadCheck";
123
124 /*** Stores the combiner. */
125 private NodeCombiner nodeCombiner;
126
127 /*** Stores the combined root node. */
128 private ConfigurationNode combinedRoot;
129
130 /*** Stores a list with the contained configurations. */
131 private List configurations;
132
133 /*** Stores a map with the named configurations. */
134 private Map namedConfigurations;
135
136 /*** A flag whether an enhanced reload check is to be performed.*/
137 private boolean forceReloadCheck;
138
139 /***
140 * Creates a new instance of <code>CombinedConfiguration</code> and
141 * initializes the combiner to be used.
142 *
143 * @param comb the node combiner (can be <b>null</b>, then a union combiner
144 * is used as default)
145 */
146 public CombinedConfiguration(NodeCombiner comb)
147 {
148 setNodeCombiner((comb != null) ? comb : DEFAULT_COMBINER);
149 clear();
150 }
151
152 /***
153 * Creates a new instance of <code>CombinedConfiguration</code> that uses
154 * a union combiner.
155 *
156 * @see org.apache.commons.configuration.tree.UnionCombiner
157 */
158 public CombinedConfiguration()
159 {
160 this(null);
161 }
162
163 /***
164 * Returns the node combiner that is used for creating the combined node
165 * structure.
166 *
167 * @return the node combiner
168 */
169 public NodeCombiner getNodeCombiner()
170 {
171 return nodeCombiner;
172 }
173
174 /***
175 * Sets the node combiner. This object will be used when the combined node
176 * structure is to be constructed. It must not be <b>null</b>, otherwise an
177 * <code>IllegalArgumentException</code> exception is thrown. Changing the
178 * node combiner causes an invalidation of this combined configuration, so
179 * that the new combiner immediately takes effect.
180 *
181 * @param nodeCombiner the node combiner
182 */
183 public void setNodeCombiner(NodeCombiner nodeCombiner)
184 {
185 if (nodeCombiner == null)
186 {
187 throw new IllegalArgumentException(
188 "Node combiner must not be null!");
189 }
190 this.nodeCombiner = nodeCombiner;
191 invalidate();
192 }
193
194 /***
195 * Returns a flag whether an enhanced reload check must be performed.
196 *
197 * @return the force reload check flag
198 * @since 1.4
199 */
200 public boolean isForceReloadCheck()
201 {
202 return forceReloadCheck;
203 }
204
205 /***
206 * Sets the force reload check flag. If this flag is set, each property
207 * access on this configuration will cause a reload check on the contained
208 * configurations. This is a workaround for a problem with some reload
209 * implementations that only check if a reload is required when they are
210 * triggered. Per default this mode is disabled. If the force reload check
211 * flag is set to <b>true</b>, accessing properties will be less
212 * performant, but reloads on contained configurations will be detected.
213 *
214 * @param forceReloadCheck the value of the flag
215 * @since 1.4
216 */
217 public void setForceReloadCheck(boolean forceReloadCheck)
218 {
219 this.forceReloadCheck = forceReloadCheck;
220 }
221
222 /***
223 * Adds a new configuration to this combined configuration. It is possible
224 * (but not mandatory) to give the new configuration a name. This name must
225 * be unique, otherwise a <code>ConfigurationRuntimeException</code> will
226 * be thrown. With the optional <code>at</code> argument you can specify
227 * where in the resulting node structure the content of the added
228 * configuration should appear. This is a string that uses dots as property
229 * delimiters (independent on the current expression engine). For instance
230 * if you pass in the string <code>"database.tables"</code>,
231 * all properties of the added configuration will occur in this branch.
232 *
233 * @param config the configuration to add (must not be <b>null</b>)
234 * @param name the name of this configuration (can be <b>null</b>)
235 * @param at the position of this configuration in the combined tree (can be
236 * <b>null</b>)
237 */
238 public void addConfiguration(AbstractConfiguration config, String name,
239 String at)
240 {
241 if (config == null)
242 {
243 throw new IllegalArgumentException(
244 "Added configuration must not be null!");
245 }
246 if (name != null && namedConfigurations.containsKey(name))
247 {
248 throw new ConfigurationRuntimeException(
249 "A configuration with the name '"
250 + name
251 + "' already exists in this combined configuration!");
252 }
253
254 ConfigData cd = new ConfigData(config, name, at);
255 configurations.add(cd);
256 if (name != null)
257 {
258 namedConfigurations.put(name, config);
259 }
260
261 config.addConfigurationListener(this);
262 invalidate();
263 }
264
265 /***
266 * Adds a new configuration to this combined configuration with an optional
267 * name. The new configuration's properties will be added under the root of
268 * the combined node structure.
269 *
270 * @param config the configuration to add (must not be <b>null</b>)
271 * @param name the name of this configuration (can be <b>null</b>)
272 */
273 public void addConfiguration(AbstractConfiguration config, String name)
274 {
275 addConfiguration(config, name, null);
276 }
277
278 /***
279 * Adds a new configuration to this combined configuration. The new
280 * configuration is not given a name. Its properties will be added under the
281 * root of the combined node structure.
282 *
283 * @param config the configuration to add (must not be <b>null</b>)
284 */
285 public void addConfiguration(AbstractConfiguration config)
286 {
287 addConfiguration(config, null, null);
288 }
289
290 /***
291 * Returns the number of configurations that are contained in this combined
292 * configuration.
293 *
294 * @return the number of contained configurations
295 */
296 public int getNumberOfConfigurations()
297 {
298 return configurations.size();
299 }
300
301 /***
302 * Returns the configuration at the specified index. The contained
303 * configurations are numbered in the order they were added to this combined
304 * configuration. The index of the first configuration is 0.
305 *
306 * @param index the index
307 * @return the configuration at this index
308 */
309 public Configuration getConfiguration(int index)
310 {
311 ConfigData cd = (ConfigData) configurations.get(index);
312 return cd.getConfiguration();
313 }
314
315 /***
316 * Returns the configuration with the given name. This can be <b>null</b>
317 * if no such configuration exists.
318 *
319 * @param name the name of the configuration
320 * @return the configuration with this name
321 */
322 public Configuration getConfiguration(String name)
323 {
324 return (Configuration) namedConfigurations.get(name);
325 }
326
327 /***
328 * Removes the specified configuration from this combined configuration.
329 *
330 * @param config the configuration to be removed
331 * @return a flag whether this configuration was found and could be removed
332 */
333 public boolean removeConfiguration(Configuration config)
334 {
335 for (int index = 0; index < getNumberOfConfigurations(); index++)
336 {
337 if (((ConfigData) configurations.get(index)).getConfiguration() == config)
338 {
339 removeConfigurationAt(index);
340 return true;
341 }
342 }
343
344 return false;
345 }
346
347 /***
348 * Removes the configuration at the specified index.
349 *
350 * @param index the index
351 * @return the removed configuration
352 */
353 public Configuration removeConfigurationAt(int index)
354 {
355 ConfigData cd = (ConfigData) configurations.remove(index);
356 if (cd.getName() != null)
357 {
358 namedConfigurations.remove(cd.getName());
359 }
360 cd.getConfiguration().removeConfigurationListener(this);
361 invalidate();
362 return cd.getConfiguration();
363 }
364
365 /***
366 * Removes the configuration with the specified name.
367 *
368 * @param name the name of the configuration to be removed
369 * @return the removed configuration (<b>null</b> if this configuration
370 * was not found)
371 */
372 public Configuration removeConfiguration(String name)
373 {
374 Configuration conf = getConfiguration(name);
375 if (conf != null)
376 {
377 removeConfiguration(conf);
378 }
379 return conf;
380 }
381
382 /***
383 * Returns a set with the names of all configurations contained in this
384 * combined configuration. Of course here are only these configurations
385 * listed, for which a name was specified when they were added.
386 *
387 * @return a set with the names of the contained configurations (never
388 * <b>null</b>)
389 */
390 public Set getConfigurationNames()
391 {
392 return namedConfigurations.keySet();
393 }
394
395 /***
396 * Invalidates this combined configuration. This means that the next time a
397 * property is accessed the combined node structure must be re-constructed.
398 * Invalidation of a combined configuration also means that an event of type
399 * <code>EVENT_COMBINED_INVALIDATE</code> is fired. Note that while other
400 * events most times appear twice (once before and once after an update),
401 * this event is only fired once (after update).
402 */
403 public void invalidate()
404 {
405 synchronized (getNodeCombiner())
406 {
407 combinedRoot = null;
408 }
409 fireEvent(EVENT_COMBINED_INVALIDATE, null, null, false);
410 }
411
412 /***
413 * Event listener call back for configuration update events. This method is
414 * called whenever one of the contained configurations was modified. It
415 * invalidates this combined configuration.
416 *
417 * @param event the update event
418 */
419 public void configurationChanged(ConfigurationEvent event)
420 {
421 invalidate();
422 }
423
424 /***
425 * Returns the configuration root node of this combined configuration. This
426 * method will construct a combined node structure using the current node
427 * combiner if necessary.
428 *
429 * @return the combined root node
430 */
431 public ConfigurationNode getRootNode()
432 {
433 synchronized (getNodeCombiner())
434 {
435 if (combinedRoot == null)
436 {
437 combinedRoot = constructCombinedNode();
438 }
439 return combinedRoot;
440 }
441 }
442
443 /***
444 * Clears this configuration. All contained configurations will be removed.
445 */
446 public void clear()
447 {
448 fireEvent(EVENT_CLEAR, null, null, true);
449 configurations = new ArrayList();
450 namedConfigurations = new HashMap();
451 fireEvent(EVENT_CLEAR, null, null, false);
452 invalidate();
453 }
454
455 /***
456 * Returns a copy of this object. This implementation performs a deep clone,
457 * i.e. all contained configurations will be cloned, too. For this to work,
458 * all contained configurations must be cloneable. Registered event
459 * listeners won't be cloned. The clone will use the same node combiner than
460 * the original.
461 *
462 * @return the copied object
463 */
464 public Object clone()
465 {
466 CombinedConfiguration copy = (CombinedConfiguration) super.clone();
467 copy.clear();
468 for (Iterator it = configurations.iterator(); it.hasNext();)
469 {
470 ConfigData cd = (ConfigData) it.next();
471 copy.addConfiguration((AbstractConfiguration) ConfigurationUtils
472 .cloneConfiguration(cd.getConfiguration()), cd.getName(),
473 cd.getAt());
474 }
475
476 copy.setRootNode(new DefaultConfigurationNode());
477 return copy;
478 }
479
480 /***
481 * Returns the value of the specified property. This implementation
482 * evaluates the <em>force reload check</em> flag. If it is set, all
483 * contained configurations will be triggered before the value of the
484 * requested property is retrieved.
485 *
486 * @param key the key of the desired property
487 * @return the value of this property
488 * @since 1.4
489 */
490 public Object getProperty(String key)
491 {
492 if (isForceReloadCheck())
493 {
494 for (Iterator it = configurations.iterator(); it.hasNext();)
495 {
496 try
497 {
498
499
500 ((ConfigData) it.next()).getConfiguration().getProperty(
501 PROP_RELOAD_CHECK);
502 }
503 catch (Exception ex)
504 {
505
506 ;
507 }
508 }
509 }
510
511 return super.getProperty(key);
512 }
513
514 /***
515 * Creates the root node of this combined configuration.
516 *
517 * @return the combined root node
518 */
519 private ConfigurationNode constructCombinedNode()
520 {
521 if (getNumberOfConfigurations() < 1)
522 {
523 return new ViewNode();
524 }
525
526 else
527 {
528 Iterator it = configurations.iterator();
529 ConfigurationNode node = ((ConfigData) it.next())
530 .getTransformedRoot();
531 while (it.hasNext())
532 {
533 node = getNodeCombiner().combine(node,
534 ((ConfigData) it.next()).getTransformedRoot());
535 }
536 return node;
537 }
538 }
539
540 /***
541 * An internal helper class for storing information about contained
542 * configurations.
543 */
544 static class ConfigData
545 {
546 /*** Stores a reference to the configuration. */
547 private AbstractConfiguration configuration;
548
549 /*** Stores the name under which the configuration is stored. */
550 private String name;
551
552 /*** Stores the at information as path of nodes. */
553 private Collection atPath;
554
555 /*** Stores the at string.*/
556 private String at;
557
558 /***
559 * Creates a new instance of <code>ConfigData</code> and initializes
560 * it.
561 *
562 * @param config the configuration
563 * @param n the name
564 * @param at the at position
565 */
566 public ConfigData(AbstractConfiguration config, String n, String at)
567 {
568 configuration = config;
569 name = n;
570 atPath = parseAt(at);
571 this.at = at;
572 }
573
574 /***
575 * Returns the stored configuration.
576 *
577 * @return the configuration
578 */
579 public AbstractConfiguration getConfiguration()
580 {
581 return configuration;
582 }
583
584 /***
585 * Returns the configuration's name.
586 *
587 * @return the name
588 */
589 public String getName()
590 {
591 return name;
592 }
593
594 /***
595 * Returns the at position of this configuration.
596 *
597 * @return the at position
598 */
599 public String getAt()
600 {
601 return at;
602 }
603
604 /***
605 * Returns the transformed root node of the stored configuration. The
606 * term "transformed" means that an eventually defined at path
607 * has been applied.
608 *
609 * @return the transformed root node
610 */
611 public ConfigurationNode getTransformedRoot()
612 {
613 ViewNode result = new ViewNode();
614 ViewNode atParent = result;
615
616 if (atPath != null)
617 {
618
619 for (Iterator it = atPath.iterator(); it.hasNext();)
620 {
621 ViewNode node = new ViewNode();
622 node.setName((String) it.next());
623 atParent.addChild(node);
624 atParent = node;
625 }
626 }
627
628
629 HierarchicalConfiguration hc = ConfigurationUtils
630 .convertToHierarchical(getConfiguration());
631 atParent.appendChildren(hc.getRootNode());
632 atParent.appendAttributes(hc.getRootNode());
633
634 return result;
635 }
636
637 /***
638 * Splits the at path into its components.
639 *
640 * @param at the at string
641 * @return a collection with the names of the single components
642 */
643 private Collection parseAt(String at)
644 {
645 if (at == null)
646 {
647 return null;
648 }
649
650 Collection result = new ArrayList();
651 DefaultConfigurationKey.KeyIterator it = new DefaultConfigurationKey(
652 AT_ENGINE, at).iterator();
653 while (it.hasNext())
654 {
655 result.add(it.nextKey());
656 }
657 return result;
658 }
659 }
660 }