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.io.File;
020    import java.io.FileNotFoundException;
021    import java.io.InputStream;
022    import java.io.OutputStream;
023    import java.io.Reader;
024    import java.io.Writer;
025    import java.math.BigDecimal;
026    import java.math.BigInteger;
027    import java.net.MalformedURLException;
028    import java.net.URL;
029    import java.util.Collection;
030    import java.util.HashMap;
031    import java.util.Iterator;
032    import java.util.List;
033    import java.util.Map;
034    import java.util.Properties;
035    
036    import org.apache.commons.configuration.event.ConfigurationErrorEvent;
037    import org.apache.commons.configuration.event.ConfigurationErrorListener;
038    import org.apache.commons.configuration.event.ConfigurationEvent;
039    import org.apache.commons.configuration.event.ConfigurationListener;
040    import org.apache.commons.configuration.tree.ConfigurationNode;
041    import org.apache.commons.configuration.tree.ExpressionEngine;
042    
043    /**
044     * This class provides access to multiple configuration files that reside in a location that
045     * can be specified by a pattern allowing applications to be multi-tenant.  For example,
046     * providing a pattern of "file:///opt/config/${product}/${client}/config.xml" will result in
047     * "product" and "client" being resolved on every call. The configuration resulting from the
048     * resolved pattern will be saved for future access.
049     * @since 1.6
050     * @author <a
051     * href="http://commons.apache.org/configuration/team-list.html">Commons
052     * Configuration team</a>
053     * @version $Id: MultiFileHierarchicalConfiguration.java 727958 2008-12-19 07:19:24Z oheger $
054     */
055    public class MultiFileHierarchicalConfiguration extends AbstractHierarchicalFileConfiguration
056        implements ConfigurationListener, ConfigurationErrorListener
057    {
058        /** FILE URL prefix */
059        private static final String FILE_URL_PREFIX = "file:";
060    
061        /**
062         * Prevent recursion while resolving unprefixed properties.
063         */
064        private static ThreadLocal recursive = new ThreadLocal()
065        {
066            protected synchronized Object initialValue()
067            {
068                return Boolean.FALSE;
069            }
070        };
071    
072        /** Map of configurations */
073        private final Map configurationsMap = new HashMap();
074    
075        /** key pattern for configurationsMap */
076        private String pattern;
077    
078        /** True if the constructor has finished */
079        private boolean init;
080    
081        /**
082         * Default Constructor.
083         */
084        public MultiFileHierarchicalConfiguration()
085        {
086            super();
087            this.init = true;
088        }
089    
090        /**
091         * Construct the configuration with the specified pattern.
092         * @param pathPattern The pattern to use to locate configuration files.
093         */
094        public MultiFileHierarchicalConfiguration(String pathPattern)
095        {
096            super();
097            this.pattern = pathPattern;
098            this.init = true;
099        }
100    
101        /**
102         * Set the File pattern
103         * @param pathPattern The pattern for the path to the configuration.
104         */
105        public void setFilePattern(String pathPattern)
106        {
107            this.pattern = pathPattern;
108        }
109    
110        /**
111         * Creates the file configuration delegate, i.e. the object that implements
112         * functionality required by the <code>FileConfiguration</code> interface.
113         * This base implementation will return an instance of the
114         * <code>FileConfigurationDelegate</code> class. Derived classes may
115         * override it to create a different delegate object.
116         *
117         * @return the file configuration delegate
118         */
119        protected FileConfigurationDelegate createDelegate()
120        {
121            return new FileConfigurationDelegate();
122        }
123    
124        public void addProperty(String key, Object value)
125        {
126            this.getConfiguration().addProperty(key, value);
127        }
128    
129        public void clear()
130        {
131            this.getConfiguration().clear();
132        }
133    
134        public void clearProperty(String key)
135        {
136            this.getConfiguration().clearProperty(key);
137        }
138    
139        public boolean containsKey(String key)
140        {
141            return this.getConfiguration().containsKey(key);
142        }
143    
144        public BigDecimal getBigDecimal(String key, BigDecimal defaultValue)
145        {
146            return this.getConfiguration().getBigDecimal(key, defaultValue);
147        }
148    
149        public BigDecimal getBigDecimal(String key)
150        {
151            return this.getConfiguration().getBigDecimal(key);
152        }
153    
154        public BigInteger getBigInteger(String key, BigInteger defaultValue)
155        {
156            return this.getConfiguration().getBigInteger(key, defaultValue);
157        }
158    
159        public BigInteger getBigInteger(String key)
160        {
161            return this.getConfiguration().getBigInteger(key);
162        }
163    
164        public boolean getBoolean(String key, boolean defaultValue)
165        {
166            return this.getConfiguration().getBoolean(key, defaultValue);
167        }
168    
169        public Boolean getBoolean(String key, Boolean defaultValue)
170        {
171            return this.getConfiguration().getBoolean(key, defaultValue);
172        }
173    
174        public boolean getBoolean(String key)
175        {
176            return this.getConfiguration().getBoolean(key);
177        }
178    
179        public byte getByte(String key, byte defaultValue)
180        {
181            return this.getConfiguration().getByte(key, defaultValue);
182        }
183    
184        public Byte getByte(String key, Byte defaultValue)
185        {
186            return this.getConfiguration().getByte(key, defaultValue);
187        }
188    
189        public byte getByte(String key)
190        {
191            return this.getConfiguration().getByte(key);
192        }
193    
194        public double getDouble(String key, double defaultValue)
195        {
196            return this.getConfiguration().getDouble(key, defaultValue);
197        }
198    
199        public Double getDouble(String key, Double defaultValue)
200        {
201            return this.getConfiguration().getDouble(key, defaultValue);
202        }
203    
204        public double getDouble(String key)
205        {
206            return this.getConfiguration().getDouble(key);
207        }
208    
209        public float getFloat(String key, float defaultValue)
210        {
211            return this.getConfiguration().getFloat(key, defaultValue);
212        }
213    
214        public Float getFloat(String key, Float defaultValue)
215        {
216            return this.getConfiguration().getFloat(key, defaultValue);
217        }
218    
219        public float getFloat(String key)
220        {
221            return this.getConfiguration().getFloat(key);
222        }
223    
224        public int getInt(String key, int defaultValue)
225        {
226            return this.getConfiguration().getInt(key, defaultValue);
227        }
228    
229        public int getInt(String key)
230        {
231            return this.getConfiguration().getInt(key);
232        }
233    
234        public Integer getInteger(String key, Integer defaultValue)
235        {
236            return this.getConfiguration().getInteger(key, defaultValue);
237        }
238    
239        public Iterator getKeys()
240        {
241            return this.getConfiguration().getKeys();
242        }
243    
244        public Iterator getKeys(String prefix)
245        {
246            return this.getConfiguration().getKeys(prefix);
247        }
248    
249        public List getList(String key, List defaultValue)
250        {
251            return this.getConfiguration().getList(key, defaultValue);
252        }
253    
254        public List getList(String key)
255        {
256            return this.getConfiguration().getList(key);
257        }
258    
259        public long getLong(String key, long defaultValue)
260        {
261            return this.getConfiguration().getLong(key, defaultValue);
262        }
263    
264        public Long getLong(String key, Long defaultValue)
265        {
266            return this.getConfiguration().getLong(key, defaultValue);
267        }
268    
269        public long getLong(String key)
270        {
271            return this.getConfiguration().getLong(key);
272        }
273    
274        public Properties getProperties(String key)
275        {
276            return this.getConfiguration().getProperties(key);
277        }
278    
279        public Object getProperty(String key)
280        {
281            return this.getConfiguration().getProperty(key);
282        }
283    
284        public short getShort(String key, short defaultValue)
285        {
286            return this.getConfiguration().getShort(key, defaultValue);
287        }
288    
289        public Short getShort(String key, Short defaultValue)
290        {
291            return this.getConfiguration().getShort(key, defaultValue);
292        }
293    
294        public short getShort(String key)
295        {
296            return this.getConfiguration().getShort(key);
297        }
298    
299        public String getString(String key, String defaultValue)
300        {
301            return this.getConfiguration().getString(key, defaultValue);
302        }
303    
304        public String getString(String key)
305        {
306            return this.getConfiguration().getString(key);
307        }
308    
309        public String[] getStringArray(String key)
310        {
311            return this.getConfiguration().getStringArray(key);
312        }
313    
314        public boolean isEmpty()
315        {
316            return this.getConfiguration().isEmpty();
317        }
318    
319        public void setProperty(String key, Object value)
320        {
321            if (init)
322            {
323                this.getConfiguration().setProperty(key, value);
324            }
325        }
326    
327        public Configuration subset(String prefix)
328        {
329            return this.getConfiguration().subset(prefix);
330        }
331    
332        public Node getRoot()
333        {
334            return this.getConfiguration().getRoot();
335        }
336    
337        public void setRoot(Node node)
338        {
339            if (init)
340            {
341                this.getConfiguration().setRoot(node);
342            }
343            else
344            {
345                super.setRoot(node);
346            }
347        }
348    
349        public ConfigurationNode getRootNode()
350        {
351            return this.getConfiguration().getRootNode();
352        }
353    
354        public void setRootNode(ConfigurationNode rootNode)
355        {
356            if (init)
357            {
358                this.getConfiguration().setRootNode(rootNode);
359            }
360            else
361            {
362                super.setRootNode(rootNode);
363            }
364        }
365    
366        public ExpressionEngine getExpressionEngine()
367        {
368            return super.getExpressionEngine();
369        }
370    
371        public void setExpressionEngine(ExpressionEngine expressionEngine)
372        {
373            super.setExpressionEngine(expressionEngine);
374        }
375    
376        public void addNodes(String key, Collection nodes)
377        {
378            this.getConfiguration().addNodes(key, nodes);
379        }
380    
381        public SubnodeConfiguration configurationAt(String key, boolean supportUpdates)
382        {
383            return this.getConfiguration().configurationAt(key, supportUpdates);
384        }
385    
386        public SubnodeConfiguration configurationAt(String key)
387        {
388            return this.getConfiguration().configurationAt(key);
389        }
390    
391        public List configurationsAt(String key)
392        {
393            return this.getConfiguration().configurationsAt(key);
394        }
395    
396        public void clearTree(String key)
397        {
398            this.getConfiguration().clearTree(key);
399        }
400    
401        public int getMaxIndex(String key)
402        {
403            return this.getConfiguration().getMaxIndex(key);
404        }
405    
406        public Configuration interpolatedConfiguration()
407        {
408            return this.getConfiguration().interpolatedConfiguration();
409        }
410    
411        public void addConfigurationListener(ConfigurationListener l)
412        {
413            super.addConfigurationListener(l);
414        }
415    
416        public boolean removeConfigurationListener(ConfigurationListener l)
417        {
418            return super.removeConfigurationListener(l);
419        }
420    
421        public Collection getConfigurationListeners()
422        {
423            return super.getConfigurationListeners();
424        }
425    
426        public void clearConfigurationListeners()
427        {
428            super.clearConfigurationListeners();
429        }
430    
431        public void addErrorListener(ConfigurationErrorListener l)
432        {
433            super.addErrorListener(l);
434        }
435    
436        public boolean removeErrorListener(ConfigurationErrorListener l)
437        {
438            return super.removeErrorListener(l);
439        }
440    
441        public void clearErrorListeners()
442        {
443            super.clearErrorListeners();
444        }
445    
446        public Collection getErrorListeners()
447        {
448            return super.getErrorListeners();
449        }
450    
451        public void save(Writer writer) throws ConfigurationException
452        {
453            if (init)
454            {
455                this.getConfiguration().save(writer);
456            }
457        }
458    
459        public void load(Reader reader) throws ConfigurationException
460        {
461            if (init)
462            {
463                this.getConfiguration().load(reader);
464            }
465        }
466    
467        public void load() throws ConfigurationException
468        {
469            this.getConfiguration().load();
470        }
471    
472        public void load(String fileName) throws ConfigurationException
473        {
474            this.getConfiguration().load(fileName);
475        }
476    
477        public void load(File file) throws ConfigurationException
478        {
479            this.getConfiguration().load(file);
480        }
481    
482        public void load(URL url) throws ConfigurationException
483        {
484            this.getConfiguration().load(url);
485        }
486    
487        public void load(InputStream in) throws ConfigurationException
488        {
489            this.getConfiguration().load(in);
490        }
491    
492        public void load(InputStream in, String encoding) throws ConfigurationException
493        {
494            this.getConfiguration().load(in, encoding);
495        }
496    
497        public void save() throws ConfigurationException
498        {
499            this.getConfiguration().save();
500        }
501    
502        public void save(String fileName) throws ConfigurationException
503        {
504            this.getConfiguration().save(fileName);
505        }
506    
507        public void save(File file) throws ConfigurationException
508        {
509            this.getConfiguration().save(file);
510        }
511    
512        public void save(URL url) throws ConfigurationException
513        {
514            this.getConfiguration().save(url);
515        }
516    
517        public void save(OutputStream out) throws ConfigurationException
518        {
519            this.getConfiguration().save(out);
520        }
521    
522        public void save(OutputStream out, String encoding) throws ConfigurationException
523        {
524            this.getConfiguration().save(out, encoding);
525        }
526    
527        public void configurationChanged(ConfigurationEvent event)
528        {
529            if (event.getSource() instanceof XMLConfiguration)
530            {
531                Iterator iter = getConfigurationListeners().iterator();
532                while (iter.hasNext())
533                {
534                    ConfigurationListener listener = (ConfigurationListener) iter.next();
535                    listener.configurationChanged(event);
536                }
537            }
538        }
539    
540        public void configurationError(ConfigurationErrorEvent event)
541        {
542            if (event.getSource() instanceof XMLConfiguration)
543            {
544                Iterator iter = getErrorListeners().iterator();
545                while (iter.hasNext())
546                {
547                    ConfigurationErrorListener listener = (ConfigurationErrorListener) iter.next();
548                    listener.configurationError(event);
549                }
550            }
551        }
552    
553        /*
554         * Don't allow resolveContainerStore to be called recursively.
555         * @param key The key to resolve.
556         * @return The value of the key.
557         */
558        protected Object resolveContainerStore(String key)
559        {
560            if (((Boolean) recursive.get()).booleanValue())
561            {
562                return null;
563            }
564            recursive.set(Boolean.TRUE);
565            try
566            {
567                return super.resolveContainerStore(key);
568            }
569            finally
570            {
571                recursive.set(Boolean.FALSE);
572            }
573        }
574    
575        /**
576         * Remove the current Configuration.
577         */
578        public void removeConfiguration()
579        {
580            String path = getSubstitutor().replace(pattern);
581            synchronized (configurationsMap)
582            {
583                configurationsMap.remove(path);
584            }
585        }
586    
587        /**
588         * First checks to see if the cache exists, if it does, get the associated Configuration.
589         * If not it will load a new Configuration and save it in the cache.
590         *
591         * @return the Configuration associated with the current value of the path pattern.
592         */
593        private AbstractHierarchicalFileConfiguration getConfiguration()
594        {
595            if (pattern == null)
596            {
597                throw new ConfigurationRuntimeException("File pattern must be defined");
598            }
599            String path = getSubstitutor().replace(pattern);
600            synchronized (configurationsMap)
601            {
602                if (configurationsMap.containsKey(path))
603                {
604                    return (AbstractHierarchicalFileConfiguration) configurationsMap.get(path);
605                }
606            }
607    
608            if (path.equals(pattern))
609            {
610                XMLConfiguration configuration = new XMLConfiguration()
611                {
612                    public void load() throws ConfigurationException
613                    {
614                    }
615                    public void save() throws ConfigurationException
616                    {
617                    }
618                };
619                synchronized (configurationsMap)
620                {
621                    configurationsMap.put(pattern, configuration);
622                }
623                return configuration;
624            }
625    
626            XMLConfiguration configuration = new XMLConfiguration();
627            try
628            {
629                URL url = getURL(path);
630                configuration.setURL(url);
631                configuration.load();
632                configuration.setExpressionEngine(getExpressionEngine());
633                configuration.setReloadingStrategy(getReloadingStrategy());
634                configuration.addConfigurationListener(this);
635                configuration.addErrorListener(this);
636                synchronized (configurationsMap)
637                {
638                    if (!configurationsMap.containsKey(path))
639                    {
640                        configurationsMap.put(path, configuration);
641                    }
642                }
643            }
644            catch (ConfigurationException ce)
645            {
646                throw new ConfigurationRuntimeException(ce);
647            }
648            catch (FileNotFoundException fnfe)
649            {
650                throw new ConfigurationRuntimeException(fnfe);
651            }
652    
653            return configuration;
654        }
655    
656        private URL getURL(String resourceLocation) throws FileNotFoundException
657        {
658            if (resourceLocation == null)
659            {
660                throw new IllegalArgumentException("A path pattern must be configured");
661            }
662            try
663            {
664                // try URL
665                return new URL(resourceLocation);
666            }
667            catch (MalformedURLException ex)
668            {
669                // no URL -> treat as file path
670                try
671                {
672                    return new URL(FILE_URL_PREFIX + resourceLocation);
673                }
674                catch (MalformedURLException ex2)
675                {
676                    throw new FileNotFoundException("Resource location [" + resourceLocation
677                            + "] is not a URL or a well-formed file path");
678                }
679            }
680        }
681    }