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