001    /*
002     * Licensed to the Apache Software Foundation (ASF) under one or more
003     * contributor license agreements.  See the NOTICE file distributed with
004     * this work for additional information regarding copyright ownership.
005     * The ASF licenses this file to You under the Apache License, Version 2.0
006     * (the "License"); you may not use this file except in compliance with
007     * the License.  You may obtain a copy of the License at
008     *
009     *     http://www.apache.org/licenses/LICENSE-2.0
010     *
011     * Unless required by applicable law or agreed to in writing, software
012     * distributed under the License is distributed on an "AS IS" BASIS,
013     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014     * See the License for the specific language governing permissions and
015     * limitations under the License.
016     */
017    package org.apache.commons.configuration;
018    
019    import java.math.BigDecimal;
020    import java.math.BigInteger;
021    import java.util.ArrayList;
022    import java.util.Collection;
023    import java.util.HashMap;
024    import java.util.Iterator;
025    import java.util.List;
026    import java.util.Map;
027    import java.util.Properties;
028    import java.util.Set;
029    
030    import org.apache.commons.configuration.event.ConfigurationErrorListener;
031    import org.apache.commons.configuration.event.ConfigurationListener;
032    import org.apache.commons.configuration.tree.ConfigurationNode;
033    import org.apache.commons.configuration.tree.ExpressionEngine;
034    import org.apache.commons.configuration.tree.NodeCombiner;
035    
036    /**
037     * DynamicCombinedConfiguration allows a set of CombinedConfigurations to be used. Each CombinedConfiguration
038     * is referenced by a key that is dynamically constructed from a key pattern on each call. The key pattern
039     * will be resolved using the configured ConfigurationInterpolator.
040     * @since 1.6
041     * @author <a
042     * href="http://commons.apache.org/configuration/team-list.html">Commons
043     * Configuration team</a>
044     * @version $Id: DynamicCombinedConfiguration.java 727955 2008-12-19 07:06:16Z oheger $
045     */
046    public class DynamicCombinedConfiguration extends CombinedConfiguration
047    {
048        /**
049         * Prevent recursion while resolving unprefixed properties.
050         */
051        private static ThreadLocal recursive = new ThreadLocal()
052        {
053            protected synchronized Object initialValue()
054            {
055                return Boolean.FALSE;
056            }
057        };
058    
059        /** The CombinedConfigurations */
060        private Map configs = new HashMap();
061    
062        /** Stores a list with the contained configurations. */
063        private List configurations = new ArrayList();
064    
065        /** Stores a map with the named configurations. */
066        private Map namedConfigurations = new HashMap();
067    
068        /** The key pattern for the CombinedConfiguration map */
069        private String keyPattern;
070    
071        /** Stores the combiner. */
072        private NodeCombiner nodeCombiner;
073    
074        /**
075         * Creates a new instance of <code>CombinedConfiguration</code> and
076         * initializes the combiner to be used.
077         *
078         * @param comb the node combiner (can be <b>null</b>, then a union combiner
079         * is used as default)
080         */
081        public DynamicCombinedConfiguration(NodeCombiner comb)
082        {
083            super();
084            setNodeCombiner(comb);
085        }
086    
087        /**
088         * Creates a new instance of <code>CombinedConfiguration</code> that uses
089         * a union combiner.
090         *
091         * @see org.apache.commons.configuration.tree.UnionCombiner
092         */
093        public DynamicCombinedConfiguration()
094        {
095            super();
096        }
097    
098        public void setKeyPattern(String pattern)
099        {
100            this.keyPattern = pattern;
101        }
102    
103        public String getKeyPattern()
104        {
105            return this.keyPattern;
106        }
107    
108        /**
109         * Returns the node combiner that is used for creating the combined node
110         * structure.
111         *
112         * @return the node combiner
113         */
114        public NodeCombiner getNodeCombiner()
115        {
116            return nodeCombiner;
117        }
118    
119        /**
120         * Sets the node combiner. This object will be used when the combined node
121         * structure is to be constructed. It must not be <b>null</b>, otherwise an
122         * <code>IllegalArgumentException</code> exception is thrown. Changing the
123         * node combiner causes an invalidation of this combined configuration, so
124         * that the new combiner immediately takes effect.
125         *
126         * @param nodeCombiner the node combiner
127         */
128        public void setNodeCombiner(NodeCombiner nodeCombiner)
129        {
130            if (nodeCombiner == null)
131            {
132                throw new IllegalArgumentException(
133                        "Node combiner must not be null!");
134            }
135            this.nodeCombiner = nodeCombiner;
136            invalidateAll();
137        }
138        /**
139         * Adds a new configuration to this combined configuration. It is possible
140         * (but not mandatory) to give the new configuration a name. This name must
141         * be unique, otherwise a <code>ConfigurationRuntimeException</code> will
142         * be thrown. With the optional <code>at</code> argument you can specify
143         * where in the resulting node structure the content of the added
144         * configuration should appear. This is a string that uses dots as property
145         * delimiters (independent on the current expression engine). For instance
146         * if you pass in the string <code>&quot;database.tables&quot;</code>,
147         * all properties of the added configuration will occur in this branch.
148         *
149         * @param config the configuration to add (must not be <b>null</b>)
150         * @param name the name of this configuration (can be <b>null</b>)
151         * @param at the position of this configuration in the combined tree (can be
152         * <b>null</b>)
153         */
154        public void addConfiguration(AbstractConfiguration config, String name,
155                String at)
156        {
157            ConfigData cd = new ConfigData(config, name, at);
158            configurations.add(cd);
159            if (name != null)
160            {
161                namedConfigurations.put(name, config);
162            }
163        }
164           /**
165         * Returns the number of configurations that are contained in this combined
166         * configuration.
167         *
168         * @return the number of contained configurations
169         */
170        public int getNumberOfConfigurations()
171        {
172            return configurations.size();
173        }
174    
175        /**
176         * Returns the configuration at the specified index. The contained
177         * configurations are numbered in the order they were added to this combined
178         * configuration. The index of the first configuration is 0.
179         *
180         * @param index the index
181         * @return the configuration at this index
182         */
183        public Configuration getConfiguration(int index)
184        {
185            ConfigData cd = (ConfigData) configurations.get(index);
186            return cd.getConfiguration();
187        }
188    
189        /**
190         * Returns the configuration with the given name. This can be <b>null</b>
191         * if no such configuration exists.
192         *
193         * @param name the name of the configuration
194         * @return the configuration with this name
195         */
196        public Configuration getConfiguration(String name)
197        {
198            return (Configuration) namedConfigurations.get(name);
199        }
200    
201        /**
202         * Returns a set with the names of all configurations contained in this
203         * combined configuration. Of course here are only these configurations
204         * listed, for which a name was specified when they were added.
205         *
206         * @return a set with the names of the contained configurations (never
207         * <b>null</b>)
208         */
209        public Set getConfigurationNames()
210        {
211            return namedConfigurations.keySet();
212        }
213    
214        /**
215         * Removes the configuration with the specified name.
216         *
217         * @param name the name of the configuration to be removed
218         * @return the removed configuration (<b>null</b> if this configuration
219         * was not found)
220         */
221        public Configuration removeConfiguration(String name)
222        {
223            Configuration conf = getConfiguration(name);
224            if (conf != null)
225            {
226                removeConfiguration(conf);
227            }
228            return conf;
229        }
230    
231        /**
232         * Removes the specified configuration from this combined configuration.
233         *
234         * @param config the configuration to be removed
235         * @return a flag whether this configuration was found and could be removed
236         */
237        public boolean removeConfiguration(Configuration config)
238        {
239            for (int index = 0; index < getNumberOfConfigurations(); index++)
240            {
241                if (((ConfigData) configurations.get(index)).getConfiguration() == config)
242                {
243                    removeConfigurationAt(index);
244    
245                }
246            }
247    
248            return super.removeConfiguration(config);
249        }
250    
251        /**
252         * Removes the configuration at the specified index.
253         *
254         * @param index the index
255         * @return the removed configuration
256         */
257        public Configuration removeConfigurationAt(int index)
258        {
259            ConfigData cd = (ConfigData) configurations.remove(index);
260            if (cd.getName() != null)
261            {
262                namedConfigurations.remove(cd.getName());
263            }
264            return super.removeConfigurationAt(index);
265        }
266        /**
267         * Returns the configuration root node of this combined configuration. This
268         * method will construct a combined node structure using the current node
269         * combiner if necessary.
270         *
271         * @return the combined root node
272         */
273        public ConfigurationNode getRootNode()
274        {
275            return getCurrentConfig().getRootNode();
276        }
277    
278        public void setRootNode(ConfigurationNode rootNode)
279        {
280            if (configs != null)
281            {
282                this.getCurrentConfig().setRootNode(rootNode);
283            }
284            else
285            {
286                super.setRootNode(rootNode);
287            }
288        }
289    
290        public void addProperty(String key, Object value)
291        {
292            this.getCurrentConfig().addProperty(key, value);
293        }
294    
295        public void clear()
296        {
297            if (configs != null)
298            {
299                this.getCurrentConfig().clear();
300            }
301        }
302    
303        public void clearProperty(String key)
304        {
305            this.getCurrentConfig().clearProperty(key);
306        }
307    
308        public boolean containsKey(String key)
309        {
310            return this.getCurrentConfig().containsKey(key);
311        }
312    
313        public BigDecimal getBigDecimal(String key, BigDecimal defaultValue)
314        {
315            return this.getCurrentConfig().getBigDecimal(key, defaultValue);
316        }
317    
318        public BigDecimal getBigDecimal(String key)
319        {
320            return this.getCurrentConfig().getBigDecimal(key);
321        }
322    
323        public BigInteger getBigInteger(String key, BigInteger defaultValue)
324        {
325            return this.getCurrentConfig().getBigInteger(key, defaultValue);
326        }
327    
328        public BigInteger getBigInteger(String key)
329        {
330            return this.getCurrentConfig().getBigInteger(key);
331        }
332    
333        public boolean getBoolean(String key, boolean defaultValue)
334        {
335            return this.getCurrentConfig().getBoolean(key, defaultValue);
336        }
337    
338        public Boolean getBoolean(String key, Boolean defaultValue)
339        {
340            return this.getCurrentConfig().getBoolean(key, defaultValue);
341        }
342    
343        public boolean getBoolean(String key)
344        {
345            return this.getCurrentConfig().getBoolean(key);
346        }
347    
348        public byte getByte(String key, byte defaultValue)
349        {
350            return this.getCurrentConfig().getByte(key, defaultValue);
351        }
352    
353        public Byte getByte(String key, Byte defaultValue)
354        {
355            return this.getCurrentConfig().getByte(key, defaultValue);
356        }
357    
358        public byte getByte(String key)
359        {
360            return this.getCurrentConfig().getByte(key);
361        }
362    
363        public double getDouble(String key, double defaultValue)
364        {
365            return this.getCurrentConfig().getDouble(key, defaultValue);
366        }
367    
368        public Double getDouble(String key, Double defaultValue)
369        {
370            return this.getCurrentConfig().getDouble(key, defaultValue);
371        }
372    
373        public double getDouble(String key)
374        {
375            return this.getCurrentConfig().getDouble(key);
376        }
377    
378        public float getFloat(String key, float defaultValue)
379        {
380            return this.getCurrentConfig().getFloat(key, defaultValue);
381        }
382    
383        public Float getFloat(String key, Float defaultValue)
384        {
385            return this.getCurrentConfig().getFloat(key, defaultValue);
386        }
387    
388        public float getFloat(String key)
389        {
390            return this.getCurrentConfig().getFloat(key);
391        }
392    
393        public int getInt(String key, int defaultValue)
394        {
395            return this.getCurrentConfig().getInt(key, defaultValue);
396        }
397    
398        public int getInt(String key)
399        {
400            return this.getCurrentConfig().getInt(key);
401        }
402    
403        public Integer getInteger(String key, Integer defaultValue)
404        {
405            return this.getCurrentConfig().getInteger(key, defaultValue);
406        }
407    
408        public Iterator getKeys()
409        {
410            return this.getCurrentConfig().getKeys();
411        }
412    
413        public Iterator getKeys(String prefix)
414        {
415            return this.getCurrentConfig().getKeys(prefix);
416        }
417    
418        public List getList(String key, List defaultValue)
419        {
420            return this.getCurrentConfig().getList(key, defaultValue);
421        }
422    
423        public List getList(String key)
424        {
425            return this.getCurrentConfig().getList(key);
426        }
427    
428        public long getLong(String key, long defaultValue)
429        {
430            return this.getCurrentConfig().getLong(key, defaultValue);
431        }
432    
433        public Long getLong(String key, Long defaultValue)
434        {
435            return this.getCurrentConfig().getLong(key, defaultValue);
436        }
437    
438        public long getLong(String key)
439        {
440            return this.getCurrentConfig().getLong(key);
441        }
442    
443        public Properties getProperties(String key)
444        {
445            return this.getCurrentConfig().getProperties(key);
446        }
447    
448        public Object getProperty(String key)
449        {
450            return this.getCurrentConfig().getProperty(key);
451        }
452    
453        public short getShort(String key, short defaultValue)
454        {
455            return this.getCurrentConfig().getShort(key, defaultValue);
456        }
457    
458        public Short getShort(String key, Short defaultValue)
459        {
460            return this.getCurrentConfig().getShort(key, defaultValue);
461        }
462    
463        public short getShort(String key)
464        {
465            return this.getCurrentConfig().getShort(key);
466        }
467    
468        public String getString(String key, String defaultValue)
469        {
470            return this.getCurrentConfig().getString(key, defaultValue);
471        }
472    
473        public String getString(String key)
474        {
475            return this.getCurrentConfig().getString(key);
476        }
477    
478        public String[] getStringArray(String key)
479        {
480            return this.getCurrentConfig().getStringArray(key);
481        }
482    
483        public boolean isEmpty()
484        {
485            return this.getCurrentConfig().isEmpty();
486        }
487    
488        public void setProperty(String key, Object value)
489        {
490            if (configs != null)
491            {
492                this.getCurrentConfig().setProperty(key, value);
493            }
494        }
495    
496        public Configuration subset(String prefix)
497        {
498            return this.getCurrentConfig().subset(prefix);
499        }
500    
501        public Node getRoot()
502        {
503            return this.getCurrentConfig().getRoot();
504        }
505    
506        public void setRoot(Node node)
507        {
508            if (configs != null)
509            {
510                this.getCurrentConfig().setRoot(node);
511            }
512            else
513            {
514                super.setRoot(node);
515            }
516        }
517    
518        public ExpressionEngine getExpressionEngine()
519        {
520            return super.getExpressionEngine();
521        }
522    
523        public void setExpressionEngine(ExpressionEngine expressionEngine)
524        {
525            super.setExpressionEngine(expressionEngine);
526        }
527    
528        public void addNodes(String key, Collection nodes)
529        {
530            this.getCurrentConfig().addNodes(key, nodes);
531        }
532    
533        public SubnodeConfiguration configurationAt(String key, boolean supportUpdates)
534        {
535            return this.getCurrentConfig().configurationAt(key, supportUpdates);
536        }
537    
538        public SubnodeConfiguration configurationAt(String key)
539        {
540            return this.getCurrentConfig().configurationAt(key);
541        }
542    
543        public List configurationsAt(String key)
544        {
545            return this.getCurrentConfig().configurationsAt(key);
546        }
547    
548        public void clearTree(String key)
549        {
550            this.getCurrentConfig().clearTree(key);
551        }
552    
553        public int getMaxIndex(String key)
554        {
555            return this.getCurrentConfig().getMaxIndex(key);
556        }
557    
558        public Configuration interpolatedConfiguration()
559        {
560            return this.getCurrentConfig().interpolatedConfiguration();
561        }
562    
563    
564        /**
565         * Returns the configuration source, in which the specified key is defined.
566         * This method will determine the configuration node that is identified by
567         * the given key. The following constellations are possible:
568         * <ul>
569         * <li>If no node object is found for this key, <b>null</b> is returned.</li>
570         * <li>If the key maps to multiple nodes belonging to different
571         * configuration sources, a <code>IllegalArgumentException</code> is
572         * thrown (in this case no unique source can be determined).</li>
573         * <li>If exactly one node is found for the key, the (child) configuration
574         * object, to which the node belongs is determined and returned.</li>
575         * <li>For keys that have been added directly to this combined
576         * configuration and that do not belong to the namespaces defined by
577         * existing child configurations this configuration will be returned.</li>
578         * </ul>
579         *
580         * @param key the key of a configuration property
581         * @return the configuration, to which this property belongs or <b>null</b>
582         * if the key cannot be resolved
583         * @throws IllegalArgumentException if the key maps to multiple properties
584         * and the source cannot be determined, or if the key is <b>null</b>
585         */
586        public Configuration getSource(String key)
587        {
588            if (key == null)
589            {
590                throw new IllegalArgumentException("Key must not be null!");
591            }
592            return getCurrentConfig().getSource(key);
593        }
594    
595        public void addConfigurationListener(ConfigurationListener l)
596        {
597            super.addConfigurationListener(l);
598    
599            Iterator iter = configs.values().iterator();
600            while (iter.hasNext())
601            {
602                CombinedConfiguration config = (CombinedConfiguration) iter.next();
603                config.addConfigurationListener(l);
604            }
605        }
606    
607        public boolean removeConfigurationListener(ConfigurationListener l)
608        {
609            Iterator iter = configs.values().iterator();
610            while (iter.hasNext())
611            {
612                CombinedConfiguration config = (CombinedConfiguration) iter.next();
613                config.removeConfigurationListener(l);
614            }
615            return super.removeConfigurationListener(l);
616        }
617    
618        public Collection getConfigurationListeners()
619        {
620            return super.getConfigurationListeners();
621        }
622    
623        public void clearConfigurationListeners()
624        {
625            Iterator iter = configs.values().iterator();
626            while (iter.hasNext())
627            {
628                CombinedConfiguration config = (CombinedConfiguration) iter.next();
629                config.clearConfigurationListeners();
630            }
631            super.clearConfigurationListeners();
632        }
633    
634        public void addErrorListener(ConfigurationErrorListener l)
635        {
636            Iterator iter = configs.values().iterator();
637            while (iter.hasNext())
638            {
639                CombinedConfiguration config = (CombinedConfiguration) iter.next();
640                config.addErrorListener(l);
641            }
642            super.addErrorListener(l);
643        }
644    
645        public boolean removeErrorListener(ConfigurationErrorListener l)
646        {
647            Iterator iter = configs.values().iterator();
648            while (iter.hasNext())
649            {
650                CombinedConfiguration config = (CombinedConfiguration) iter.next();
651                config.removeErrorListener(l);
652            }
653            return super.removeErrorListener(l);
654        }
655    
656        public void clearErrorListeners()
657        {
658            Iterator iter = configs.values().iterator();
659            while (iter.hasNext())
660            {
661                CombinedConfiguration config = (CombinedConfiguration) iter.next();
662                config.clearErrorListeners();
663            }
664            super.clearErrorListeners();
665        }
666    
667        public Collection getErrorListeners()
668        {
669            return super.getErrorListeners();
670        }
671    
672    
673    
674        /**
675         * Returns a copy of this object. This implementation performs a deep clone,
676         * i.e. all contained configurations will be cloned, too. For this to work,
677         * all contained configurations must be cloneable. Registered event
678         * listeners won't be cloned. The clone will use the same node combiner than
679         * the original.
680         *
681         * @return the copied object
682         */
683        public Object clone()
684        {
685            return super.clone();
686        }
687    
688    
689    
690        /**
691         * Invalidates the current combined configuration. This means that the next time a
692         * property is accessed the combined node structure must be re-constructed.
693         * Invalidation of a combined configuration also means that an event of type
694         * <code>EVENT_COMBINED_INVALIDATE</code> is fired. Note that while other
695         * events most times appear twice (once before and once after an update),
696         * this event is only fired once (after update).
697         */
698        public void invalidate()
699        {
700            getCurrentConfig().invalidate();
701        }
702    
703        public void invalidateAll()
704        {
705            if (configs == null)
706            {
707                return;
708            }
709            Iterator iter = configs.values().iterator();
710            while (iter.hasNext())
711            {
712               CombinedConfiguration config = (CombinedConfiguration) iter.next();
713               config.invalidate();
714            }
715        }
716    
717        /*
718         * Don't allow resolveContainerStore to be called recursively.
719         * @param key The key to resolve.
720         * @return The value of the key.
721         */
722        protected Object resolveContainerStore(String key)
723        {
724            if (((Boolean) recursive.get()).booleanValue())
725            {
726                return null;
727            }
728            recursive.set(Boolean.TRUE);
729            try
730            {
731                return super.resolveContainerStore(key);
732            }
733            finally
734            {
735                recursive.set(Boolean.FALSE);
736            }
737        }
738    
739        private CombinedConfiguration getCurrentConfig()
740        {
741            String key = getSubstitutor().replace(keyPattern);
742            CombinedConfiguration config;
743            synchronized (getNodeCombiner())
744            {
745                config = (CombinedConfiguration) configs.get(key);
746                if (config == null)
747                {
748                    config = new CombinedConfiguration(getNodeCombiner());
749                    config.setExpressionEngine(this.getExpressionEngine());
750                    Iterator iter = config.getErrorListeners().iterator();
751                    while (iter.hasNext())
752                    {
753                        ConfigurationErrorListener listener = (ConfigurationErrorListener) iter.next();
754                        config.addErrorListener(listener);
755                    }
756                    iter = config.getConfigurationListeners().iterator();
757                    while (iter.hasNext())
758                    {
759                        ConfigurationListener listener = (ConfigurationListener) iter.next();
760                        config.addConfigurationListener(listener);
761                    }
762                    config.setForceReloadCheck(isForceReloadCheck());
763                    iter = configurations.iterator();
764                    while (iter.hasNext())
765                    {
766                        ConfigData data = (ConfigData) iter.next();
767                        config.addConfiguration(data.getConfiguration(), data.getName(),
768                                data.getAt());
769                    }
770                    configs.put(key, config);
771                }
772            }
773            return config;
774        }
775    
776        /**
777         * Internal class that identifies each Configuration.
778         */
779        static class ConfigData
780        {
781            /** Stores a reference to the configuration. */
782            private AbstractConfiguration configuration;
783    
784            /** Stores the name under which the configuration is stored. */
785            private String name;
786    
787            /** Stores the at string.*/
788            private String at;
789    
790                    /**
791             * Creates a new instance of <code>ConfigData</code> and initializes
792             * it.
793             *
794             * @param config the configuration
795             * @param n the name
796             * @param at the at position
797             */
798            public ConfigData(AbstractConfiguration config, String n, String at)
799            {
800                configuration = config;
801                name = n;
802                this.at = at;
803            }
804    
805                    /**
806             * Returns the stored configuration.
807             *
808             * @return the configuration
809             */
810            public AbstractConfiguration getConfiguration()
811            {
812                return configuration;
813            }
814    
815            /**
816             * Returns the configuration's name.
817             *
818             * @return the name
819             */
820            public String getName()
821            {
822                return name;
823            }
824    
825            /**
826             * Returns the at position of this configuration.
827             *
828             * @return the at position
829             */
830            public String getAt()
831            {
832                return at;
833            }
834    
835        }
836    }