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 * https://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.configuration2.builder.combined;
18
19 import java.net.URL;
20 import java.util.ArrayList;
21 import java.util.Collection;
22 import java.util.Collections;
23 import java.util.HashMap;
24 import java.util.Iterator;
25 import java.util.LinkedList;
26 import java.util.List;
27 import java.util.Map;
28 import java.util.Set;
29 import java.util.stream.Collectors;
30
31 import org.apache.commons.configuration2.CombinedConfiguration;
32 import org.apache.commons.configuration2.Configuration;
33 import org.apache.commons.configuration2.ConfigurationLookup;
34 import org.apache.commons.configuration2.HierarchicalConfiguration;
35 import org.apache.commons.configuration2.SystemConfiguration;
36 import org.apache.commons.configuration2.XMLConfiguration;
37 import org.apache.commons.configuration2.beanutils.BeanDeclaration;
38 import org.apache.commons.configuration2.beanutils.BeanHelper;
39 import org.apache.commons.configuration2.beanutils.CombinedBeanDeclaration;
40 import org.apache.commons.configuration2.beanutils.XMLBeanDeclaration;
41 import org.apache.commons.configuration2.builder.BasicBuilderParameters;
42 import org.apache.commons.configuration2.builder.BasicConfigurationBuilder;
43 import org.apache.commons.configuration2.builder.BuilderParameters;
44 import org.apache.commons.configuration2.builder.ConfigurationBuilder;
45 import org.apache.commons.configuration2.builder.ConfigurationBuilderEvent;
46 import org.apache.commons.configuration2.builder.FileBasedBuilderParametersImpl;
47 import org.apache.commons.configuration2.builder.FileBasedBuilderProperties;
48 import org.apache.commons.configuration2.builder.FileBasedConfigurationBuilder;
49 import org.apache.commons.configuration2.builder.XMLBuilderParametersImpl;
50 import org.apache.commons.configuration2.builder.XMLBuilderProperties;
51 import org.apache.commons.configuration2.event.EventListener;
52 import org.apache.commons.configuration2.ex.ConfigurationException;
53 import org.apache.commons.configuration2.interpol.ConfigurationInterpolator;
54 import org.apache.commons.configuration2.interpol.Lookup;
55 import org.apache.commons.configuration2.io.FileSystem;
56 import org.apache.commons.configuration2.resolver.CatalogResolver;
57 import org.apache.commons.configuration2.tree.DefaultExpressionEngineSymbols;
58 import org.apache.commons.configuration2.tree.OverrideCombiner;
59 import org.apache.commons.configuration2.tree.UnionCombiner;
60 import org.xml.sax.EntityResolver;
61
62 /**
63 * <p>
64 * A specialized {@code ConfigurationBuilder} implementation that creates a {@link CombinedConfiguration} from multiple
65 * configuration sources defined by an XML-based <em>configuration definition file</em>.
66 * </p>
67 * <p>
68 * This class provides an easy and flexible means for loading multiple configuration sources and combining the results
69 * into a single configuration object. The sources to be loaded are defined in an XML document that can contain certain
70 * tags representing the different supported configuration classes. If such a tag is found, a corresponding
71 * {@code ConfigurationBuilder} class is instantiated and initialized using the classes of the {@code beanutils} package
72 * (namely {@link org.apache.commons.configuration2.beanutils.XMLBeanDeclaration XMLBeanDeclaration} will be used to
73 * extract the configuration's initialization parameters, which allows for complex initialization scenarios).
74 * </p>
75 * <p>
76 * It is also possible to add custom tags to the configuration definition file. For this purpose an implementation of
77 * {@link CombinedConfigurationBuilderProvider} has to be created which is responsible for the creation of a
78 * {@code ConfigurationBuilder} associated with the custom tag. An instance of this class has to be registered at the
79 * {@link CombinedBuilderParametersImpl} object which is used to initialize this {@code CombinedConfigurationBuilder}.
80 * This provider will then be called when the corresponding custom tag is detected. For many default configuration
81 * classes providers are already registered.
82 * </p>
83 * <p>
84 * The configuration definition file has the following basic structure:
85 * </p>
86 *
87 * <pre>
88 * <configuration systemProperties="properties file name">
89 * <header>
90 * <!-- Optional meta information about the combined configuration -->
91 * </header>
92 * <override>
93 * <!-- Declarations for override configurations -->
94 * </override>
95 * <additional>
96 * <!-- Declarations for union configurations -->
97 * </additional>
98 * </configuration>
99 * </pre>
100 *
101 * <p>
102 * The name of the root element (here {@code configuration}) is arbitrary. The optional {@code systemProperties}
103 * attribute identifies the path to a property file containing properties that should be added to the system properties.
104 * If specified on the root element, the system properties are set before the rest of the configuration is processed.
105 * </p>
106 * <p>
107 * There are two sections (both of them are optional) for declaring <em>override</em> and <em>additional</em>
108 * configurations. Configurations in the former section are evaluated in the order of their declaration, and properties
109 * of configurations declared earlier hide those of configurations declared later. Configurations in the latter section
110 * are combined to a union configuration, i.e. all of their properties are added to a large hierarchical configuration.
111 * Configuration declarations that occur as direct children of the root element are treated as override declarations.
112 * </p>
113 * <p>
114 * Each configuration declaration consists of a tag whose name is associated with a
115 * {@code CombinedConfigurationBuilderProvider}. This can be one of the predefined tags like {@code properties}, or
116 * {@code xml}, or a custom tag, for which a configuration builder provider was registered (as described above).
117 * Attributes and sub elements with specific initialization parameters can be added. There are some reserved attributes
118 * with a special meaning that can be used in every configuration declaration:
119 * </p>
120 * <table border="1">
121 * <caption>Standard attributes for configuration declarations</caption>
122 * <tr>
123 * <th>Attribute</th>
124 * <th>Meaning</th>
125 * </tr>
126 * <tr>
127 * <td>{@code config-name}</td>
128 * <td>Allows specifying a name for this configuration. This name can be used to obtain a reference to the configuration
129 * from the resulting combined configuration (see below). It can also be passed to the {@link #getNamedBuilder(String)}
130 * method.</td>
131 * </tr>
132 * <tr>
133 * <td>{@code config-at}</td>
134 * <td>With this attribute an optional prefix can be specified for the properties of the corresponding
135 * configuration.</td>
136 * </tr>
137 * <tr>
138 * <td>{@code config-optional}</td>
139 * <td>Declares a configuration source as optional. This means that errors that occur when creating the configuration
140 * are ignored.</td>
141 * </tr>
142 * <tr>
143 * <td>{@code config-reload}</td>
144 * <td>Many configuration sources support a reloading mechanism. For those sources it is possible to enable reloading by
145 * providing this attribute with a value of <strong>true</strong>.</td>
146 * </tr>
147 * </table>
148 * <p>
149 * The optional <em>header</em> section can contain some meta data about the created configuration itself. For instance,
150 * it is possible to set further properties of the {@code NodeCombiner} objects used for constructing the resulting
151 * configuration.
152 * </p>
153 * <p>
154 * The default configuration object returned by this builder is an instance of the {@link CombinedConfiguration} class.
155 * This allows for convenient access to the configuration objects maintained by the combined configuration (for example for
156 * updates of single configuration objects). It has also the advantage that the properties stored in all declared
157 * configuration objects are collected and transformed into a single hierarchical structure, which can be accessed using
158 * different expression engines. The actual {@code CombinedConfiguration} implementation can be overridden by specifying
159 * the class in the <em>config-class</em> attribute of the result element.
160 * </p>
161 * <p>
162 * A custom EntityResolver can be used for all XMLConfigurations by adding
163 * </p>
164 *
165 * <pre>
166 * <entity-resolver config-class="EntityResolver fully qualified class name">
167 * </pre>
168 *
169 * <p>
170 * A specific CatalogResolver can be specified for all XMLConfiguration sources by adding
171 * </p>
172 *
173 * <pre>
174 * <entity-resolver catalogFiles="comma separated list of catalog files">
175 * </pre>
176 *
177 * <p>
178 * Additional ConfigurationProviders can be added by configuring them in the <em>header</em> section.
179 * </p>
180 *
181 * <pre>
182 * <providers>
183 * <provider config-tag="tag name" config-class="provider fully qualified class name"/>
184 * </providers>
185 * </pre>
186 *
187 * <p>
188 * Additional variable resolvers can be added by configuring them in the <em>header</em> section.
189 * </p>
190 *
191 * <pre>
192 * <lookups>
193 * <lookup config-prefix="prefix" config-class="StrLookup fully qualified class name"/>
194 * </lookups>
195 * </pre>
196 *
197 * <p>
198 * All declared override configurations are directly added to the resulting combined configuration. If they are given
199 * names (using the {@code config-name} attribute), they can directly be accessed using the
200 * {@code getConfiguration(String)} method of {@code CombinedConfiguration}. The additional configurations are
201 * altogether added to another combined configuration, which uses a union combiner. Then this union configuration is
202 * added to the resulting combined configuration under the name defined by the {@code ADDITIONAL_NAME} constant. The
203 * {@link #getNamedBuilder(String)} method can be used to access the {@code ConfigurationBuilder} objects for all
204 * configuration sources which have been assigned a name; care has to be taken that these names are unique.
205 * </p>
206 *
207 * @since 1.3
208 */
209 public class CombinedConfigurationBuilder extends BasicConfigurationBuilder<CombinedConfiguration> {
210
211 /**
212 * A data class for storing information about all configuration sources defined for a combined builder.
213 */
214 private final class ConfigurationSourceData {
215
216 /** A list with data for all builders for override configurations. */
217 private final List<ConfigurationDeclaration> overrideDeclarations;
218
219 /** A list with data for all builders for union configurations. */
220 private final List<ConfigurationDeclaration> unionDeclarations;
221
222 /** A list with the builders for override configurations. */
223 private final List<ConfigurationBuilder<? extends Configuration>> overrideBuilders;
224
225 /** A list with the builders for union configurations. */
226 private final List<ConfigurationBuilder<? extends Configuration>> unionBuilders;
227
228 /** A map for direct access to a builder by its name. */
229 private final Map<String, ConfigurationBuilder<? extends Configuration>> namedBuilders;
230
231 /** A collection with all child builders. */
232 private final Collection<ConfigurationBuilder<? extends Configuration>> allBuilders;
233
234 /** A listener for reacting on changes of sub builders. */
235 private final EventListener<ConfigurationBuilderEvent> changeListener;
236
237 /**
238 * Creates a new instance of {@code ConfigurationSourceData}.
239 */
240 public ConfigurationSourceData() {
241 overrideDeclarations = new ArrayList<>();
242 unionDeclarations = new ArrayList<>();
243 overrideBuilders = new ArrayList<>();
244 unionBuilders = new ArrayList<>();
245 namedBuilders = new HashMap<>();
246 allBuilders = new LinkedList<>();
247 changeListener = createBuilderChangeListener();
248 }
249
250 /**
251 * Creates a new configuration using the specified builder and adds it to the resulting combined configuration.
252 *
253 * @param ccResult the resulting combined configuration
254 * @param decl the current {@code ConfigurationDeclaration}
255 * @param builder the configuration builder
256 * @throws ConfigurationException if an error occurs
257 */
258 private void addChildConfiguration(final CombinedConfiguration ccResult, final ConfigurationDeclaration decl,
259 final ConfigurationBuilder<? extends Configuration> builder) throws ConfigurationException {
260 try {
261 ccResult.addConfiguration(builder.getConfiguration(), decl.getName(), decl.getAt());
262 } catch (final ConfigurationException cex) {
263 // ignore exceptions for optional configurations
264 if (!decl.isOptional()) {
265 throw cex;
266 }
267 }
268 }
269
270 /**
271 * Returns a set with the names of all known named builders.
272 *
273 * @return the names of the available sub builders
274 */
275 public Set<String> builderNames() {
276 return namedBuilders.keySet();
277 }
278
279 /**
280 * Frees resources used by this object and performs clean up. This method is called when the owning builder is reset.
281 */
282 public void cleanUp() {
283 getChildBuilders().forEach(b -> b.removeEventListener(ConfigurationBuilderEvent.RESET, changeListener));
284 namedBuilders.clear();
285 }
286
287 /**
288 * Processes the declaration of configuration builder providers, creates the corresponding builder if necessary, obtains
289 * configurations, and adds them to the specified result configuration.
290 *
291 * @param ccResult the result configuration.
292 * @param srcDecl the collection with the declarations of configuration sources to process.
293 * @param builders List of configuration builders.
294 * @return a list with configuration builders.
295 * @throws ConfigurationException if an error occurs.
296 */
297 public List<ConfigurationBuilder<? extends Configuration>> createAndAddConfigurations(final CombinedConfiguration ccResult,
298 final List<ConfigurationDeclaration> srcDecl, final List<ConfigurationBuilder<? extends Configuration>> builders) throws ConfigurationException {
299 final boolean createBuilders = builders.isEmpty();
300 final List<ConfigurationBuilder<? extends Configuration>> newBuilders;
301 if (createBuilders) {
302 newBuilders = new ArrayList<>(srcDecl.size());
303 } else {
304 newBuilders = builders;
305 }
306
307 for (int i = 0; i < srcDecl.size(); i++) {
308 final ConfigurationBuilder<? extends Configuration> b;
309 if (createBuilders) {
310 b = createConfigurationBuilder(srcDecl.get(i));
311 newBuilders.add(b);
312 } else {
313 b = builders.get(i);
314 }
315 addChildConfiguration(ccResult, srcDecl.get(i), b);
316 }
317
318 return newBuilders;
319 }
320
321 /**
322 * Creates a listener for builder change events. This listener is registered at all builders for child configurations.
323 */
324 private EventListener<ConfigurationBuilderEvent> createBuilderChangeListener() {
325 return event -> resetResult();
326 }
327
328 /**
329 * Creates a configuration builder based on a source declaration in the definition configuration.
330 *
331 * @param decl the current {@code ConfigurationDeclaration}
332 * @return the newly created builder
333 * @throws ConfigurationException if an error occurs
334 */
335 private ConfigurationBuilder<? extends Configuration> createConfigurationBuilder(final ConfigurationDeclaration decl) throws ConfigurationException {
336 final ConfigurationBuilderProvider provider = providerForTag(decl.getConfiguration().getRootElementName());
337 if (provider == null) {
338 throw new ConfigurationException("Unsupported configuration source: %s", decl.getConfiguration().getRootElementName());
339 }
340
341 final ConfigurationBuilder<? extends Configuration> builder = provider.getConfigurationBuilder(decl);
342 if (decl.getName() != null) {
343 namedBuilders.put(decl.getName(), builder);
344 }
345 allBuilders.add(builder);
346 builder.addEventListener(ConfigurationBuilderEvent.RESET, changeListener);
347 return builder;
348 }
349
350 /**
351 * Finds the override configurations that are defined as top level elements in the configuration definition file. This
352 * method fetches the child elements of the root node and removes the nodes that represent other configuration sections.
353 * The remaining nodes are treated as definitions for override configurations.
354 *
355 * @param config the definition configuration
356 * @return a list with sub configurations for the top level override configurations
357 */
358 private List<? extends HierarchicalConfiguration<?>> fetchTopLevelOverrideConfigs(final HierarchicalConfiguration<?> config) {
359
360 final List<? extends HierarchicalConfiguration<?>> configs = config.childConfigurationsAt(null);
361 for (final Iterator<? extends HierarchicalConfiguration<?>> it = configs.iterator(); it.hasNext();) {
362 final String nodeName = it.next().getRootElementName();
363 for (final String element : CONFIG_SECTIONS) {
364 if (element.equals(nodeName)) {
365 it.remove();
366 break;
367 }
368 }
369 }
370 return configs;
371 }
372
373 /**
374 * Gets a collection containing the builders for all child configuration sources.
375 *
376 * @return the child configuration builders
377 */
378 public Collection<ConfigurationBuilder<? extends Configuration>> getChildBuilders() {
379 return allBuilders;
380 }
381
382 /**
383 * Gets the {@code ConfigurationBuilder} with the given name. If no such builder is defined in the definition
384 * configuration, result is <strong>null</strong>.
385 *
386 * @param name the name of the builder in question
387 * @return the builder with this name or <strong>null</strong>
388 */
389 public ConfigurationBuilder<? extends Configuration> getNamedBuilder(final String name) {
390 return namedBuilders.get(name);
391 }
392
393 /**
394 * Gets a collection with all configuration source declarations defined in the override section.
395 *
396 * @return the override configuration builders
397 */
398 public List<ConfigurationDeclaration> getOverrideSources() {
399 return overrideDeclarations;
400 }
401
402 /**
403 * Gets a collection with all configuration source declarations defined in the union section.
404 *
405 * @return the union configuration builders
406 */
407 public List<ConfigurationDeclaration> getUnionSources() {
408 return unionDeclarations;
409 }
410
411 /**
412 * Initializes this object from the specified definition configuration.
413 *
414 * @param config the definition configuration
415 * @throws ConfigurationException if an error occurs
416 */
417 public void initFromDefinitionConfiguration(final HierarchicalConfiguration<?> config) throws ConfigurationException {
418 overrideDeclarations.addAll(createDeclarations(fetchTopLevelOverrideConfigs(config)));
419 overrideDeclarations.addAll(createDeclarations(config.childConfigurationsAt(KEY_OVERRIDE)));
420 unionDeclarations.addAll(createDeclarations(config.childConfigurationsAt(KEY_UNION)));
421 }
422 }
423
424 /**
425 * Constant for the name of the additional configuration. If the configuration definition file contains an
426 * {@code additional} section, a special union configuration is created and added under this name to the resulting
427 * combined configuration.
428 */
429 public static final String ADDITIONAL_NAME = CombinedConfigurationBuilder.class.getName() + "/ADDITIONAL_CONFIG";
430
431 /** Constant for the name of the configuration bean factory. */
432 static final String CONFIG_BEAN_FACTORY_NAME = CombinedConfigurationBuilder.class.getName() + ".CONFIG_BEAN_FACTORY_NAME";
433
434 /** Constant for the reserved name attribute. */
435 static final String ATTR_NAME = DefaultExpressionEngineSymbols.DEFAULT_ATTRIBUTE_START + XMLBeanDeclaration.RESERVED_PREFIX + "name"
436 + DefaultExpressionEngineSymbols.DEFAULT_ATTRIBUTE_END;
437
438 /** Constant for the name of the at attribute. */
439 static final String ATTR_ATNAME = "at";
440
441 /** Constant for the reserved at attribute. */
442 static final String ATTR_AT_RES = DefaultExpressionEngineSymbols.DEFAULT_ATTRIBUTE_START + XMLBeanDeclaration.RESERVED_PREFIX + ATTR_ATNAME
443 + DefaultExpressionEngineSymbols.DEFAULT_ATTRIBUTE_END;
444
445 /** Constant for the at attribute without the reserved prefix. */
446 static final String ATTR_AT = DefaultExpressionEngineSymbols.DEFAULT_ATTRIBUTE_START + ATTR_ATNAME + DefaultExpressionEngineSymbols.DEFAULT_ATTRIBUTE_END;
447
448 /** Constant for the name of the optional attribute. */
449 static final String ATTR_OPTIONALNAME = "optional";
450
451 /** Constant for the reserved optional attribute. */
452 static final String ATTR_OPTIONAL_RES = DefaultExpressionEngineSymbols.DEFAULT_ATTRIBUTE_START + XMLBeanDeclaration.RESERVED_PREFIX + ATTR_OPTIONALNAME
453 + DefaultExpressionEngineSymbols.DEFAULT_ATTRIBUTE_END;
454
455 /** Constant for the optional attribute without the reserved prefix. */
456 static final String ATTR_OPTIONAL = DefaultExpressionEngineSymbols.DEFAULT_ATTRIBUTE_START + ATTR_OPTIONALNAME
457 + DefaultExpressionEngineSymbols.DEFAULT_ATTRIBUTE_END;
458
459 /** Constant for the forceCreate attribute. */
460 static final String ATTR_FORCECREATE = DefaultExpressionEngineSymbols.DEFAULT_ATTRIBUTE_START + XMLBeanDeclaration.RESERVED_PREFIX + "forceCreate"
461 + DefaultExpressionEngineSymbols.DEFAULT_ATTRIBUTE_END;
462
463 /** Constant for the reload attribute. */
464 static final String ATTR_RELOAD = DefaultExpressionEngineSymbols.DEFAULT_ATTRIBUTE_START + XMLBeanDeclaration.RESERVED_PREFIX + "reload"
465 + DefaultExpressionEngineSymbols.DEFAULT_ATTRIBUTE_END;
466
467 /**
468 * Constant for the tag attribute for providers.
469 */
470 static final String KEY_SYSTEM_PROPS = "[@systemProperties]";
471
472 /** Constant for the name of the header section. */
473 static final String SEC_HEADER = "header";
474
475 /** Constant for an expression that selects the union configurations. */
476 static final String KEY_UNION = "additional";
477
478 /** An array with the names of top level configuration sections. */
479 static final String[] CONFIG_SECTIONS = {"additional", "override", SEC_HEADER};
480
481 /**
482 * Constant for an expression that selects override configurations in the override section.
483 */
484 static final String KEY_OVERRIDE = "override";
485
486 /**
487 * Constant for the key that points to the list nodes definition of the override combiner.
488 */
489 static final String KEY_OVERRIDE_LIST = SEC_HEADER + ".combiner.override.list-nodes.node";
490
491 /**
492 * Constant for the key that points to the list nodes definition of the additional combiner.
493 */
494 static final String KEY_ADDITIONAL_LIST = SEC_HEADER + ".combiner.additional.list-nodes.node";
495
496 /**
497 * Constant for the key for defining providers in the configuration file.
498 */
499 static final String KEY_CONFIGURATION_PROVIDERS = SEC_HEADER + ".providers.provider";
500
501 /**
502 * Constant for the tag attribute for providers.
503 */
504 static final String KEY_PROVIDER_KEY = XMLBeanDeclaration.ATTR_PREFIX + "tag]";
505
506 /**
507 * Constant for the key for defining variable resolvers
508 */
509 static final String KEY_CONFIGURATION_LOOKUPS = SEC_HEADER + ".lookups.lookup";
510
511 /**
512 * Constant for the key for defining entity resolvers
513 */
514 static final String KEY_ENTITY_RESOLVER = SEC_HEADER + ".entity-resolver";
515
516 /**
517 * Constant for the prefix attribute for lookups.
518 */
519 static final String KEY_LOOKUP_KEY = XMLBeanDeclaration.ATTR_PREFIX + "prefix]";
520
521 /**
522 * Constant for the FileSystem.
523 */
524 static final String FILE_SYSTEM = SEC_HEADER + ".fileSystem";
525
526 /**
527 * Constant for the key of the result declaration. This key can point to a bean declaration, which defines properties of
528 * the resulting combined configuration.
529 */
530 static final String KEY_RESULT = SEC_HEADER + ".result";
531
532 /** Constant for the key of the combiner in the result declaration. */
533 static final String KEY_COMBINER = KEY_RESULT + ".nodeCombiner";
534
535 /** Constant for the XML file extension. */
536 static final String EXT_XML = "xml";
537
538 /** Constant for the basic configuration builder class. */
539 private static final String BASIC_BUILDER = "org.apache.commons.configuration2.builder.BasicConfigurationBuilder";
540
541 /** Constant for the file-based configuration builder class. */
542 private static final String FILE_BUILDER = "org.apache.commons.configuration2.builder.FileBasedConfigurationBuilder";
543
544 /** Constant for the reloading file-based configuration builder class. */
545 private static final String RELOADING_BUILDER = "org.apache.commons.configuration2.builder.ReloadingFileBasedConfigurationBuilder";
546
547 /** Constant for the name of the file-based builder parameters class. */
548 private static final String FILE_PARAMS = "org.apache.commons.configuration2.builder.FileBasedBuilderParametersImpl";
549
550 /** Constant for the provider for properties files. */
551 private static final ConfigurationBuilderProvider PROPERTIES_PROVIDER = new FileExtensionConfigurationBuilderProvider(FILE_BUILDER, RELOADING_BUILDER,
552 "org.apache.commons.configuration2.XMLPropertiesConfiguration", "org.apache.commons.configuration2.PropertiesConfiguration", EXT_XML,
553 Collections.singletonList(FILE_PARAMS));
554
555 /** Constant for the provider for XML files. */
556 private static final ConfigurationBuilderProvider XML_PROVIDER = new BaseConfigurationBuilderProvider(FILE_BUILDER, RELOADING_BUILDER,
557 "org.apache.commons.configuration2.XMLConfiguration", Collections.singletonList("org.apache.commons.configuration2.builder.XMLBuilderParametersImpl"));
558
559 /** Constant for the provider for JNDI sources. */
560 private static final BaseConfigurationBuilderProvider JNDI_PROVIDER = new BaseConfigurationBuilderProvider(BASIC_BUILDER, null,
561 "org.apache.commons.configuration2.JNDIConfiguration",
562 Collections.singletonList("org.apache.commons.configuration2.builder.JndiBuilderParametersImpl"));
563
564 /** Constant for the provider for system properties. */
565 private static final BaseConfigurationBuilderProvider SYSTEM_PROVIDER = new BaseConfigurationBuilderProvider(BASIC_BUILDER, null,
566 "org.apache.commons.configuration2.SystemConfiguration", Collections.singletonList("org.apache.commons.configuration2.builder.BasicBuilderParameters"));
567
568 /** Constant for the provider for ini files. */
569 private static final BaseConfigurationBuilderProvider INI_PROVIDER = new BaseConfigurationBuilderProvider(FILE_BUILDER, RELOADING_BUILDER,
570 "org.apache.commons.configuration2.INIConfiguration", Collections.singletonList(FILE_PARAMS));
571
572 /** Constant for the provider for environment properties. */
573 private static final BaseConfigurationBuilderProvider ENV_PROVIDER = new BaseConfigurationBuilderProvider(BASIC_BUILDER, null,
574 "org.apache.commons.configuration2.EnvironmentConfiguration",
575 Collections.singletonList("org.apache.commons.configuration2.builder.BasicBuilderParameters"));
576
577 /** Constant for the provider for plist files. */
578 private static final BaseConfigurationBuilderProvider PLIST_PROVIDER = new FileExtensionConfigurationBuilderProvider(FILE_BUILDER, RELOADING_BUILDER,
579 "org.apache.commons.configuration2.plist.XMLPropertyListConfiguration", "org.apache.commons.configuration2.plist.PropertyListConfiguration", EXT_XML,
580 Collections.singletonList(FILE_PARAMS));
581
582 /** Constant for the provider for configuration definition files. */
583 private static final BaseConfigurationBuilderProvider COMBINED_PROVIDER = new CombinedConfigurationBuilderProvider();
584
585 /** Constant for the provider for multiple XML configurations. */
586 private static final MultiFileConfigurationBuilderProvider MULTI_XML_PROVIDER = new MultiFileConfigurationBuilderProvider(
587 "org.apache.commons.configuration2.XMLConfiguration", "org.apache.commons.configuration2.builder.XMLBuilderParametersImpl");
588
589 /** An array with the names of the default tags. */
590 private static final String[] DEFAULT_TAGS = {"properties", "xml", "hierarchicalXml", "plist", "ini", "system", "env", "jndi", "configuration",
591 "multiFile"};
592
593 /** An array with the providers for the default tags. */
594 private static final ConfigurationBuilderProvider[] DEFAULT_PROVIDERS = {PROPERTIES_PROVIDER, XML_PROVIDER, XML_PROVIDER, PLIST_PROVIDER, INI_PROVIDER,
595 SYSTEM_PROVIDER, ENV_PROVIDER, JNDI_PROVIDER, COMBINED_PROVIDER, MULTI_XML_PROVIDER};
596
597 /** A map with the default configuration builder providers. */
598 private static final Map<String, ConfigurationBuilderProvider> DEFAULT_PROVIDERS_MAP;
599
600 static {
601 DEFAULT_PROVIDERS_MAP = createDefaultProviders();
602 }
603
604 /**
605 * Creates the map with the default configuration builder providers.
606 *
607 * @return the map with default providers
608 */
609 private static Map<String, ConfigurationBuilderProvider> createDefaultProviders() {
610 final Map<String, ConfigurationBuilderProvider> providers = new HashMap<>();
611 for (int i = 0; i < DEFAULT_TAGS.length; i++) {
612 providers.put(DEFAULT_TAGS[i], DEFAULT_PROVIDERS[i]);
613 }
614 return providers;
615 }
616
617 /**
618 * Initializes the list nodes of the node combiner for the given combined configuration. This information can be set in
619 * the header section of the configuration definition file for both the override and the union combiners.
620 *
621 * @param cc the combined configuration to initialize
622 * @param defConfig the definition configuration
623 * @param key the key for the list nodes
624 */
625 private static void initNodeCombinerListNodes(final CombinedConfiguration cc, final HierarchicalConfiguration<?> defConfig, final String key) {
626 defConfig.getList(key).forEach(listNode -> cc.getNodeCombiner().addListNode((String) listNode));
627 }
628
629 /** The builder for the definition configuration. */
630 private ConfigurationBuilder<? extends HierarchicalConfiguration<?>> definitionBuilder;
631
632 /** Stores temporarily the configuration with the builder definitions. */
633 private HierarchicalConfiguration<?> definitionConfiguration;
634
635 /** The object with data about configuration sources. */
636 private ConfigurationSourceData sourceData;
637
638 /** Stores the current parameters object. */
639 private CombinedBuilderParametersImpl currentParameters;
640
641 /** The current XML parameters object. */
642 private XMLBuilderParametersImpl currentXMLParameters;
643
644 /** The configuration that is currently constructed. */
645 private CombinedConfiguration currentConfiguration;
646
647 /**
648 * A {@code ConfigurationInterpolator} to be used as parent for all child configurations to enable cross-source
649 * interpolation.
650 */
651 private ConfigurationInterpolator parentInterpolator;
652
653 /**
654 * Creates a new instance of {@code CombinedConfigurationBuilder}. No parameters are set.
655 */
656 public CombinedConfigurationBuilder() {
657 super(CombinedConfiguration.class);
658 }
659
660 /**
661 *
662 * Creates a new instance of {@code CombinedConfigurationBuilder} and sets the specified initialization parameters.
663 *
664 * @param params a map with initialization parameters
665 */
666 public CombinedConfigurationBuilder(final Map<String, Object> params) {
667 super(CombinedConfiguration.class, params);
668 }
669
670 /**
671 *
672 * Creates a new instance of {@code CombinedConfigurationBuilder} and sets the specified initialization parameters and
673 * the <em>allowFailOnInit</em> flag.
674 *
675 * @param params a map with initialization parameters
676 * @param allowFailOnInit the <em>allowFailOnInit</em> flag
677 */
678 public CombinedConfigurationBuilder(final Map<String, Object> params, final boolean allowFailOnInit) {
679 super(CombinedConfiguration.class, params, allowFailOnInit);
680 }
681
682 /**
683 * Adds a listener at the given definition builder which resets this builder when a reset of the definition builder
684 * happens. This way it is ensured that this builder produces a new combined configuration when its definition
685 * configuration changes.
686 *
687 * @param defBuilder the definition builder
688 */
689 private void addDefinitionBuilderChangeListener(final ConfigurationBuilder<? extends HierarchicalConfiguration<?>> defBuilder) {
690 defBuilder.addEventListener(ConfigurationBuilderEvent.RESET, event -> {
691 synchronized (this) {
692 reset();
693 definitionBuilder = defBuilder;
694 }
695 });
696 }
697
698 /**
699 * <p>
700 * Returns a set with the names of all child configuration builders. A tag defining a configuration source in the
701 * configuration definition file can have the {@code config-name} attribute. If this attribute is present, the
702 * corresponding builder is assigned this name and can be directly accessed through the {@link #getNamedBuilder(String)}
703 * method. This method returns a collection with all available builder names.
704 * </p>
705 * <p>
706 * <strong>Important note:</strong> This method only returns a meaningful result after the result configuration has been
707 * created by calling {@code getConfiguration()}. If called before, always an empty set is returned.
708 * </p>
709 *
710 * @return a set with the names of all builders
711 */
712 public synchronized Set<String> builderNames() {
713 if (sourceData == null) {
714 return Collections.emptySet();
715 }
716 return Collections.unmodifiableSet(sourceData.builderNames());
717 }
718
719 /**
720 * {@inheritDoc} This method is overridden to adapt the return type.
721 */
722 @Override
723 public CombinedConfigurationBuilder configure(final BuilderParameters... params) {
724 super.configure(params);
725 return this;
726 }
727
728 /**
729 * Creates and initializes a default {@code EntityResolver} if the definition configuration contains a corresponding
730 * declaration.
731 *
732 * @param config the definition configuration
733 * @param xmlParams the (already partly initialized) object with XML parameters; here the new resolver is to be stored
734 * @throws ConfigurationException if an error occurs
735 */
736 protected void configureEntityResolver(final HierarchicalConfiguration<?> config, final XMLBuilderParametersImpl xmlParams) throws ConfigurationException {
737 if (config.getMaxIndex(KEY_ENTITY_RESOLVER) == 0) {
738 final XMLBeanDeclaration decl = new XMLBeanDeclaration(config, KEY_ENTITY_RESOLVER, true);
739 final EntityResolver resolver = (EntityResolver) fetchBeanHelper().createBean(decl, CatalogResolver.class);
740 final FileSystem fileSystem = xmlParams.getFileHandler().getFileSystem();
741 if (fileSystem != null) {
742 BeanHelper.setProperty(resolver, "fileSystem", fileSystem);
743 }
744 final String basePath = xmlParams.getFileHandler().getBasePath();
745 if (basePath != null) {
746 BeanHelper.setProperty(resolver, "baseDir", basePath);
747 }
748 final ConfigurationInterpolator ci = new ConfigurationInterpolator();
749 ci.registerLookups(fetchPrefixLookups());
750 BeanHelper.setProperty(resolver, "interpolator", ci);
751
752 xmlParams.setEntityResolver(resolver);
753 }
754 }
755
756 /**
757 * Creates the {@code CombinedConfiguration} for the configuration sources in the {@code <additional>} section.
758 * This method is called when the builder constructs the final configuration. It creates a new
759 * {@code CombinedConfiguration} and initializes some properties from the result configuration.
760 *
761 * @param resultConfig the result configuration (this is the configuration that will be returned by the builder)
762 * @return the {@code CombinedConfiguration} for the additional configuration sources
763 * @since 1.7
764 */
765 protected CombinedConfiguration createAdditionalsConfiguration(final CombinedConfiguration resultConfig) {
766 final CombinedConfiguration addConfig = new CombinedConfiguration(new UnionCombiner());
767 addConfig.setListDelimiterHandler(resultConfig.getListDelimiterHandler());
768 return addConfig;
769 }
770
771 /**
772 * Creates {@code ConfigurationDeclaration} objects for the specified configurations.
773 *
774 * @param configs the list with configurations
775 * @return a collection with corresponding declarations
776 */
777 private Collection<ConfigurationDeclaration> createDeclarations(final Collection<? extends HierarchicalConfiguration<?>> configs) {
778 return configs.stream().map(c -> new ConfigurationDeclaration(this, c)).collect(Collectors.toList());
779 }
780
781 /**
782 * {@inheritDoc} This implementation evaluates the {@code result} property of the definition configuration. It creates a
783 * combined bean declaration with both the properties specified in the definition file and the properties defined as
784 * initialization parameters.
785 */
786 @Override
787 protected BeanDeclaration createResultDeclaration(final Map<String, Object> params) throws ConfigurationException {
788 final BeanDeclaration paramsDecl = super.createResultDeclaration(params);
789 final XMLBeanDeclaration resultDecl = new XMLBeanDeclaration(getDefinitionConfiguration(), KEY_RESULT, true, CombinedConfiguration.class.getName());
790 return new CombinedBeanDeclaration(resultDecl, paramsDecl);
791 }
792
793 /**
794 * Creates the data object for configuration sources and the corresponding builders.
795 *
796 * @return the newly created data object
797 * @throws ConfigurationException if an error occurs
798 */
799 private ConfigurationSourceData createSourceData() throws ConfigurationException {
800 final ConfigurationSourceData result = new ConfigurationSourceData();
801 result.initFromDefinitionConfiguration(getDefinitionConfiguration());
802 return result;
803 }
804
805 /**
806 * Creates a default builder for the definition configuration and initializes it with a parameters object. This method
807 * is called if no definition builder is defined in this builder's parameters. This implementation creates a default
808 * file-based builder which produces an {@code XMLConfiguration}; it expects a corresponding file specification. Note:
809 * This method is called in a synchronized block.
810 *
811 * @param builderParams the parameters object for the builder
812 * @return the standard builder for the definition configuration
813 */
814 protected ConfigurationBuilder<? extends HierarchicalConfiguration<?>> createXMLDefinitionBuilder(final BuilderParameters builderParams) {
815 return new FileBasedConfigurationBuilder<>(XMLConfiguration.class).configure(builderParams);
816 }
817
818 /**
819 * Returns a map with the current prefix lookup objects. This map is obtained from the {@code ConfigurationInterpolator}
820 * of the configuration under construction.
821 *
822 * @return the map with current prefix lookups (may be <strong>null</strong>)
823 */
824 private Map<String, ? extends Lookup> fetchPrefixLookups() {
825 final CombinedConfiguration cc = getConfigurationUnderConstruction();
826 return cc != null ? cc.getInterpolator().getLookups() : null;
827 }
828
829 /**
830 * Gets the current base path of this configuration builder. This is used for instance by all file-based child
831 * configurations.
832 *
833 * @return the base path
834 */
835 private String getBasePath() {
836 return currentXMLParameters.getFileHandler().getBasePath();
837 }
838
839 /**
840 * Gets a collection with the builders for all child configuration sources. This method can be used by derived
841 * classes providing additional functionality on top of the declared configuration sources. It only returns a defined
842 * value during construction of the result configuration instance.
843 *
844 * @return a collection with the builders for child configuration sources
845 */
846 protected synchronized Collection<ConfigurationBuilder<? extends Configuration>> getChildBuilders() {
847 return sourceData.getChildBuilders();
848 }
849
850 /**
851 * Gets the configuration object that is currently constructed. This method can be called during construction of the
852 * result configuration. It is intended for internal usage, for example some specialized builder providers need access to this
853 * configuration to perform advanced initialization.
854 *
855 * @return the configuration that us currently under construction
856 */
857 CombinedConfiguration getConfigurationUnderConstruction() {
858 return currentConfiguration;
859 }
860
861 /**
862 * Gets the {@code ConfigurationBuilder} which creates the definition configuration.
863 *
864 * @return the builder for the definition configuration
865 * @throws ConfigurationException if an error occurs
866 */
867 public synchronized ConfigurationBuilder<? extends HierarchicalConfiguration<?>> getDefinitionBuilder() throws ConfigurationException {
868 if (definitionBuilder == null) {
869 definitionBuilder = setupDefinitionBuilder(getParameters());
870 addDefinitionBuilderChangeListener(definitionBuilder);
871 }
872 return definitionBuilder;
873 }
874
875 /**
876 * Gets the configuration containing the definition of the combined configuration to be created. This method only
877 * returns a defined result during construction of the result configuration. The definition configuration is obtained
878 * from the definition builder at first access and then stored temporarily to ensure that during result construction
879 * always the same configuration instance is used. (Otherwise, it would be possible that the definition builder returns
880 * a different instance when queried multiple times.)
881 *
882 * @return the definition configuration
883 * @throws ConfigurationException if an error occurs
884 */
885 protected HierarchicalConfiguration<?> getDefinitionConfiguration() throws ConfigurationException {
886 if (definitionConfiguration == null) {
887 definitionConfiguration = getDefinitionBuilder().getConfiguration();
888 }
889 return definitionConfiguration;
890 }
891
892 /**
893 * <p>
894 * Gets the configuration builder with the given name. With this method a builder of a child configuration which was
895 * given a name in the configuration definition file can be accessed directly.
896 * </p>
897 * <p>
898 * <strong>Important note:</strong> This method only returns a meaningful result after the result configuration has been
899 * created by calling {@code getConfiguration()}. If called before, always an exception is thrown.
900 * </p>
901 *
902 * @param name the name of the builder in question
903 * @return the child configuration builder with this name
904 * @throws ConfigurationException if information about named builders is not yet available or no builder with this name
905 * exists
906 */
907 public synchronized ConfigurationBuilder<? extends Configuration> getNamedBuilder(final String name) throws ConfigurationException {
908 if (sourceData == null) {
909 throw new ConfigurationException("Information about child builders has not been setup yet! Call getConfiguration() first.");
910 }
911 final ConfigurationBuilder<? extends Configuration> builder = sourceData.getNamedBuilder(name);
912 if (builder == null) {
913 throw new ConfigurationException("Builder cannot be resolved: %s", name);
914 }
915 return builder;
916 }
917
918 /**
919 * Obtains the data object for the configuration sources and the corresponding builders. This object is created on first
920 * access and reset when the definition builder sends a change event. This method is called in a synchronized block.
921 *
922 * @return the object with information about configuration sources
923 * @throws ConfigurationException if an error occurs
924 */
925 private ConfigurationSourceData getSourceData() throws ConfigurationException {
926 if (sourceData == null) {
927 if (currentParameters == null) {
928 setUpCurrentParameters();
929 setUpCurrentXMLParameters();
930 }
931 sourceData = createSourceData();
932 }
933 return sourceData;
934 }
935
936 /**
937 * Initializes a bean using the current {@code BeanHelper}. This is needed by builder providers when the configuration
938 * objects for sub builders are constructed.
939 *
940 * @param bean the bean to be initialized
941 * @param decl the {@code BeanDeclaration}
942 */
943 void initBean(final Object bean, final BeanDeclaration decl) {
944 fetchBeanHelper().initBean(bean, decl);
945 }
946
947 /**
948 * Initializes basic builder parameters for a child configuration with default settings set for this builder. This
949 * implementation ensures that all {@code Lookup} objects are propagated to child configurations and interpolation is
950 * setup correctly.
951 *
952 * @param params the parameters object
953 */
954 private void initChildBasicParameters(final BasicBuilderParameters params) {
955 params.setPrefixLookups(fetchPrefixLookups());
956 params.setParentInterpolator(parentInterpolator);
957 if (currentParameters.isInheritSettings()) {
958 params.inheritFrom(getParameters());
959 }
960 }
961
962 /**
963 * Initializes a parameters object for a child builder. This combined configuration builder has a bunch of properties
964 * which may be inherited by child configurations, for example the base path, the file system, etc. While processing the
965 * builders for child configurations, this method is called for each parameters object for a child builder. It
966 * initializes some properties of the passed in parameters objects which are derived from this parent builder.
967 *
968 * @param params the parameters object to be initialized
969 */
970 protected void initChildBuilderParameters(final BuilderParameters params) {
971 initDefaultChildParameters(params);
972
973 if (params instanceof BasicBuilderParameters) {
974 initChildBasicParameters((BasicBuilderParameters) params);
975 }
976 if (params instanceof XMLBuilderProperties<?>) {
977 initChildXMLParameters((XMLBuilderProperties<?>) params);
978 }
979 if (params instanceof FileBasedBuilderProperties<?>) {
980 initChildFileBasedParameters((FileBasedBuilderProperties<?>) params);
981 }
982 if (params instanceof CombinedBuilderParametersImpl) {
983 initChildCombinedParameters((CombinedBuilderParametersImpl) params);
984 }
985 }
986
987 /**
988 * Initializes a parameters object for a combined configuration builder with properties already set for this parent
989 * builder. This implementation deals only with a subset of properties. Other properties are already handled by the
990 * specialized builder provider.
991 *
992 * @param params the parameters object
993 */
994 private void initChildCombinedParameters(final CombinedBuilderParametersImpl params) {
995 params.registerMissingProviders(currentParameters);
996 params.setBasePath(getBasePath());
997 }
998
999 /**
1000 * Initializes the event listeners of the specified builder from this object. This method is used to inherit all
1001 * listeners from a parent builder.
1002 *
1003 * @param dest the destination builder object which is to be initialized
1004 */
1005 void initChildEventListeners(final BasicConfigurationBuilder<? extends Configuration> dest) {
1006 copyEventListeners(dest);
1007 }
1008
1009 /**
1010 * Initializes a parameters object for a file-based configuration with properties already set for this parent builder.
1011 * This method handles properties like a default file system or a base path.
1012 *
1013 * @param params the parameters object
1014 */
1015 private void initChildFileBasedParameters(final FileBasedBuilderProperties<?> params) {
1016 params.setBasePath(getBasePath());
1017 params.setFileSystem(currentXMLParameters.getFileHandler().getFileSystem());
1018 }
1019
1020 /**
1021 * Initializes a parameters object for an XML configuration with properties already set for this parent builder.
1022 *
1023 * @param params the parameters object
1024 */
1025 private void initChildXMLParameters(final XMLBuilderProperties<?> params) {
1026 params.setEntityResolver(currentXMLParameters.getEntityResolver());
1027 }
1028
1029 /**
1030 * Initializes the default base path for all file-based child configuration sources. The base path can be explicitly
1031 * defined in the parameters of this builder. Otherwise, if the definition builder is a file-based builder, it is
1032 * obtained from there.
1033 *
1034 * @throws ConfigurationException if an error occurs
1035 */
1036 private void initDefaultBasePath() throws ConfigurationException {
1037 assert currentParameters != null : "Current parameters undefined!";
1038 if (currentParameters.getBasePath() != null) {
1039 currentXMLParameters.setBasePath(currentParameters.getBasePath());
1040 } else {
1041 final ConfigurationBuilder<? extends HierarchicalConfiguration<?>> defBuilder = getDefinitionBuilder();
1042 if (defBuilder instanceof FileBasedConfigurationBuilder) {
1043 @SuppressWarnings("rawtypes")
1044 final FileBasedConfigurationBuilder fileBuilder = (FileBasedConfigurationBuilder) defBuilder;
1045 final URL url = fileBuilder.getFileHandler().getURL();
1046 currentXMLParameters.setBasePath(url != null ? url.toExternalForm() : fileBuilder.getFileHandler().getBasePath());
1047 }
1048 }
1049 }
1050
1051 /**
1052 * Executes the {@link org.apache.commons.configuration2.builder.DefaultParametersManager DefaultParametersManager}
1053 * stored in the current parameters on the passed in parameters object. If default handlers have been registered for
1054 * this type of parameters, an initialization is now performed. This method is called before the parameters object is
1055 * initialized from the configuration definition file. So default values can be overridden later with concrete property
1056 * definitions.
1057 *
1058 * @param params the parameters to be initialized
1059 * @throws org.apache.commons.configuration2.ex.ConfigurationRuntimeException if an error occurs when copying properties
1060 */
1061 private void initDefaultChildParameters(final BuilderParameters params) {
1062 currentParameters.getChildDefaultParametersManager().initializeParameters(params);
1063 }
1064
1065 /**
1066 * Creates and initializes a default {@code FileSystem} if the definition configuration contains a corresponding
1067 * declaration. The file system returned by this method is used as default for all file-based child configuration
1068 * sources.
1069 *
1070 * @param config the definition configuration
1071 * @return the default {@code FileSystem} (may be <strong>null</strong>)
1072 * @throws ConfigurationException if an error occurs
1073 */
1074 protected FileSystem initFileSystem(final HierarchicalConfiguration<?> config) throws ConfigurationException {
1075 if (config.getMaxIndex(FILE_SYSTEM) == 0) {
1076 final XMLBeanDeclaration decl = new XMLBeanDeclaration(config, FILE_SYSTEM);
1077 return (FileSystem) fetchBeanHelper().createBean(decl);
1078 }
1079 return null;
1080 }
1081
1082 /**
1083 * {@inheritDoc} This implementation processes the definition configuration in order to
1084 * <ul>
1085 * <li>initialize the resulting {@code CombinedConfiguration}</li>
1086 * <li>determine the builders for all configuration sources</li>
1087 * <li>populate the resulting {@code CombinedConfiguration}</li>
1088 * </ul>
1089 */
1090 @Override
1091 protected void initResultInstance(final CombinedConfiguration result) throws ConfigurationException {
1092 super.initResultInstance(result);
1093
1094 currentConfiguration = result;
1095 final HierarchicalConfiguration<?> config = getDefinitionConfiguration();
1096 if (config.getMaxIndex(KEY_COMBINER) < 0) {
1097 // No combiner defined => set default
1098 result.setNodeCombiner(new OverrideCombiner());
1099 }
1100
1101 setUpCurrentParameters();
1102 initNodeCombinerListNodes(result, config, KEY_OVERRIDE_LIST);
1103 registerConfiguredProviders(config);
1104 setUpCurrentXMLParameters();
1105 currentXMLParameters.setFileSystem(initFileSystem(config));
1106 initSystemProperties(config, getBasePath());
1107 registerConfiguredLookups(config, result);
1108 configureEntityResolver(config, currentXMLParameters);
1109 setUpParentInterpolator(currentConfiguration, config);
1110
1111 final ConfigurationSourceData data = getSourceData();
1112 final boolean createBuilders = data.getChildBuilders().isEmpty();
1113 final List<ConfigurationBuilder<? extends Configuration>> overrideBuilders = data.createAndAddConfigurations(result, data.getOverrideSources(),
1114 data.overrideBuilders);
1115 if (createBuilders) {
1116 data.overrideBuilders.addAll(overrideBuilders);
1117 }
1118 if (!data.getUnionSources().isEmpty()) {
1119 final CombinedConfiguration addConfig = createAdditionalsConfiguration(result);
1120 result.addConfiguration(addConfig, ADDITIONAL_NAME);
1121 initNodeCombinerListNodes(addConfig, config, KEY_ADDITIONAL_LIST);
1122 final List<ConfigurationBuilder<? extends Configuration>> unionBuilders = data.createAndAddConfigurations(addConfig, data.unionDeclarations,
1123 data.unionBuilders);
1124 if (createBuilders) {
1125 data.unionBuilders.addAll(unionBuilders);
1126 }
1127 }
1128
1129 result.isEmpty(); // this sets up the node structure
1130 currentConfiguration = null;
1131 }
1132
1133 /**
1134 * Handles a file with system properties that may be defined in the definition configuration. If such property file is
1135 * configured, all of its properties are added to the system properties.
1136 *
1137 * @param config the definition configuration
1138 * @param basePath the base path defined for this builder (may be <strong>null</strong>)
1139 * @throws ConfigurationException if an error occurs.
1140 */
1141 protected void initSystemProperties(final HierarchicalConfiguration<?> config, final String basePath) throws ConfigurationException {
1142 final String fileName = config.getString(KEY_SYSTEM_PROPS);
1143 if (fileName != null) {
1144 try {
1145 SystemConfiguration.setSystemProperties(basePath, fileName);
1146 } catch (final Exception e) {
1147 throw new ConfigurationException(e, "Error setting system properties from %s (basePath = %s)", fileName, basePath);
1148 }
1149 }
1150 }
1151
1152 /**
1153 * Returns the {@code ConfigurationBuilderProvider} for the given tag. This method is called during creation of the
1154 * result configuration. (It is not allowed to call it at another point of time; result is then unpredictable!) It
1155 * supports all default providers and custom providers added through the parameters object as well.
1156 *
1157 * @param tagName the name of the tag
1158 * @return the provider that was registered for this tag or <strong>null</strong> if there is none
1159 */
1160 protected ConfigurationBuilderProvider providerForTag(final String tagName) {
1161 return currentParameters.providerForTag(tagName);
1162 }
1163
1164 /**
1165 * Processes custom {@link Lookup} objects that might be declared in the definition configuration. Each {@code Lookup}
1166 * object is registered at the definition configuration and at the result configuration. It is also added to all child
1167 * configurations added to the resulting combined configuration.
1168 *
1169 * @param defConfig the definition configuration
1170 * @param resultConfig the resulting configuration
1171 * @throws ConfigurationException if an error occurs
1172 */
1173 protected void registerConfiguredLookups(final HierarchicalConfiguration<?> defConfig, final Configuration resultConfig) throws ConfigurationException {
1174 final Map<String, Lookup> lookups = defConfig.configurationsAt(KEY_CONFIGURATION_LOOKUPS).stream().collect(
1175 Collectors.toMap(config -> config.getString(KEY_LOOKUP_KEY), config -> (Lookup) fetchBeanHelper().createBean(new XMLBeanDeclaration(config))));
1176
1177 if (!lookups.isEmpty()) {
1178 final ConfigurationInterpolator defCI = defConfig.getInterpolator();
1179 if (defCI != null) {
1180 defCI.registerLookups(lookups);
1181 }
1182 resultConfig.getInterpolator().registerLookups(lookups);
1183 }
1184 }
1185
1186 /**
1187 * Registers providers defined in the configuration.
1188 *
1189 * @param defConfig the definition configuration
1190 */
1191 private void registerConfiguredProviders(final HierarchicalConfiguration<?> defConfig) {
1192 defConfig.configurationsAt(KEY_CONFIGURATION_PROVIDERS).forEach(config -> {
1193 final XMLBeanDeclaration decl = new XMLBeanDeclaration(config);
1194 final String key = config.getString(KEY_PROVIDER_KEY);
1195 currentParameters.registerProvider(key, (ConfigurationBuilderProvider) fetchBeanHelper().createBean(decl));
1196 });
1197 }
1198
1199 /**
1200 * {@inheritDoc} This implementation resets some specific internal state of this builder.
1201 */
1202 @Override
1203 public synchronized void resetParameters() {
1204 super.resetParameters();
1205 definitionBuilder = null;
1206 definitionConfiguration = null;
1207 currentParameters = null;
1208 currentXMLParameters = null;
1209
1210 if (sourceData != null) {
1211 sourceData.cleanUp();
1212 sourceData = null;
1213 }
1214 }
1215
1216 /**
1217 * Initializes the current parameters object. This object has either been passed at builder configuration time or it is
1218 * newly created. In any case, it is manipulated during result creation.
1219 */
1220 private void setUpCurrentParameters() {
1221 currentParameters = CombinedBuilderParametersImpl.fromParameters(getParameters(), true);
1222 currentParameters.registerMissingProviders(DEFAULT_PROVIDERS_MAP);
1223 }
1224
1225 /**
1226 * Sets up an XML parameters object which is used to store properties related to XML and file-based configurations
1227 * during creation of the result configuration. The properties stored in this object can be inherited to child
1228 * configurations.
1229 *
1230 * @throws ConfigurationException if an error occurs
1231 */
1232 private void setUpCurrentXMLParameters() throws ConfigurationException {
1233 currentXMLParameters = new XMLBuilderParametersImpl();
1234 initDefaultBasePath();
1235 }
1236
1237 /**
1238 * Obtains the {@code ConfigurationBuilder} object which provides access to the configuration containing the definition
1239 * of the combined configuration to create. If a definition builder is defined in the parameters, it is used. Otherwise,
1240 * we check whether the combined builder parameters object contains a parameters object for the definition builder. If
1241 * this is the case, a builder for an {@code XMLConfiguration} is created and configured with this object. As a last
1242 * resort, it is looked for a {@link FileBasedBuilderParametersImpl} object in the properties. If found, also a XML
1243 * configuration builder is created which loads this file. Note: This method is called from a synchronized block.
1244 *
1245 * @param params the current parameters for this builder
1246 * @return the builder for the definition configuration
1247 * @throws ConfigurationException if an error occurs
1248 */
1249 protected ConfigurationBuilder<? extends HierarchicalConfiguration<?>> setupDefinitionBuilder(final Map<String, Object> params)
1250 throws ConfigurationException {
1251 final CombinedBuilderParametersImpl cbParams = CombinedBuilderParametersImpl.fromParameters(params);
1252 if (cbParams != null) {
1253 final ConfigurationBuilder<? extends HierarchicalConfiguration<?>> defBuilder = cbParams.getDefinitionBuilder();
1254 if (defBuilder != null) {
1255 return defBuilder;
1256 }
1257
1258 if (cbParams.getDefinitionBuilderParameters() != null) {
1259 return createXMLDefinitionBuilder(cbParams.getDefinitionBuilderParameters());
1260 }
1261 }
1262
1263 final BuilderParameters fileParams = FileBasedBuilderParametersImpl.fromParameters(params);
1264 if (fileParams != null) {
1265 return createXMLDefinitionBuilder(fileParams);
1266 }
1267
1268 throw new ConfigurationException("No builder for configuration definition specified.");
1269 }
1270
1271 /**
1272 * Sets up a parent {@code ConfigurationInterpolator} object. This object has a default {@link Lookup} querying the
1273 * resulting combined configuration. Thus interpolation works globally across all configuration sources.
1274 *
1275 * @param resultConfig the result configuration
1276 * @param defConfig the definition configuration
1277 */
1278 private void setUpParentInterpolator(final Configuration resultConfig, final Configuration defConfig) {
1279 parentInterpolator = new ConfigurationInterpolator();
1280 parentInterpolator.addDefaultLookup(new ConfigurationLookup(resultConfig));
1281 final ConfigurationInterpolator defInterpolator = defConfig.getInterpolator();
1282 if (defInterpolator != null) {
1283 defInterpolator.setParentInterpolator(parentInterpolator);
1284 }
1285 }
1286 }