blob: 6188f83db427f784ce32570db9fab9073c847ced [file] [log] [blame]
/*
* 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.tamaya.builder;
import org.apache.tamaya.ConfigException;
import org.apache.tamaya.Configuration;
import org.apache.tamaya.builder.spi.PropertyConverter;
import org.apache.tamaya.TypeLiteral;
import org.apache.tamaya.format.ConfigurationData;
import org.apache.tamaya.format.ConfigurationFormats;
import org.apache.tamaya.format.FlattenedDefaultPropertySource;
import org.apache.tamaya.builder.spi.ConfigurationContext;
import org.apache.tamaya.builder.spi.PropertyFilter;
import org.apache.tamaya.builder.spi.PropertySource;
import org.apache.tamaya.builder.spi.PropertySourceProvider;
import org.apache.tamaya.builder.spi.PropertyValueCombinationPolicy;
import org.apache.tamaya.spisupport.DefaultConfiguration;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Collection;
import java.util.Objects;
import static java.lang.String.format;
/**
* <p>Builder class used for building a configuration manually without relying
* only on the Service Provider Interface API.</p>
*
* <p><strong>Features of the builder</strong></p>
*
* <ol>
* <li>Adding of property converters manually</li>
* <li>Adding of property sources directly</li>
* <li>Adding of property sources via URL</li>
* <li>Adding of property source providers directly</li>
* <li>Enabling and disabling of via SPI mechanism provided resources as converters,
* property sources, etc.</li>
* </ol>
*
* <p><strong>Example</strong></p>
*
* <pre>{@code ConfigurationBuilder builder = new ConfigurationBuilder();
* builder.disableProvidedPropertySources() // Do not load provided property
* .disableProvidedPropertySourceProviders() // sources and providers automatically
* .addPropertySource("file:/etc/conf.properties"); // Load properties from conf.properties
*
* Configuration config = builder.build();
* }</pre>
*
* <p><strong>Support for configuration formats</strong></p>
*
* The configuration builder allows you to put property resources
* via a URL, as shown in the code example above, without implementing
* a {@link PropertySource PropertySource} or providing an
* instance of a {@link PropertySource PropertySource}.
* If a property resource in
* a specific format can be added to configuration builder or not depends
* on the available implementations of
* {@link org.apache.tamaya.format.ConfigurationFormat} in the classpath.
* Which formats are available can be checked via
* {@link org.apache.tamaya.format.ConfigurationFormats#getFormats()}.
*/
public class ConfigurationBuilder {
/** Builder used to create new ConfigurationContext instances. */
private final ProgrammaticConfigurationContext.Builder contextBuilder = new ProgrammaticConfigurationContext.Builder();
/**
* Flag if the config has already been built.
* Configuration can be built only once
*/
private boolean built;
/**
* Flag if all existing property converter service providers
* should be loaded if the configuration is build.
*/
private boolean loadProvidedPropertyConverters = true;
/**
* Flag if all existing property source service providers
* will be loaded if the configuration is build.
*/
private boolean loadProvidedPropertySources = false;
private boolean loadProvidedPropertySourceProviders = false;
private boolean isLoadProvidedPropertyFilters = false;
/**
* Creates a new builder instance.
*/
public ConfigurationBuilder() {
}
/**
* Allows to set configuration context during unit tests.
*/
ConfigurationBuilder setConfigurationContext(ConfigurationContext configurationContext) {
//noinspection deprecation
contextBuilder.setConfigurationContext(configurationContext);
return this;
}
/**
* Adds one resources with properties in an arbitrary format
* to the configuration to be build.
*
* <p>If a specific format is supported depends on the available
* {@link org.apache.tamaya.format.ConfigurationFormat} implementations.</p>
*
* <pre>{@code URL resource = new URL("file:/etc/service/config.json");
*
* builder.addPropertySources(resource);}
* </pre>
*
* @param url resource with properties for the the configuration to be build.
*
* @return the builder instance currently used
*
* @see org.apache.tamaya.format.ConfigurationFormat
* @see org.apache.tamaya.format.ConfigurationFormats#getFormats()
*/
public ConfigurationBuilder addPropertySource(URL url) {
try {
ConfigurationData data = getConfigurationDataFromURL(url);
FlattenedDefaultPropertySource propertySource = new FlattenedDefaultPropertySource(data);
addPropertySources(propertySource);
} catch (IOException e) {
throw new ConfigException("Failed to read " + url.toString(), e);
}
return this;
}
protected ConfigurationData getConfigurationDataFromURL(URL url) throws IOException {
ConfigurationData data = ConfigurationFormats.readConfigurationData(url);
if (null == data) {
String mesg = format("No configuration format found which is able " +
"to read properties from %s.", url.toString());
throw new ConfigException(mesg);
}
return data;
}
/**
* Adds one or more resources with properties in an arbitrary format
* to the configuration to be build.
*
* <p>If a specific format is supported depends on the available
* {@link org.apache.tamaya.format.ConfigurationFormat} implementations.</p>
*
*<pre>{@code URL first = new URL("file:/etc/service/config.json");
* URL second = new URL("file:/etc/defaults/values.properties");
*
* builder.addPropertySources(first, second);}
*</pre>
*
* @param urls list of resources with properties for the configuration to be
* build.
*
* @return the builder instance currently used
*
* @see org.apache.tamaya.format.ConfigurationFormat
* @see org.apache.tamaya.format.ConfigurationFormats#getFormats()
*/
public ConfigurationBuilder addPropertySources(URL... urls) {
for(URL url:urls){
if(url!=null){
addPropertySource(url);
}
}
return this;
}
/**
* Adds one or more resources with properties in an arbitrary format
* to the configuration to be build.
*
* <p>If a specific format is supported depends on the available
* {@link org.apache.tamaya.format.ConfigurationFormat} implementations.</p>
*
*<pre>{@code URL first = new URL("file:/etc/service/config.json");
* URL second = new URL("file:/etc/defaults/values.properties");
*
* builder.addPropertySources(first, second);}
*</pre>
*
* @param urls list of resources with properties for the configuration to be
* build.
*
* @return the builder instance currently used
*
* @see org.apache.tamaya.format.ConfigurationFormat
* @see org.apache.tamaya.format.ConfigurationFormats#getFormats()
*/
public ConfigurationBuilder addPropertySources(Collection<URL> urls) {
for(URL url:urls) {
if (url != null) {
addPropertySource(url);
}
}
return this;
}
/**
* Adds one or more resources with properties in an arbitrary format
* to the configuration to be build.
*
* <p>If a specific format is supported depends on the available
* {@link org.apache.tamaya.format.ConfigurationFormat} implementations.</p>
*
*<pre>{@code builder.addPropertySources("file:/etc/service/config.json",
* "file:/etc/defaults/values.properties");}
*</pre>
*
* @param urls list of resources with properties for the configuration to be
* build.
*
* @return the builder instance currently used
*
* @see org.apache.tamaya.format.ConfigurationFormat
* @see org.apache.tamaya.format.ConfigurationFormats#getFormats()
*/
public ConfigurationBuilder addPropertySources(String... urls) {
for(String url:urls) {
if (url != null) {
try{
addPropertySource(new URL(url));
} catch(Exception e){
throw new ConfigException("Invalid URL: " + url);
}
}
}
return this;
}
/**
* Adds one or more property source instances to the configuration to be build.
*
*<pre>{@code PropertySource first = new CustomPropertySource();
* PropertySource second = new YetAnotherPropertySource();
*
* builder.addPropertySources(first, second)};
*</pre>
*
* @param sources list of property source instances with properties for the
* configuration to be build.
*
* @return the builder instance currently used
*
* @see PropertySource
*/
public ConfigurationBuilder addPropertySources(PropertySource... sources){
checkBuilderState();
contextBuilder.addPropertySources(Objects.requireNonNull(sources));
return this;
}
private void checkBuilderState() {
if (built) {
throw new IllegalStateException("Configuration has already been build.");
}
}
/**
* Adds one or more property source provider instances to the configuration to be build.
*
* <pre>{@code PropertySourceProvider jc = new JavaConfigurationProvider();
*
* builder.addPropertySources(jc)};
* </pre>
*
* @param providers list of property source provider instances each providing a set
* of property source instances for the configuration to be build.
*
* @return the builder instance currently used
*
* @see PropertySourceProvider
*/
public ConfigurationBuilder addPropertySourceProviders(PropertySourceProvider... providers){
contextBuilder.addPropertySourceProviders(providers);
return this;
}
/**
* Adds one or more property filter instances to the configuration to be build.
*
* <pre>{@code PropertyFilter quoteReplacingFilter = new QuoteFilter();
* PropertyFilter commaRemovingFilter = new CommaFilter();
*
* builder.addPropertyFilters(commaRemovingFilter, quoteReplacingFilter)};
* </pre>
*
* @param filters list of property filter instances which should be applied
* to the properties of the configuration to be build.
*
* @return the builder instance currently used
*
* @see PropertyFilter
* @see #disableProvidedPropertyFilters()
* @see #enabledProvidedPropertyFilters()
*/
public ConfigurationBuilder addPropertyFilters(PropertyFilter... filters){
Objects.requireNonNull(filters);
contextBuilder.addPropertyFilters(filters);
return this;
}
/**
* @param propertyValueCombinationPolicy combination policy to use for this builder.
* @return the builder instance currently in use.
*/
public ConfigurationBuilder setPropertyValueCombinationPolicy(PropertyValueCombinationPolicy propertyValueCombinationPolicy){
contextBuilder.setPropertyValueCombinationPolicy(propertyValueCombinationPolicy);
return this;
}
/**
* Adds a property converter for the a given type to the configuration to
* be build.
*
* <pre>{@code PropertyConverter<MyType> converter = value -> new MyType(value, 42);
*
* builder.addPropertyConverter(MyType.class, converter}
* </pre>
*
* @param <T> the type of the configuration
* @param type the required target type the converter should be applied to
* @param converter the converter to be used to convert the string property
* to the given target type.
*
* @return the builder instance currently used
*
* @see PropertyConverter
* @see #enableProvidedPropertyConverters()
* @see #disableProvidedPropertyConverters()
*/
public <T> ConfigurationBuilder addPropertyConverter(Class<T> type, PropertyConverter<T> converter) {
Objects.requireNonNull(type);
Objects.requireNonNull(converter);
return addPropertyConverter(TypeLiteral.of(type), (PropertyConverter<Object>)converter);
}
/**
* Adds a propertyConverter of a given type.
*
* @param <T> the type of the configuration
* @param type type literal of this converter.
* @param propertyConverter property converter.
* @return the builder instance currently used
*/
public <T> ConfigurationBuilder addPropertyConverter(TypeLiteral<T> type, PropertyConverter<T> propertyConverter){
Objects.requireNonNull(type);
Objects.requireNonNull(propertyConverter);
contextBuilder.addPropertyConverter(type, propertyConverter);
return this;
}
/**
* Checks if the automatic loading of all {@link PropertyConverter
* PropertyConverter} service providers is enabled or disabled.
*
* @return {@code true} if the automatic loading is enabled,
* otherwise {@code false}.
*
* @see #enableProvidedPropertyConverters()
* @see #disableProvidedPropertyConverters()
* @see #addPropertyConverter(Class, PropertyConverter)
* @see #addPropertyConverter(org.apache.tamaya.TypeLiteral, PropertyConverter)
*/
public boolean isPropertyConverterLoadingEnabled() {
return loadProvidedPropertyConverters;
}
/**
* Enables the loading of all {@link PropertyConverter}
* service providers.
*
* @return the builder instance currently used
*
* @see PropertyConverter
* @see #disableProvidedPropertyConverters()
* @see #enableProvidedPropertyConverters()
*/
public ConfigurationBuilder enableProvidedPropertyConverters() {
checkBuilderState();
loadProvidedPropertyConverters = true;
return this;
}
/**
* Disables the automatic loading of all {@link PropertyConverter}
* service providers.
*
* @return the builder instance currently used
*
* @see PropertyConverter
* @see #enableProvidedPropertyConverters()
* @see #addPropertyConverter(Class, PropertyConverter)
*/
public ConfigurationBuilder disableProvidedPropertyConverters() {
checkBuilderState();
loadProvidedPropertyConverters = false;
return this;
}
/**
* Enables the automatic loading of all {@link PropertySource}
* service providers.
*
* @return the builder instance currently used
*
* @see PropertySource
* @see #disableProvidedPropertySources()
*/
public ConfigurationBuilder enableProvidedPropertySources() {
checkBuilderState();
loadProvidedPropertySources = true;
return this;
}
/**
* Checks if the automatic loading of all {@link PropertySource
* PropertySource} service providers is enabled or disabled.
*
* @return {@code true} if the automatic loading is enabled,
* otherwise {@code false}.
*/
public boolean isPropertySourcesLoadingEnabled() {
return loadProvidedPropertySources;
}
/**
* Checks if the automatic loading of all {@link PropertyFilter
* PropertyFilter} service providers is enabled or disabled.
*
* @return {@code true} if the automatic loading is enabled,
* otherwise {@code false}.
*/
public boolean isPropertyFilterLoadingEnabled() {
return isLoadProvidedPropertyFilters;
}
/**
* Enables the automatic loading of all {@link PropertyFilter}
* service providers.
*
* @return the builder instance currently used
*
* @see PropertyFilter
* @see #disableProvidedPropertyFilters()
* @see #addPropertyFilters(PropertyFilter...)
*/
public ConfigurationBuilder enabledProvidedPropertyFilters() {
checkBuilderState();
isLoadProvidedPropertyFilters = true;
return this;
}
/**
* Disables the automatic loading of all {@link PropertyFilter}
* service providers.
*
* @see PropertyFilter
* @see #enabledProvidedPropertyFilters()
* @see #addPropertyFilters(PropertyFilter...)
*
* @return the builder instance currently used
*/
public ConfigurationBuilder disableProvidedPropertyFilters() {
checkBuilderState();
isLoadProvidedPropertyFilters = false;
return this;
}
/**
* Disables the automatic loading of all {@link PropertySource}
* service providers.
*
* @return the builder instance currently used
*
* @see PropertySource
* @see #enableProvidedPropertySources()
*/
public ConfigurationBuilder disableProvidedPropertySources() {
checkBuilderState();
loadProvidedPropertySources = false;
return this;
}
/**
* Enables the automatic loading of {@link PropertySourceProvider
* property source providers} provided via the SPI API.
*
* @return the builder instance currently used
*
* @see PropertySourceProvider
*/
public ConfigurationBuilder enableProvidedPropertySourceProviders() {
checkBuilderState();
loadProvidedPropertySourceProviders = true;
return this;
}
/**
* Disables the automatic loading of {@link PropertySourceProvider
* property source providers} provided via the SPI API.
*
* @return the builder instance currently used
*/
public ConfigurationBuilder disableProvidedPropertySourceProviders() {
checkBuilderState();
loadProvidedPropertySourceProviders = false;
return this;
}
/**
* Checks if the automatic loading of {@link PropertySourceProvider
* PropertySourceProviders} is enabled or disabled.
*
* @return {@code true} if the automatic loading is enabled,
* otherwise {@code false}.
*/
public boolean isPropertySourceProvidersLoadingEnabled() {
return loadProvidedPropertySourceProviders;
}
//X TODO think on a functionality/API for using the default PropertyConverters and use the configured ones here
//X TODO as overrides used first.
/**
* Builds a new configuration based on the configuration of this builder instance.
*
* @return a new {@link org.apache.tamaya.Configuration configuration instance},
* never {@code null}.
*/
public Configuration build() {
checkBuilderState();
built = true;
contextBuilder.loadProvidedPropertyConverters(isPropertyConverterLoadingEnabled());
contextBuilder.loadProvidedPropertySources(isPropertySourcesLoadingEnabled());
contextBuilder.loadProvidedPropertySourceProviders(isPropertySourceProvidersLoadingEnabled());
contextBuilder.loadProvidedPropertyFilters(isLoadProvidedPropertyFilters);
return new DefaultConfiguration(contextBuilder.build());
}
/**
* Mapper to map a URL given as string to an URL instance.
*/
private static class StringToURLMapper {
public URL apply(String u) {
try {
return new URL(u);
} catch (MalformedURLException e) {
throw new ConfigException(u + " is not a valid URL", e);
}
}
}
}