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