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