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.math.BigDecimal;
020 import java.math.BigInteger;
021 import java.util.ArrayList;
022 import java.util.Collection;
023 import java.util.HashMap;
024 import java.util.Iterator;
025 import java.util.List;
026 import java.util.Map;
027 import java.util.Properties;
028 import java.util.Set;
029
030 import org.apache.commons.configuration.event.ConfigurationErrorListener;
031 import org.apache.commons.configuration.event.ConfigurationListener;
032 import org.apache.commons.configuration.interpol.ConfigurationInterpolator;
033 import org.apache.commons.configuration.tree.ConfigurationNode;
034 import org.apache.commons.configuration.tree.ExpressionEngine;
035 import org.apache.commons.configuration.tree.NodeCombiner;
036 import org.apache.commons.lang.text.StrSubstitutor;
037 import org.apache.commons.logging.Log;
038 import org.apache.commons.logging.LogFactory;
039
040 /**
041 * DynamicCombinedConfiguration allows a set of CombinedConfigurations to be used. Each CombinedConfiguration
042 * is referenced by a key that is dynamically constructed from a key pattern on each call. The key pattern
043 * will be resolved using the configured ConfigurationInterpolator.
044 * @since 1.6
045 * @author <a
046 * href="http://commons.apache.org/configuration/team-list.html">Commons
047 * Configuration team</a>
048 * @version $Id: DynamicCombinedConfiguration.java 1231721 2012-01-15 18:32:07Z oheger $
049 */
050 public class DynamicCombinedConfiguration extends CombinedConfiguration
051 {
052 /**
053 * Prevent recursion while resolving unprefixed properties.
054 */
055 private static ThreadLocal<Boolean> recursive = new ThreadLocal<Boolean>()
056 {
057 @Override
058 protected synchronized Boolean initialValue()
059 {
060 return Boolean.FALSE;
061 }
062 };
063
064 /** The CombinedConfigurations */
065 private Map<String, CombinedConfiguration> configs =
066 new HashMap<String, CombinedConfiguration>();
067
068 /** Stores a list with the contained configurations. */
069 private List<ConfigData> configurations = new ArrayList<ConfigData>();
070
071 /** Stores a map with the named configurations. */
072 private Map<String, AbstractConfiguration> namedConfigurations =
073 new HashMap<String, AbstractConfiguration>();
074
075 /** The key pattern for the CombinedConfiguration map */
076 private String keyPattern;
077
078 /** Stores the combiner. */
079 private NodeCombiner nodeCombiner;
080
081 /** The name of the logger to use for each CombinedConfiguration */
082 private String loggerName = DynamicCombinedConfiguration.class.getName();
083
084 /** The object for handling variable substitution in key patterns. */
085 private StrSubstitutor localSubst = new StrSubstitutor(new ConfigurationInterpolator());
086
087 /**
088 * Creates a new instance of {@code DynamicCombinedConfiguration} and
089 * initializes the combiner to be used.
090 *
091 * @param comb the node combiner (can be <b>null</b>, then a union combiner
092 * is used as default)
093 */
094 public DynamicCombinedConfiguration(NodeCombiner comb)
095 {
096 super();
097 setNodeCombiner(comb);
098 setIgnoreReloadExceptions(false);
099 setLogger(LogFactory.getLog(DynamicCombinedConfiguration.class));
100 }
101
102 /**
103 * Creates a new instance of {@code DynamicCombinedConfiguration} that uses
104 * a union combiner.
105 *
106 * @see org.apache.commons.configuration.tree.UnionCombiner
107 */
108 public DynamicCombinedConfiguration()
109 {
110 super();
111 setIgnoreReloadExceptions(false);
112 setLogger(LogFactory.getLog(DynamicCombinedConfiguration.class));
113 }
114
115 public void setKeyPattern(String pattern)
116 {
117 this.keyPattern = pattern;
118 }
119
120 public String getKeyPattern()
121 {
122 return this.keyPattern;
123 }
124
125 /**
126 * Set the name of the Logger to use on each CombinedConfiguration.
127 * @param name The Logger name.
128 */
129 public void setLoggerName(String name)
130 {
131 this.loggerName = name;
132 }
133
134 /**
135 * Returns the node combiner that is used for creating the combined node
136 * structure.
137 *
138 * @return the node combiner
139 */
140 @Override
141 public NodeCombiner getNodeCombiner()
142 {
143 return nodeCombiner;
144 }
145
146 /**
147 * Sets the node combiner. This object will be used when the combined node
148 * structure is to be constructed. It must not be <b>null</b>, otherwise an
149 * {@code IllegalArgumentException} exception is thrown. Changing the
150 * node combiner causes an invalidation of this combined configuration, so
151 * that the new combiner immediately takes effect.
152 *
153 * @param nodeCombiner the node combiner
154 */
155 @Override
156 public void setNodeCombiner(NodeCombiner nodeCombiner)
157 {
158 if (nodeCombiner == null)
159 {
160 throw new IllegalArgumentException(
161 "Node combiner must not be null!");
162 }
163 this.nodeCombiner = nodeCombiner;
164 invalidateAll();
165 }
166 /**
167 * Adds a new configuration to this combined configuration. It is possible
168 * (but not mandatory) to give the new configuration a name. This name must
169 * be unique, otherwise a {@code ConfigurationRuntimeException} will
170 * be thrown. With the optional {@code at} argument you can specify
171 * where in the resulting node structure the content of the added
172 * configuration should appear. This is a string that uses dots as property
173 * delimiters (independent on the current expression engine). For instance
174 * if you pass in the string {@code "database.tables"},
175 * all properties of the added configuration will occur in this branch.
176 *
177 * @param config the configuration to add (must not be <b>null</b>)
178 * @param name the name of this configuration (can be <b>null</b>)
179 * @param at the position of this configuration in the combined tree (can be
180 * <b>null</b>)
181 */
182 @Override
183 public void addConfiguration(AbstractConfiguration config, String name,
184 String at)
185 {
186 ConfigData cd = new ConfigData(config, name, at);
187 configurations.add(cd);
188 if (name != null)
189 {
190 namedConfigurations.put(name, config);
191 }
192 }
193 /**
194 * Returns the number of configurations that are contained in this combined
195 * configuration.
196 *
197 * @return the number of contained configurations
198 */
199 @Override
200 public int getNumberOfConfigurations()
201 {
202 return configurations.size();
203 }
204
205 /**
206 * Returns the configuration at the specified index. The contained
207 * configurations are numbered in the order they were added to this combined
208 * configuration. The index of the first configuration is 0.
209 *
210 * @param index the index
211 * @return the configuration at this index
212 */
213 @Override
214 public Configuration getConfiguration(int index)
215 {
216 ConfigData cd = configurations.get(index);
217 return cd.getConfiguration();
218 }
219
220 /**
221 * Returns the configuration with the given name. This can be <b>null</b>
222 * if no such configuration exists.
223 *
224 * @param name the name of the configuration
225 * @return the configuration with this name
226 */
227 @Override
228 public Configuration getConfiguration(String name)
229 {
230 return namedConfigurations.get(name);
231 }
232
233 /**
234 * Returns a set with the names of all configurations contained in this
235 * combined configuration. Of course here are only these configurations
236 * listed, for which a name was specified when they were added.
237 *
238 * @return a set with the names of the contained configurations (never
239 * <b>null</b>)
240 */
241 @Override
242 public Set<String> getConfigurationNames()
243 {
244 return namedConfigurations.keySet();
245 }
246
247 /**
248 * Removes the configuration with the specified name.
249 *
250 * @param name the name of the configuration to be removed
251 * @return the removed configuration (<b>null</b> if this configuration
252 * was not found)
253 */
254 @Override
255 public Configuration removeConfiguration(String name)
256 {
257 Configuration conf = getConfiguration(name);
258 if (conf != null)
259 {
260 removeConfiguration(conf);
261 }
262 return conf;
263 }
264
265 /**
266 * Removes the specified configuration from this combined configuration.
267 *
268 * @param config the configuration to be removed
269 * @return a flag whether this configuration was found and could be removed
270 */
271 @Override
272 public boolean removeConfiguration(Configuration config)
273 {
274 for (int index = 0; index < getNumberOfConfigurations(); index++)
275 {
276 if (configurations.get(index).getConfiguration() == config)
277 {
278 removeConfigurationAt(index);
279
280 }
281 }
282
283 return super.removeConfiguration(config);
284 }
285
286 /**
287 * Removes the configuration at the specified index.
288 *
289 * @param index the index
290 * @return the removed configuration
291 */
292 @Override
293 public Configuration removeConfigurationAt(int index)
294 {
295 ConfigData cd = configurations.remove(index);
296 if (cd.getName() != null)
297 {
298 namedConfigurations.remove(cd.getName());
299 }
300 return super.removeConfigurationAt(index);
301 }
302 /**
303 * Returns the configuration root node of this combined configuration. This
304 * method will construct a combined node structure using the current node
305 * combiner if necessary.
306 *
307 * @return the combined root node
308 */
309 @Override
310 public ConfigurationNode getRootNode()
311 {
312 return getCurrentConfig().getRootNode();
313 }
314
315 @Override
316 public void setRootNode(ConfigurationNode rootNode)
317 {
318 if (configs != null)
319 {
320 this.getCurrentConfig().setRootNode(rootNode);
321 }
322 else
323 {
324 super.setRootNode(rootNode);
325 }
326 }
327
328 @Override
329 public void addProperty(String key, Object value)
330 {
331 this.getCurrentConfig().addProperty(key, value);
332 }
333
334 @Override
335 public void clear()
336 {
337 if (configs != null)
338 {
339 this.getCurrentConfig().clear();
340 }
341 }
342
343 @Override
344 public void clearProperty(String key)
345 {
346 this.getCurrentConfig().clearProperty(key);
347 }
348
349 @Override
350 public boolean containsKey(String key)
351 {
352 return this.getCurrentConfig().containsKey(key);
353 }
354
355 @Override
356 public BigDecimal getBigDecimal(String key, BigDecimal defaultValue)
357 {
358 return this.getCurrentConfig().getBigDecimal(key, defaultValue);
359 }
360
361 @Override
362 public BigDecimal getBigDecimal(String key)
363 {
364 return this.getCurrentConfig().getBigDecimal(key);
365 }
366
367 @Override
368 public BigInteger getBigInteger(String key, BigInteger defaultValue)
369 {
370 return this.getCurrentConfig().getBigInteger(key, defaultValue);
371 }
372
373 @Override
374 public BigInteger getBigInteger(String key)
375 {
376 return this.getCurrentConfig().getBigInteger(key);
377 }
378
379 @Override
380 public boolean getBoolean(String key, boolean defaultValue)
381 {
382 return this.getCurrentConfig().getBoolean(key, defaultValue);
383 }
384
385 @Override
386 public Boolean getBoolean(String key, Boolean defaultValue)
387 {
388 return this.getCurrentConfig().getBoolean(key, defaultValue);
389 }
390
391 @Override
392 public boolean getBoolean(String key)
393 {
394 return this.getCurrentConfig().getBoolean(key);
395 }
396
397 @Override
398 public byte getByte(String key, byte defaultValue)
399 {
400 return this.getCurrentConfig().getByte(key, defaultValue);
401 }
402
403 @Override
404 public Byte getByte(String key, Byte defaultValue)
405 {
406 return this.getCurrentConfig().getByte(key, defaultValue);
407 }
408
409 @Override
410 public byte getByte(String key)
411 {
412 return this.getCurrentConfig().getByte(key);
413 }
414
415 @Override
416 public double getDouble(String key, double defaultValue)
417 {
418 return this.getCurrentConfig().getDouble(key, defaultValue);
419 }
420
421 @Override
422 public Double getDouble(String key, Double defaultValue)
423 {
424 return this.getCurrentConfig().getDouble(key, defaultValue);
425 }
426
427 @Override
428 public double getDouble(String key)
429 {
430 return this.getCurrentConfig().getDouble(key);
431 }
432
433 @Override
434 public float getFloat(String key, float defaultValue)
435 {
436 return this.getCurrentConfig().getFloat(key, defaultValue);
437 }
438
439 @Override
440 public Float getFloat(String key, Float defaultValue)
441 {
442 return this.getCurrentConfig().getFloat(key, defaultValue);
443 }
444
445 @Override
446 public float getFloat(String key)
447 {
448 return this.getCurrentConfig().getFloat(key);
449 }
450
451 @Override
452 public int getInt(String key, int defaultValue)
453 {
454 return this.getCurrentConfig().getInt(key, defaultValue);
455 }
456
457 @Override
458 public int getInt(String key)
459 {
460 return this.getCurrentConfig().getInt(key);
461 }
462
463 @Override
464 public Integer getInteger(String key, Integer defaultValue)
465 {
466 return this.getCurrentConfig().getInteger(key, defaultValue);
467 }
468
469 @Override
470 public Iterator<String> getKeys()
471 {
472 return this.getCurrentConfig().getKeys();
473 }
474
475 @Override
476 public Iterator<String> getKeys(String prefix)
477 {
478 return this.getCurrentConfig().getKeys(prefix);
479 }
480
481 @Override
482 public List<Object> getList(String key, List<Object> defaultValue)
483 {
484 return this.getCurrentConfig().getList(key, defaultValue);
485 }
486
487 @Override
488 public List<Object> getList(String key)
489 {
490 return this.getCurrentConfig().getList(key);
491 }
492
493 @Override
494 public long getLong(String key, long defaultValue)
495 {
496 return this.getCurrentConfig().getLong(key, defaultValue);
497 }
498
499 @Override
500 public Long getLong(String key, Long defaultValue)
501 {
502 return this.getCurrentConfig().getLong(key, defaultValue);
503 }
504
505 @Override
506 public long getLong(String key)
507 {
508 return this.getCurrentConfig().getLong(key);
509 }
510
511 @Override
512 public Properties getProperties(String key)
513 {
514 return this.getCurrentConfig().getProperties(key);
515 }
516
517 @Override
518 public Object getProperty(String key)
519 {
520 return this.getCurrentConfig().getProperty(key);
521 }
522
523 @Override
524 public short getShort(String key, short defaultValue)
525 {
526 return this.getCurrentConfig().getShort(key, defaultValue);
527 }
528
529 @Override
530 public Short getShort(String key, Short defaultValue)
531 {
532 return this.getCurrentConfig().getShort(key, defaultValue);
533 }
534
535 @Override
536 public short getShort(String key)
537 {
538 return this.getCurrentConfig().getShort(key);
539 }
540
541 @Override
542 public String getString(String key, String defaultValue)
543 {
544 return this.getCurrentConfig().getString(key, defaultValue);
545 }
546
547 @Override
548 public String getString(String key)
549 {
550 return this.getCurrentConfig().getString(key);
551 }
552
553 @Override
554 public String[] getStringArray(String key)
555 {
556 return this.getCurrentConfig().getStringArray(key);
557 }
558
559 @Override
560 public boolean isEmpty()
561 {
562 return this.getCurrentConfig().isEmpty();
563 }
564
565 @Override
566 public void setProperty(String key, Object value)
567 {
568 if (configs != null)
569 {
570 this.getCurrentConfig().setProperty(key, value);
571 }
572 }
573
574 @Override
575 public Configuration subset(String prefix)
576 {
577 return this.getCurrentConfig().subset(prefix);
578 }
579
580 @Override
581 public Node getRoot()
582 {
583 return this.getCurrentConfig().getRoot();
584 }
585
586 @Override
587 public void setRoot(Node node)
588 {
589 if (configs != null)
590 {
591 this.getCurrentConfig().setRoot(node);
592 }
593 else
594 {
595 super.setRoot(node);
596 }
597 }
598
599 @Override
600 public ExpressionEngine getExpressionEngine()
601 {
602 return super.getExpressionEngine();
603 }
604
605 @Override
606 public void setExpressionEngine(ExpressionEngine expressionEngine)
607 {
608 super.setExpressionEngine(expressionEngine);
609 }
610
611 @Override
612 public void addNodes(String key, Collection<? extends ConfigurationNode> nodes)
613 {
614 this.getCurrentConfig().addNodes(key, nodes);
615 }
616
617 @Override
618 public SubnodeConfiguration configurationAt(String key, boolean supportUpdates)
619 {
620 return this.getCurrentConfig().configurationAt(key, supportUpdates);
621 }
622
623 @Override
624 public SubnodeConfiguration configurationAt(String key)
625 {
626 return this.getCurrentConfig().configurationAt(key);
627 }
628
629 @Override
630 public List<HierarchicalConfiguration> configurationsAt(String key)
631 {
632 return this.getCurrentConfig().configurationsAt(key);
633 }
634
635 @Override
636 public void clearTree(String key)
637 {
638 this.getCurrentConfig().clearTree(key);
639 }
640
641 @Override
642 public int getMaxIndex(String key)
643 {
644 return this.getCurrentConfig().getMaxIndex(key);
645 }
646
647 @Override
648 public Configuration interpolatedConfiguration()
649 {
650 return this.getCurrentConfig().interpolatedConfiguration();
651 }
652
653
654 /**
655 * Returns the configuration source, in which the specified key is defined.
656 * This method will determine the configuration node that is identified by
657 * the given key. The following constellations are possible:
658 * <ul>
659 * <li>If no node object is found for this key, <b>null</b> is returned.</li>
660 * <li>If the key maps to multiple nodes belonging to different
661 * configuration sources, a {@code IllegalArgumentException} is
662 * thrown (in this case no unique source can be determined).</li>
663 * <li>If exactly one node is found for the key, the (child) configuration
664 * object, to which the node belongs is determined and returned.</li>
665 * <li>For keys that have been added directly to this combined
666 * configuration and that do not belong to the namespaces defined by
667 * existing child configurations this configuration will be returned.</li>
668 * </ul>
669 *
670 * @param key the key of a configuration property
671 * @return the configuration, to which this property belongs or <b>null</b>
672 * if the key cannot be resolved
673 * @throws IllegalArgumentException if the key maps to multiple properties
674 * and the source cannot be determined, or if the key is <b>null</b>
675 */
676 @Override
677 public Configuration getSource(String key)
678 {
679 if (key == null)
680 {
681 throw new IllegalArgumentException("Key must not be null!");
682 }
683 return getCurrentConfig().getSource(key);
684 }
685
686 @Override
687 public void addConfigurationListener(ConfigurationListener l)
688 {
689 super.addConfigurationListener(l);
690
691 for (CombinedConfiguration cc : configs.values())
692 {
693 cc.addConfigurationListener(l);
694 }
695 }
696
697 @Override
698 public boolean removeConfigurationListener(ConfigurationListener l)
699 {
700 for (CombinedConfiguration cc : configs.values())
701 {
702 cc.removeConfigurationListener(l);
703 }
704 return super.removeConfigurationListener(l);
705 }
706
707 @Override
708 public Collection<ConfigurationListener> getConfigurationListeners()
709 {
710 return super.getConfigurationListeners();
711 }
712
713 @Override
714 public void clearConfigurationListeners()
715 {
716 for (CombinedConfiguration cc : configs.values())
717 {
718 cc.clearConfigurationListeners();
719 }
720 super.clearConfigurationListeners();
721 }
722
723 @Override
724 public void addErrorListener(ConfigurationErrorListener l)
725 {
726 for (CombinedConfiguration cc : configs.values())
727 {
728 cc.addErrorListener(l);
729 }
730 super.addErrorListener(l);
731 }
732
733 @Override
734 public boolean removeErrorListener(ConfigurationErrorListener l)
735 {
736 for (CombinedConfiguration cc : configs.values())
737 {
738 cc.removeErrorListener(l);
739 }
740 return super.removeErrorListener(l);
741 }
742
743 @Override
744 public void clearErrorListeners()
745 {
746 for (CombinedConfiguration cc : configs.values())
747 {
748 cc.clearErrorListeners();
749 }
750 super.clearErrorListeners();
751 }
752
753 @Override
754 public Collection<ConfigurationErrorListener> getErrorListeners()
755 {
756 return super.getErrorListeners();
757 }
758
759 /**
760 * Returns a copy of this object. This implementation performs a deep clone,
761 * i.e. all contained configurations will be cloned, too. For this to work,
762 * all contained configurations must be cloneable. Registered event
763 * listeners won't be cloned. The clone will use the same node combiner than
764 * the original.
765 *
766 * @return the copied object
767 */
768 @Override
769 public Object clone()
770 {
771 return super.clone();
772 }
773
774 /**
775 * Invalidates the current combined configuration. This means that the next time a
776 * property is accessed the combined node structure must be re-constructed.
777 * Invalidation of a combined configuration also means that an event of type
778 * {@code EVENT_COMBINED_INVALIDATE} is fired. Note that while other
779 * events most times appear twice (once before and once after an update),
780 * this event is only fired once (after update).
781 */
782 @Override
783 public void invalidate()
784 {
785 getCurrentConfig().invalidate();
786 }
787
788 public void invalidateAll()
789 {
790 if (configs == null)
791 {
792 return;
793 }
794 for (CombinedConfiguration cc : configs.values())
795 {
796 cc.invalidate();
797 }
798 }
799
800 /*
801 * Don't allow resolveContainerStore to be called recursively.
802 * @param key The key to resolve.
803 * @return The value of the key.
804 */
805 @Override
806 protected Object resolveContainerStore(String key)
807 {
808 if (recursive.get().booleanValue())
809 {
810 return null;
811 }
812 recursive.set(Boolean.TRUE);
813 try
814 {
815 return super.resolveContainerStore(key);
816 }
817 finally
818 {
819 recursive.set(Boolean.FALSE);
820 }
821 }
822
823 private CombinedConfiguration getCurrentConfig()
824 {
825 String key = localSubst.replace(keyPattern);
826 CombinedConfiguration config;
827 synchronized (getNodeCombiner())
828 {
829 config = configs.get(key);
830 if (config == null)
831 {
832 config = new CombinedConfiguration(getNodeCombiner());
833 if (loggerName != null)
834 {
835 Log log = LogFactory.getLog(loggerName);
836 if (log != null)
837 {
838 config.setLogger(log);
839 }
840 }
841 config.setIgnoreReloadExceptions(isIgnoreReloadExceptions());
842 config.setExpressionEngine(this.getExpressionEngine());
843 config.setDelimiterParsingDisabled(isDelimiterParsingDisabled());
844 config.setConversionExpressionEngine(getConversionExpressionEngine());
845 config.setListDelimiter(getListDelimiter());
846 for (ConfigurationErrorListener listener : getErrorListeners())
847 {
848 config.addErrorListener(listener);
849 }
850 for (ConfigurationListener listener : getConfigurationListeners())
851 {
852 config.addConfigurationListener(listener);
853 }
854 config.setForceReloadCheck(isForceReloadCheck());
855 for (ConfigData data : configurations)
856 {
857 config.addConfiguration(data.getConfiguration(), data.getName(),
858 data.getAt());
859 }
860 configs.put(key, config);
861 }
862 }
863 if (getLogger().isDebugEnabled())
864 {
865 getLogger().debug("Returning config for " + key + ": " + config);
866 }
867 return config;
868 }
869
870 /**
871 * Internal class that identifies each Configuration.
872 */
873 static class ConfigData
874 {
875 /** Stores a reference to the configuration. */
876 private AbstractConfiguration configuration;
877
878 /** Stores the name under which the configuration is stored. */
879 private String name;
880
881 /** Stores the at string.*/
882 private String at;
883
884 /**
885 * Creates a new instance of {@code ConfigData} and initializes
886 * it.
887 *
888 * @param config the configuration
889 * @param n the name
890 * @param at the at position
891 */
892 public ConfigData(AbstractConfiguration config, String n, String at)
893 {
894 configuration = config;
895 name = n;
896 this.at = at;
897 }
898
899 /**
900 * Returns the stored configuration.
901 *
902 * @return the configuration
903 */
904 public AbstractConfiguration getConfiguration()
905 {
906 return configuration;
907 }
908
909 /**
910 * Returns the configuration's name.
911 *
912 * @return the name
913 */
914 public String getName()
915 {
916 return name;
917 }
918
919 /**
920 * Returns the at position of this configuration.
921 *
922 * @return the at position
923 */
924 public String getAt()
925 {
926 return at;
927 }
928
929 }
930 }