001/*
002 *  Licensed to the Apache Software Foundation (ASF) under one
003 *  or more contributor license agreements.  See the NOTICE file
004 *  distributed with this work for additional information
005 *  regarding copyright ownership.  The ASF licenses this file
006 *  to you under the Apache License, Version 2.0 (the
007 *  "License"); you may not use this file except in compliance
008 *  with the License.  You may obtain a copy of the License at
009 *
010 *        http://www.apache.org/licenses/LICENSE-2.0
011 *
012 *  Unless required by applicable law or agreed to in writing,
013 *  software distributed under the License is distributed on an
014 *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 *  KIND, either express or implied.  See the License for the
016 *  specific language governing permissions and limitations
017 *  under the License.
018 */
019
020package org.apache.isis.core.commons.config;
021
022import java.io.IOException;
023import java.util.ArrayList;
024import java.util.List;
025import java.util.Set;
026
027import com.google.common.base.Objects;
028import com.google.common.collect.Sets;
029
030import org.slf4j.Logger;
031import org.slf4j.LoggerFactory;
032
033import org.apache.isis.core.commons.exceptions.IsisException;
034import org.apache.isis.core.commons.resource.ResourceStreamSource;
035import org.apache.isis.core.commons.resource.ResourceStreamSourceChainOfResponsibility;
036import org.apache.isis.core.commons.resource.ResourceStreamSourceFileSystem;
037
038/**
039 * Adapter for {@link IsisConfigurationBuilder}, loading the specified
040 * configuration resource (file) from the given {@link ResourceStreamSource}(s).
041 * 
042 * <p>
043 * If a property is in multiple configuration resources then the latter
044 * resources will overwrite the former.
045 */
046public class IsisConfigurationBuilderResourceStreams implements IsisConfigurationBuilder {
047
048    private static final Logger LOG = LoggerFactory.getLogger(IsisConfigurationBuilderResourceStreams.class);
049    
050    static class ConfigurationResourceAndPolicy {
051        private final String configurationResource;
052        private final NotFoundPolicy notFoundPolicy;
053
054        public ConfigurationResourceAndPolicy(final String configurationResource, final NotFoundPolicy notFoundPolicy) {
055            this.configurationResource = configurationResource;
056            this.notFoundPolicy = notFoundPolicy;
057        }
058
059        public String getConfigurationResource() {
060            return configurationResource;
061        }
062
063        public NotFoundPolicy getNotFoundPolicy() {
064            return notFoundPolicy;
065        }
066
067        @Override
068        public String toString() {
069            return String.format("%s{%s}", configurationResource, notFoundPolicy);
070        }
071    }
072
073    private final Set<String> configurationResourcesFound = Sets.newLinkedHashSet();
074    private final Set<String> configurationResourcesNotFound = Sets.newLinkedHashSet();
075    private final ResourceStreamSource resourceStreamSource;
076    private final IsisConfigurationDefault configuration;
077    private final List<ConfigurationResourceAndPolicy> configurationResources = new ArrayList<ConfigurationResourceAndPolicy>();
078    private boolean locked;
079
080    // ////////////////////////////////////////////////////////////
081    // Constructor, initialization
082    // ////////////////////////////////////////////////////////////
083
084    private static ResourceStreamSource createComposite(final ResourceStreamSource... resourceStreamSources) {
085        final ResourceStreamSourceChainOfResponsibility composite = new ResourceStreamSourceChainOfResponsibility();
086        for (final ResourceStreamSource rss : resourceStreamSources) {
087            if (rss == null) {
088                continue;
089            }
090            composite.addResourceStreamSource(rss);
091        }
092        return composite;
093    }
094
095    public IsisConfigurationBuilderResourceStreams() {
096        this(ResourceStreamSourceFileSystem.create(ConfigurationConstants.DEFAULT_CONFIG_DIRECTORY));
097    }
098
099    public IsisConfigurationBuilderResourceStreams(final ResourceStreamSource... resourceStreamSources) {
100        this(createComposite(resourceStreamSources));
101    }
102
103    public IsisConfigurationBuilderResourceStreams(final ResourceStreamSource resourceStreamSource) {
104        this.resourceStreamSource = resourceStreamSource;
105        configuration = new IsisConfigurationDefault(resourceStreamSource);
106    }
107
108    /**
109     * May be overridden by subclasses if required.
110     */
111    public void addDefaultConfigurationResources() {
112        addConfigurationResource(ConfigurationConstants.DEFAULT_CONFIG_FILE, NotFoundPolicy.FAIL_FAST);
113        addConfigurationResource(ConfigurationConstants.WEB_CONFIG_FILE, NotFoundPolicy.CONTINUE);
114    }
115
116    // ////////////////////////////////////////////////////////////
117    // ResourceStreamSource
118    // ////////////////////////////////////////////////////////////
119
120    @Override
121    public ResourceStreamSource getResourceStreamSource() {
122        return resourceStreamSource;
123    }
124
125    // ////////////////////////////////////////////////////////////
126    // populating or updating
127    // ////////////////////////////////////////////////////////////
128
129    /**
130     * Registers the configuration resource (usually, a file) with the specified
131     * name from the first {@link ResourceStreamSource} available.
132     * 
133     * <p>
134     * If the configuration resource cannot be found then the provided
135     * {@link NotFoundPolicy} determines whether an exception is thrown or not.
136     * 
137     * <p>
138     * Must be called before {@link #getConfiguration()}; the resource is
139     * actually read on {@link #getConfiguration()}.
140     */
141    @Override
142    public synchronized void addConfigurationResource(final String configurationResource, final NotFoundPolicy notFoundPolicy) {
143        LOG.debug("looking for properties file " + configurationResource);
144        loadConfigurationResource(configuration, new ConfigurationResourceAndPolicy(configurationResource, notFoundPolicy));
145        configurationResources.add(new ConfigurationResourceAndPolicy(configurationResource, notFoundPolicy));
146    }
147
148    /**
149     * Adds additional property.
150     */
151    @Override
152    public synchronized void add(final String key, final String value) {
153        if (locked) {
154            throw new IsisException("Configuration has been locked and cannot be changed");
155        }
156        configuration.add(key, value);
157    }
158
159    public void lockConfiguration() {
160        locked = true;
161    }
162    
163    // ////////////////////////////////////////////////////////////
164    // getConfiguration
165    // ////////////////////////////////////////////////////////////
166
167    /**
168     * Returns the current {@link IsisConfiguration configuration}.
169     */
170    @Override
171    public synchronized IsisConfiguration getConfiguration() {
172        return configuration;
173     }
174
175    private void loadConfigurationResource(final IsisConfigurationDefault configuration, final ConfigurationResourceAndPolicy configResourceAndPolicy) {
176        final String configurationResource = configResourceAndPolicy.getConfigurationResource();
177        final NotFoundPolicy notFoundPolicy = configResourceAndPolicy.getNotFoundPolicy();
178        LOG.debug("checking availability of configuration resource: " + configurationResource + ", notFoundPolicy: " + notFoundPolicy);
179        loadConfigurationResource(configuration, configurationResource, notFoundPolicy);
180    }
181
182    /**
183     * Loads the configuration resource (usually, a file) with the specified
184     * name from the first {@link ResourceStreamSource} available.
185     * 
186     * <p>
187     * If the configuration resource cannot be found then the provided
188     * {@link NotFoundPolicy} determines whether an exception is thrown or not.
189     */
190    protected void loadConfigurationResource(final IsisConfigurationDefault configuration, final String configurationResource, final NotFoundPolicy notFoundPolicy) {
191        try {
192            final PropertiesReader propertiesReader = loadConfigurationResource(resourceStreamSource, configurationResource);
193            LOG.info("loading properties from " + configurationResource);
194            configuration.add(propertiesReader.getProperties());
195            configurationResourcesFound.add(configurationResource);
196            return;
197        } catch (final IOException ignore) { }
198        if (notFoundPolicy == NotFoundPolicy.FAIL_FAST) {
199            throw new IsisException("failed to load '" + configurationResource + "'; tried using: " + resourceStreamSource.getName());
200        } else {
201            configurationResourcesNotFound.add(configurationResource);
202            LOG.debug("'" + configurationResource + "' not found, but not needed");
203        }
204    }
205
206    private PropertiesReader loadConfigurationResource(final ResourceStreamSource resourceStreamSource, final String configurationResource) throws IOException {
207        return new PropertiesReader(resourceStreamSource, configurationResource);
208    }
209
210    
211    // TODO review this, should this option default to yes?
212    private void addShowExplorationOptionsIfNotSpecified(final IsisConfigurationDefault configuration) {
213        if (configuration.getString(ConfigurationConstants.SHOW_EXPLORATION_OPTIONS) == null) {
214            configuration.add(ConfigurationConstants.SHOW_EXPLORATION_OPTIONS, "yes");
215        }
216    }
217    
218    // ////////////////////////////////////////////////////////////
219    // Logging
220    // ////////////////////////////////////////////////////////////
221
222    @Override
223    public void dumpResourcesToLog() {
224        if (LOG.isInfoEnabled()) {
225            LOG.info("Configuration resources FOUND:");
226            for (String resource : configurationResourcesFound) {
227                LOG.info("*  " + resource);
228            }
229            LOG.info("Configuration resources NOT FOUND (but not needed):");
230            for (String resource : configurationResourcesNotFound) {
231                LOG.info("*  " + resource);
232            }
233        }
234    }
235
236
237    // ////////////////////////////////////////////////////////////
238    // Injectable
239    // ////////////////////////////////////////////////////////////
240
241    @Override
242    public void injectInto(final Object candidate) {
243        if (IsisConfigurationBuilderAware.class.isAssignableFrom(candidate.getClass())) {
244            final IsisConfigurationBuilderAware cast = IsisConfigurationBuilderAware.class.cast(candidate);
245            cast.setConfigurationBuilder(this);
246        }
247    }
248
249    @Override
250    public String toString() {
251        return Objects.toStringHelper(this).add("resourceStream", resourceStreamSource).add("configResources", configurationResources).toString();
252    }
253
254}