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