View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *     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;
18  
19  import java.util.ArrayList;
20  import java.util.Collection;
21  import java.util.HashMap;
22  import java.util.Map;
23  
24  import org.apache.commons.configuration2.ConfigurationDecoder;
25  import org.apache.commons.configuration2.beanutils.BeanHelper;
26  import org.apache.commons.configuration2.convert.ConversionHandler;
27  import org.apache.commons.configuration2.convert.ListDelimiterHandler;
28  import org.apache.commons.configuration2.interpol.ConfigurationInterpolator;
29  import org.apache.commons.configuration2.interpol.InterpolatorSpecification;
30  import org.apache.commons.configuration2.interpol.Lookup;
31  import org.apache.commons.configuration2.io.ConfigurationLogger;
32  import org.apache.commons.configuration2.sync.Synchronizer;
33  
34  /**
35   * <p>
36   * An implementation of {@code BuilderParameters} which handles the parameters of a {@link ConfigurationBuilder} common
37   * to all concrete {@code Configuration} implementations.
38   * </p>
39   * <p>
40   * This class provides methods for setting standard properties supported by the {@code AbstractConfiguration} base
41   * class. A fluent interface can be used to set property values.
42   * </p>
43   * <p>
44   * This class is not thread-safe. It is intended that an instance is constructed and initialized by a single thread
45   * during configuration of a {@code ConfigurationBuilder}.
46   * </p>
47   *
48   * @since 2.0
49   */
50  public class BasicBuilderParameters implements Cloneable, BuilderParameters, BasicBuilderProperties<BasicBuilderParameters> {
51  
52      /** The key of the <em>throwExceptionOnMissing</em> property. */
53      private static final String PROP_THROW_EXCEPTION_ON_MISSING = "throwExceptionOnMissing";
54  
55      /** The key of the <em>listDelimiterHandler</em> property. */
56      private static final String PROP_LIST_DELIMITER_HANDLER = "listDelimiterHandler";
57  
58      /** The key of the <em>logger</em> property. */
59      private static final String PROP_LOGGER = "logger";
60  
61      /** The key for the <em>interpolator</em> property. */
62      private static final String PROP_INTERPOLATOR = "interpolator";
63  
64      /** The key for the <em>prefixLookups</em> property. */
65      private static final String PROP_PREFIX_LOOKUPS = "prefixLookups";
66  
67      /** The key for the <em>defaultLookups</em> property. */
68      private static final String PROP_DEFAULT_LOOKUPS = "defaultLookups";
69  
70      /** The key for the <em>parentInterpolator</em> property. */
71      private static final String PROP_PARENT_INTERPOLATOR = "parentInterpolator";
72  
73      /** The key for the <em>synchronizer</em> property. */
74      private static final String PROP_SYNCHRONIZER = "synchronizer";
75  
76      /** The key for the <em>conversionHandler</em> property. */
77      private static final String PROP_CONVERSION_HANDLER = "conversionHandler";
78  
79      /** The key for the <em>configurationDecoder</em> property. */
80      private static final String PROP_CONFIGURATION_DECODER = "configurationDecoder";
81  
82      /** The key for the {@code BeanHelper}. */
83      private static final String PROP_BEAN_HELPER = RESERVED_PARAMETER_PREFIX + "BeanHelper";
84  
85      /**
86       * Checks whether a map with parameters is present. Throws an exception if not.
87       *
88       * @param params the map with parameters to check
89       * @throws IllegalArgumentException if the map is <strong>null</strong>
90       */
91      private static void checkParameters(final Map<String, Object> params) {
92          if (params == null) {
93              throw new IllegalArgumentException("Parameters map must not be null!");
94          }
95      }
96  
97      /**
98       * Creates defensive copies for collection structures when constructing the map with parameters. It should not be
99       * possible to modify this object's internal state when having access to the parameters map.
100      *
101      * @param params the map with parameters to be passed to the caller
102      */
103     private static void createDefensiveCopies(final HashMap<String, Object> params) {
104         final Map<String, ? extends Lookup> prefixLookups = fetchPrefixLookups(params);
105         if (prefixLookups != null) {
106             params.put(PROP_PREFIX_LOOKUPS, new HashMap<>(prefixLookups));
107         }
108         final Collection<? extends Lookup> defLookups = fetchDefaultLookups(params);
109         if (defLookups != null) {
110             params.put(PROP_DEFAULT_LOOKUPS, new ArrayList<>(defLookups));
111         }
112     }
113 
114     /**
115      * Tests whether the passed in map with parameters contains a valid collection with default lookups. This method works
116      * like {@link #fetchAndCheckPrefixLookups(Map)}, but tests the default lookups collection.
117      *
118      * @param params the map with parameters
119      * @return the collection with default lookups (may be <strong>null</strong>)
120      * @throws IllegalArgumentException if invalid data is found
121      */
122     private static Collection<? extends Lookup> fetchAndCheckDefaultLookups(final Map<String, Object> params) {
123         final Collection<?> col = fetchParameter(params, PROP_DEFAULT_LOOKUPS, Collection.class);
124         if (col == null) {
125             return null;
126         }
127 
128         if (col.stream().noneMatch(Lookup.class::isInstance)) {
129             throw new IllegalArgumentException("Collection with default lookups contains invalid data: " + col);
130         }
131         return fetchDefaultLookups(params);
132     }
133 
134     /**
135      * Tests whether the passed in map with parameters contains a map with prefix lookups. This method is used if the
136      * parameters map is from an insecure source and we cannot be sure that it contains valid data. Therefore, we have to
137      * map that the key for the prefix lookups actually points to a map containing keys and values of expected data types.
138      *
139      * @param params the parameters map
140      * @return the obtained map with prefix lookups
141      * @throws IllegalArgumentException if the map contains invalid data
142      */
143     private static Map<String, ? extends Lookup> fetchAndCheckPrefixLookups(final Map<String, Object> params) {
144         final Map<?, ?> prefixes = fetchParameter(params, PROP_PREFIX_LOOKUPS, Map.class);
145         if (prefixes == null) {
146             return null;
147         }
148         prefixes.forEach((k, v) -> {
149             if (!(k instanceof String) || !(v instanceof Lookup)) {
150                 throw new IllegalArgumentException("Map with prefix lookups contains invalid data: " + prefixes);
151             }
152         });
153         return fetchPrefixLookups(params);
154     }
155 
156     /**
157      * Obtains the {@code BeanHelper} object from the specified map with parameters. This method can be used to obtain an
158      * instance from a parameters map that has been set via the {@code setBeanHelper()} method. If no such instance is
159      * found, result is <strong>null</strong>.
160      *
161      * @param params the map with parameters (must not be <strong>null</strong>)
162      * @return the {@code BeanHelper} stored in this map or <strong>null</strong>
163      * @throws IllegalArgumentException if the map is <strong>null</strong>
164      */
165     public static BeanHelper fetchBeanHelper(final Map<String, Object> params) {
166         checkParameters(params);
167         return (BeanHelper) params.get(PROP_BEAN_HELPER);
168     }
169 
170     /**
171      * Obtains the collection with default lookups from the parameters map.
172      *
173      * @param params the map with parameters
174      * @return the collection with default lookups (may be <strong>null</strong>)
175      */
176     private static Collection<? extends Lookup> fetchDefaultLookups(final Map<String, Object> params) {
177         // This is safe to cast because we either have full control over the map
178         // and thus know the types of the contained values or have checked
179         // the content before
180         @SuppressWarnings("unchecked")
181         final Collection<? extends Lookup> defLookups = (Collection<? extends Lookup>) params.get(PROP_DEFAULT_LOOKUPS);
182         return defLookups;
183     }
184 
185     /**
186      * Obtains a specification for a {@link ConfigurationInterpolator} from the specified map with parameters. All
187      * properties related to interpolation are evaluated and added to the specification object.
188      *
189      * @param params the map with parameters (must not be <strong>null</strong>)
190      * @return an {@code InterpolatorSpecification} object constructed with data from the map
191      * @throws IllegalArgumentException if the map is <strong>null</strong> or contains invalid data
192      */
193     public static InterpolatorSpecification fetchInterpolatorSpecification(final Map<String, Object> params) {
194         checkParameters(params);
195         return new InterpolatorSpecification.Builder().withInterpolator(fetchParameter(params, PROP_INTERPOLATOR, ConfigurationInterpolator.class))
196             .withParentInterpolator(fetchParameter(params, PROP_PARENT_INTERPOLATOR, ConfigurationInterpolator.class))
197             .withPrefixLookups(fetchAndCheckPrefixLookups(params)).withDefaultLookups(fetchAndCheckDefaultLookups(params)).create();
198     }
199 
200     /**
201      * Obtains a parameter from a map and performs a type check.
202      *
203      * @param params the map with parameters
204      * @param key the key of the parameter
205      * @param expClass the expected class of the parameter value
206      * @param <T> the parameter type
207      * @return the value of the parameter in the correct data type
208      * @throws IllegalArgumentException if the parameter is not of the expected type
209      */
210     private static <T> T fetchParameter(final Map<String, Object> params, final String key, final Class<T> expClass) {
211         final Object value = params.get(key);
212         if (value == null) {
213             return null;
214         }
215         if (!expClass.isInstance(value)) {
216             throw new IllegalArgumentException(String.format("Parameter %s is not of type %s!", key, expClass.getSimpleName()));
217         }
218         return expClass.cast(value);
219     }
220 
221     /**
222      * Obtains the map with prefix lookups from the parameters map.
223      *
224      * @param params the map with parameters
225      * @return the map with prefix lookups (may be <strong>null</strong>)
226      */
227     private static Map<String, ? extends Lookup> fetchPrefixLookups(final Map<String, Object> params) {
228         // This is safe to cast because we either have full control over the map
229         // and thus know the types of the contained values or have checked
230         // the content before
231         @SuppressWarnings("unchecked")
232         final Map<String, ? extends Lookup> prefixLookups = (Map<String, ? extends Lookup>) params.get(PROP_PREFIX_LOOKUPS);
233         return prefixLookups;
234     }
235 
236     /** The map for storing the current property values. */
237     private Map<String, Object> properties;
238 
239     /**
240      * Creates a new instance of {@code BasicBuilderParameters}.
241      */
242     public BasicBuilderParameters() {
243         properties = new HashMap<>();
244     }
245 
246     /**
247      * Clones this object. This is useful because multiple builder instances may use a similar set of parameters. However,
248      * single instances of parameter objects must not assigned to multiple builders. Therefore, cloning a parameters object
249      * provides a solution for this use case. This method creates a new parameters object with the same content as this one.
250      * The internal map storing the parameter values is cloned, too, also collection structures contained in this map.
251      * However, no a full deep clone operation is performed. Objects like a {@code ConfigurationInterpolator} or
252      * {@code Lookup}s are shared between this and the newly created instance.
253      *
254      * @return a clone of this object
255      */
256     @Override
257     public BasicBuilderParameters clone() {
258         try {
259             final BasicBuilderParameters copy = (BasicBuilderParameters) super.clone();
260             copy.properties = getParameters();
261             return copy;
262         } catch (final CloneNotSupportedException cnex) {
263             // should not happen
264             throw new AssertionError(cnex);
265         }
266     }
267 
268     /**
269      * Copies a number of properties from the given map into this object. Properties are only copied if they are defined in
270      * the source map.
271      *
272      * @param source the source map
273      * @param keys the keys to be copied
274      */
275     protected void copyPropertiesFrom(final Map<String, ?> source, final String... keys) {
276         for (final String key : keys) {
277             final Object value = source.get(key);
278             if (value != null) {
279                 storeProperty(key, value);
280             }
281         }
282     }
283 
284     /**
285      * Obtains the value of the specified property from the internal map. This method can be used by derived classes if a
286      * specific property is to be accessed. If the given key is not found, result is <strong>null</strong>.
287      *
288      * @param key the key of the property in question
289      * @return the value of the property with this key or <strong>null</strong>
290      */
291     protected Object fetchProperty(final String key) {
292         return properties.get(key);
293     }
294 
295     /**
296      * {@inheritDoc} This implementation returns a copy of the internal parameters map with the values set so far.
297      * Collection structures (for example for lookup objects) are stored as defensive copies, so the original data cannot be
298      * modified.
299      */
300     @Override
301     public Map<String, Object> getParameters() {
302         final HashMap<String, Object> result = new HashMap<>(properties);
303         if (result.containsKey(PROP_INTERPOLATOR)) {
304             // A custom ConfigurationInterpolator overrides lookups
305             result.remove(PROP_PREFIX_LOOKUPS);
306             result.remove(PROP_DEFAULT_LOOKUPS);
307             result.remove(PROP_PARENT_INTERPOLATOR);
308         }
309 
310         createDefensiveCopies(result);
311         return result;
312     }
313 
314     /**
315      * Inherits properties from the specified map. This can be used for instance to reuse parameters from one builder in
316      * another builder - also in parent-child relations in which a parent builder creates child builders. The purpose of
317      * this method is to let a concrete implementation decide which properties can be inherited. Because parameters are
318      * basically organized as a map it would be possible to simply copy over all properties from the source object. However,
319      * this is not appropriate in all cases. For instance, some properties - like a {@code ConfigurationInterpolator} - are
320      * tightly connected to a configuration and cannot be reused in a different context. For other properties, for example a file
321      * name, it does not make sense to copy it. Therefore, an implementation has to be explicit in the properties it wants
322      * to take over.
323      *
324      * @param source the source properties to inherit from
325      * @throws IllegalArgumentException if the source map is <strong>null</strong>
326      */
327     public void inheritFrom(final Map<String, ?> source) {
328         if (source == null) {
329             throw new IllegalArgumentException("Source properties must not be null!");
330         }
331         copyPropertiesFrom(source, PROP_BEAN_HELPER, PROP_CONFIGURATION_DECODER, PROP_CONVERSION_HANDLER, PROP_LIST_DELIMITER_HANDLER, PROP_LOGGER,
332             PROP_SYNCHRONIZER, PROP_THROW_EXCEPTION_ON_MISSING);
333     }
334 
335     /**
336      * Merges this object with the given parameters object. This method adds all property values defined by the passed in
337      * parameters object to the internal storage which are not already in. So properties already defined in this object take
338      * precedence. Property names starting with the reserved parameter prefix are ignored.
339      *
340      * @param p the object whose properties should be merged (must not be <strong>null</strong>)
341      * @throws IllegalArgumentException if the passed in object is <strong>null</strong>
342      */
343     public void merge(final BuilderParameters p) {
344         if (p == null) {
345             throw new IllegalArgumentException("Parameters to merge must not be null!");
346         }
347         p.getParameters().forEach((k, v) -> {
348             if (!properties.containsKey(k) && !k.startsWith(RESERVED_PARAMETER_PREFIX)) {
349                 storeProperty(k, v);
350             }
351         });
352     }
353 
354     /**
355      * {@inheritDoc} This implementation stores the passed in {@code BeanHelper} object in the internal parameters map, but
356      * uses a reserved key, so that it is not used for the initialization of properties of the managed configuration object.
357      * The {@code fetchBeanHelper()} method can be used to obtain the {@code BeanHelper} instance from a parameters map.
358      */
359     @Override
360     public BasicBuilderParameters setBeanHelper(final BeanHelper beanHelper) {
361         return setProperty(PROP_BEAN_HELPER, beanHelper);
362     }
363 
364     /**
365      * {@inheritDoc} This implementation stores the passed in {@code ConfigurationDecoder} object in the internal parameters
366      * map.
367      */
368     @Override
369     public BasicBuilderParameters setConfigurationDecoder(final ConfigurationDecoder decoder) {
370         return setProperty(PROP_CONFIGURATION_DECODER, decoder);
371     }
372 
373     /**
374      * {@inheritDoc} This implementation stores the passed in {@code ConversionHandler} object in the internal parameters
375      * map.
376      */
377     @Override
378     public BasicBuilderParameters setConversionHandler(final ConversionHandler handler) {
379         return setProperty(PROP_CONVERSION_HANDLER, handler);
380     }
381 
382     /**
383      * {@inheritDoc} A defensive copy of the passed in collection is created. A <strong>null</strong> argument causes all default
384      * lookups to be removed from the internal parameters map.
385      */
386     @Override
387     public BasicBuilderParameters setDefaultLookups(final Collection<? extends Lookup> lookups) {
388         if (lookups == null) {
389             properties.remove(PROP_DEFAULT_LOOKUPS);
390             return this;
391         }
392         return setProperty(PROP_DEFAULT_LOOKUPS, new ArrayList<>(lookups));
393     }
394 
395     /**
396      * {@inheritDoc} The passed in {@code ConfigurationInterpolator} is set without modifications.
397      */
398     @Override
399     public BasicBuilderParameters setInterpolator(final ConfigurationInterpolator ci) {
400         return setProperty(PROP_INTERPOLATOR, ci);
401     }
402 
403     /**
404      * Sets the value of the <em>listDelimiterHandler</em> property. This property defines the object responsible for
405      * dealing with list delimiter and escaping characters. Note:
406      * {@link org.apache.commons.configuration2.AbstractConfiguration AbstractConfiguration} does not allow setting this
407      * property to <strong>null</strong>. If the default {@code ListDelimiterHandler} is to be used, do not call this method.
408      *
409      * @param handler the {@code ListDelimiterHandler}
410      * @return a reference to this object for method chaining
411      */
412     @Override
413     public BasicBuilderParameters setListDelimiterHandler(final ListDelimiterHandler handler) {
414         return setProperty(PROP_LIST_DELIMITER_HANDLER, handler);
415     }
416 
417     /**
418      * Sets the <em>logger</em> property. With this property a concrete {@code Log} object can be set for the configuration.
419      * Thus logging behavior can be controlled.
420      *
421      * @param log the {@code Log} for the configuration produced by this builder
422      * @return a reference to this object for method chaining
423      */
424     @Override
425     public BasicBuilderParameters setLogger(final ConfigurationLogger log) {
426         return setProperty(PROP_LOGGER, log);
427     }
428 
429     /**
430      * {@inheritDoc} This implementation stores the passed in {@code ConfigurationInterpolator} object in the internal
431      * parameters map.
432      */
433     @Override
434     public BasicBuilderParameters setParentInterpolator(final ConfigurationInterpolator parent) {
435         return setProperty(PROP_PARENT_INTERPOLATOR, parent);
436     }
437 
438     /**
439      * {@inheritDoc} A defensive copy of the passed in map is created. A <strong>null</strong> argument causes all prefix lookups to
440      * be removed from the internal parameters map.
441      */
442     @Override
443     public BasicBuilderParameters setPrefixLookups(final Map<String, ? extends Lookup> lookups) {
444         if (lookups == null) {
445             properties.remove(PROP_PREFIX_LOOKUPS);
446             return this;
447         }
448         return setProperty(PROP_PREFIX_LOOKUPS, new HashMap<>(lookups));
449     }
450 
451     /**
452      * Helper method for setting a property value.
453      *
454      * @param key the key of the property
455      * @param value the value of the property
456      * @return a reference to this object
457      */
458     private BasicBuilderParameters setProperty(final String key, final Object value) {
459         storeProperty(key, value);
460         return this;
461     }
462 
463     /**
464      * {@inheritDoc} This implementation stores the passed in {@code Synchronizer} object in the internal parameters map.
465      */
466     @Override
467     public BasicBuilderParameters setSynchronizer(final Synchronizer sync) {
468         return setProperty(PROP_SYNCHRONIZER, sync);
469     }
470 
471     /**
472      * Sets the value of the <em>throwExceptionOnMissing</em> property. This property controls the configuration's behavior
473      * if missing properties are queried: a value of <strong>true</strong> causes the configuration to throw an exception, for a value
474      * of <strong>false</strong> it will return <strong>null</strong> values. (Note: Methods returning a primitive data type will always throw
475      * an exception if the property is not defined.)
476      *
477      * @param b the value of the property
478      * @return a reference to this object for method chaining
479      */
480     @Override
481     public BasicBuilderParameters setThrowExceptionOnMissing(final boolean b) {
482         return setProperty(PROP_THROW_EXCEPTION_ON_MISSING, Boolean.valueOf(b));
483     }
484 
485     /**
486      * Sets a property for this parameters object. Properties are stored in an internal map. With this method a new entry
487      * can be added to this map. If the value is <strong>null</strong>, the key is removed from the internal map. This method can be
488      * used by sub classes which also store properties in a map.
489      *
490      * @param key the key of the property
491      * @param value the value of the property
492      */
493     protected void storeProperty(final String key, final Object value) {
494         if (value == null) {
495             properties.remove(key);
496         } else {
497             properties.put(key, value);
498         }
499     }
500 }