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 1790899 2017-04-10 21:56:46Z 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 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(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(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 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(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 Map<String, ? extends Lookup> lookups) 193 { 194 if (lookups == null) 195 { 196 properties.remove(PROP_PREFIX_LOOKUPS); 197 return this; 198 } 199 else 200 { 201 return setProperty(PROP_PREFIX_LOOKUPS, 202 new HashMap<>(lookups)); 203 } 204 } 205 206 /** 207 * {@inheritDoc} A defensive copy of the passed in collection is created. A 208 * <b>null</b> argument causes all default lookups to be removed from the 209 * internal parameters map. 210 */ 211 @Override 212 public BasicBuilderParameters setDefaultLookups( 213 Collection<? extends Lookup> lookups) 214 { 215 if (lookups == null) 216 { 217 properties.remove(PROP_DEFAULT_LOOKUPS); 218 return this; 219 } 220 else 221 { 222 return setProperty(PROP_DEFAULT_LOOKUPS, new ArrayList<>( 223 lookups)); 224 } 225 } 226 227 /** 228 * {@inheritDoc} This implementation stores the passed in 229 * {@code ConfigurationInterpolator} object in the internal parameters map. 230 */ 231 @Override 232 public BasicBuilderParameters setParentInterpolator( 233 ConfigurationInterpolator parent) 234 { 235 return setProperty(PROP_PARENT_INTERPOLATOR, parent); 236 } 237 238 /** 239 * {@inheritDoc} This implementation stores the passed in 240 * {@code Synchronizer} object in the internal parameters map. 241 */ 242 @Override 243 public BasicBuilderParameters setSynchronizer(Synchronizer sync) 244 { 245 return setProperty(PROP_SYNCHRONIZER, sync); 246 } 247 248 /** 249 * {@inheritDoc} This implementation stores the passed in 250 * {@code ConversionHandler} object in the internal parameters map. 251 */ 252 @Override 253 public BasicBuilderParameters setConversionHandler(ConversionHandler handler) 254 { 255 return setProperty(PROP_CONVERSION_HANDLER, handler); 256 } 257 258 /** 259 * {@inheritDoc} This implementation stores the passed in {@code BeanHelper} 260 * object in the internal parameters map, but uses a reserved key, so that 261 * it is not used for the initialization of properties of the managed 262 * configuration object. The {@code fetchBeanHelper()} method can be used to 263 * obtain the {@code BeanHelper} instance from a parameters map. 264 */ 265 @Override 266 public BasicBuilderParameters setBeanHelper(BeanHelper beanHelper) 267 { 268 return setProperty(PROP_BEAN_HELPER, beanHelper); 269 } 270 271 /** 272 * {@inheritDoc} This implementation stores the passed in 273 * {@code ConfigurationDecoder} object in the internal parameters map. 274 */ 275 @Override 276 public BasicBuilderParameters setConfigurationDecoder( 277 ConfigurationDecoder decoder) 278 { 279 return setProperty(PROP_CONFIGURATION_DECODER, decoder); 280 } 281 282 /** 283 * Merges this object with the given parameters object. This method adds all 284 * property values defined by the passed in parameters object to the 285 * internal storage which are not already in. So properties already defined 286 * in this object take precedence. Property names starting with the reserved 287 * parameter prefix are ignored. 288 * 289 * @param p the object whose properties should be merged (must not be 290 * <b>null</b>) 291 * @throws IllegalArgumentException if the passed in object is <b>null</b> 292 */ 293 public void merge(BuilderParameters p) 294 { 295 if (p == null) 296 { 297 throw new IllegalArgumentException( 298 "Parameters to merge must not be null!"); 299 } 300 301 for (Map.Entry<String, Object> e : p.getParameters().entrySet()) 302 { 303 if (!properties.containsKey(e.getKey()) 304 && !e.getKey().startsWith(RESERVED_PARAMETER_PREFIX)) 305 { 306 storeProperty(e.getKey(), e.getValue()); 307 } 308 } 309 } 310 311 /** 312 * Inherits properties from the specified map. This can be used for instance 313 * to reuse parameters from one builder in another builder - also in 314 * parent-child relations in which a parent builder creates child builders. 315 * The purpose of this method is to let a concrete implementation decide 316 * which properties can be inherited. Because parameters are basically 317 * organized as a map it would be possible to simply copy over all 318 * properties from the source object. However, this is not appropriate in 319 * all cases. For instance, some properties - like a 320 * {@code ConfigurationInterpolator} - are tightly connected to a 321 * configuration and cannot be reused in a different context. For other 322 * properties, e.g. a file name, it does not make sense to copy it. 323 * Therefore, an implementation has to be explicit in the properties it 324 * wants to take over. 325 * 326 * @param source the source properties to inherit from 327 * @throws IllegalArgumentException if the source map is <b>null</b> 328 */ 329 public void inheritFrom(Map<String, ?> source) 330 { 331 if (source == null) 332 { 333 throw new IllegalArgumentException( 334 "Source properties must not be null!"); 335 } 336 copyPropertiesFrom(source, PROP_BEAN_HELPER, PROP_CONFIGURATION_DECODER, 337 PROP_CONVERSION_HANDLER, PROP_LIST_DELIMITER_HANDLER, 338 PROP_LOGGER, PROP_SYNCHRONIZER, 339 PROP_THROW_EXCEPTION_ON_MISSING); 340 } 341 342 /** 343 * Obtains a specification for a {@link ConfigurationInterpolator} from the 344 * specified map with parameters. All properties related to interpolation 345 * are evaluated and added to the specification object. 346 * 347 * @param params the map with parameters (must not be <b>null</b>) 348 * @return an {@code InterpolatorSpecification} object constructed with data 349 * from the map 350 * @throws IllegalArgumentException if the map is <b>null</b> or contains 351 * invalid data 352 */ 353 public static InterpolatorSpecification fetchInterpolatorSpecification( 354 Map<String, Object> params) 355 { 356 checkParameters(params); 357 return new InterpolatorSpecification.Builder() 358 .withInterpolator( 359 fetchParameter(params, PROP_INTERPOLATOR, 360 ConfigurationInterpolator.class)) 361 .withParentInterpolator( 362 fetchParameter(params, PROP_PARENT_INTERPOLATOR, 363 ConfigurationInterpolator.class)) 364 .withPrefixLookups(fetchAndCheckPrefixLookups(params)) 365 .withDefaultLookups(fetchAndCheckDefaultLookups(params)) 366 .create(); 367 } 368 369 /** 370 * Obtains the {@code BeanHelper} object from the specified map with 371 * parameters. This method can be used to obtain an instance from a 372 * parameters map that has been set via the {@code setBeanHelper()} method. 373 * If no such instance is found, result is <b>null</b>. 374 * 375 * @param params the map with parameters (must not be <b>null</b>) 376 * @return the {@code BeanHelper} stored in this map or <b>null</b> 377 * @throws IllegalArgumentException if the map is <b>null</b> 378 */ 379 public static BeanHelper fetchBeanHelper(Map<String, Object> params) 380 { 381 checkParameters(params); 382 return (BeanHelper) params.get(PROP_BEAN_HELPER); 383 } 384 385 /** 386 * Clones this object. This is useful because multiple builder instances may 387 * use a similar set of parameters. However, single instances of parameter 388 * objects must not assigned to multiple builders. Therefore, cloning a 389 * parameters object provides a solution for this use case. This method 390 * creates a new parameters object with the same content as this one. The 391 * internal map storing the parameter values is cloned, too, also collection 392 * structures contained in this map. However, no a full deep clone operation 393 * is performed. Objects like a {@code ConfigurationInterpolator} or 394 * {@code Lookup}s are shared between this and the newly created instance. 395 * 396 * @return a clone of this object 397 */ 398 @Override 399 public BasicBuilderParameters clone() 400 { 401 try 402 { 403 BasicBuilderParameters copy = 404 (BasicBuilderParameters) super.clone(); 405 copy.properties = getParameters(); 406 return copy; 407 } 408 catch (CloneNotSupportedException cnex) 409 { 410 // should not happen 411 throw new AssertionError(cnex); 412 } 413 } 414 415 /** 416 * Sets a property for this parameters object. Properties are stored in an 417 * internal map. With this method a new entry can be added to this map. If 418 * the value is <b>null</b>, the key is removed from the internal map. This 419 * method can be used by sub classes which also store properties in a map. 420 * 421 * @param key the key of the property 422 * @param value the value of the property 423 */ 424 protected void storeProperty(String key, Object value) 425 { 426 if (value == null) 427 { 428 properties.remove(key); 429 } 430 else 431 { 432 properties.put(key, value); 433 } 434 } 435 436 /** 437 * Obtains the value of the specified property from the internal map. This 438 * method can be used by derived classes if a specific property is to be 439 * accessed. If the given key is not found, result is <b>null</b>. 440 * 441 * @param key the key of the property in question 442 * @return the value of the property with this key or <b>null</b> 443 */ 444 protected Object fetchProperty(String key) 445 { 446 return properties.get(key); 447 } 448 449 /** 450 * Copies a number of properties from the given map into this object. 451 * Properties are only copied if they are defined in the source map. 452 * 453 * @param source the source map 454 * @param keys the keys to be copied 455 */ 456 protected void copyPropertiesFrom(Map<String, ?> source, String... keys) 457 { 458 for (String key : keys) 459 { 460 Object value = source.get(key); 461 if (value != null) 462 { 463 storeProperty(key, value); 464 } 465 } 466 } 467 468 /** 469 * Helper method for setting a property value. 470 * 471 * @param key the key of the property 472 * @param value the value of the property 473 * @return a reference to this object 474 */ 475 private BasicBuilderParameters setProperty(String key, Object value) 476 { 477 storeProperty(key, value); 478 return this; 479 } 480 481 /** 482 * Creates defensive copies for collection structures when constructing the 483 * map with parameters. It should not be possible to modify this object's 484 * internal state when having access to the parameters map. 485 * 486 * @param params the map with parameters to be passed to the caller 487 */ 488 private static void createDefensiveCopies(HashMap<String, Object> params) 489 { 490 Map<String, ? extends Lookup> prefixLookups = 491 fetchPrefixLookups(params); 492 if (prefixLookups != null) 493 { 494 params.put(PROP_PREFIX_LOOKUPS, new HashMap<>( 495 prefixLookups)); 496 } 497 Collection<? extends Lookup> defLookups = fetchDefaultLookups(params); 498 if (defLookups != null) 499 { 500 params.put(PROP_DEFAULT_LOOKUPS, new ArrayList<>(defLookups)); 501 } 502 } 503 504 /** 505 * Obtains the map with prefix lookups from the parameters map. 506 * 507 * @param params the map with parameters 508 * @return the map with prefix lookups (may be <b>null</b>) 509 */ 510 private static Map<String, ? extends Lookup> fetchPrefixLookups( 511 Map<String, Object> params) 512 { 513 // This is safe to cast because we either have full control over the map 514 // and thus know the types of the contained values or have checked 515 // the content before 516 @SuppressWarnings("unchecked") 517 Map<String, ? extends Lookup> prefixLookups = 518 (Map<String, ? extends Lookup>) params.get(PROP_PREFIX_LOOKUPS); 519 return prefixLookups; 520 } 521 522 /** 523 * Tests whether the passed in map with parameters contains a map with 524 * prefix lookups. This method is used if the parameters map is from an 525 * insecure source and we cannot be sure that it contains valid data. 526 * Therefore, we have to map that the key for the prefix lookups actually 527 * points to a map containing keys and values of expected data types. 528 * 529 * @param params the parameters map 530 * @return the obtained map with prefix lookups 531 * @throws IllegalArgumentException if the map contains invalid data 532 */ 533 private static Map<String, ? extends Lookup> fetchAndCheckPrefixLookups( 534 Map<String, Object> params) 535 { 536 Map<?, ?> prefixes = 537 fetchParameter(params, PROP_PREFIX_LOOKUPS, Map.class); 538 if (prefixes == null) 539 { 540 return null; 541 } 542 543 for (Map.Entry<?, ?> e : prefixes.entrySet()) 544 { 545 if (!(e.getKey() instanceof String) 546 || !(e.getValue() instanceof Lookup)) 547 { 548 throw new IllegalArgumentException( 549 "Map with prefix lookups contains invalid data: " 550 + prefixes); 551 } 552 } 553 return fetchPrefixLookups(params); 554 } 555 556 /** 557 * Obtains the collection with default lookups from the parameters map. 558 * 559 * @param params the map with parameters 560 * @return the collection with default lookups (may be <b>null</b>) 561 */ 562 private static Collection<? extends Lookup> fetchDefaultLookups( 563 Map<String, Object> params) 564 { 565 // This is safe to cast because we either have full control over the map 566 // and thus know the types of the contained values or have checked 567 // the content before 568 @SuppressWarnings("unchecked") 569 Collection<? extends Lookup> defLookups = 570 (Collection<? extends Lookup>) params.get(PROP_DEFAULT_LOOKUPS); 571 return defLookups; 572 } 573 574 /** 575 * Tests whether the passed in map with parameters contains a valid 576 * collection with default lookups. This method works like 577 * {@link #fetchAndCheckPrefixLookups(Map)}, but tests the default lookups 578 * collection. 579 * 580 * @param params the map with parameters 581 * @return the collection with default lookups (may be <b>null</b>) 582 * @throws IllegalArgumentException if invalid data is found 583 */ 584 private static Collection<? extends Lookup> fetchAndCheckDefaultLookups( 585 Map<String, Object> params) 586 { 587 Collection<?> col = 588 fetchParameter(params, PROP_DEFAULT_LOOKUPS, Collection.class); 589 if (col == null) 590 { 591 return null; 592 } 593 594 for (Object o : col) 595 { 596 if (!(o instanceof Lookup)) 597 { 598 throw new IllegalArgumentException( 599 "Collection with default lookups contains invalid data: " 600 + col); 601 } 602 } 603 return fetchDefaultLookups(params); 604 } 605 606 /** 607 * Obtains a parameter from a map and performs a type check. 608 * 609 * @param params the map with parameters 610 * @param key the key of the parameter 611 * @param expClass the expected class of the parameter value 612 * @param <T> the parameter type 613 * @return the value of the parameter in the correct data type 614 * @throws IllegalArgumentException if the parameter is not of the expected 615 * type 616 */ 617 private static <T> T fetchParameter(Map<String, Object> params, String key, 618 Class<T> expClass) 619 { 620 Object value = params.get(key); 621 if (value == null) 622 { 623 return null; 624 } 625 if (!expClass.isInstance(value)) 626 { 627 throw new IllegalArgumentException(String.format( 628 "Parameter %s is not of type %s!", key, 629 expClass.getSimpleName())); 630 } 631 return expClass.cast(value); 632 } 633 634 /** 635 * Checks whether a map with parameters is present. Throws an exception if 636 * not. 637 * 638 * @param params the map with parameters to check 639 * @throws IllegalArgumentException if the map is <b>null</b> 640 */ 641 private static void checkParameters(Map<String, Object> params) 642 { 643 if (params == null) 644 { 645 throw new IllegalArgumentException( 646 "Parameters map must not be null!"); 647 } 648 } 649}