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    *     https://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.configuration2;
18  
19  import java.math.BigDecimal;
20  import java.math.BigInteger;
21  import java.util.ArrayList;
22  import java.util.Collection;
23  import java.util.Collections;
24  import java.util.HashMap;
25  import java.util.Iterator;
26  import java.util.List;
27  import java.util.Map;
28  import java.util.Properties;
29  import java.util.Set;
30  import java.util.concurrent.ConcurrentHashMap;
31  import java.util.concurrent.ConcurrentMap;
32  
33  import org.apache.commons.configuration2.event.BaseEventSource;
34  import org.apache.commons.configuration2.event.Event;
35  import org.apache.commons.configuration2.event.EventListener;
36  import org.apache.commons.configuration2.event.EventType;
37  import org.apache.commons.configuration2.interpol.ConfigurationInterpolator;
38  import org.apache.commons.configuration2.interpol.Lookup;
39  import org.apache.commons.configuration2.io.ConfigurationLogger;
40  import org.apache.commons.configuration2.tree.ImmutableNode;
41  import org.apache.commons.configuration2.tree.NodeCombiner;
42  
43  /**
44   * <p>
45   * DynamicCombinedConfiguration allows a set of CombinedConfigurations to be used.
46   * </p>
47   * <p>
48   * Each CombinedConfiguration is referenced by a key that is dynamically constructed from a key pattern on each call.
49   * The key pattern will be resolved using the configured ConfigurationInterpolator.
50   * </p>
51   * <p>
52   * This Configuration implementation uses the configured {@code Synchronizer} to guard itself against concurrent access.
53   * If there are multiple threads accessing an instance concurrently, a fully functional {@code Synchronizer}
54   * implementation (for example {@code ReadWriteSynchronizer}) has to be used to ensure consistency and to avoid exceptions. The
55   * {@code Synchronizer} assigned to an instance is also passed to child configuration objects when they are created.
56   * </p>
57   *
58   * @since 1.6
59   */
60  public class DynamicCombinedConfiguration extends CombinedConfiguration {
61      /**
62       * Internal class that identifies each Configuration.
63       */
64      static class ConfigData {
65          /** Stores a reference to the configuration. */
66          private final Configuration configuration;
67  
68          /** Stores the name under which the configuration is stored. */
69          private final String name;
70  
71          /** Stores the at string. */
72          private final String at;
73  
74          /**
75           * Creates a new instance of {@code ConfigData} and initializes it.
76           *
77           * @param config the configuration
78           * @param n the name
79           * @param at the at position
80           */
81          public ConfigData(final Configuration config, final String n, final String at) {
82              configuration = config;
83              name = n;
84              this.at = at;
85          }
86  
87          /**
88           * Gets the at position of this configuration.
89           *
90           * @return the at position
91           */
92          public String getAt() {
93              return at;
94          }
95  
96          /**
97           * Gets the stored configuration.
98           *
99           * @return the configuration
100          */
101         public Configuration getConfiguration() {
102             return configuration;
103         }
104 
105         /**
106          * Gets the configuration's name.
107          *
108          * @return the name
109          */
110         public String getName() {
111             return name;
112         }
113 
114     }
115 
116     /**
117      * A simple data class holding information about the current configuration while an operation for a thread is processed.
118      */
119     private static final class CurrentConfigHolder {
120         /** Stores the current configuration of the current thread. */
121         private CombinedConfiguration currentConfiguration;
122 
123         /**
124          * Stores the key of the configuration evaluated for the current thread at the beginning of an operation.
125          */
126         private final String key;
127 
128         /** A counter for reentrant locks. */
129         private int lockCount;
130 
131         /**
132          * Creates a new instance of {@code CurrentConfigHolder} and initializes it with the key for the current configuration.
133          *
134          * @param curKey the current key
135          */
136         public CurrentConfigHolder(final String curKey) {
137             key = curKey;
138         }
139 
140         /**
141          * Decrements the lock counter and checks whether it has reached 0. In this cause, the operation is complete, and the
142          * lock can be released.
143          *
144          * @return <strong>true</strong> if the lock count reaches 0, <strong>false</strong> otherwise
145          */
146         public boolean decrementLockCountAndCheckRelease() {
147             return --lockCount == 0;
148         }
149 
150         /**
151          * Gets the current configuration.
152          *
153          * @return the current configuration
154          */
155         public CombinedConfiguration getCurrentConfiguration() {
156             return currentConfiguration;
157         }
158 
159         /**
160          * Gets the current key.
161          *
162          * @return the current key
163          */
164         public String getKey() {
165             return key;
166         }
167 
168         /**
169          * Increments the lock counter.
170          */
171         public void incrementLockCount() {
172             lockCount++;
173         }
174 
175         /**
176          * Sets the current configuration.
177          *
178          * @param currentConfiguration the current configuration
179          */
180         public void setCurrentConfiguration(final CombinedConfiguration currentConfiguration) {
181             this.currentConfiguration = currentConfiguration;
182         }
183     }
184 
185     /**
186      * Stores the current configuration for each involved thread. This value is set at the beginning of an operation and
187      * removed at the end.
188      */
189     private static final ThreadLocal<CurrentConfigHolder> CURRENT_CONFIG = new ThreadLocal<>();
190 
191     /** The CombinedConfigurations */
192     private final ConcurrentMap<String, CombinedConfiguration> configs = new ConcurrentHashMap<>();
193 
194     /** Stores a list with the contained configurations. */
195     private final List<ConfigData> configurations = new ArrayList<>();
196 
197     /** Stores a map with the named configurations. */
198     private final Map<String, Configuration> namedConfigurations = new HashMap<>();
199 
200     /** The key pattern for the CombinedConfiguration map. */
201     private String keyPattern;
202 
203     /** Stores the combiner. */
204     private NodeCombiner nodeCombiner;
205 
206     /** The name of the logger to use for each CombinedConfiguration */
207     private String loggerName = DynamicCombinedConfiguration.class.getName();
208 
209     /** The object for handling variable substitution in key patterns. */
210     private final ConfigurationInterpolator localSubst;
211 
212     /**
213      * Creates a new instance of {@code DynamicCombinedConfiguration} that uses a union combiner.
214      *
215      * @see org.apache.commons.configuration2.tree.UnionCombiner
216      */
217     public DynamicCombinedConfiguration() {
218         initLogger(new ConfigurationLogger(DynamicCombinedConfiguration.class));
219         localSubst = initLocalInterpolator();
220     }
221 
222     /**
223      * Creates a new instance of {@code DynamicCombinedConfiguration} and initializes the combiner to be used.
224      *
225      * @param comb the node combiner (can be <strong>null</strong>, then a union combiner is used as default)
226      */
227     public DynamicCombinedConfiguration(final NodeCombiner comb) {
228         setNodeCombiner(comb);
229         initLogger(new ConfigurationLogger(DynamicCombinedConfiguration.class));
230         localSubst = initLocalInterpolator();
231     }
232 
233     /**
234      * Adds a new configuration to this combined configuration. It is possible (but not mandatory) to give the new
235      * configuration a name. This name must be unique, otherwise a {@code ConfigurationRuntimeException} will be thrown.
236      * With the optional {@code at} argument you can specify where in the resulting node structure the content of the added
237      * configuration should appear. This is a string that uses dots as property delimiters (independent on the current
238      * expression engine). For instance if you pass in the string {@code "database.tables"}, all properties of the added
239      * configuration will occur in this branch.
240      *
241      * @param config the configuration to add (must not be <strong>null</strong>)
242      * @param name the name of this configuration (can be <strong>null</strong>)
243      * @param at the position of this configuration in the combined tree (can be <strong>null</strong>)
244      */
245     @Override
246     public void addConfiguration(final Configuration config, final String name, final String at) {
247         beginWrite(true);
248         try {
249             final ConfigData cd = new ConfigData(config, name, at);
250             configurations.add(cd);
251             if (name != null) {
252                 namedConfigurations.put(name, config);
253             }
254 
255             // clear cache of all child configurations
256             configs.clear();
257         } finally {
258             endWrite();
259         }
260     }
261 
262     @Override
263     public <T extends Event> void addEventListener(final EventType<T> eventType, final EventListener<? super T> listener) {
264         configs.values().forEach(cc -> cc.addEventListener(eventType, listener));
265         super.addEventListener(eventType, listener);
266     }
267 
268     @Override
269     protected void addNodesInternal(final String key, final Collection<? extends ImmutableNode> nodes) {
270         getCurrentConfig().addNodes(key, nodes);
271     }
272 
273     @Override
274     protected void addPropertyInternal(final String key, final Object value) {
275         getCurrentConfig().addProperty(key, value);
276     }
277 
278     /**
279      * {@inheritDoc} This implementation ensures that the current configuration is initialized. The lock counter is
280      * increased.
281      */
282     @Override
283     protected void beginRead(final boolean optimize) {
284         final CurrentConfigHolder cch = ensureCurrentConfiguration();
285         cch.incrementLockCount();
286         if (!optimize && cch.getCurrentConfiguration() == null) {
287             // delegate to beginWrite() which creates the child configuration
288             beginWrite(false);
289             endWrite();
290         }
291 
292         // This actually uses our own synchronizer
293         cch.getCurrentConfiguration().beginRead(optimize);
294     }
295 
296     /**
297      * {@inheritDoc} This implementation ensures that the current configuration is initialized. If necessary, a new child
298      * configuration instance is created.
299      */
300     @Override
301     protected void beginWrite(final boolean optimize) {
302         final CurrentConfigHolder cch = ensureCurrentConfiguration();
303         cch.incrementLockCount();
304 
305         super.beginWrite(optimize);
306         if (!optimize && cch.getCurrentConfiguration() == null) {
307             cch.setCurrentConfiguration(createChildConfiguration());
308             configs.put(cch.getKey(), cch.getCurrentConfiguration());
309             initChildConfiguration(cch.getCurrentConfiguration());
310         }
311     }
312 
313     @Override
314     public void clearErrorListeners() {
315         configs.values().forEach(BaseEventSource::clearErrorListeners);
316         super.clearErrorListeners();
317     }
318 
319     @Override
320     public void clearEventListeners() {
321         configs.values().forEach(CombinedConfiguration::clearEventListeners);
322         super.clearEventListeners();
323     }
324 
325     @Override
326     protected void clearInternal() {
327         getCurrentConfig().clear();
328     }
329 
330     @Override
331     protected void clearPropertyDirect(final String key) {
332         getCurrentConfig().clearProperty(key);
333     }
334 
335     @Override
336     protected Object clearTreeInternal(final String key) {
337         getCurrentConfig().clearTree(key);
338         return Collections.emptyList();
339     }
340 
341     @Override
342     public HierarchicalConfiguration<ImmutableNode> configurationAt(final String key) {
343         return getCurrentConfig().configurationAt(key);
344     }
345 
346     @Override
347     public HierarchicalConfiguration<ImmutableNode> configurationAt(final String key, final boolean supportUpdates) {
348         return getCurrentConfig().configurationAt(key, supportUpdates);
349     }
350 
351     @Override
352     public List<HierarchicalConfiguration<ImmutableNode>> configurationsAt(final String key) {
353         return getCurrentConfig().configurationsAt(key);
354     }
355 
356     @Override
357     protected boolean containsKeyInternal(final String key) {
358         return getCurrentConfig().containsKey(key);
359     }
360 
361     /**
362      * Tests whether this configuration contains one or more matches to this value. This operation stops at first
363      * match but may be more expensive than the containsKey method.
364      * @since 2.11.0
365      */
366     @Override
367     protected boolean containsValueInternal(final Object value) {
368         return getCurrentConfig().contains(getKeys(), value);
369     }
370 
371     /**
372      * Creates a new, uninitialized child configuration.
373      *
374      * @return the new child configuration
375      */
376     private CombinedConfiguration createChildConfiguration() {
377         return new CombinedConfiguration(getNodeCombiner());
378     }
379 
380     /**
381      * {@inheritDoc} This implementation clears the current configuration if necessary.
382      */
383     @Override
384     protected void endRead() {
385         CURRENT_CONFIG.get().getCurrentConfiguration().endRead();
386         releaseLock();
387     }
388 
389     /**
390      * {@inheritDoc} This implementation clears the current configuration if necessary.
391      */
392     @Override
393     protected void endWrite() {
394         super.endWrite();
395         releaseLock();
396     }
397 
398     /**
399      * Checks whether the current configuration is set. If not, a {@code CurrentConfigHolder} is now created and
400      * initialized, and associated with the current thread. The member for the current configuration is undefined if for the
401      * current key no configuration exists yet.
402      *
403      * @return the {@code CurrentConfigHolder} instance for the current thread
404      */
405     private CurrentConfigHolder ensureCurrentConfiguration() {
406         CurrentConfigHolder cch = CURRENT_CONFIG.get();
407         if (cch == null) {
408             final String key = String.valueOf(localSubst.interpolate(keyPattern));
409             cch = new CurrentConfigHolder(key);
410             cch.setCurrentConfiguration(configs.get(key));
411             CURRENT_CONFIG.set(cch);
412         }
413         return cch;
414     }
415 
416     @Override
417     public BigDecimal getBigDecimal(final String key) {
418         return getCurrentConfig().getBigDecimal(key);
419     }
420 
421     @Override
422     public BigDecimal getBigDecimal(final String key, final BigDecimal defaultValue) {
423         return getCurrentConfig().getBigDecimal(key, defaultValue);
424     }
425 
426     @Override
427     public BigInteger getBigInteger(final String key) {
428         return getCurrentConfig().getBigInteger(key);
429     }
430 
431     @Override
432     public BigInteger getBigInteger(final String key, final BigInteger defaultValue) {
433         return getCurrentConfig().getBigInteger(key, defaultValue);
434     }
435 
436     @Override
437     public boolean getBoolean(final String key) {
438         return getCurrentConfig().getBoolean(key);
439     }
440 
441     @Override
442     public boolean getBoolean(final String key, final boolean defaultValue) {
443         return getCurrentConfig().getBoolean(key, defaultValue);
444     }
445 
446     @Override
447     public Boolean getBoolean(final String key, final Boolean defaultValue) {
448         return getCurrentConfig().getBoolean(key, defaultValue);
449     }
450 
451     @Override
452     public byte getByte(final String key) {
453         return getCurrentConfig().getByte(key);
454     }
455 
456     @Override
457     public byte getByte(final String key, final byte defaultValue) {
458         return getCurrentConfig().getByte(key, defaultValue);
459     }
460 
461     @Override
462     public Byte getByte(final String key, final Byte defaultValue) {
463         return getCurrentConfig().getByte(key, defaultValue);
464     }
465 
466     /**
467      * Gets the configuration at the specified index. The contained configurations are numbered in the order they were
468      * added to this combined configuration. The index of the first configuration is 0.
469      *
470      * @param index the index
471      * @return the configuration at this index
472      */
473     @Override
474     public Configuration getConfiguration(final int index) {
475         beginRead(false);
476         try {
477             final ConfigData cd = configurations.get(index);
478             return cd.getConfiguration();
479         } finally {
480             endRead();
481         }
482     }
483 
484     /**
485      * Gets the configuration with the given name. This can be <strong>null</strong> if no such configuration exists.
486      *
487      * @param name the name of the configuration
488      * @return the configuration with this name
489      */
490     @Override
491     public Configuration getConfiguration(final String name) {
492         beginRead(false);
493         try {
494             return namedConfigurations.get(name);
495         } finally {
496             endRead();
497         }
498     }
499 
500     /**
501      * Gets a set with the names of all configurations contained in this combined configuration. Of course here are only
502      * these configurations listed, for which a name was specified when they were added.
503      *
504      * @return a set with the names of the contained configurations (never <strong>null</strong>)
505      */
506     @Override
507     public Set<String> getConfigurationNames() {
508         beginRead(false);
509         try {
510             return namedConfigurations.keySet();
511         } finally {
512             endRead();
513         }
514     }
515 
516     /**
517      * Gets the current configuration. This configuration was initialized at the beginning of an operation and stored in
518      * a thread-local variable. Some methods of this class call this method directly without requesting a lock before. To
519      * deal with this, we always request an additional read lock.
520      *
521      * @return the current configuration
522      */
523     private CombinedConfiguration getCurrentConfig() {
524         CombinedConfiguration config;
525         String key;
526         beginRead(false);
527         try {
528             config = CURRENT_CONFIG.get().getCurrentConfiguration();
529             key = CURRENT_CONFIG.get().getKey();
530         } finally {
531             endRead();
532         }
533 
534         if (getLogger().isDebugEnabled()) {
535             getLogger().debug("Returning config for " + key + ": " + config);
536         }
537         return config;
538     }
539 
540     @Override
541     public double getDouble(final String key) {
542         return getCurrentConfig().getDouble(key);
543     }
544 
545     @Override
546     public double getDouble(final String key, final double defaultValue) {
547         return getCurrentConfig().getDouble(key, defaultValue);
548     }
549 
550     @Override
551     public Double getDouble(final String key, final Double defaultValue) {
552         return getCurrentConfig().getDouble(key, defaultValue);
553     }
554 
555     @Override
556     public float getFloat(final String key) {
557         return getCurrentConfig().getFloat(key);
558     }
559 
560     @Override
561     public float getFloat(final String key, final float defaultValue) {
562         return getCurrentConfig().getFloat(key, defaultValue);
563     }
564 
565     @Override
566     public Float getFloat(final String key, final Float defaultValue) {
567         return getCurrentConfig().getFloat(key, defaultValue);
568     }
569 
570     @Override
571     public int getInt(final String key) {
572         return getCurrentConfig().getInt(key);
573     }
574 
575     @Override
576     public int getInt(final String key, final int defaultValue) {
577         return getCurrentConfig().getInt(key, defaultValue);
578     }
579 
580     @Override
581     public Integer getInteger(final String key, final Integer defaultValue) {
582         return getCurrentConfig().getInteger(key, defaultValue);
583     }
584 
585     /**
586      * Gets the key pattern for the CombinedConfiguration map.
587      *
588      * @return the key pattern for the CombinedConfiguration map.
589      */
590     public String getKeyPattern() {
591         return this.keyPattern;
592     }
593 
594     @Override
595     protected Iterator<String> getKeysInternal() {
596         return getCurrentConfig().getKeys();
597     }
598 
599     @Override
600     protected Iterator<String> getKeysInternal(final String prefix) {
601         return getCurrentConfig().getKeys(prefix);
602     }
603 
604     @Override
605     public List<Object> getList(final String key) {
606         return getCurrentConfig().getList(key);
607     }
608 
609     @Override
610     public List<Object> getList(final String key, final List<?> defaultValue) {
611         return getCurrentConfig().getList(key, defaultValue);
612     }
613 
614     @Override
615     public long getLong(final String key) {
616         return getCurrentConfig().getLong(key);
617     }
618 
619     @Override
620     public long getLong(final String key, final long defaultValue) {
621         return getCurrentConfig().getLong(key, defaultValue);
622     }
623 
624     @Override
625     public Long getLong(final String key, final Long defaultValue) {
626         return getCurrentConfig().getLong(key, defaultValue);
627     }
628 
629     @Override
630     protected int getMaxIndexInternal(final String key) {
631         return getCurrentConfig().getMaxIndex(key);
632     }
633 
634     /**
635      * Gets the node combiner that is used for creating the combined node structure.
636      *
637      * @return the node combiner
638      */
639     @Override
640     public NodeCombiner getNodeCombiner() {
641         return nodeCombiner;
642     }
643 
644     /**
645      * Gets the number of configurations that are contained in this combined configuration.
646      *
647      * @return the number of contained configurations
648      */
649     @Override
650     public int getNumberOfConfigurations() {
651         beginRead(false);
652         try {
653             return configurations.size();
654         } finally {
655             endRead();
656         }
657     }
658 
659     @Override
660     public Properties getProperties(final String key) {
661         return getCurrentConfig().getProperties(key);
662     }
663 
664     @Override
665     protected Object getPropertyInternal(final String key) {
666         return getCurrentConfig().getProperty(key);
667     }
668 
669     @Override
670     public short getShort(final String key) {
671         return getCurrentConfig().getShort(key);
672     }
673 
674     @Override
675     public short getShort(final String key, final short defaultValue) {
676         return getCurrentConfig().getShort(key, defaultValue);
677     }
678 
679     @Override
680     public Short getShort(final String key, final Short defaultValue) {
681         return getCurrentConfig().getShort(key, defaultValue);
682     }
683 
684     /**
685      * Gets the configuration source, in which the specified key is defined. This method will determine the configuration
686      * node that is identified by the given key. The following constellations are possible:
687      * <ul>
688      * <li>If no node object is found for this key, <strong>null</strong> is returned.</li>
689      * <li>If the key maps to multiple nodes belonging to different configuration sources, a
690      * {@code IllegalArgumentException} is thrown (in this case no unique source can be determined).</li>
691      * <li>If exactly one node is found for the key, the (child) configuration object, to which the node belongs is
692      * determined and returned.</li>
693      * <li>For keys that have been added directly to this combined configuration and that do not belong to the namespaces
694      * defined by existing child configurations this configuration will be returned.</li>
695      * </ul>
696      *
697      * @param key the key of a configuration property
698      * @return the configuration, to which this property belongs or <strong>null</strong> if the key cannot be resolved
699      * @throws IllegalArgumentException if the key maps to multiple properties and the source cannot be determined, or if
700      *         the key is <strong>null</strong>
701      */
702     @Override
703     public Configuration getSource(final String key) {
704         if (key == null) {
705             throw new IllegalArgumentException("Key must not be null!");
706         }
707         return getCurrentConfig().getSource(key);
708     }
709 
710     @Override
711     public String getString(final String key) {
712         return getCurrentConfig().getString(key);
713     }
714 
715     @Override
716     public String getString(final String key, final String defaultValue) {
717         return getCurrentConfig().getString(key, defaultValue);
718     }
719 
720     @Override
721     public String[] getStringArray(final String key) {
722         return getCurrentConfig().getStringArray(key);
723     }
724 
725     /**
726      * Initializes a newly created child configuration. This method copies a bunch of settings from this instance to the
727      * child configuration.
728      *
729      * @param config the child configuration to be initialized
730      */
731     private void initChildConfiguration(final CombinedConfiguration config) {
732         if (loggerName != null) {
733             config.setLogger(new ConfigurationLogger(loggerName));
734         }
735         config.setExpressionEngine(getExpressionEngine());
736         config.setConversionExpressionEngine(getConversionExpressionEngine());
737         config.setListDelimiterHandler(getListDelimiterHandler());
738         copyEventListeners(config);
739         configurations.forEach(data -> config.addConfiguration(data.getConfiguration(), data.getName(), data.getAt()));
740         config.setSynchronizer(getSynchronizer());
741     }
742 
743     /**
744      * Creates a {@code ConfigurationInterpolator} instance for performing local variable substitutions. This implementation
745      * returns an object which shares the prefix lookups from this configuration's {@code ConfigurationInterpolator}, but
746      * does not define any other lookups.
747      *
748      * @return the {@code ConfigurationInterpolator}
749      */
750     private ConfigurationInterpolator initLocalInterpolator() {
751         return new ConfigurationInterpolator() {
752             @Override
753             protected Lookup fetchLookupForPrefix(final String prefix) {
754                 return nullSafeLookup(getInterpolator().getLookups().get(prefix));
755             }
756         };
757     }
758 
759     @Override
760     public Configuration interpolatedConfiguration() {
761         return getCurrentConfig().interpolatedConfiguration();
762     }
763 
764     /**
765      * Invalidates the current combined configuration. This means that the next time a property is accessed the combined
766      * node structure must be re-constructed. Invalidation of a combined configuration also means that an event of type
767      * {@code EVENT_COMBINED_INVALIDATE} is fired. Note that while other events most times appear twice (once before and
768      * once after an update), this event is only fired once (after update).
769      */
770     @Override
771     public void invalidate() {
772         getCurrentConfig().invalidate();
773     }
774 
775     /**
776      * Invalidates all CombinedConfigurations.
777      */
778     public void invalidateAll() {
779         configs.values().forEach(CombinedConfiguration::invalidate);
780     }
781 
782     @Override
783     protected boolean isEmptyInternal() {
784         return getCurrentConfig().isEmpty();
785     }
786 
787     /**
788      * Decrements the lock count of the current configuration holder. If it reaches 0, the current configuration is removed.
789      * (It is then reevaluated when the next operation starts.)
790      */
791     private void releaseLock() {
792         final CurrentConfigHolder cch = CURRENT_CONFIG.get();
793         assert cch != null : "No current configuration!";
794         if (cch.decrementLockCountAndCheckRelease()) {
795             CURRENT_CONFIG.remove();
796         }
797     }
798 
799     /**
800      * Removes the specified configuration from this combined configuration.
801      *
802      * @param config the configuration to be removed
803      * @return a flag whether this configuration was found and could be removed
804      */
805     @Override
806     public boolean removeConfiguration(final Configuration config) {
807         beginWrite(false);
808         try {
809             for (int index = 0; index < getNumberOfConfigurations(); index++) {
810                 if (configurations.get(index).getConfiguration() == config) {
811                     removeConfigurationAt(index);
812                     return true;
813                 }
814             }
815 
816             return false;
817         } finally {
818             endWrite();
819         }
820     }
821 
822     /**
823      * Removes the configuration with the specified name.
824      *
825      * @param name the name of the configuration to be removed
826      * @return the removed configuration (<strong>null</strong> if this configuration was not found)
827      */
828     @Override
829     public Configuration removeConfiguration(final String name) {
830         final Configuration conf = getConfiguration(name);
831         if (conf != null) {
832             removeConfiguration(conf);
833         }
834         return conf;
835     }
836 
837     /**
838      * Removes the configuration at the specified index.
839      *
840      * @param index the index
841      * @return the removed configuration
842      */
843     @Override
844     public Configuration removeConfigurationAt(final int index) {
845         beginWrite(false);
846         try {
847             final ConfigData cd = configurations.remove(index);
848             if (cd.getName() != null) {
849                 namedConfigurations.remove(cd.getName());
850             }
851             return cd.getConfiguration();
852         } finally {
853             endWrite();
854         }
855     }
856 
857     @Override
858     public <T extends Event> boolean removeEventListener(final EventType<T> eventType, final EventListener<? super T> listener) {
859         configs.values().forEach(cc -> cc.removeEventListener(eventType, listener));
860         return super.removeEventListener(eventType, listener);
861     }
862 
863     /**
864      * Sets the key pattern for the CombinedConfiguration map.
865      *
866      * @param pattern the key pattern for the CombinedConfiguration map.
867      */
868     public void setKeyPattern(final String pattern) {
869         this.keyPattern = pattern;
870     }
871 
872     /**
873      * Sets the name of the Logger to use on each CombinedConfiguration.
874      *
875      * @param name The Logger name.
876      */
877     public void setLoggerName(final String name) {
878         this.loggerName = name;
879     }
880 
881     /**
882      * Sets the node combiner. This object will be used when the combined node structure is to be constructed. It must not
883      * be <strong>null</strong>, otherwise an {@code IllegalArgumentException} exception is thrown. Changing the node combiner causes
884      * an invalidation of this combined configuration, so that the new combiner immediately takes effect.
885      *
886      * @param nodeCombiner the node combiner
887      */
888     @Override
889     public void setNodeCombiner(final NodeCombiner nodeCombiner) {
890         if (nodeCombiner == null) {
891             throw new IllegalArgumentException("Node combiner must not be null!");
892         }
893         this.nodeCombiner = nodeCombiner;
894         invalidateAll();
895     }
896 
897     @Override
898     protected void setPropertyInternal(final String key, final Object value) {
899         getCurrentConfig().setProperty(key, value);
900     }
901 
902     @Override
903     protected int sizeInternal() {
904         return getCurrentConfig().size();
905     }
906 
907     @Override
908     public Configuration subset(final String prefix) {
909         return getCurrentConfig().subset(prefix);
910     }
911 }