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.InputStream;
021    import java.io.OutputStream;
022    import java.io.Reader;
023    import java.io.Writer;
024    import java.math.BigDecimal;
025    import java.math.BigInteger;
026    import java.net.URL;
027    import java.util.Collection;
028    import java.util.Iterator;
029    import java.util.List;
030    import java.util.Properties;
031    import java.util.concurrent.ConcurrentHashMap;
032    import java.util.concurrent.ConcurrentMap;
033    
034    import org.apache.commons.beanutils.BeanUtils;
035    import org.apache.commons.configuration.event.ConfigurationErrorEvent;
036    import org.apache.commons.configuration.event.ConfigurationErrorListener;
037    import org.apache.commons.configuration.event.ConfigurationEvent;
038    import org.apache.commons.configuration.event.ConfigurationListener;
039    import org.apache.commons.configuration.interpol.ConfigurationInterpolator;
040    import org.apache.commons.configuration.reloading.ReloadingStrategy;
041    import org.apache.commons.configuration.resolver.EntityResolverSupport;
042    import org.apache.commons.configuration.tree.ConfigurationNode;
043    import org.apache.commons.configuration.tree.ExpressionEngine;
044    import org.apache.commons.lang.text.StrSubstitutor;
045    import org.apache.commons.logging.Log;
046    import org.apache.commons.logging.LogFactory;
047    import org.xml.sax.EntityResolver;
048    import org.xml.sax.SAXParseException;
049    
050    /**
051     * This class provides access to multiple configuration files that reside in a location that
052     * can be specified by a pattern allowing applications to be multi-tenant.  For example,
053     * providing a pattern of "file:///opt/config/${product}/${client}/config.xml" will result in
054     * "product" and "client" being resolved on every call. The configuration resulting from the
055     * resolved pattern will be saved for future access.
056     * @since 1.6
057     * @author <a
058     * href="http://commons.apache.org/configuration/team-list.html">Commons
059     * Configuration team</a>
060     * @version $Id: MultiFileHierarchicalConfiguration.java 1210174 2011-12-04 18:45:00Z oheger $
061     */
062    public class MultiFileHierarchicalConfiguration extends AbstractHierarchicalFileConfiguration
063        implements ConfigurationListener, ConfigurationErrorListener, EntityResolverSupport
064    {
065        /**
066         * Prevent recursion while resolving unprefixed properties.
067         */
068        private static ThreadLocal<Boolean> recursive = new ThreadLocal<Boolean>()
069        {
070            @Override
071            protected synchronized Boolean initialValue()
072            {
073                return Boolean.FALSE;
074            }
075        };
076    
077        /** Map of configurations */
078        private final ConcurrentMap<String, XMLConfiguration> configurationsMap =
079                new ConcurrentHashMap<String, XMLConfiguration>();
080    
081        /** key pattern for configurationsMap */
082        private String pattern;
083    
084        /** True if the constructor has finished */
085        private boolean init;
086    
087        /** Return an empty configuration if loading fails */
088        private boolean ignoreException = true;
089    
090        /** Capture the schema validation setting */
091        private boolean schemaValidation;
092    
093        /** Stores a flag whether DTD or Schema validation should be performed.*/
094        private boolean validating;
095    
096        /** A flag whether attribute splitting is disabled.*/
097        private boolean attributeSplittingDisabled;
098    
099        /** The Logger name to use */
100        private String loggerName = MultiFileHierarchicalConfiguration.class.getName();
101    
102        /** The Reloading strategy to use on created configurations */
103        private ReloadingStrategy fileStrategy;
104    
105        /** The EntityResolver */
106        private EntityResolver entityResolver;
107    
108        /** The internally used helper object for variable substitution. */
109        private StrSubstitutor localSubst = new StrSubstitutor(new ConfigurationInterpolator());
110    
111        /**
112         * Default Constructor.
113         */
114        public MultiFileHierarchicalConfiguration()
115        {
116            super();
117            this.init = true;
118            setLogger(LogFactory.getLog(loggerName));
119        }
120    
121        /**
122         * Construct the configuration with the specified pattern.
123         * @param pathPattern The pattern to use to locate configuration files.
124         */
125        public MultiFileHierarchicalConfiguration(String pathPattern)
126        {
127            super();
128            this.pattern = pathPattern;
129            this.init = true;
130            setLogger(LogFactory.getLog(loggerName));
131        }
132    
133        public void setLoggerName(String name)
134        {
135            this.loggerName = name;
136        }
137    
138        /**
139         * Set the File pattern
140         * @param pathPattern The pattern for the path to the configuration.
141         */
142        public void setFilePattern(String pathPattern)
143        {
144            this.pattern = pathPattern;
145        }
146    
147        public boolean isSchemaValidation()
148        {
149            return schemaValidation;
150        }
151    
152        public void setSchemaValidation(boolean schemaValidation)
153        {
154            this.schemaValidation = schemaValidation;
155        }
156    
157        public boolean isValidating()
158        {
159            return validating;
160        }
161    
162        public void setValidating(boolean validating)
163        {
164            this.validating = validating;
165        }
166    
167        public boolean isAttributeSplittingDisabled()
168        {
169            return attributeSplittingDisabled;
170        }
171    
172        public void setAttributeSplittingDisabled(boolean attributeSplittingDisabled)
173        {
174            this.attributeSplittingDisabled = attributeSplittingDisabled;
175        }
176    
177        @Override
178        public ReloadingStrategy getReloadingStrategy()
179        {
180            return fileStrategy;
181        }
182    
183        @Override
184        public void setReloadingStrategy(ReloadingStrategy strategy)
185        {
186            this.fileStrategy = strategy;
187        }
188    
189        public void setEntityResolver(EntityResolver entityResolver)
190        {
191            this.entityResolver = entityResolver;
192        }
193    
194        public EntityResolver getEntityResolver()
195        {
196            return this.entityResolver;
197        }
198    
199        /**
200         * Set to true if an empty Configuration should be returned when loading fails. If
201         * false an exception will be thrown.
202         * @param ignoreException The ignore value.
203         */
204        public void setIgnoreException(boolean ignoreException)
205        {
206            this.ignoreException = ignoreException;
207        }
208    
209        @Override
210        public void addProperty(String key, Object value)
211        {
212            this.getConfiguration().addProperty(key, value);
213        }
214    
215        @Override
216        public void clear()
217        {
218            this.getConfiguration().clear();
219        }
220    
221        @Override
222        public void clearProperty(String key)
223        {
224            this.getConfiguration().clearProperty(key);
225        }
226    
227        @Override
228        public boolean containsKey(String key)
229        {
230            return this.getConfiguration().containsKey(key);
231        }
232    
233        @Override
234        public BigDecimal getBigDecimal(String key, BigDecimal defaultValue)
235        {
236            return this.getConfiguration().getBigDecimal(key, defaultValue);
237        }
238    
239        @Override
240        public BigDecimal getBigDecimal(String key)
241        {
242            return this.getConfiguration().getBigDecimal(key);
243        }
244    
245        @Override
246        public BigInteger getBigInteger(String key, BigInteger defaultValue)
247        {
248            return this.getConfiguration().getBigInteger(key, defaultValue);
249        }
250    
251        @Override
252        public BigInteger getBigInteger(String key)
253        {
254            return this.getConfiguration().getBigInteger(key);
255        }
256    
257        @Override
258        public boolean getBoolean(String key, boolean defaultValue)
259        {
260            return this.getConfiguration().getBoolean(key, defaultValue);
261        }
262    
263        @Override
264        public Boolean getBoolean(String key, Boolean defaultValue)
265        {
266            return this.getConfiguration().getBoolean(key, defaultValue);
267        }
268    
269        @Override
270        public boolean getBoolean(String key)
271        {
272            return this.getConfiguration().getBoolean(key);
273        }
274    
275        @Override
276        public byte getByte(String key, byte defaultValue)
277        {
278            return this.getConfiguration().getByte(key, defaultValue);
279        }
280    
281        @Override
282        public Byte getByte(String key, Byte defaultValue)
283        {
284            return this.getConfiguration().getByte(key, defaultValue);
285        }
286    
287        @Override
288        public byte getByte(String key)
289        {
290            return this.getConfiguration().getByte(key);
291        }
292    
293        @Override
294        public double getDouble(String key, double defaultValue)
295        {
296            return this.getConfiguration().getDouble(key, defaultValue);
297        }
298    
299        @Override
300        public Double getDouble(String key, Double defaultValue)
301        {
302            return this.getConfiguration().getDouble(key, defaultValue);
303        }
304    
305        @Override
306        public double getDouble(String key)
307        {
308            return this.getConfiguration().getDouble(key);
309        }
310    
311        @Override
312        public float getFloat(String key, float defaultValue)
313        {
314            return this.getConfiguration().getFloat(key, defaultValue);
315        }
316    
317        @Override
318        public Float getFloat(String key, Float defaultValue)
319        {
320            return this.getConfiguration().getFloat(key, defaultValue);
321        }
322    
323        @Override
324        public float getFloat(String key)
325        {
326            return this.getConfiguration().getFloat(key);
327        }
328    
329        @Override
330        public int getInt(String key, int defaultValue)
331        {
332            return this.getConfiguration().getInt(key, defaultValue);
333        }
334    
335        @Override
336        public int getInt(String key)
337        {
338            return this.getConfiguration().getInt(key);
339        }
340    
341        @Override
342        public Integer getInteger(String key, Integer defaultValue)
343        {
344            return this.getConfiguration().getInteger(key, defaultValue);
345        }
346    
347        @Override
348        public Iterator<String> getKeys()
349        {
350            return this.getConfiguration().getKeys();
351        }
352    
353        @Override
354        public Iterator<String> getKeys(String prefix)
355        {
356            return this.getConfiguration().getKeys(prefix);
357        }
358    
359        @Override
360        public List<Object> getList(String key, List<Object> defaultValue)
361        {
362            return this.getConfiguration().getList(key, defaultValue);
363        }
364    
365        @Override
366        public List<Object> getList(String key)
367        {
368            return this.getConfiguration().getList(key);
369        }
370    
371        @Override
372        public long getLong(String key, long defaultValue)
373        {
374            return this.getConfiguration().getLong(key, defaultValue);
375        }
376    
377        @Override
378        public Long getLong(String key, Long defaultValue)
379        {
380            return this.getConfiguration().getLong(key, defaultValue);
381        }
382    
383        @Override
384        public long getLong(String key)
385        {
386            return this.getConfiguration().getLong(key);
387        }
388    
389        @Override
390        public Properties getProperties(String key)
391        {
392            return this.getConfiguration().getProperties(key);
393        }
394    
395        @Override
396        public Object getProperty(String key)
397        {
398            return this.getConfiguration().getProperty(key);
399        }
400    
401        @Override
402        public short getShort(String key, short defaultValue)
403        {
404            return this.getConfiguration().getShort(key, defaultValue);
405        }
406    
407        @Override
408        public Short getShort(String key, Short defaultValue)
409        {
410            return this.getConfiguration().getShort(key, defaultValue);
411        }
412    
413        @Override
414        public short getShort(String key)
415        {
416            return this.getConfiguration().getShort(key);
417        }
418    
419        @Override
420        public String getString(String key, String defaultValue)
421        {
422            return this.getConfiguration().getString(key, defaultValue);
423        }
424    
425        @Override
426        public String getString(String key)
427        {
428            return this.getConfiguration().getString(key);
429        }
430    
431        @Override
432        public String[] getStringArray(String key)
433        {
434            return this.getConfiguration().getStringArray(key);
435        }
436    
437        @Override
438        public boolean isEmpty()
439        {
440            return this.getConfiguration().isEmpty();
441        }
442    
443        @Override
444        public void setProperty(String key, Object value)
445        {
446            if (init)
447            {
448                this.getConfiguration().setProperty(key, value);
449            }
450        }
451    
452        @Override
453        public Configuration subset(String prefix)
454        {
455            return this.getConfiguration().subset(prefix);
456        }
457    
458        @Override
459        public Object getReloadLock()
460        {
461            return this.getConfiguration().getReloadLock();
462        }
463    
464        @Override
465        public Node getRoot()
466        {
467            return this.getConfiguration().getRoot();
468        }
469    
470        @Override
471        public void setRoot(Node node)
472        {
473            if (init)
474            {
475                this.getConfiguration().setRoot(node);
476            }
477            else
478            {
479                super.setRoot(node);
480            }
481        }
482    
483        @Override
484        public ConfigurationNode getRootNode()
485        {
486            return this.getConfiguration().getRootNode();
487        }
488    
489        @Override
490        public void setRootNode(ConfigurationNode rootNode)
491        {
492            if (init)
493            {
494                this.getConfiguration().setRootNode(rootNode);
495            }
496            else
497            {
498                super.setRootNode(rootNode);
499            }
500        }
501    
502        @Override
503        public ExpressionEngine getExpressionEngine()
504        {
505            return super.getExpressionEngine();
506        }
507    
508        @Override
509        public void setExpressionEngine(ExpressionEngine expressionEngine)
510        {
511            super.setExpressionEngine(expressionEngine);
512        }
513    
514        @Override
515        public void addNodes(String key, Collection<? extends ConfigurationNode> nodes)
516        {
517            this.getConfiguration().addNodes(key, nodes);
518        }
519    
520        @Override
521        public SubnodeConfiguration configurationAt(String key, boolean supportUpdates)
522        {
523            return this.getConfiguration().configurationAt(key, supportUpdates);
524        }
525    
526        @Override
527        public SubnodeConfiguration configurationAt(String key)
528        {
529            return this.getConfiguration().configurationAt(key);
530        }
531    
532        @Override
533        public List<HierarchicalConfiguration> configurationsAt(String key)
534        {
535            return this.getConfiguration().configurationsAt(key);
536        }
537    
538        @Override
539        public void clearTree(String key)
540        {
541            this.getConfiguration().clearTree(key);
542        }
543    
544        @Override
545        public int getMaxIndex(String key)
546        {
547            return this.getConfiguration().getMaxIndex(key);
548        }
549    
550        @Override
551        public Configuration interpolatedConfiguration()
552        {
553            return this.getConfiguration().interpolatedConfiguration();
554        }
555    
556        @Override
557        public void addConfigurationListener(ConfigurationListener l)
558        {
559            super.addConfigurationListener(l);
560        }
561    
562        @Override
563        public boolean removeConfigurationListener(ConfigurationListener l)
564        {
565            return super.removeConfigurationListener(l);
566        }
567    
568        @Override
569        public Collection<ConfigurationListener> getConfigurationListeners()
570        {
571            return super.getConfigurationListeners();
572        }
573    
574        @Override
575        public void clearConfigurationListeners()
576        {
577            super.clearConfigurationListeners();
578        }
579    
580        @Override
581        public void addErrorListener(ConfigurationErrorListener l)
582        {
583            super.addErrorListener(l);
584        }
585    
586        @Override
587        public boolean removeErrorListener(ConfigurationErrorListener l)
588        {
589            return super.removeErrorListener(l);
590        }
591    
592        @Override
593        public void clearErrorListeners()
594        {
595            super.clearErrorListeners();
596        }
597    
598        @Override
599        public Collection<ConfigurationErrorListener> getErrorListeners()
600        {
601            return super.getErrorListeners();
602        }
603    
604        public void save(Writer writer) throws ConfigurationException
605        {
606            if (init)
607            {
608                this.getConfiguration().save(writer);
609            }
610        }
611    
612        public void load(Reader reader) throws ConfigurationException
613        {
614            if (init)
615            {
616                this.getConfiguration().load(reader);
617            }
618        }
619    
620        @Override
621        public void load() throws ConfigurationException
622        {
623            this.getConfiguration();
624        }
625    
626        @Override
627        public void load(String fileName) throws ConfigurationException
628        {
629            this.getConfiguration().load(fileName);
630        }
631    
632        @Override
633        public void load(File file) throws ConfigurationException
634        {
635            this.getConfiguration().load(file);
636        }
637    
638        @Override
639        public void load(URL url) throws ConfigurationException
640        {
641            this.getConfiguration().load(url);
642        }
643    
644        @Override
645        public void load(InputStream in) throws ConfigurationException
646        {
647            this.getConfiguration().load(in);
648        }
649    
650        @Override
651        public void load(InputStream in, String encoding) throws ConfigurationException
652        {
653            this.getConfiguration().load(in, encoding);
654        }
655    
656        @Override
657        public void save() throws ConfigurationException
658        {
659            this.getConfiguration().save();
660        }
661    
662        @Override
663        public void save(String fileName) throws ConfigurationException
664        {
665            this.getConfiguration().save(fileName);
666        }
667    
668        @Override
669        public void save(File file) throws ConfigurationException
670        {
671            this.getConfiguration().save(file);
672        }
673    
674        @Override
675        public void save(URL url) throws ConfigurationException
676        {
677            this.getConfiguration().save(url);
678        }
679    
680        @Override
681        public void save(OutputStream out) throws ConfigurationException
682        {
683            this.getConfiguration().save(out);
684        }
685    
686        @Override
687        public void save(OutputStream out, String encoding) throws ConfigurationException
688        {
689            this.getConfiguration().save(out, encoding);
690        }
691    
692        @Override
693        public void configurationChanged(ConfigurationEvent event)
694        {
695            if (event.getSource() instanceof XMLConfiguration)
696            {
697                for (ConfigurationListener listener : getConfigurationListeners())
698                {
699                    listener.configurationChanged(event);
700                }
701            }
702        }
703    
704        @Override
705        public void configurationError(ConfigurationErrorEvent event)
706        {
707            if (event.getSource() instanceof XMLConfiguration)
708            {
709                for (ConfigurationErrorListener listener : getErrorListeners())
710                {
711                    listener.configurationError(event);
712                }
713            }
714    
715            if (event.getType() == AbstractFileConfiguration.EVENT_RELOAD)
716            {
717                if (isThrowable(event.getCause()))
718                {
719                    throw new ConfigurationRuntimeException(event.getCause());
720                }
721            }
722        }
723    
724        /*
725         * Don't allow resolveContainerStore to be called recursively.
726         * @param key The key to resolve.
727         * @return The value of the key.
728         */
729        @Override
730        protected Object resolveContainerStore(String key)
731        {
732            if (recursive.get().booleanValue())
733            {
734                return null;
735            }
736            recursive.set(Boolean.TRUE);
737            try
738            {
739                return super.resolveContainerStore(key);
740            }
741            finally
742            {
743                recursive.set(Boolean.FALSE);
744            }
745        }
746    
747        /**
748         * Remove the current Configuration.
749         */
750        public void removeConfiguration()
751        {
752            String path = getSubstitutor().replace(pattern);
753            configurationsMap.remove(path);
754        }
755    
756        /**
757         * First checks to see if the cache exists, if it does, get the associated Configuration.
758         * If not it will load a new Configuration and save it in the cache.
759         *
760         * @return the Configuration associated with the current value of the path pattern.
761         */
762        private AbstractHierarchicalFileConfiguration getConfiguration()
763        {
764            if (pattern == null)
765            {
766                throw new ConfigurationRuntimeException("File pattern must be defined");
767            }
768            String path = localSubst.replace(pattern);
769    
770            if (configurationsMap.containsKey(path))
771            {
772                return configurationsMap.get(path);
773            }
774    
775            if (path.equals(pattern))
776            {
777                XMLConfiguration configuration = new XMLConfiguration()
778                {
779                    @Override
780                    public void load() throws ConfigurationException
781                    {
782                    }
783                    @Override
784                    public void save() throws ConfigurationException
785                    {
786                    }
787                };
788    
789                configurationsMap.putIfAbsent(pattern, configuration);
790    
791                return configuration;
792            }
793    
794            XMLConfiguration configuration = new XMLConfiguration();
795            if (loggerName != null)
796            {
797                Log log = LogFactory.getLog(loggerName);
798                if (log != null)
799                {
800                    configuration.setLogger(log);
801                }
802            }
803            configuration.setBasePath(getBasePath());
804            configuration.setFileName(path);
805            configuration.setFileSystem(getFileSystem());
806            configuration.setExpressionEngine(getExpressionEngine());
807            ReloadingStrategy strategy = createReloadingStrategy();
808            if (strategy != null)
809            {
810                configuration.setReloadingStrategy(strategy);
811            }
812            configuration.setDelimiterParsingDisabled(isDelimiterParsingDisabled());
813            configuration.setAttributeSplittingDisabled(isAttributeSplittingDisabled());
814            configuration.setValidating(validating);
815            configuration.setSchemaValidation(schemaValidation);
816            configuration.setEntityResolver(entityResolver);
817            configuration.setListDelimiter(getListDelimiter());
818            configuration.addConfigurationListener(this);
819            configuration.addErrorListener(this);
820            try
821            {
822                configuration.load();
823            }
824            catch (ConfigurationException ce)
825            {
826                if (isThrowable(ce))
827                {
828                    throw new ConfigurationRuntimeException(ce);
829                }
830            }
831            configurationsMap.putIfAbsent(path, configuration);
832            return configurationsMap.get(path);
833        }
834    
835        private boolean isThrowable(Throwable throwable)
836        {
837            if (!ignoreException)
838            {
839                return true;
840            }
841            Throwable cause = throwable.getCause();
842            while (cause != null && !(cause instanceof SAXParseException))
843            {
844                cause = cause.getCause();
845            }
846            return cause != null;
847        }
848    
849        /**
850         * Clone the FileReloadingStrategy since each file needs its own.
851         * @return A new FileReloadingStrategy.
852         */
853        private ReloadingStrategy createReloadingStrategy()
854        {
855            if (fileStrategy == null)
856            {
857                return null;
858            }
859            try
860            {
861                ReloadingStrategy strategy = (ReloadingStrategy) BeanUtils.cloneBean(fileStrategy);
862                strategy.setConfiguration(null);
863                return strategy;
864            }
865            catch (Exception ex)
866            {
867                return null;
868            }
869        }
870    
871    }