| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package org.apache.commons.configuration2.builder.combined; |
| |
| import java.util.HashMap; |
| import java.util.Map; |
| import java.util.concurrent.ConcurrentHashMap; |
| import java.util.concurrent.ConcurrentMap; |
| import java.util.concurrent.atomic.AtomicReference; |
| |
| import org.apache.commons.configuration2.ConfigurationUtils; |
| import org.apache.commons.configuration2.FileBasedConfiguration; |
| import org.apache.commons.configuration2.builder.BasicBuilderParameters; |
| import org.apache.commons.configuration2.builder.BasicConfigurationBuilder; |
| import org.apache.commons.configuration2.builder.BuilderParameters; |
| import org.apache.commons.configuration2.builder.ConfigurationBuilderEvent; |
| import org.apache.commons.configuration2.builder.ConfigurationBuilderResultCreatedEvent; |
| import org.apache.commons.configuration2.builder.FileBasedConfigurationBuilder; |
| import org.apache.commons.configuration2.event.Event; |
| import org.apache.commons.configuration2.event.EventListener; |
| import org.apache.commons.configuration2.event.EventListenerList; |
| import org.apache.commons.configuration2.event.EventType; |
| import org.apache.commons.configuration2.ex.ConfigurationException; |
| import org.apache.commons.configuration2.interpol.ConfigurationInterpolator; |
| import org.apache.commons.configuration2.interpol.InterpolatorSpecification; |
| import org.apache.commons.lang3.concurrent.ConcurrentUtils; |
| |
| /** |
| * <p> |
| * A specialized {@code ConfigurationBuilder} implementation providing access to |
| * multiple file-based configurations based on a file name pattern. |
| * </p> |
| * <p> |
| * This builder class is initialized with a pattern string and a |
| * {@link ConfigurationInterpolator} object. Each time a configuration is |
| * requested, the pattern is evaluated against the |
| * {@code ConfigurationInterpolator} (so all variables are replaced by their |
| * current values). The resulting string is interpreted as a file name for a |
| * configuration file to be loaded. For example, providing a pattern of |
| * <em>file:///opt/config/${product}/${client}/config.xml</em> will result in |
| * <em>product</em> and <em>client</em> being resolved on every call. By storing |
| * configuration files in a corresponding directory structure, specialized |
| * configuration files associated with a specific product and client can be |
| * loaded. Thus an application can be made multi-tenant in a transparent way. |
| * </p> |
| * <p> |
| * This builder class keeps a map with configuration builders for configurations |
| * already loaded. The {@code getConfiguration()} method first evaluates the |
| * pattern string and checks whether a builder for the resulting file name is |
| * available. If yes, it is queried for its configuration. Otherwise, a new |
| * file-based configuration builder is created now and initialized. |
| * </p> |
| * <p> |
| * Configuration of an instance happens in the usual way for configuration |
| * builders. A {@link MultiFileBuilderParametersImpl} parameters object is |
| * expected which must contain a file name pattern string and a |
| * {@code ConfigurationInterpolator}. Other properties of this parameters object |
| * are used to initialize the builders for managed configurations. |
| * </p> |
| * |
| * @since 2.0 |
| * @param <T> the concrete type of {@code Configuration} objects created by this |
| * builder |
| */ |
| public class MultiFileConfigurationBuilder<T extends FileBasedConfiguration> |
| extends BasicConfigurationBuilder<T> |
| { |
| /** |
| * Constant for the name of the key referencing the |
| * {@code ConfigurationInterpolator} in this builder's parameters. |
| */ |
| private static final String KEY_INTERPOLATOR = "interpolator"; |
| |
| /** A cache for already created managed builders. */ |
| private final ConcurrentMap<String, FileBasedConfigurationBuilder<T>> managedBuilders = |
| new ConcurrentHashMap<>(); |
| |
| /** Stores the {@code ConfigurationInterpolator} object. */ |
| private final AtomicReference<ConfigurationInterpolator> interpolator = |
| new AtomicReference<>(); |
| |
| /** |
| * A flag for preventing reentrant access to managed builders on |
| * interpolation of the file name pattern. |
| */ |
| private final ThreadLocal<Boolean> inInterpolation = |
| new ThreadLocal<>(); |
| |
| /** A list for the event listeners to be passed to managed builders. */ |
| private final EventListenerList configurationListeners = new EventListenerList(); |
| |
| /** |
| * A specialized event listener which gets registered at all managed |
| * builders. This listener just propagates notifications from managed |
| * builders to the listeners registered at this |
| * {@code MultiFileConfigurationBuilder}. |
| */ |
| private final EventListener<ConfigurationBuilderEvent> managedBuilderDelegationListener = |
| event -> handleManagedBuilderEvent(event); |
| |
| /** |
| * Creates a new instance of {@code MultiFileConfigurationBuilder} and sets |
| * initialization parameters and a flag whether initialization failures |
| * should be ignored. |
| * |
| * @param resCls the result configuration class |
| * @param params a map with initialization parameters |
| * @param allowFailOnInit a flag whether initialization errors should be |
| * ignored |
| * @throws IllegalArgumentException if the result class is <b>null</b> |
| */ |
| public MultiFileConfigurationBuilder(final Class<? extends T> resCls, |
| final Map<String, Object> params, final boolean allowFailOnInit) |
| { |
| super(resCls, params, allowFailOnInit); |
| } |
| |
| /** |
| * Creates a new instance of {@code MultiFileConfigurationBuilder} and sets |
| * initialization parameters. |
| * |
| * @param resCls the result configuration class |
| * @param params a map with initialization parameters |
| * @throws IllegalArgumentException if the result class is <b>null</b> |
| */ |
| public MultiFileConfigurationBuilder(final Class<? extends T> resCls, |
| final Map<String, Object> params) |
| { |
| super(resCls, params); |
| } |
| |
| /** |
| * Creates a new instance of {@code MultiFileConfigurationBuilder} without |
| * setting initialization parameters. |
| * |
| * @param resCls the result configuration class |
| * @throws IllegalArgumentException if the result class is <b>null</b> |
| */ |
| public MultiFileConfigurationBuilder(final Class<? extends T> resCls) |
| { |
| super(resCls); |
| } |
| |
| /** |
| * {@inheritDoc} This method is overridden to adapt the return type. |
| */ |
| @Override |
| public MultiFileConfigurationBuilder<T> configure(final BuilderParameters... params) |
| { |
| super.configure(params); |
| return this; |
| } |
| |
| /** |
| * {@inheritDoc} This implementation evaluates the file name pattern using |
| * the configured {@code ConfigurationInterpolator}. If this file has |
| * already been loaded, the corresponding builder is accessed. Otherwise, a |
| * new builder is created for loading this configuration file. |
| */ |
| @Override |
| public T getConfiguration() throws ConfigurationException |
| { |
| return getManagedBuilder().getConfiguration(); |
| } |
| |
| /** |
| * Returns the managed {@code FileBasedConfigurationBuilder} for the current |
| * file name pattern. It is determined based on the evaluation of the file |
| * name pattern using the configured {@code ConfigurationInterpolator}. If |
| * this is the first access to this configuration file, the builder is |
| * created. |
| * |
| * @return the configuration builder for the configuration corresponding to |
| * the current evaluation of the file name pattern |
| * @throws ConfigurationException if the builder cannot be determined (e.g. |
| * due to missing initialization parameters) |
| */ |
| public FileBasedConfigurationBuilder<T> getManagedBuilder() |
| throws ConfigurationException |
| { |
| final Map<String, Object> params = getParameters(); |
| final MultiFileBuilderParametersImpl multiParams = |
| MultiFileBuilderParametersImpl.fromParameters(params, true); |
| if (multiParams.getFilePattern() == null) |
| { |
| throw new ConfigurationException("No file name pattern is set!"); |
| } |
| final String fileName = fetchFileName(multiParams); |
| |
| FileBasedConfigurationBuilder<T> builder = |
| getManagedBuilders().get(fileName); |
| if (builder == null) |
| { |
| builder = |
| createInitializedManagedBuilder(fileName, |
| createManagedBuilderParameters(params, multiParams)); |
| final FileBasedConfigurationBuilder<T> newBuilder = |
| ConcurrentUtils.putIfAbsent(getManagedBuilders(), fileName, |
| builder); |
| if (newBuilder == builder) |
| { |
| initListeners(newBuilder); |
| } |
| else |
| { |
| builder = newBuilder; |
| } |
| } |
| return builder; |
| } |
| |
| /** |
| * {@inheritDoc} This implementation ensures that the listener is also added |
| * to managed configuration builders if necessary. Listeners for the builder-related |
| * event types are excluded because otherwise they would be triggered by the |
| * internally used configuration builders. |
| */ |
| @Override |
| public synchronized <E extends Event> void addEventListener( |
| final EventType<E> eventType, final EventListener<? super E> l) |
| { |
| super.addEventListener(eventType, l); |
| if (isEventTypeForManagedBuilders(eventType)) |
| { |
| for (final FileBasedConfigurationBuilder<T> b : getManagedBuilders() |
| .values()) |
| { |
| b.addEventListener(eventType, l); |
| } |
| configurationListeners.addEventListener(eventType, l); |
| } |
| } |
| |
| /** |
| * {@inheritDoc} This implementation ensures that the listener is also |
| * removed from managed configuration builders if necessary. |
| */ |
| @Override |
| public synchronized <E extends Event> boolean removeEventListener( |
| final EventType<E> eventType, final EventListener<? super E> l) |
| { |
| final boolean result = super.removeEventListener(eventType, l); |
| if (isEventTypeForManagedBuilders(eventType)) |
| { |
| for (final FileBasedConfigurationBuilder<T> b : getManagedBuilders() |
| .values()) |
| { |
| b.removeEventListener(eventType, l); |
| } |
| configurationListeners.removeEventListener(eventType, l); |
| } |
| return result; |
| } |
| |
| /** |
| * {@inheritDoc} This implementation clears the cache with all managed |
| * builders. |
| */ |
| @Override |
| public synchronized void resetParameters() |
| { |
| for (final FileBasedConfigurationBuilder<T> b : getManagedBuilders().values()) |
| { |
| b.removeEventListener(ConfigurationBuilderEvent.ANY, |
| managedBuilderDelegationListener); |
| } |
| getManagedBuilders().clear(); |
| interpolator.set(null); |
| super.resetParameters(); |
| } |
| |
| /** |
| * Returns the {@code ConfigurationInterpolator} used by this instance. This |
| * is the object used for evaluating the file name pattern. It is created on |
| * demand. |
| * |
| * @return the {@code ConfigurationInterpolator} |
| */ |
| protected ConfigurationInterpolator getInterpolator() |
| { |
| ConfigurationInterpolator result; |
| boolean done; |
| |
| // This might create multiple instances under high load, |
| // however, always the same instance is returned. |
| do |
| { |
| result = interpolator.get(); |
| if (result != null) |
| { |
| done = true; |
| } |
| else |
| { |
| result = createInterpolator(); |
| done = interpolator.compareAndSet(null, result); |
| } |
| } while (!done); |
| |
| return result; |
| } |
| |
| /** |
| * Creates the {@code ConfigurationInterpolator} to be used by this |
| * instance. This method is called when a file name is to be constructed, |
| * but no current {@code ConfigurationInterpolator} instance is available. |
| * It obtains an instance from this builder's parameters. If no properties |
| * of the {@code ConfigurationInterpolator} are specified in the parameters, |
| * a default instance without lookups is returned (which is probably not |
| * very helpful). |
| * |
| * @return the {@code ConfigurationInterpolator} to be used |
| */ |
| protected ConfigurationInterpolator createInterpolator() |
| { |
| final InterpolatorSpecification spec = |
| BasicBuilderParameters |
| .fetchInterpolatorSpecification(getParameters()); |
| return ConfigurationInterpolator.fromSpecification(spec); |
| } |
| |
| /** |
| * Determines the file name of a configuration based on the file name |
| * pattern. This method is called on every access to this builder's |
| * configuration. It obtains the {@link ConfigurationInterpolator} from this |
| * builder's parameters and uses it to interpolate the file name pattern. |
| * |
| * @param multiParams the parameters object for this builder |
| * @return the name of the configuration file to be loaded |
| */ |
| protected String constructFileName( |
| final MultiFileBuilderParametersImpl multiParams) |
| { |
| final ConfigurationInterpolator ci = getInterpolator(); |
| return String.valueOf(ci.interpolate(multiParams.getFilePattern())); |
| } |
| |
| /** |
| * Creates a builder for a managed configuration. This method is called |
| * whenever a configuration for a file name is requested which has not yet |
| * been loaded. The passed in map with parameters is populated from this |
| * builder's configuration (i.e. the basic parameters plus the optional |
| * parameters for managed builders). This base implementation creates a |
| * standard builder for file-based configurations. Derived classes may |
| * override it to create special purpose builders. |
| * |
| * @param fileName the name of the file to be loaded |
| * @param params a map with initialization parameters for the new builder |
| * @return the newly created builder instance |
| * @throws ConfigurationException if an error occurs |
| */ |
| protected FileBasedConfigurationBuilder<T> createManagedBuilder( |
| final String fileName, final Map<String, Object> params) |
| throws ConfigurationException |
| { |
| return new FileBasedConfigurationBuilder<>(getResultClass(), params, |
| isAllowFailOnInit()); |
| } |
| |
| /** |
| * Creates a fully initialized builder for a managed configuration. This |
| * method is called by {@code getConfiguration()} whenever a configuration |
| * file is requested which has not yet been loaded. This implementation |
| * delegates to {@code createManagedBuilder()} for actually creating the |
| * builder object. Then it sets the location to the configuration file. |
| * |
| * @param fileName the name of the file to be loaded |
| * @param params a map with initialization parameters for the new builder |
| * @return the newly created and initialized builder instance |
| * @throws ConfigurationException if an error occurs |
| */ |
| protected FileBasedConfigurationBuilder<T> createInitializedManagedBuilder( |
| final String fileName, final Map<String, Object> params) |
| throws ConfigurationException |
| { |
| final FileBasedConfigurationBuilder<T> managedBuilder = |
| createManagedBuilder(fileName, params); |
| managedBuilder.getFileHandler().setFileName(fileName); |
| return managedBuilder; |
| } |
| |
| /** |
| * Returns the map with the managed builders created so far by this |
| * {@code MultiFileConfigurationBuilder}. This map is exposed to derived |
| * classes so they can access managed builders directly. However, derived |
| * classes are not expected to manipulate this map. |
| * |
| * @return the map with the managed builders |
| */ |
| protected ConcurrentMap<String, FileBasedConfigurationBuilder<T>> getManagedBuilders() |
| { |
| return managedBuilders; |
| } |
| |
| /** |
| * Registers event listeners at the passed in newly created managed builder. |
| * This method registers a special {@code EventListener} which propagates |
| * builder events to listeners registered at this builder. In addition, |
| * {@code ConfigurationListener} and {@code ConfigurationErrorListener} |
| * objects are registered at the new builder. |
| * |
| * @param newBuilder the builder to be initialized |
| */ |
| private void initListeners(final FileBasedConfigurationBuilder<T> newBuilder) |
| { |
| copyEventListeners(newBuilder, configurationListeners); |
| newBuilder.addEventListener(ConfigurationBuilderEvent.ANY, |
| managedBuilderDelegationListener); |
| } |
| |
| /** |
| * Generates a file name for a managed builder based on the file name |
| * pattern. This method prevents infinite loops which could happen if the |
| * file name pattern cannot be resolved and the |
| * {@code ConfigurationInterpolator} used by this object causes a recursive |
| * lookup to this builder's configuration. |
| * |
| * @param multiParams the current builder parameters |
| * @return the file name for a managed builder |
| */ |
| private String fetchFileName(final MultiFileBuilderParametersImpl multiParams) |
| { |
| String fileName; |
| final Boolean reentrant = inInterpolation.get(); |
| if (reentrant != null && reentrant.booleanValue()) |
| { |
| fileName = multiParams.getFilePattern(); |
| } |
| else |
| { |
| inInterpolation.set(Boolean.TRUE); |
| try |
| { |
| fileName = constructFileName(multiParams); |
| } |
| finally |
| { |
| inInterpolation.set(Boolean.FALSE); |
| } |
| } |
| return fileName; |
| } |
| |
| /** |
| * Handles events received from managed configuration builders. This method |
| * creates a new event with a source pointing to this builder and propagates |
| * it to all registered listeners. |
| * |
| * @param event the event received from a managed builder |
| */ |
| private void handleManagedBuilderEvent(final ConfigurationBuilderEvent event) |
| { |
| if (ConfigurationBuilderEvent.RESET.equals(event.getEventType())) |
| { |
| resetResult(); |
| } |
| else |
| { |
| fireBuilderEvent(createEventWithChangedSource(event)); |
| } |
| } |
| |
| /** |
| * Creates a new {@code ConfigurationBuilderEvent} based on the passed in |
| * event, but with the source changed to this builder. This method is called |
| * when an event was received from a managed builder. In this case, the |
| * event has to be passed to the builder listeners registered at this |
| * object, but with the correct source property. |
| * |
| * @param event the event received from a managed builder |
| * @return the event to be propagated |
| */ |
| private ConfigurationBuilderEvent createEventWithChangedSource( |
| final ConfigurationBuilderEvent event) |
| { |
| if (ConfigurationBuilderResultCreatedEvent.RESULT_CREATED.equals(event |
| .getEventType())) |
| { |
| return new ConfigurationBuilderResultCreatedEvent(this, |
| ConfigurationBuilderResultCreatedEvent.RESULT_CREATED, |
| ((ConfigurationBuilderResultCreatedEvent) event) |
| .getConfiguration()); |
| } |
| @SuppressWarnings("unchecked") |
| final |
| // This is safe due to the constructor of ConfigurationBuilderEvent |
| EventType<? extends ConfigurationBuilderEvent> type = |
| (EventType<? extends ConfigurationBuilderEvent>) event |
| .getEventType(); |
| return new ConfigurationBuilderEvent(this, type); |
| } |
| |
| /** |
| * Creates a map with parameters for a new managed configuration builder. |
| * This method merges the basic parameters set for this builder with the |
| * specific parameters object for managed builders (if provided). |
| * |
| * @param params the parameters of this builder |
| * @param multiParams the parameters object for this builder |
| * @return the parameters for a new managed builder |
| */ |
| private static Map<String, Object> createManagedBuilderParameters( |
| final Map<String, Object> params, |
| final MultiFileBuilderParametersImpl multiParams) |
| { |
| final Map<String, Object> newParams = new HashMap<>(params); |
| newParams.remove(KEY_INTERPOLATOR); |
| final BuilderParameters managedBuilderParameters = |
| multiParams.getManagedBuilderParameters(); |
| if (managedBuilderParameters != null) |
| { |
| // clone parameters as they are applied to multiple builders |
| final BuilderParameters copy = |
| (BuilderParameters) ConfigurationUtils |
| .cloneIfPossible(managedBuilderParameters); |
| newParams.putAll(copy.getParameters()); |
| } |
| return newParams; |
| } |
| |
| /** |
| * Checks whether the given event type is of interest for the managed |
| * configuration builders. This method is called by the methods for managing |
| * event listeners to find out whether a listener should be passed to the |
| * managed builders, too. |
| * |
| * @param eventType the event type object |
| * @return a flag whether this event type is of interest for managed |
| * builders |
| */ |
| private static boolean isEventTypeForManagedBuilders(final EventType<?> eventType) |
| { |
| return !EventType |
| .isInstanceOf(eventType, ConfigurationBuilderEvent.ANY); |
| } |
| } |