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 }