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.combined;
018
019import java.util.Arrays;
020
021import org.apache.commons.configuration2.Configuration;
022import org.apache.commons.configuration2.ConfigurationUtils;
023import org.apache.commons.configuration2.HierarchicalConfiguration;
024import org.apache.commons.configuration2.builder.BuilderConfigurationWrapperFactory;
025import org.apache.commons.configuration2.builder.BuilderConfigurationWrapperFactory.EventSourceSupport;
026import org.apache.commons.configuration2.builder.ConfigurationBuilder;
027import org.apache.commons.configuration2.event.Event;
028import org.apache.commons.configuration2.event.EventListener;
029import org.apache.commons.configuration2.event.EventType;
030import org.apache.commons.configuration2.ex.ConfigurationException;
031import org.apache.commons.configuration2.reloading.ReloadingController;
032import org.apache.commons.configuration2.reloading.ReloadingControllerSupport;
033
034/**
035 * <p>
036 * A specialized {@code ConfigurationBuilderProvider} implementation for integrating
037 * {@link MultiFileConfigurationBuilder} with {@code CombinedConfigurationBuilder}.
038 * </p>
039 * <p>
040 * When using a configuration source managed by {@code MultiFileConfigurationBuilder} it is not sufficient to store the
041 * configuration once obtained from the builder in the resulting combined configuration. Rather, it has to be ensured
042 * that each access to this configuration queries the builder anew so that it can evaluate its file pattern and return a
043 * different configuration if necessary. Therefore, this class returns a specialized wrapper over a
044 * {@code MultiFileConfigurationBuilder} which returns a configuration wrapping the builder; so accessing the
045 * configuration's properties actually calls back to the builder. This constellation is compatible with the way
046 * {@code DynamicCombinedConfiguration} manages its data.
047 * </p>
048 *
049 * @since 2.0
050 */
051public class MultiFileConfigurationBuilderProvider extends BaseConfigurationBuilderProvider {
052    /**
053     * A wrapper builder implementation which also provides a {@code ReloadingController}. This class assumes that the
054     * wrapped builder implements {@code ReloadingControllerSupport}. So the reloading controller can be obtained from this
055     * object.
056     */
057    private static final class ReloadableWrapperBuilder extends WrapperBuilder implements ReloadingControllerSupport {
058        /** The object for obtaining the reloading controller. */
059        private final ReloadingControllerSupport ctrlSupport;
060
061        /**
062         * Creates a new instance of {@code ReloadableWrapperBuilder}.
063         *
064         * @param conf the managed configuration
065         * @param bldr the underlying builder (must implement {@code ReloadingControllerSupport})
066         */
067        public ReloadableWrapperBuilder(final Configuration conf, final ConfigurationBuilder<? extends Configuration> bldr) {
068            super(conf, bldr);
069            ctrlSupport = (ReloadingControllerSupport) bldr;
070        }
071
072        @Override
073        public ReloadingController getReloadingController() {
074            return ctrlSupport.getReloadingController();
075        }
076    }
077
078    /**
079     * A simple wrapper implementation of the {@code ConfigurationBuilder} interface which returns a fix configuration and
080     * delegates to another builder for event listener management.
081     */
082    private static class WrapperBuilder implements ConfigurationBuilder<Configuration> {
083        /** The configuration managed by this builder. */
084        private final Configuration configuration;
085
086        /** The builder to which this instance delegates. */
087        private final ConfigurationBuilder<? extends Configuration> builder;
088
089        /**
090         * Creates a new instance of {@code WrapperBuilder}.
091         *
092         * @param conf the managed configuration
093         * @param bldr the underlying builder
094         */
095        public WrapperBuilder(final Configuration conf, final ConfigurationBuilder<? extends Configuration> bldr) {
096            configuration = conf;
097            builder = bldr;
098        }
099
100        @Override
101        public <T extends Event> void addEventListener(final EventType<T> eventType, final EventListener<? super T> listener) {
102            builder.addEventListener(eventType, listener);
103        }
104
105        @Override
106        public Configuration getConfiguration() throws ConfigurationException {
107            return configuration;
108        }
109
110        @Override
111        public <T extends Event> boolean removeEventListener(final EventType<T> eventType, final EventListener<? super T> listener) {
112            return builder.removeEventListener(eventType, listener);
113        }
114    }
115
116    /** Constant for the name of the builder class. */
117    private static final String BUILDER_CLASS = "org.apache.commons.configuration2.builder.combined.MultiFileConfigurationBuilder";
118
119    /** Constant for the name of the reloading builder class. */
120    private static final String RELOADING_BUILDER_CLASS = "org.apache.commons.configuration2.builder.combined.ReloadingMultiFileConfigurationBuilder";
121
122    /** Constant for the name of the parameters class. */
123    private static final String PARAM_CLASS = "org.apache.commons.configuration2.builder.combined.MultiFileBuilderParametersImpl";
124
125    /**
126     * Creates the {@code ConfigurationBuilder} to be returned by this provider. This is a very simple implementation which
127     * always returns the same wrapper configuration instance. The handling of builder listeners is delegated to the wrapped
128     * {@code MultiFileConfigurationBuilder}. If reloading is support, the builder returned by this method also implements
129     * the {@link ReloadingControllerSupport} interface.
130     *
131     * @param multiBuilder the {@code MultiFileConfigurationBuilder}
132     * @param wrapConfig the configuration to be returned
133     * @return the wrapper builder
134     */
135    private static ConfigurationBuilder<? extends Configuration> createWrapperBuilder(final ConfigurationBuilder<? extends Configuration> multiBuilder,
136        final Configuration wrapConfig) {
137        if (multiBuilder instanceof ReloadingControllerSupport) {
138            return new ReloadableWrapperBuilder(wrapConfig, multiBuilder);
139        }
140        return new WrapperBuilder(wrapConfig, multiBuilder);
141    }
142
143    /**
144     * Creates a new instance of {@code MultiFileConfigurationBuilderProvider} and sets the name of the configuration class
145     * to be returned by {@code MultiFileConfigurationBuilder}.
146     *
147     * @param configCls the name of the managed configuration class
148     * @param paramCls the name of the class of the parameters object to configure the managed configuration
149     */
150    public MultiFileConfigurationBuilderProvider(final String configCls, final String paramCls) {
151        super(BUILDER_CLASS, RELOADING_BUILDER_CLASS, configCls, Arrays.asList(paramCls, PARAM_CLASS));
152    }
153
154    /**
155     * Creates a configuration which wraps the specified builder.
156     *
157     * @param builder the builder
158     * @return the wrapping configuration
159     */
160    // It is safe to disable any type checks because we manually determine
161    // the interface class to be passed to BuilderConfigurationWrapperFactory
162    @SuppressWarnings({"unchecked", "rawtypes"})
163    private Configuration createWrapperConfiguration(final ConfigurationBuilder builder) {
164        final Class<?> configClass = ConfigurationUtils.loadClassNoEx(getConfigurationClass());
165        final Class ifcClass = HierarchicalConfiguration.class.isAssignableFrom(configClass) ? HierarchicalConfiguration.class : Configuration.class;
166        return (Configuration) BuilderConfigurationWrapperFactory.createBuilderConfigurationWrapper(ifcClass, builder, EventSourceSupport.BUILDER);
167    }
168
169    /**
170     * {@inheritDoc} This implementation lets the super class create a fully configured builder. Then it returns a special
171     * wrapper around it.
172     */
173    @Override
174    public ConfigurationBuilder<? extends Configuration> getConfigurationBuilder(final ConfigurationDeclaration decl) throws ConfigurationException {
175        final ConfigurationBuilder<? extends Configuration> multiBuilder = super.getConfigurationBuilder(decl);
176        final Configuration wrapConfig = createWrapperConfiguration(multiBuilder);
177        return createWrapperBuilder(multiBuilder, wrapConfig);
178    }
179}