/*
* 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.isis.core.commons.configbuilder;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.Properties;
import java.util.Set;
import org.apache.commons.cli.BasicParser;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.CommandLineParser;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
import org.apache.isis.core.commons.config.ConfigurationConstants;
import org.apache.isis.core.commons.config.IsisConfiguration;
import org.apache.isis.core.commons.config.IsisConfigurationDefault;
import org.apache.isis.core.commons.config.IsisConfigurationDefault.ContainsPolicy;
import org.apache.isis.core.commons.config.NotFoundPolicy;
import org.apache.isis.core.commons.exceptions.IsisException;
import org.apache.isis.core.commons.resource.ResourceStreamSource;
import org.apache.isis.core.commons.resource.ResourceStreamSourceChainOfResponsibility;
import org.apache.isis.core.commons.resource.ResourceStreamSourceFileSystem;
import org.apache.isis.core.runtime.optionhandler.BootPrinter;
import org.apache.isis.core.runtime.optionhandler.OptionHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.base.Objects;
import com.google.common.collect.Sets;
/**
* Holds a mutable set of properties representing the configuration.
*
* This implementation loads the specified
* configuration resource (file) from the given {@link ResourceStreamSource}(s).
*
* <p>
* If a property is in multiple configuration resources then the latter
* resources will overwrite the former.
*
* <p>
* Mutable/immutable pair with the {@link IsisConfiguration}. To obtain the
* configuration, use {@link #getConfiguration()}.
*
* @see {@link IsisConfiguration} for more details on the mutable/immutable pair pattern.
*
*/
public final class IsisConfigurationBuilder {
private static final Logger LOG = LoggerFactory.getLogger(IsisConfigurationBuilder.class);
//region > constructor, fields
private final ResourceStreamSourceChainOfResponsibility resourceStreamSourceChain;
/* package */ final IsisConfigurationDefault configuration;
private boolean locked;
private final Set<String> configurationResourcesFound = Sets.newLinkedHashSet();
private final Set<String> configurationResourcesNotFound = Sets.newLinkedHashSet();
public IsisConfigurationBuilder() {
this(ResourceStreamSourceFileSystem.create(ConfigurationConstants.DEFAULT_CONFIG_DIRECTORY));
}
public IsisConfigurationBuilder(final ResourceStreamSource... resourceStreamSources) {
this(createComposite(Arrays.asList(resourceStreamSources)));
}
public IsisConfigurationBuilder(final List<ResourceStreamSource> resourceStreamSources) {
this(createComposite(resourceStreamSources));
}
public IsisConfigurationBuilder(final ResourceStreamSourceChainOfResponsibility resourceStreamSourceChain) {
this.resourceStreamSourceChain = resourceStreamSourceChain;
configuration = new IsisConfigurationDefault(resourceStreamSourceChain);
}
private static ResourceStreamSourceChainOfResponsibility createComposite(
final List<ResourceStreamSource> resourceStreamSources) {
final ResourceStreamSourceChainOfResponsibility composite = new ResourceStreamSourceChainOfResponsibility();
for (final ResourceStreamSource rss : resourceStreamSources) {
if (rss == null) {
continue;
}
composite.addResourceStreamSource(rss);
}
return composite;
}
public void addDefaultConfigurationResourcesAndPrimers() {
addDefaultConfigurationResources();
addDefaultPrimers();
}
private void addDefaultConfigurationResources() {
IsisConfigurationDefault.ContainsPolicy ignorePolicy = IsisConfigurationDefault.ContainsPolicy.IGNORE;
NotFoundPolicy continuePolicy = NotFoundPolicy.CONTINUE;
addConfigurationResource(ConfigurationConstants.DEFAULT_CONFIG_FILE, NotFoundPolicy.FAIL_FAST, ignorePolicy);
addConfigurationResource(ConfigurationConstants.WEB_CONFIG_FILE, continuePolicy, ignorePolicy);
addConfigurationResource("war.properties", continuePolicy, ignorePolicy);
addConfigurationResource("viewer_wicket.properties", continuePolicy, ignorePolicy);
addConfigurationResource("viewer_restful.properties", continuePolicy, ignorePolicy);
addConfigurationResource("viewer_restfulobjects.properties", continuePolicy, ignorePolicy);
addConfigurationResource("persistor_datanucleus.properties", continuePolicy, ignorePolicy);
addConfigurationResource("persistor.properties", continuePolicy, ignorePolicy);
addConfigurationResource("authentication_shiro.properties", continuePolicy, ignorePolicy);
addConfigurationResource("authentication_bypass.properties", continuePolicy, ignorePolicy);
addConfigurationResource("authentication.properties", continuePolicy, ignorePolicy);
addConfigurationResource("authorization_shiro.properties", continuePolicy, ignorePolicy);
addConfigurationResource("authorization_bypass.properties", continuePolicy, ignorePolicy);
addConfigurationResource("authorization.properties", continuePolicy, ignorePolicy);
addConfigurationResource("reflector_java.properties", continuePolicy, ignorePolicy);
addConfigurationResource("reflector.properties", continuePolicy, ignorePolicy);
addConfigurationResource("fixtures-installer_configuration.properties", continuePolicy, ignorePolicy);
addConfigurationResource("fixtures-installer.properties", continuePolicy, ignorePolicy);
addConfigurationResource("services-installer_annotation.properties", continuePolicy, ignorePolicy);
addConfigurationResource("services-installer_configuration.properties", continuePolicy, ignorePolicy);
addConfigurationResource("services-installer_configuration-and-annotation.properties", continuePolicy, ignorePolicy);
addConfigurationResource("services-installer.properties", continuePolicy, ignorePolicy);
// both override and overrides are accepted (because I keep forgetting which)
addConfigurationResource("override.properties", NotFoundPolicy.CONTINUE, IsisConfigurationDefault.ContainsPolicy.OVERWRITE);
addConfigurationResource("overrides.properties", NotFoundPolicy.CONTINUE, IsisConfigurationDefault.ContainsPolicy.OVERWRITE);
}
private void addDefaultPrimers() {
primeWith(new PrimerForSystemProperties());
primeWith(new PrimerForEnvironmentVariablesIsisPrefix());
primeWith(new PrimerForEnvironmentVariableISIS_OPTS());
}
//endregion
//region > addResourceStreamSource, addResourceStreamSources
public void addResourceStreamSource(final ResourceStreamSource resourceStreamSource) {
addResourceStreamSources(resourceStreamSource);
}
public void addResourceStreamSources(final ResourceStreamSource... resourceStreamSources) {
addResourceStreamSources(Arrays.asList(resourceStreamSources));
}
public void addResourceStreamSources(final List<ResourceStreamSource> resourceStreamSources) {
ensureNotLocked();
for (ResourceStreamSource resourceStreamSource : resourceStreamSources) {
this.resourceStreamSourceChain.addResourceStreamSource(resourceStreamSource);
}
}
//endregion
//region > addConfigurationResource
/**
* Registers the configuration resource (usually, a file) with the specified
* name from the first {@link ResourceStreamSource} available.
*
* <p>
* If the configuration resource cannot be found then the provided
* {@link NotFoundPolicy} determines whether an exception is thrown or not.
*
* <p>
* Must be called before {@link IsisConfigurationBuilder#getConfiguration()}.
*/
public void addConfigurationResource(
final String configurationResource,
final NotFoundPolicy notFoundPolicy,
final IsisConfigurationDefault.ContainsPolicy containsPolicy) {
ensureNotLocked();
if(LOG.isDebugEnabled()) {
LOG.debug(String.format(
"checking availability of configuration resource: %s, notFoundPolicy: %s",
configurationResource, notFoundPolicy));
}
loadConfigurationResource(configurationResource, notFoundPolicy, containsPolicy);
}
/**
* Loads the configuration resource (usually, a file) with the specified
* name from the first {@link ResourceStreamSource} available.
*
* <p>
* If the configuration resource cannot be found then the provided
* {@link NotFoundPolicy} determines whether an exception is thrown or not.
*/
private void loadConfigurationResource(
final String configurationResource,
final NotFoundPolicy notFoundPolicy,
final IsisConfigurationDefault.ContainsPolicy containsPolicy) {
try {
final PropertiesReader propertiesReader =
loadConfigurationResource(resourceStreamSourceChain, configurationResource);
LOG.info("loading properties from " + configurationResource);
configuration.add(propertiesReader.getProperties(), containsPolicy);
configurationResourcesFound.add(configurationResource);
return;
} catch (final IOException ignore) {
// ignore
}
if (notFoundPolicy == NotFoundPolicy.FAIL_FAST) {
throw new IsisException(String.format(
"failed to load '%s'; tried using: %s",
configurationResource, resourceStreamSourceChain.getName()));
}
configurationResourcesNotFound.add(configurationResource);
if(LOG.isDebugEnabled()) {
LOG.debug(String.format("'%s' not found, but not needed", configurationResource));
}
}
private PropertiesReader loadConfigurationResource(final ResourceStreamSource resourceStreamSource, final String configurationResource) throws IOException {
return new PropertiesReader(resourceStreamSource, configurationResource);
}
//endregion
//region > add, put
/**
* Adds additional property; if already present then will _not_ be replaced.
*/
public void add(final String key, final String value) {
ensureNotLocked();
configuration.add(key, value);
}
/**
* Adds/updates property; if already present then _will_ be replaced.
*/
public void put(final String key, final String value) {
ensureNotLocked();
configuration.put(key, value);
}
//endregion
//region > parseAndPrimeWith, primeWith
public boolean parseAndPrimeWith(final List<OptionHandler> optionHandlers, final String[] args) {
// add options (ie cmd line flags)
final Options options = new Options();
for (final OptionHandler optionHandler : optionHandlers) {
optionHandler.addOption(options);
}
// parse options from the cmd line
final boolean parsedOk = parseAndPrimeWith(options, optionHandlers, args);
if(parsedOk) {
for (final OptionHandler optionHandler : optionHandlers) {
primeWith(optionHandler);
}
}
return parsedOk;
}
private boolean parseAndPrimeWith(final Options options, final List<OptionHandler> optionHandlers, final String[] args) {
final BootPrinter printer = new BootPrinter(getClass());
final CommandLineParser parser = new BasicParser();
try {
final CommandLine commandLine = parser.parse(options, args);
for (final OptionHandler optionHandler : optionHandlers) {
if (!optionHandler.handle(commandLine, printer, options)) {
return false;
}
}
} catch (final ParseException e) {
printer.printErrorMessage(e.getMessage());
printer.printHelp(options);
return false;
}
return true;
}
public interface Primer {
void prime(IsisConfigurationBuilder isisConfigurationBuilder);
}
public void primeWith(final Primer primer) {
ensureNotLocked();
LOG.debug("priming configurations for '{}'", primer);
primer.prime(this);
}
//endregion
//region > getConfiguration, peekConfiguration, isLocked
/**
* Returns the {@link IsisConfiguration}; this will cause the configuration to be locked
*/
public IsisConfigurationDefault getConfiguration() {
if(!locked) {
locked = true;
dumpResourcesToLog();
}
return configuration;
}
/**
* Set once {@link #getConfiguration()} is called.
*/
public boolean isLocked() {
return locked;
}
/**
* Creates a copy of the current {@link #getConfiguration()}, without locking.
*
* <p>
* Used while bootstrapping, to obtain the web.server port etc.
* </p>
*/
public IsisConfiguration peekConfiguration() {
IsisConfigurationDefault cfg = new IsisConfigurationDefault(resourceStreamSourceChain);
// no locking
Properties props = new Properties();
props.putAll(configuration.asMap());
cfg.add(props, ContainsPolicy.OVERWRITE);
return cfg;
}
private void ensureNotLocked() {
if (locked) {
throw new IsisException("Configuration has been locked and cannot be changed");
}
}
//endregion
//region > dumpResourcesToLog, toString
/**
* Log a summary of resources found or not found.
*/
public void dumpResourcesToLog() {
if (LOG.isDebugEnabled()) {
LOG.debug("Configuration resources FOUND:");
for (String resource : configurationResourcesFound) {
LOG.debug("* {}", resource);
}
LOG.debug("Configuration resources NOT FOUND (but not needed):");
for (String resource : configurationResourcesNotFound) {
LOG.debug("* {}", resource);
}
}
}
@Override
public String toString() {
return Objects.toStringHelper(this)
.add("resourceStream", resourceStreamSourceChain)
.add("configResources", configurationResourcesFound)
.toString();
}
//endregion
}