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