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.Collection; 020import java.util.Collections; 021import java.util.HashMap; 022import java.util.Map; 023 024import org.apache.commons.configuration2.ConfigurationUtils; 025import org.apache.commons.configuration2.ImmutableConfiguration; 026import org.apache.commons.configuration2.Initializable; 027import org.apache.commons.configuration2.beanutils.BeanDeclaration; 028import org.apache.commons.configuration2.beanutils.BeanHelper; 029import org.apache.commons.configuration2.beanutils.ConstructorArg; 030import org.apache.commons.configuration2.event.Event; 031import org.apache.commons.configuration2.event.EventListener; 032import org.apache.commons.configuration2.event.EventListenerList; 033import org.apache.commons.configuration2.event.EventListenerRegistrationData; 034import org.apache.commons.configuration2.event.EventSource; 035import org.apache.commons.configuration2.event.EventType; 036import org.apache.commons.configuration2.ex.ConfigurationException; 037import org.apache.commons.configuration2.ex.ConfigurationRuntimeException; 038import org.apache.commons.configuration2.reloading.ReloadingController; 039 040/** 041 * <p> 042 * An implementation of the {@code ConfigurationBuilder} interface which is able to create different concrete 043 * {@code ImmutableConfiguration} implementations based on reflection. 044 * </p> 045 * <p> 046 * When constructing an instance of this class the concrete {@code ImmutableConfiguration} implementation class has to 047 * be provided. Then properties for the new {@code ImmutableConfiguration} instance can be set. The first call to 048 * {@code getConfiguration()} creates and initializes the new {@code ImmutableConfiguration} object. It is cached and 049 * returned by subsequent calls. This cache - and also the initialization properties set so far - can be flushed by 050 * calling one of the {@code reset()} methods. That way other {@code ImmutableConfiguration} instances with different 051 * properties can be created. 052 * </p> 053 * <p> 054 * If the newly created {@code ImmutableConfiguration} object implements the {@code Initializable} interface, its 055 * {@code initialize()} method is called after all initialization properties have been set. This way a concrete 056 * implementation class can perform arbitrary initialization steps. 057 * </p> 058 * <p> 059 * There are multiple options for setting up a {@code BasicConfigurationBuilder} instance: 060 * </p> 061 * <ul> 062 * <li>All initialization properties can be set in one or multiple calls of the {@code configure()} method. In each call 063 * an arbitrary number of {@link BuilderParameters} objects can be passed. The API allows method chaining and is 064 * intended to be used from Java code.</li> 065 * <li>If builder instances are created by other means - e.g. using a dependency injection framework -, the fluent API 066 * approach may not be suitable. For those use cases it is also possible to pass in all initialization parameters as a 067 * map. The keys of the map have to match initialization properties of the {@code ImmutableConfiguration} object to be 068 * created, the values are the corresponding property values. For instance, the key <em>throwExceptionOnMissing</em> in 069 * the map will cause the method {@code setThrowExceptionOnMissing()} on the {@code ImmutableConfiguration} object to be 070 * called with the corresponding value as parameter.</li> 071 * </ul> 072 * <p> 073 * A builder instance can be constructed with an <em>allowFailOnInit</em> flag. If set to <strong>true</strong>, 074 * exceptions during initialization of the configuration are ignored; in such a case an empty configuration object is 075 * returned. A use case for this flag is a scenario in which a configuration is optional and created on demand the first 076 * time configuration data is to be stored. Consider an application that stores user-specific configuration data in the 077 * user's home directory: When started for the first time by a new user there is no configuration file; so it makes 078 * sense to start with an empty configuration object. On application exit, settings can be stored in this object and 079 * written to the associated file. Then they are available on next application start. 080 * </p> 081 * <p> 082 * This class is thread-safe. Multiple threads can modify initialization properties and call {@code getConfiguration()}. 083 * However, the intended use case is that the builder is configured by a single thread first. Then 084 * {@code getConfiguration()} can be called concurrently, and it is guaranteed that always the same 085 * {@code ImmutableConfiguration} instance is returned until the builder is reset. 086 * </p> 087 * 088 * @since 2.0 089 * @param <T> the concrete type of {@code ImmutableConfiguration} objects created by this builder 090 */ 091public class BasicConfigurationBuilder<T extends ImmutableConfiguration> implements ConfigurationBuilder<T> { 092 /** The class of the objects produced by this builder instance. */ 093 private final Class<? extends T> resultClass; 094 095 /** An object managing the event listeners registered at this builder. */ 096 private final EventListenerList eventListeners; 097 098 /** A flag whether exceptions on initializing configurations are allowed. */ 099 private final boolean allowFailOnInit; 100 101 /** The map with current initialization parameters. */ 102 private Map<String, Object> parameters; 103 104 /** The current bean declaration. */ 105 private BeanDeclaration resultDeclaration; 106 107 /** The result object of this builder. */ 108 private volatile T result; 109 110 /** 111 * Creates a new instance of {@code BasicConfigurationBuilder} and initializes it with the given result class. No 112 * initialization properties are set. 113 * 114 * @param resCls the result class (must not be <b>null</b>) 115 * @throws IllegalArgumentException if the result class is <b>null</b> 116 */ 117 public BasicConfigurationBuilder(final Class<? extends T> resCls) { 118 this(resCls, null); 119 } 120 121 /** 122 * Creates a new instance of {@code BasicConfigurationBuilder} and initializes it with the given result class and an 123 * initial set of builder parameters. The <em>allowFailOnInit</em> flag is set to <strong>false</strong>. 124 * 125 * @param resCls the result class (must not be <b>null</b>) 126 * @param params a map with initialization parameters 127 * @throws IllegalArgumentException if the result class is <b>null</b> 128 */ 129 public BasicConfigurationBuilder(final Class<? extends T> resCls, final Map<String, Object> params) { 130 this(resCls, params, false); 131 } 132 133 /** 134 * Creates a new instance of {@code BasicConfigurationBuilder} and initializes it with the given result class, an 135 * initial set of builder parameters, and the <em>allowFailOnInit</em> flag. The map with parameters may be <b>null</b>, 136 * in this case no initialization parameters are set. 137 * 138 * @param resCls the result class (must not be <b>null</b>) 139 * @param params a map with initialization parameters 140 * @param allowFailOnInit a flag whether exceptions on initializing a newly created {@code ImmutableConfiguration} 141 * object are allowed 142 * @throws IllegalArgumentException if the result class is <b>null</b> 143 */ 144 public BasicConfigurationBuilder(final Class<? extends T> resCls, final Map<String, Object> params, final boolean allowFailOnInit) { 145 if (resCls == null) { 146 throw new IllegalArgumentException("Result class must not be null!"); 147 } 148 149 resultClass = resCls; 150 this.allowFailOnInit = allowFailOnInit; 151 eventListeners = new EventListenerList(); 152 updateParameters(params); 153 } 154 155 /** 156 * Returns the result class of this builder. The objects produced by this builder have the class returned here. 157 * 158 * @return the result class of this builder 159 */ 160 public Class<? extends T> getResultClass() { 161 return resultClass; 162 } 163 164 /** 165 * Returns the <em>allowFailOnInit</em> flag. See the header comment for information about this flag. 166 * 167 * @return the <em>allowFailOnInit</em> flag 168 */ 169 public boolean isAllowFailOnInit() { 170 return allowFailOnInit; 171 } 172 173 /** 174 * Sets the initialization parameters of this builder. Already existing parameters are replaced by the content of the 175 * given map. 176 * 177 * @param params the new initialization parameters of this builder; can be <b>null</b>, then all initialization 178 * parameters are removed 179 * @return a reference to this builder for method chaining 180 */ 181 public synchronized BasicConfigurationBuilder<T> setParameters(final Map<String, Object> params) { 182 updateParameters(params); 183 return this; 184 } 185 186 /** 187 * Adds the content of the given map to the already existing initialization parameters. 188 * 189 * @param params the map with additional initialization parameters; may be <b>null</b>, then this call has no effect 190 * @return a reference to this builder for method chaining 191 */ 192 public synchronized BasicConfigurationBuilder<T> addParameters(final Map<String, Object> params) { 193 final Map<String, Object> newParams = new HashMap<>(getParameters()); 194 if (params != null) { 195 newParams.putAll(params); 196 } 197 updateParameters(newParams); 198 return this; 199 } 200 201 /** 202 * Appends the content of the specified {@code BuilderParameters} objects to the current initialization parameters. 203 * Calling this method multiple times will create a union of the parameters provided. 204 * 205 * @param params an arbitrary number of objects with builder parameters 206 * @return a reference to this builder for method chaining 207 * @throws NullPointerException if a <b>null</b> array is passed 208 */ 209 public BasicConfigurationBuilder<T> configure(final BuilderParameters... params) { 210 final Map<String, Object> newParams = new HashMap<>(); 211 for (final BuilderParameters p : params) { 212 newParams.putAll(p.getParameters()); 213 handleEventListenerProviders(p); 214 } 215 216 return setParameters(newParams); 217 } 218 219 /** 220 * {@inheritDoc} This implementation creates the result configuration on first access. Later invocations return the same 221 * object until this builder is reset. The double-check idiom for lazy initialization is used (Bloch, Effective Java, 222 * item 71). 223 */ 224 @Override 225 public T getConfiguration() throws ConfigurationException { 226 fireBuilderEvent(new ConfigurationBuilderEvent(this, ConfigurationBuilderEvent.CONFIGURATION_REQUEST)); 227 228 T resObj = result; 229 boolean created = false; 230 if (resObj == null) { 231 synchronized (this) { 232 resObj = result; 233 if (resObj == null) { 234 result = resObj = createResult(); 235 created = true; 236 } 237 } 238 } 239 240 if (created) { 241 fireBuilderEvent(new ConfigurationBuilderResultCreatedEvent(this, ConfigurationBuilderResultCreatedEvent.RESULT_CREATED, resObj)); 242 } 243 return resObj; 244 } 245 246 /** 247 * {@inheritDoc} This implementation also takes care that the event listener is added to the managed configuration 248 * object. 249 * 250 * @throws IllegalArgumentException if the event type or the listener is <b>null</b> 251 */ 252 @Override 253 public <E extends Event> void addEventListener(final EventType<E> eventType, final EventListener<? super E> listener) { 254 installEventListener(eventType, listener); 255 } 256 257 /** 258 * {@inheritDoc} This implementation also takes care that the event listener is removed from the managed configuration 259 * object. 260 */ 261 @Override 262 public <E extends Event> boolean removeEventListener(final EventType<E> eventType, final EventListener<? super E> listener) { 263 fetchEventSource().removeEventListener(eventType, listener); 264 return eventListeners.removeEventListener(eventType, listener); 265 } 266 267 /** 268 * Clears an existing result object. An invocation of this method causes a new {@code ImmutableConfiguration} object to 269 * be created the next time {@link #getConfiguration()} is called. 270 */ 271 public void resetResult() { 272 final T oldResult; 273 synchronized (this) { 274 oldResult = result; 275 result = null; 276 resultDeclaration = null; 277 } 278 279 if (oldResult != null) { 280 removeEventListeners(oldResult); 281 } 282 fireBuilderEvent(new ConfigurationBuilderEvent(this, ConfigurationBuilderEvent.RESET)); 283 } 284 285 /** 286 * Removes all initialization parameters of this builder. This method can be called if this builder is to be reused for 287 * creating result objects with a different configuration. 288 */ 289 public void resetParameters() { 290 setParameters(null); 291 } 292 293 /** 294 * Resets this builder. This is a convenience method which combines calls to {@link #resetResult()} and 295 * {@link #resetParameters()}. 296 */ 297 public synchronized void reset() { 298 resetParameters(); 299 resetResult(); 300 } 301 302 /** 303 * Connects this builder with a {@code ReloadingController}. With this method support for reloading can be added to an 304 * arbitrary builder object. Event listeners are registered at the reloading controller and this builder with connect 305 * both objects: 306 * <ul> 307 * <li>When the reloading controller detects that a reload is required, the builder's {@link #resetResult()} method is 308 * called; so the managed result object is invalidated.</li> 309 * <li>When a new result object has been created the controller's reloading state is reset, so that new changes can be 310 * detected again.</li> 311 * </ul> 312 * 313 * @param controller the {@code ReloadingController} to connect to (must not be <b>null</b>) 314 * @throws IllegalArgumentException if the controller is <b>null</b> 315 */ 316 public final void connectToReloadingController(final ReloadingController controller) { 317 if (controller == null) { 318 throw new IllegalArgumentException("ReloadingController must not be null!"); 319 } 320 ReloadingBuilderSupportListener.connect(this, controller); 321 } 322 323 /** 324 * Creates a new, initialized result object. This method is called by {@code getConfiguration()} if no valid result 325 * object exists. This base implementation performs two steps: 326 * <ul> 327 * <li>{@code createResultInstance()} is called to create a new, uninitialized result object.</li> 328 * <li>{@code initResultInstance()} is called to process all initialization parameters.</li> 329 * </ul> 330 * It also evaluates the <em>allowFailOnInit</em> flag, i.e. if initialization causes an exception and this flag is set, 331 * the exception is ignored, and the newly created, uninitialized configuration is returned. Note that this method is 332 * called in a synchronized block. 333 * 334 * @return the newly created result object 335 * @throws ConfigurationException if an error occurs 336 */ 337 protected T createResult() throws ConfigurationException { 338 final T resObj = createResultInstance(); 339 340 try { 341 initResultInstance(resObj); 342 } catch (final ConfigurationException cex) { 343 if (!isAllowFailOnInit()) { 344 throw cex; 345 } 346 } 347 348 return resObj; 349 } 350 351 /** 352 * Creates the new, uninitialized result object. This is the first step of the process of producing a result object for 353 * this builder. This implementation uses the {@link BeanHelper} class to create a new object based on the 354 * {@link BeanDeclaration} returned by {@link #getResultDeclaration()}. Note: This method is invoked in a synchronized 355 * block. 356 * 357 * @return the newly created, yet uninitialized result object 358 * @throws ConfigurationException if an exception occurs 359 */ 360 protected T createResultInstance() throws ConfigurationException { 361 final Object bean = fetchBeanHelper().createBean(getResultDeclaration()); 362 checkResultInstance(bean); 363 return getResultClass().cast(bean); 364 } 365 366 /** 367 * Initializes a newly created result object. This is the second step of the process of producing a result object for 368 * this builder. This implementation uses the {@link BeanHelper} class to initialize the object's property based on the 369 * {@link BeanDeclaration} returned by {@link #getResultDeclaration()}. Note: This method is invoked in a synchronized 370 * block. This is required because internal state is accessed. Sub classes must not call this method without proper 371 * synchronization. 372 * 373 * @param obj the object to be initialized 374 * @throws ConfigurationException if an error occurs 375 */ 376 protected void initResultInstance(final T obj) throws ConfigurationException { 377 fetchBeanHelper().initBean(obj, getResultDeclaration()); 378 registerEventListeners(obj); 379 handleInitializable(obj); 380 } 381 382 /** 383 * Returns the {@code BeanDeclaration} that is used to create and initialize result objects. The declaration is created 384 * on first access (by invoking {@link #createResultDeclaration(Map)}) based on the current initialization parameters. 385 * 386 * @return the {@code BeanDeclaration} for dynamically creating a result object 387 * @throws ConfigurationException if an error occurs 388 */ 389 protected final synchronized BeanDeclaration getResultDeclaration() throws ConfigurationException { 390 if (resultDeclaration == null) { 391 resultDeclaration = createResultDeclaration(getFilteredParameters()); 392 } 393 return resultDeclaration; 394 } 395 396 /** 397 * Returns a (unmodifiable) map with the current initialization parameters set for this builder. The map is populated 398 * with the parameters set using the various configuration options. 399 * 400 * @return a map with the current set of initialization parameters 401 */ 402 protected final synchronized Map<String, Object> getParameters() { 403 if (parameters != null) { 404 return parameters; 405 } 406 return Collections.emptyMap(); 407 } 408 409 /** 410 * Obtains the {@code BeanHelper} object to be used when dealing with bean declarations. This method checks whether this 411 * builder was configured with a specific {@code BeanHelper} instance. If so, this instance is used. Otherwise, the 412 * default {@code BeanHelper} is returned. 413 * 414 * @return the {@code BeanHelper} to be used 415 */ 416 protected final BeanHelper fetchBeanHelper() { 417 final BeanHelper helper = BasicBuilderParameters.fetchBeanHelper(getParameters()); 418 return helper != null ? helper : BeanHelper.INSTANCE; 419 } 420 421 /** 422 * Creates a new {@code BeanDeclaration} which is used for creating new result objects dynamically. This implementation 423 * creates a specialized {@code BeanDeclaration} object that is initialized from the given map of initialization 424 * parameters. The {@code BeanDeclaration} must be initialized with the result class of this builder, otherwise 425 * exceptions will be thrown when the result object is created. Note: This method is invoked in a synchronized block. 426 * 427 * @param params a snapshot of the current initialization parameters 428 * @return the {@code BeanDeclaration} for creating result objects 429 * @throws ConfigurationException if an error occurs 430 */ 431 protected BeanDeclaration createResultDeclaration(final Map<String, Object> params) throws ConfigurationException { 432 return new BeanDeclaration() { 433 @Override 434 public Map<String, Object> getNestedBeanDeclarations() { 435 // no nested beans 436 return Collections.emptyMap(); 437 } 438 439 @Override 440 public Collection<ConstructorArg> getConstructorArgs() { 441 // no constructor arguments 442 return Collections.emptySet(); 443 } 444 445 @Override 446 public Map<String, Object> getBeanProperties() { 447 // the properties are equivalent to the parameters 448 return params; 449 } 450 451 @Override 452 public Object getBeanFactoryParameter() { 453 return null; 454 } 455 456 @Override 457 public String getBeanFactoryName() { 458 return null; 459 } 460 461 @Override 462 public String getBeanClassName() { 463 return getResultClass().getName(); 464 } 465 }; 466 } 467 468 /** 469 * Copies all {@code EventListener} objects registered at this builder to the specified target configuration builder. 470 * This method is intended to be used by derived classes which support inheritance of their properties to other builder 471 * objects. 472 * 473 * @param target the target configuration builder (must not be <b>null</b>) 474 * @throws NullPointerException if the target builder is <b>null</b> 475 */ 476 protected synchronized void copyEventListeners(final BasicConfigurationBuilder<?> target) { 477 copyEventListeners(target, eventListeners); 478 } 479 480 /** 481 * Copies all event listeners in the specified list to the specified target configuration builder. This method is 482 * intended to be used by derived classes which have to deal with managed configuration builders that need to be 483 * initialized with event listeners. 484 * 485 * @param target the target configuration builder (must not be <b>null</b>) 486 * @param listeners the event listeners to be copied over 487 * @throws NullPointerException if the target builder is <b>null</b> 488 */ 489 protected void copyEventListeners(final BasicConfigurationBuilder<?> target, final EventListenerList listeners) { 490 target.eventListeners.addAll(listeners); 491 } 492 493 /** 494 * Adds the specified event listener to this object. This method is called by {@code addEventListener()}, it does the 495 * actual listener registration. Because it is final it can be called by sub classes in the constructor if there is 496 * already the need to register an event listener. 497 * 498 * @param eventType the event type object 499 * @param listener the listener to be registered 500 * @param <E> the event type 501 */ 502 protected final <E extends Event> void installEventListener(final EventType<E> eventType, final EventListener<? super E> listener) { 503 fetchEventSource().addEventListener(eventType, listener); 504 eventListeners.addEventListener(eventType, listener); 505 } 506 507 /** 508 * Sends the specified builder event to all registered listeners. 509 * 510 * @param event the event to be fired 511 */ 512 protected void fireBuilderEvent(final ConfigurationBuilderEvent event) { 513 eventListeners.fire(event); 514 } 515 516 /** 517 * Replaces the current map with parameters by a new one. 518 * 519 * @param newParams the map with new parameters (may be <b>null</b>) 520 */ 521 private void updateParameters(final Map<String, Object> newParams) { 522 final Map<String, Object> map = new HashMap<>(); 523 if (newParams != null) { 524 map.putAll(newParams); 525 } 526 parameters = Collections.unmodifiableMap(map); 527 } 528 529 /** 530 * Registers the available event listeners at the given object. This method is called for each result object created by 531 * the builder. 532 * 533 * @param obj the object to initialize 534 */ 535 private void registerEventListeners(final T obj) { 536 final EventSource evSrc = ConfigurationUtils.asEventSource(obj, true); 537 for (final EventListenerRegistrationData<?> regData : eventListeners.getRegistrations()) { 538 registerListener(evSrc, regData); 539 } 540 } 541 542 /** 543 * Removes all available event listeners from the given result object. This method is called when the result of this 544 * builder is reset. Then the old managed configuration should no longer generate events. 545 * 546 * @param obj the affected result object 547 */ 548 private void removeEventListeners(final T obj) { 549 final EventSource evSrc = ConfigurationUtils.asEventSource(obj, true); 550 for (final EventListenerRegistrationData<?> regData : eventListeners.getRegistrations()) { 551 removeListener(evSrc, regData); 552 } 553 } 554 555 /** 556 * Returns an {@code EventSource} for the current result object. If there is no current result or if it does not extend 557 * {@code EventSource}, a dummy event source is returned. 558 * 559 * @return the {@code EventSource} for the current result object 560 */ 561 private EventSource fetchEventSource() { 562 return ConfigurationUtils.asEventSource(result, true); 563 } 564 565 /** 566 * Checks whether the specified parameters object implements the {@code EventListenerProvider} interface. If so, the 567 * event listeners it provides are added to this builder. 568 * 569 * @param params the parameters object 570 */ 571 private void handleEventListenerProviders(final BuilderParameters params) { 572 if (params instanceof EventListenerProvider) { 573 eventListeners.addAll(((EventListenerProvider) params).getListeners()); 574 } 575 } 576 577 /** 578 * Checks whether the class of the result configuration is compatible with this builder's result class. This is done to 579 * ensure that only objects of the expected result class are created. 580 * 581 * @param inst the result instance to be checked 582 * @throws ConfigurationRuntimeException if an invalid result class is detected 583 */ 584 private void checkResultInstance(final Object inst) { 585 if (!getResultClass().isInstance(inst)) { 586 throw new ConfigurationRuntimeException("Incompatible result object: " + inst); 587 } 588 } 589 590 /** 591 * Returns a map with initialization parameters where all parameters starting with the reserved prefix have been 592 * filtered out. 593 * 594 * @return the filtered parameters map 595 */ 596 private Map<String, Object> getFilteredParameters() { 597 final Map<String, Object> filteredMap = new HashMap<>(getParameters()); 598 filteredMap.keySet().removeIf(key -> key.startsWith(BuilderParameters.RESERVED_PARAMETER_PREFIX)); 599 return filteredMap; 600 } 601 602 /** 603 * Performs special initialization of the result object. This method is called after parameters have been set on a newly 604 * created result instance. If supported by the result class, the {@code initialize()} method is now called. 605 * 606 * @param obj the newly created result object 607 */ 608 private void handleInitializable(final T obj) { 609 if (obj instanceof Initializable) { 610 ((Initializable) obj).initialize(); 611 } 612 } 613 614 /** 615 * Registers an event listener at an event source object. 616 * 617 * @param evSrc the event source 618 * @param regData the registration data object 619 * @param <E> the type of the event listener 620 */ 621 private static <E extends Event> void registerListener(final EventSource evSrc, final EventListenerRegistrationData<E> regData) { 622 evSrc.addEventListener(regData.getEventType(), regData.getListener()); 623 } 624 625 /** 626 * Removes an event listener from an event source object. 627 * 628 * @param evSrc the event source 629 * @param regData the registration data object 630 * @param <E> the type of the event listener 631 */ 632 private static <E extends Event> void removeListener(final EventSource evSrc, final EventListenerRegistrationData<E> regData) { 633 evSrc.removeEventListener(regData.getEventType(), regData.getListener()); 634 } 635}