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 */
017package org.apache.commons.configuration;
018
019import java.io.File;
020import java.io.InputStream;
021import java.io.OutputStream;
022import java.io.Reader;
023import java.io.Writer;
024import java.math.BigDecimal;
025import java.math.BigInteger;
026import java.net.URL;
027import java.util.Collection;
028import java.util.Iterator;
029import java.util.List;
030import java.util.Properties;
031import java.util.concurrent.ConcurrentHashMap;
032import java.util.concurrent.ConcurrentMap;
033
034import org.apache.commons.beanutils.BeanUtils;
035import org.apache.commons.configuration.event.ConfigurationErrorEvent;
036import org.apache.commons.configuration.event.ConfigurationErrorListener;
037import org.apache.commons.configuration.event.ConfigurationEvent;
038import org.apache.commons.configuration.event.ConfigurationListener;
039import org.apache.commons.configuration.interpol.ConfigurationInterpolator;
040import org.apache.commons.configuration.reloading.ReloadingStrategy;
041import org.apache.commons.configuration.resolver.EntityResolverSupport;
042import org.apache.commons.configuration.tree.ConfigurationNode;
043import org.apache.commons.configuration.tree.ExpressionEngine;
044import org.apache.commons.lang.text.StrSubstitutor;
045import org.apache.commons.logging.Log;
046import org.apache.commons.logging.LogFactory;
047import org.xml.sax.EntityResolver;
048import 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.html 901729 2014-03-15 20:24:09Z oheger $
061 */
062public 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<?> 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}