View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *     http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.configuration;
18  
19  import java.io.File;
20  import java.io.InputStream;
21  import java.io.OutputStream;
22  import java.io.Reader;
23  import java.io.Writer;
24  import java.math.BigDecimal;
25  import java.math.BigInteger;
26  import java.net.URL;
27  import java.util.Collection;
28  import java.util.Iterator;
29  import java.util.List;
30  import java.util.Properties;
31  import java.util.concurrent.ConcurrentHashMap;
32  import java.util.concurrent.ConcurrentMap;
33  
34  import org.apache.commons.beanutils.BeanUtils;
35  import org.apache.commons.configuration.event.ConfigurationErrorEvent;
36  import org.apache.commons.configuration.event.ConfigurationErrorListener;
37  import org.apache.commons.configuration.event.ConfigurationEvent;
38  import org.apache.commons.configuration.event.ConfigurationListener;
39  import org.apache.commons.configuration.interpol.ConfigurationInterpolator;
40  import org.apache.commons.configuration.reloading.ReloadingStrategy;
41  import org.apache.commons.configuration.resolver.EntityResolverSupport;
42  import org.apache.commons.configuration.tree.ConfigurationNode;
43  import org.apache.commons.configuration.tree.ExpressionEngine;
44  import org.apache.commons.lang.text.StrSubstitutor;
45  import org.apache.commons.logging.Log;
46  import org.apache.commons.logging.LogFactory;
47  import org.xml.sax.EntityResolver;
48  import org.xml.sax.SAXParseException;
49  
50  /**
51   * This class provides access to multiple configuration files that reside in a location that
52   * can be specified by a pattern allowing applications to be multi-tenant.  For example,
53   * providing a pattern of "file:///opt/config/${product}/${client}/config.xml" will result in
54   * "product" and "client" being resolved on every call. The configuration resulting from the
55   * resolved pattern will be saved for future access.
56   * @since 1.6
57   * @author <a
58   * href="http://commons.apache.org/configuration/team-list.html">Commons
59   * Configuration team</a>
60   * @version $Id: MultiFileHierarchicalConfiguration.java 1210174 2011-12-04 18:45:00Z oheger $
61   */
62  public class MultiFileHierarchicalConfiguration extends AbstractHierarchicalFileConfiguration
63      implements ConfigurationListener, ConfigurationErrorListener, EntityResolverSupport
64  {
65      /**
66       * Prevent recursion while resolving unprefixed properties.
67       */
68      private static ThreadLocal<Boolean> recursive = new ThreadLocal<Boolean>()
69      {
70          @Override
71          protected synchronized Boolean initialValue()
72          {
73              return Boolean.FALSE;
74          }
75      };
76  
77      /** Map of configurations */
78      private final ConcurrentMap<String, XMLConfiguration> configurationsMap =
79              new ConcurrentHashMap<String, XMLConfiguration>();
80  
81      /** key pattern for configurationsMap */
82      private String pattern;
83  
84      /** True if the constructor has finished */
85      private boolean init;
86  
87      /** Return an empty configuration if loading fails */
88      private boolean ignoreException = true;
89  
90      /** Capture the schema validation setting */
91      private boolean schemaValidation;
92  
93      /** Stores a flag whether DTD or Schema validation should be performed.*/
94      private boolean validating;
95  
96      /** A flag whether attribute splitting is disabled.*/
97      private boolean attributeSplittingDisabled;
98  
99      /** 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 }