    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    }