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.Reader;
020    import java.io.Writer;
021    import java.math.BigDecimal;
022    import java.math.BigInteger;
023    import java.util.Collection;
024    import java.util.Iterator;
025    import java.util.List;
026    import java.util.Properties;
027    
028    import org.apache.commons.configuration.event.ConfigurationErrorListener;
029    import org.apache.commons.configuration.event.ConfigurationListener;
030    import org.apache.commons.configuration.tree.ConfigurationNode;
031    import org.apache.commons.configuration.tree.ExpressionEngine;
032    
033    /**
034     * Wraps a HierarchicalConfiguration and allows subtrees to be access via a configured path with
035     * replaceable tokens derived from the ConfigurationInterpolator. When used with injection frameworks
036     * such as Spring it allows components to be injected with subtrees of the configuration.
037     * @since 1.6
038     * @author <a
039     * href="http://commons.apache.org/configuration/team-list.html">Commons
040     * Configuration team</a>
041     * @version $Id: PatternSubtreeConfigurationWrapper.java 1210175 2011-12-04 18:49:51Z oheger $
042     */
043    public class PatternSubtreeConfigurationWrapper extends AbstractHierarchicalFileConfiguration
044    {
045        /**
046         * Prevent recursion while resolving unprefixed properties.
047         */
048        private static ThreadLocal<Boolean> recursive = new ThreadLocal<Boolean>()
049        {
050            @Override
051            protected synchronized Boolean initialValue()
052            {
053                return Boolean.FALSE;
054            }
055        };
056    
057        /** The wrapped configuration */
058        private final AbstractHierarchicalFileConfiguration config;
059    
060        /** The path to the subtree */
061        private final String path;
062    
063        /** True if the path ends with '/', false otherwise */
064        private final boolean trailing;
065    
066        /** True if the constructor has finished */
067        private boolean init;
068    
069        /**
070         * Constructor
071         * @param config The Configuration to be wrapped.
072         * @param path The base path pattern.
073         */
074        public PatternSubtreeConfigurationWrapper(AbstractHierarchicalFileConfiguration config, String path)
075        {
076            this.config = config;
077            this.path = path;
078            this.trailing = path.endsWith("/");
079            this.init = true;
080        }
081    
082        @Override
083        public Object getReloadLock()
084        {
085            return config.getReloadLock();
086        }
087    
088        @Override
089        public void addProperty(String key, Object value)
090        {
091            config.addProperty(makePath(key), value);
092        }
093    
094        @Override
095        public void clear()
096        {
097            getConfig().clear();
098        }
099    
100        @Override
101        public void clearProperty(String key)
102        {
103            config.clearProperty(makePath(key));
104        }
105    
106        @Override
107        public boolean containsKey(String key)
108        {
109            return config.containsKey(makePath(key));
110        }
111    
112        @Override
113        public BigDecimal getBigDecimal(String key, BigDecimal defaultValue)
114        {
115            return config.getBigDecimal(makePath(key), defaultValue);
116        }
117    
118        @Override
119        public BigDecimal getBigDecimal(String key)
120        {
121            return config.getBigDecimal(makePath(key));
122        }
123    
124        @Override
125        public BigInteger getBigInteger(String key, BigInteger defaultValue)
126        {
127            return config.getBigInteger(makePath(key), defaultValue);
128        }
129    
130        @Override
131        public BigInteger getBigInteger(String key)
132        {
133            return config.getBigInteger(makePath(key));
134        }
135    
136        @Override
137        public boolean getBoolean(String key, boolean defaultValue)
138        {
139            return config.getBoolean(makePath(key), defaultValue);
140        }
141    
142        @Override
143        public Boolean getBoolean(String key, Boolean defaultValue)
144        {
145            return config.getBoolean(makePath(key), defaultValue);
146        }
147    
148        @Override
149        public boolean getBoolean(String key)
150        {
151            return config.getBoolean(makePath(key));
152        }
153    
154        @Override
155        public byte getByte(String key, byte defaultValue)
156        {
157            return config.getByte(makePath(key), defaultValue);
158        }
159    
160        @Override
161        public Byte getByte(String key, Byte defaultValue)
162        {
163            return config.getByte(makePath(key), defaultValue);
164        }
165    
166        @Override
167        public byte getByte(String key)
168        {
169            return config.getByte(makePath(key));
170        }
171    
172        @Override
173        public double getDouble(String key, double defaultValue)
174        {
175            return config.getDouble(makePath(key), defaultValue);
176        }
177    
178        @Override
179        public Double getDouble(String key, Double defaultValue)
180        {
181            return config.getDouble(makePath(key), defaultValue);
182        }
183    
184        @Override
185        public double getDouble(String key)
186        {
187            return config.getDouble(makePath(key));
188        }
189    
190        @Override
191        public float getFloat(String key, float defaultValue)
192        {
193            return config.getFloat(makePath(key), defaultValue);
194        }
195    
196        @Override
197        public Float getFloat(String key, Float defaultValue)
198        {
199            return config.getFloat(makePath(key), defaultValue);
200        }
201    
202        @Override
203        public float getFloat(String key)
204        {
205            return config.getFloat(makePath(key));
206        }
207    
208        @Override
209        public int getInt(String key, int defaultValue)
210        {
211            return config.getInt(makePath(key), defaultValue);
212        }
213    
214        @Override
215        public int getInt(String key)
216        {
217            return config.getInt(makePath(key));
218        }
219    
220        @Override
221        public Integer getInteger(String key, Integer defaultValue)
222        {
223            return config.getInteger(makePath(key), defaultValue);
224        }
225    
226        @Override
227        public Iterator<String> getKeys()
228        {
229            return config.getKeys(makePath());
230        }
231    
232        @Override
233        public Iterator<String> getKeys(String prefix)
234        {
235            return config.getKeys(makePath(prefix));
236        }
237    
238        @Override
239        public List<Object> getList(String key, List<Object> defaultValue)
240        {
241            return config.getList(makePath(key), defaultValue);
242        }
243    
244        @Override
245        public List<Object> getList(String key)
246        {
247            return config.getList(makePath(key));
248        }
249    
250        @Override
251        public long getLong(String key, long defaultValue)
252        {
253            return config.getLong(makePath(key), defaultValue);
254        }
255    
256        @Override
257        public Long getLong(String key, Long defaultValue)
258        {
259            return config.getLong(makePath(key), defaultValue);
260        }
261    
262        @Override
263        public long getLong(String key)
264        {
265            return config.getLong(makePath(key));
266        }
267    
268        @Override
269        public Properties getProperties(String key)
270        {
271            return config.getProperties(makePath(key));
272        }
273    
274        @Override
275        public Object getProperty(String key)
276        {
277            return config.getProperty(makePath(key));
278        }
279    
280        @Override
281        public short getShort(String key, short defaultValue)
282        {
283            return config.getShort(makePath(key), defaultValue);
284        }
285    
286        @Override
287        public Short getShort(String key, Short defaultValue)
288        {
289            return config.getShort(makePath(key), defaultValue);
290        }
291    
292        @Override
293        public short getShort(String key)
294        {
295            return config.getShort(makePath(key));
296        }
297    
298        @Override
299        public String getString(String key, String defaultValue)
300        {
301            return config.getString(makePath(key), defaultValue);
302        }
303    
304        @Override
305        public String getString(String key)
306        {
307            return config.getString(makePath(key));
308        }
309    
310        @Override
311        public String[] getStringArray(String key)
312        {
313            return config.getStringArray(makePath(key));
314        }
315    
316        @Override
317        public boolean isEmpty()
318        {
319            return getConfig().isEmpty();
320        }
321    
322        @Override
323        public void setProperty(String key, Object value)
324        {
325            getConfig().setProperty(key, value);
326        }
327    
328        @Override
329        public Configuration subset(String prefix)
330        {
331            return getConfig().subset(prefix);
332        }
333    
334        @Override
335        public Node getRoot()
336        {
337            return getConfig().getRoot();
338        }
339    
340        @Override
341        public void setRoot(Node node)
342        {
343            if (init)
344            {
345                getConfig().setRoot(node);
346            }
347            else
348            {
349                super.setRoot(node);
350            }
351        }
352    
353        @Override
354        public ConfigurationNode getRootNode()
355        {
356            return getConfig().getRootNode();
357        }
358    
359        @Override
360        public void setRootNode(ConfigurationNode rootNode)
361        {
362            if (init)
363            {
364                getConfig().setRootNode(rootNode);
365            }
366            else
367            {
368                super.setRootNode(rootNode);
369            }
370        }
371    
372        @Override
373        public ExpressionEngine getExpressionEngine()
374        {
375            return config.getExpressionEngine();
376        }
377    
378        @Override
379        public void setExpressionEngine(ExpressionEngine expressionEngine)
380        {
381            if (init)
382            {
383                config.setExpressionEngine(expressionEngine);
384            }
385            else
386            {
387                super.setExpressionEngine(expressionEngine);
388            }
389        }
390    
391        @Override
392        public void addNodes(String key, Collection<? extends ConfigurationNode> nodes)
393        {
394            getConfig().addNodes(key, nodes);
395        }
396    
397        @Override
398        public SubnodeConfiguration configurationAt(String key, boolean supportUpdates)
399        {
400            return config.configurationAt(makePath(key), supportUpdates);
401        }
402    
403        @Override
404        public SubnodeConfiguration configurationAt(String key)
405        {
406            return config.configurationAt(makePath(key));
407        }
408    
409        @Override
410        public List<HierarchicalConfiguration> configurationsAt(String key)
411        {
412            return config.configurationsAt(makePath(key));
413        }
414    
415        @Override
416        public void clearTree(String key)
417        {
418            config.clearTree(makePath(key));
419        }
420    
421        @Override
422        public int getMaxIndex(String key)
423        {
424            return config.getMaxIndex(makePath(key));
425        }
426    
427        @Override
428        public Configuration interpolatedConfiguration()
429        {
430            return getConfig().interpolatedConfiguration();
431        }
432    
433        @Override
434        public void addConfigurationListener(ConfigurationListener l)
435        {
436            getConfig().addConfigurationListener(l);
437        }
438    
439        @Override
440        public boolean removeConfigurationListener(ConfigurationListener l)
441        {
442            return getConfig().removeConfigurationListener(l);
443        }
444    
445        @Override
446        public Collection<ConfigurationListener> getConfigurationListeners()
447        {
448            return getConfig().getConfigurationListeners();
449        }
450    
451        @Override
452        public void clearConfigurationListeners()
453        {
454            getConfig().clearConfigurationListeners();
455        }
456    
457        @Override
458        public void addErrorListener(ConfigurationErrorListener l)
459        {
460            getConfig().addErrorListener(l);
461        }
462    
463        @Override
464        public boolean removeErrorListener(ConfigurationErrorListener l)
465        {
466            return getConfig().removeErrorListener(l);
467        }
468    
469        @Override
470        public void clearErrorListeners()
471        {
472            getConfig().clearErrorListeners();
473        }
474    
475        public void save(Writer writer) throws ConfigurationException
476        {
477            config.save(writer);
478        }
479    
480        public void load(Reader reader) throws ConfigurationException
481        {
482            config.load(reader);
483        }
484    
485        @Override
486        public Collection<ConfigurationErrorListener> getErrorListeners()
487        {
488            return getConfig().getErrorListeners();
489        }
490    
491        @Override
492        protected Object resolveContainerStore(String key)
493        {
494            if (recursive.get().booleanValue())
495            {
496                return null;
497            }
498            recursive.set(Boolean.TRUE);
499            try
500            {
501                return super.resolveContainerStore(key);
502            }
503            finally
504            {
505                recursive.set(Boolean.FALSE);
506            }
507        }
508    
509        private HierarchicalConfiguration getConfig()
510        {
511            return config.configurationAt(makePath());
512        }
513    
514        private String makePath()
515        {
516            String pathPattern = trailing ? path.substring(0, path.length() - 1) : path;
517            return getSubstitutor().replace(pathPattern);
518        }
519    
520        /*
521         * Resolve the root expression and then add the item being retrieved. Insert a
522         * separator character as required.
523         */
524        private String makePath(String item)
525        {
526            String pathPattern;
527            if ((item.length() == 0 || item.startsWith("/")) && trailing)
528            {
529                pathPattern = path.substring(0, path.length() - 1);
530            }
531            else  if (!item.startsWith("/") || !trailing)
532            {
533                pathPattern = path + "/";
534            }
535            else
536            {
537                pathPattern = path;
538            }
539            return getSubstitutor().replace(pathPattern) + item;
540        }
541    }