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