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 * https://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 */ 017package org.apache.commons.configuration2.builder; 018 019import java.util.ArrayList; 020import java.util.Collection; 021import java.util.HashMap; 022import java.util.Map; 023 024import org.apache.commons.configuration2.ConfigurationDecoder; 025import org.apache.commons.configuration2.beanutils.BeanHelper; 026import org.apache.commons.configuration2.convert.ConversionHandler; 027import org.apache.commons.configuration2.convert.ListDelimiterHandler; 028import org.apache.commons.configuration2.interpol.ConfigurationInterpolator; 029import org.apache.commons.configuration2.interpol.InterpolatorSpecification; 030import org.apache.commons.configuration2.interpol.Lookup; 031import org.apache.commons.configuration2.io.ConfigurationLogger; 032import org.apache.commons.configuration2.sync.Synchronizer; 033 034/** 035 * <p> 036 * An implementation of {@code BuilderParameters} which handles the parameters of a {@link ConfigurationBuilder} common 037 * to all concrete {@code Configuration} implementations. 038 * </p> 039 * <p> 040 * This class provides methods for setting standard properties supported by the {@code AbstractConfiguration} base 041 * class. A fluent interface can be used to set property values. 042 * </p> 043 * <p> 044 * This class is not thread-safe. It is intended that an instance is constructed and initialized by a single thread 045 * during configuration of a {@code ConfigurationBuilder}. 046 * </p> 047 * 048 * @since 2.0 049 */ 050public class BasicBuilderParameters implements Cloneable, BuilderParameters, BasicBuilderProperties<BasicBuilderParameters> { 051 052 /** The key of the <em>throwExceptionOnMissing</em> property. */ 053 private static final String PROP_THROW_EXCEPTION_ON_MISSING = "throwExceptionOnMissing"; 054 055 /** The key of the <em>listDelimiterHandler</em> property. */ 056 private static final String PROP_LIST_DELIMITER_HANDLER = "listDelimiterHandler"; 057 058 /** The key of the <em>logger</em> property. */ 059 private static final String PROP_LOGGER = "logger"; 060 061 /** The key for the <em>interpolator</em> property. */ 062 private static final String PROP_INTERPOLATOR = "interpolator"; 063 064 /** The key for the <em>prefixLookups</em> property. */ 065 private static final String PROP_PREFIX_LOOKUPS = "prefixLookups"; 066 067 /** The key for the <em>defaultLookups</em> property. */ 068 private static final String PROP_DEFAULT_LOOKUPS = "defaultLookups"; 069 070 /** The key for the <em>parentInterpolator</em> property. */ 071 private static final String PROP_PARENT_INTERPOLATOR = "parentInterpolator"; 072 073 /** The key for the <em>synchronizer</em> property. */ 074 private static final String PROP_SYNCHRONIZER = "synchronizer"; 075 076 /** The key for the <em>conversionHandler</em> property. */ 077 private static final String PROP_CONVERSION_HANDLER = "conversionHandler"; 078 079 /** The key for the <em>configurationDecoder</em> property. */ 080 private static final String PROP_CONFIGURATION_DECODER = "configurationDecoder"; 081 082 /** The key for the {@code BeanHelper}. */ 083 private static final String PROP_BEAN_HELPER = RESERVED_PARAMETER_PREFIX + "BeanHelper"; 084 085 /** 086 * Checks whether a map with parameters is present. Throws an exception if not. 087 * 088 * @param params the map with parameters to check 089 * @throws IllegalArgumentException if the map is <strong>null</strong> 090 */ 091 private static void checkParameters(final Map<String, Object> params) { 092 if (params == null) { 093 throw new IllegalArgumentException("Parameters map must not be null!"); 094 } 095 } 096 097 /** 098 * Creates defensive copies for collection structures when constructing the map with parameters. It should not be 099 * 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}