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