View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *     http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
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>&quot;database.tables&quot;</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()) // use combiner as lock
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                     // simply retrieve a property; this is enough for
499                     // triggering a reload
500                     ((ConfigData) it.next()).getConfiguration().getProperty(
501                             PROP_RELOAD_CHECK);
502                 }
503                 catch (Exception ex)
504                 {
505                     // ignore all exceptions, e.g. missing property exceptions
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 &quot;transformed&quot; 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                 // Build the complete path
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             // Copy data of the root node to the new path
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 }