    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    }