001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 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.io.ConfigurationLogger; 026import org.apache.commons.configuration2.beanutils.BeanHelper; 027import org.apache.commons.configuration2.convert.ConversionHandler; 028import org.apache.commons.configuration2.convert.ListDelimiterHandler; 029import org.apache.commons.configuration2.interpol.ConfigurationInterpolator; 030import org.apache.commons.configuration2.interpol.InterpolatorSpecification; 031import org.apache.commons.configuration2.interpol.Lookup; 032import org.apache.commons.configuration2.sync.Synchronizer; 033 034/** 035 * <p> 036 * An implementation of {@code BuilderParameters} which handles the parameters 037 * of a {@link ConfigurationBuilder} common to all concrete 038 * {@code Configuration} implementations. 039 * </p> 040 * <p> 041 * This class provides methods for setting standard properties supported by the 042 * {@code AbstractConfiguration} base class. A fluent interface can be used to 043 * set property values. 044 * </p> 045 * <p> 046 * This class is not thread-safe. It is intended that an instance is constructed 047 * and initialized by a single thread during configuration of a 048 * {@code ConfigurationBuilder}. 049 * </p> 050 * 051 * @version $Id: BasicBuilderParameters.java 1842194 2018-09-27 22:24:23Z ggregory $ 052 * @since 2.0 053 */ 054public class BasicBuilderParameters implements Cloneable, BuilderParameters, 055 BasicBuilderProperties<BasicBuilderParameters> 056{ 057 /** The key of the <em>throwExceptionOnMissing</em> property. */ 058 private static final String PROP_THROW_EXCEPTION_ON_MISSING = 059 "throwExceptionOnMissing"; 060 061 /** The key of the <em>listDelimiterHandler</em> property. */ 062 private static final String PROP_LIST_DELIMITER_HANDLER = "listDelimiterHandler"; 063 064 /** The key of the <em>logger</em> property. */ 065 private static final String PROP_LOGGER = "logger"; 066 067 /** The key for the <em>interpolator</em> property. */ 068 private static final String PROP_INTERPOLATOR = "interpolator"; 069 070 /** The key for the <em>prefixLookups</em> property. */ 071 private static final String PROP_PREFIX_LOOKUPS = "prefixLookups"; 072 073 /** The key for the <em>defaultLookups</em> property. */ 074 private static final String PROP_DEFAULT_LOOKUPS = "defaultLookups"; 075 076 /** The key for the <em>parentInterpolator</em> property. */ 077 private static final String PROP_PARENT_INTERPOLATOR = "parentInterpolator"; 078 079 /** The key for the <em>synchronizer</em> property. */ 080 private static final String PROP_SYNCHRONIZER = "synchronizer"; 081 082 /** The key for the <em>conversionHandler</em> property. */ 083 private static final String PROP_CONVERSION_HANDLER = "conversionHandler"; 084 085 /** The key for the <em>configurationDecoder</em> property. */ 086 private static final String PROP_CONFIGURATION_DECODER = "configurationDecoder"; 087 088 /** The key for the {@code BeanHelper}. */ 089 private static final String PROP_BEAN_HELPER = RESERVED_PARAMETER_PREFIX 090 + "BeanHelper"; 091 092 /** The map for storing the current property values. */ 093 private Map<String, Object> properties; 094 095 /** 096 * Creates a new instance of {@code BasicBuilderParameters}. 097 */ 098 public BasicBuilderParameters() 099 { 100 properties = new HashMap<>(); 101 } 102 103 /** 104 * {@inheritDoc} This implementation returns a copy of the internal 105 * parameters map with the values set so far. Collection structures 106 * (e.g. for lookup objects) are stored as defensive copies, so the 107 * original data cannot be modified. 108 */ 109 @Override 110 public Map<String, Object> getParameters() 111 { 112 final HashMap<String, Object> result = 113 new HashMap<>(properties); 114 if (result.containsKey(PROP_INTERPOLATOR)) 115 { 116 // A custom ConfigurationInterpolator overrides lookups 117 result.remove(PROP_PREFIX_LOOKUPS); 118 result.remove(PROP_DEFAULT_LOOKUPS); 119 result.remove(PROP_PARENT_INTERPOLATOR); 120 } 121 122 createDefensiveCopies(result); 123 return result; 124 } 125 126 /** 127 * Sets the <em>logger</em> property. With this property a concrete 128 * {@code Log} object can be set for the configuration. Thus logging 129 * behavior can be controlled. 130 * 131 * @param log the {@code Log} for the configuration produced by this builder 132 * @return a reference to this object for method chaining 133 */ 134 @Override 135 public BasicBuilderParameters setLogger(final ConfigurationLogger log) 136 { 137 return setProperty(PROP_LOGGER, log); 138 } 139 140 /** 141 * Sets the value of the <em>throwExceptionOnMissing</em> property. This 142 * property controls the configuration's behavior if missing properties are 143 * queried: a value of <b>true</b> causes the configuration to throw an 144 * exception, for a value of <b>false</b> it will return <b>null</b> values. 145 * (Note: Methods returning a primitive data type will always throw an 146 * exception if the property is not defined.) 147 * 148 * @param b the value of the property 149 * @return a reference to this object for method chaining 150 */ 151 @Override 152 public BasicBuilderParameters setThrowExceptionOnMissing(final boolean b) 153 { 154 return setProperty(PROP_THROW_EXCEPTION_ON_MISSING, Boolean.valueOf(b)); 155 } 156 157 /** 158 * Sets the value of the <em>listDelimiterHandler</em> property. This 159 * property defines the object responsible for dealing with list delimiter 160 * and escaping characters. Note: 161 * {@link org.apache.commons.configuration2.AbstractConfiguration AbstractConfiguration} 162 * does not allow setting this property to <b>null</b>. If the default 163 * {@code ListDelimiterHandler} is to be used, do not call this method. 164 * 165 * @param handler the {@code ListDelimiterHandler} 166 * @return a reference to this object for method chaining 167 */ 168 @Override 169 public BasicBuilderParameters setListDelimiterHandler( 170 final ListDelimiterHandler handler) 171 { 172 return setProperty(PROP_LIST_DELIMITER_HANDLER, handler); 173 } 174 175 /** 176 * {@inheritDoc} The passed in {@code ConfigurationInterpolator} is set 177 * without modifications. 178 */ 179 @Override 180 public BasicBuilderParameters setInterpolator(final ConfigurationInterpolator ci) 181 { 182 return setProperty(PROP_INTERPOLATOR, ci); 183 } 184 185 /** 186 * {@inheritDoc} A defensive copy of the passed in map is created. A 187 * <b>null</b> argument causes all prefix lookups to be removed from the 188 * internal parameters map. 189 */ 190 @Override 191 public BasicBuilderParameters setPrefixLookups( 192 final Map<String, ? extends Lookup> lookups) 193 { 194 if (lookups == null) 195 { 196 properties.remove(PROP_PREFIX_LOOKUPS); 197 return this; 198 } 199 return setProperty(PROP_PREFIX_LOOKUPS, 200 new HashMap<>(lookups)); 201 } 202 203 /** 204 * {@inheritDoc} A defensive copy of the passed in collection is created. A 205 * <b>null</b> argument causes all default lookups to be removed from the 206 * internal parameters map. 207 */ 208 @Override 209 public BasicBuilderParameters setDefaultLookups( 210 final Collection<? extends Lookup> lookups) 211 { 212 if (lookups == null) 213 { 214 properties.remove(PROP_DEFAULT_LOOKUPS); 215 return this; 216 } 217 return setProperty(PROP_DEFAULT_LOOKUPS, new ArrayList<>( 218 lookups)); 219 } 220 221 /** 222 * {@inheritDoc} This implementation stores the passed in 223 * {@code ConfigurationInterpolator} object in the internal parameters map. 224 */ 225 @Override 226 public BasicBuilderParameters setParentInterpolator( 227 final ConfigurationInterpolator parent) 228 { 229 return setProperty(PROP_PARENT_INTERPOLATOR, parent); 230 } 231 232 /** 233 * {@inheritDoc} This implementation stores the passed in 234 * {@code Synchronizer} object in the internal parameters map. 235 */ 236 @Override 237 public BasicBuilderParameters setSynchronizer(final Synchronizer sync) 238 { 239 return setProperty(PROP_SYNCHRONIZER, sync); 240 } 241 242 /** 243 * {@inheritDoc} This implementation stores the passed in 244 * {@code ConversionHandler} object in the internal parameters map. 245 */ 246 @Override 247 public BasicBuilderParameters setConversionHandler(final ConversionHandler handler) 248 { 249 return setProperty(PROP_CONVERSION_HANDLER, handler); 250 } 251 252 /** 253 * {@inheritDoc} This implementation stores the passed in {@code BeanHelper} 254 * object in the internal parameters map, but uses a reserved key, so that 255 * it is not used for the initialization of properties of the managed 256 * configuration object. The {@code fetchBeanHelper()} method can be used to 257 * obtain the {@code BeanHelper} instance from a parameters map. 258 */ 259 @Override 260 public BasicBuilderParameters setBeanHelper(final BeanHelper beanHelper) 261 { 262 return setProperty(PROP_BEAN_HELPER, beanHelper); 263 } 264 265 /** 266 * {@inheritDoc} This implementation stores the passed in 267 * {@code ConfigurationDecoder} object in the internal parameters map. 268 */ 269 @Override 270 public BasicBuilderParameters setConfigurationDecoder( 271 final ConfigurationDecoder decoder) 272 { 273 return setProperty(PROP_CONFIGURATION_DECODER, decoder); 274 } 275 276 /** 277 * Merges this object with the given parameters object. This method adds all 278 * property values defined by the passed in parameters object to the 279 * internal storage which are not already in. So properties already defined 280 * in this object take precedence. Property names starting with the reserved 281 * parameter prefix are ignored. 282 * 283 * @param p the object whose properties should be merged (must not be 284 * <b>null</b>) 285 * @throws IllegalArgumentException if the passed in object is <b>null</b> 286 */ 287 public void merge(final BuilderParameters p) 288 { 289 if (p == null) 290 { 291 throw new IllegalArgumentException( 292 "Parameters to merge must not be null!"); 293 } 294 295 for (final Map.Entry<String, Object> e : p.getParameters().entrySet()) 296 { 297 if (!properties.containsKey(e.getKey()) 298 && !e.getKey().startsWith(RESERVED_PARAMETER_PREFIX)) 299 { 300 storeProperty(e.getKey(), e.getValue()); 301 } 302 } 303 } 304 305 /** 306 * Inherits properties from the specified map. This can be used for instance 307 * to reuse parameters from one builder in another builder - also in 308 * parent-child relations in which a parent builder creates child builders. 309 * The purpose of this method is to let a concrete implementation decide 310 * which properties can be inherited. Because parameters are basically 311 * organized as a map it would be possible to simply copy over all 312 * properties from the source object. However, this is not appropriate in 313 * all cases. For instance, some properties - like a 314 * {@code ConfigurationInterpolator} - are tightly connected to a 315 * configuration and cannot be reused in a different context. For other 316 * properties, e.g. a file name, it does not make sense to copy it. 317 * Therefore, an implementation has to be explicit in the properties it 318 * wants to take over. 319 * 320 * @param source the source properties to inherit from 321 * @throws IllegalArgumentException if the source map is <b>null</b> 322 */ 323 public void inheritFrom(final Map<String, ?> source) 324 { 325 if (source == null) 326 { 327 throw new IllegalArgumentException( 328 "Source properties must not be null!"); 329 } 330 copyPropertiesFrom(source, PROP_BEAN_HELPER, PROP_CONFIGURATION_DECODER, 331 PROP_CONVERSION_HANDLER, PROP_LIST_DELIMITER_HANDLER, 332 PROP_LOGGER, PROP_SYNCHRONIZER, 333 PROP_THROW_EXCEPTION_ON_MISSING); 334 } 335 336 /** 337 * Obtains a specification for a {@link ConfigurationInterpolator} from the 338 * specified map with parameters. All properties related to interpolation 339 * are evaluated and added to the specification object. 340 * 341 * @param params the map with parameters (must not be <b>null</b>) 342 * @return an {@code InterpolatorSpecification} object constructed with data 343 * from the map 344 * @throws IllegalArgumentException if the map is <b>null</b> or contains 345 * invalid data 346 */ 347 public static InterpolatorSpecification fetchInterpolatorSpecification( 348 final Map<String, Object> params) 349 { 350 checkParameters(params); 351 return new InterpolatorSpecification.Builder() 352 .withInterpolator( 353 fetchParameter(params, PROP_INTERPOLATOR, 354 ConfigurationInterpolator.class)) 355 .withParentInterpolator( 356 fetchParameter(params, PROP_PARENT_INTERPOLATOR, 357 ConfigurationInterpolator.class)) 358 .withPrefixLookups(fetchAndCheckPrefixLookups(params)) 359 .withDefaultLookups(fetchAndCheckDefaultLookups(params)) 360 .create(); 361 } 362 363 /** 364 * Obtains the {@code BeanHelper} object from the specified map with 365 * parameters. This method can be used to obtain an instance from a 366 * parameters map that has been set via the {@code setBeanHelper()} method. 367 * If no such instance is found, result is <b>null</b>. 368 * 369 * @param params the map with parameters (must not be <b>null</b>) 370 * @return the {@code BeanHelper} stored in this map or <b>null</b> 371 * @throws IllegalArgumentException if the map is <b>null</b> 372 */ 373 public static BeanHelper fetchBeanHelper(final Map<String, Object> params) 374 { 375 checkParameters(params); 376 return (BeanHelper) params.get(PROP_BEAN_HELPER); 377 } 378 379 /** 380 * Clones this object. This is useful because multiple builder instances may 381 * use a similar set of parameters. However, single instances of parameter 382 * objects must not assigned to multiple builders. Therefore, cloning a 383 * parameters object provides a solution for this use case. This method 384 * creates a new parameters object with the same content as this one. The 385 * internal map storing the parameter values is cloned, too, also collection 386 * structures contained in this map. However, no a full deep clone operation 387 * is performed. Objects like a {@code ConfigurationInterpolator} or 388 * {@code Lookup}s are shared between this and the newly created instance. 389 * 390 * @return a clone of this object 391 */ 392 @Override 393 public BasicBuilderParameters clone() 394 { 395 try 396 { 397 final BasicBuilderParameters copy = 398 (BasicBuilderParameters) super.clone(); 399 copy.properties = getParameters(); 400 return copy; 401 } 402 catch (final CloneNotSupportedException cnex) 403 { 404 // should not happen 405 throw new AssertionError(cnex); 406 } 407 } 408 409 /** 410 * Sets a property for this parameters object. Properties are stored in an 411 * internal map. With this method a new entry can be added to this map. If 412 * the value is <b>null</b>, the key is removed from the internal map. This 413 * method can be used by sub classes which also store properties in a map. 414 * 415 * @param key the key of the property 416 * @param value the value of the property 417 */ 418 protected void storeProperty(final String key, final Object value) 419 { 420 if (value == null) 421 { 422 properties.remove(key); 423 } 424 else 425 { 426 properties.put(key, value); 427 } 428 } 429 430 /** 431 * Obtains the value of the specified property from the internal map. This 432 * method can be used by derived classes if a specific property is to be 433 * accessed. If the given key is not found, result is <b>null</b>. 434 * 435 * @param key the key of the property in question 436 * @return the value of the property with this key or <b>null</b> 437 */ 438 protected Object fetchProperty(final String key) 439 { 440 return properties.get(key); 441 } 442 443 /** 444 * Copies a number of properties from the given map into this object. 445 * Properties are only copied if they are defined in the source map. 446 * 447 * @param source the source map 448 * @param keys the keys to be copied 449 */ 450 protected void copyPropertiesFrom(final Map<String, ?> source, final String... keys) 451 { 452 for (final String key : keys) 453 { 454 final Object value = source.get(key); 455 if (value != null) 456 { 457 storeProperty(key, value); 458 } 459 } 460 } 461 462 /** 463 * Helper method for setting a property value. 464 * 465 * @param key the key of the property 466 * @param value the value of the property 467 * @return a reference to this object 468 */ 469 private BasicBuilderParameters setProperty(final String key, final Object value) 470 { 471 storeProperty(key, value); 472 return this; 473 } 474 475 /** 476 * Creates defensive copies for collection structures when constructing the 477 * map with parameters. It should not be possible to modify this object's 478 * internal state when having access to the parameters map. 479 * 480 * @param params the map with parameters to be passed to the caller 481 */ 482 private static void createDefensiveCopies(final HashMap<String, Object> params) 483 { 484 final Map<String, ? extends Lookup> prefixLookups = 485 fetchPrefixLookups(params); 486 if (prefixLookups != null) 487 { 488 params.put(PROP_PREFIX_LOOKUPS, new HashMap<>( 489 prefixLookups)); 490 } 491 final Collection<? extends Lookup> defLookups = fetchDefaultLookups(params); 492 if (defLookups != null) 493 { 494 params.put(PROP_DEFAULT_LOOKUPS, new ArrayList<>(defLookups)); 495 } 496 } 497 498 /** 499 * Obtains the map with prefix lookups from the parameters map. 500 * 501 * @param params the map with parameters 502 * @return the map with prefix lookups (may be <b>null</b>) 503 */ 504 private static Map<String, ? extends Lookup> fetchPrefixLookups( 505 final Map<String, Object> params) 506 { 507 // This is safe to cast because we either have full control over the map 508 // and thus know the types of the contained values or have checked 509 // the content before 510 @SuppressWarnings("unchecked") 511 final 512 Map<String, ? extends Lookup> prefixLookups = 513 (Map<String, ? extends Lookup>) params.get(PROP_PREFIX_LOOKUPS); 514 return prefixLookups; 515 } 516 517 /** 518 * Tests whether the passed in map with parameters contains a map with 519 * prefix lookups. This method is used if the parameters map is from an 520 * insecure source and we cannot be sure that it contains valid data. 521 * Therefore, we have to map that the key for the prefix lookups actually 522 * points to a map containing keys and values of expected data types. 523 * 524 * @param params the parameters map 525 * @return the obtained map with prefix lookups 526 * @throws IllegalArgumentException if the map contains invalid data 527 */ 528 private static Map<String, ? extends Lookup> fetchAndCheckPrefixLookups( 529 final Map<String, Object> params) 530 { 531 final Map<?, ?> prefixes = 532 fetchParameter(params, PROP_PREFIX_LOOKUPS, Map.class); 533 if (prefixes == null) 534 { 535 return null; 536 } 537 538 for (final Map.Entry<?, ?> e : prefixes.entrySet()) 539 { 540 if (!(e.getKey() instanceof String) 541 || !(e.getValue() instanceof Lookup)) 542 { 543 throw new IllegalArgumentException( 544 "Map with prefix lookups contains invalid data: " 545 + prefixes); 546 } 547 } 548 return fetchPrefixLookups(params); 549 } 550 551 /** 552 * Obtains the collection with default lookups from the parameters map. 553 * 554 * @param params the map with parameters 555 * @return the collection with default lookups (may be <b>null</b>) 556 */ 557 private static Collection<? extends Lookup> fetchDefaultLookups( 558 final Map<String, Object> params) 559 { 560 // This is safe to cast because we either have full control over the map 561 // and thus know the types of the contained values or have checked 562 // the content before 563 @SuppressWarnings("unchecked") 564 final 565 Collection<? extends Lookup> defLookups = 566 (Collection<? extends Lookup>) params.get(PROP_DEFAULT_LOOKUPS); 567 return defLookups; 568 } 569 570 /** 571 * Tests whether the passed in map with parameters contains a valid 572 * collection with default lookups. This method works like 573 * {@link #fetchAndCheckPrefixLookups(Map)}, but tests the default lookups 574 * collection. 575 * 576 * @param params the map with parameters 577 * @return the collection with default lookups (may be <b>null</b>) 578 * @throws IllegalArgumentException if invalid data is found 579 */ 580 private static Collection<? extends Lookup> fetchAndCheckDefaultLookups( 581 final Map<String, Object> params) 582 { 583 final Collection<?> col = 584 fetchParameter(params, PROP_DEFAULT_LOOKUPS, Collection.class); 585 if (col == null) 586 { 587 return null; 588 } 589 590 for (final Object o : col) 591 { 592 if (!(o instanceof Lookup)) 593 { 594 throw new IllegalArgumentException( 595 "Collection with default lookups contains invalid data: " 596 + col); 597 } 598 } 599 return fetchDefaultLookups(params); 600 } 601 602 /** 603 * Obtains a parameter from a map and performs a type check. 604 * 605 * @param params the map with parameters 606 * @param key the key of the parameter 607 * @param expClass the expected class of the parameter value 608 * @param <T> the parameter type 609 * @return the value of the parameter in the correct data type 610 * @throws IllegalArgumentException if the parameter is not of the expected 611 * type 612 */ 613 private static <T> T fetchParameter(final Map<String, Object> params, final String key, 614 final Class<T> expClass) 615 { 616 final Object value = params.get(key); 617 if (value == null) 618 { 619 return null; 620 } 621 if (!expClass.isInstance(value)) 622 { 623 throw new IllegalArgumentException(String.format( 624 "Parameter %s is not of type %s!", key, 625 expClass.getSimpleName())); 626 } 627 return expClass.cast(value); 628 } 629 630 /** 631 * Checks whether a map with parameters is present. Throws an exception if 632 * not. 633 * 634 * @param params the map with parameters to check 635 * @throws IllegalArgumentException if the map is <b>null</b> 636 */ 637 private static void checkParameters(final Map<String, Object> params) 638 { 639 if (params == null) 640 { 641 throw new IllegalArgumentException( 642 "Parameters map must not be null!"); 643 } 644 } 645}