/*
* 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.core.internal;
import org.apache.tamaya.Configuration;
import org.apache.tamaya.TypeLiteral;
import org.apache.tamaya.core.internal.converters.*;
import org.apache.tamaya.core.propertysource.CLIPropertySource;
import org.apache.tamaya.core.propertysource.EnvironmentPropertySource;
import org.apache.tamaya.core.propertysource.SystemPropertySource;
import org.apache.tamaya.core.propertysource.JavaConfigurationPropertySource;
import org.apache.tamaya.spi.ConfigurationContext;
import org.apache.tamaya.spi.ConfigurationContextBuilder;
import org.apache.tamaya.spi.PropertyConverter;
import org.apache.tamaya.spi.PropertyFilter;
import org.apache.tamaya.spi.PropertySource;
import org.apache.tamaya.spi.PropertySourceProvider;
import org.apache.tamaya.spi.PropertyValueCombinationPolicy;
import org.apache.tamaya.spi.ServiceContextManager;
import java.io.File;
import java.lang.reflect.Type;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.net.URI;
import java.net.URL;
import java.nio.file.Path;
import java.util.*;
import java.util.logging.Logger;
/**
* Default implementation of {@link org.apache.tamaya.spi.ConfigurationContextBuilder}.
*/
public class DefaultConfigurationContextBuilder implements ConfigurationContextBuilder {
private static final Logger LOG = Logger.getLogger(DefaultConfigurationContextBuilder.class.getName());
List<PropertyFilter> propertyFilters = new ArrayList<>();
List<PropertySource> propertySources = new ArrayList<>();
PropertyValueCombinationPolicy combinationPolicy =
PropertyValueCombinationPolicy.DEFAULT_OVERRIDING_COLLECTOR;
Map<TypeLiteral<?>, Collection<PropertyConverter<?>>> propertyConverters = new HashMap<>();
/**
* Flag if the config has already been built.
* Configuration can be built only once
*/
private boolean built;
/**
* Creates a new builder instance.
*/
public DefaultConfigurationContextBuilder() {
}
/**
* Creates a new builder instance.
*/
public DefaultConfigurationContextBuilder(ConfigurationContext context) {
this.propertyConverters.putAll(context.getPropertyConverters());
this.propertyFilters.addAll(context.getPropertyFilters());
for(PropertySource ps:context.getPropertySources()) {
addPropertySources(ps);
}
this.combinationPolicy = context.getPropertyValueCombinationPolicy();
}
/**
* Allows to set configuration context during unit tests.
*/
ConfigurationContextBuilder setConfigurationContext(ConfigurationContext configurationContext) {
checkBuilderState();
//noinspection deprecation
this.propertyFilters.clear();
this.propertyFilters.addAll(configurationContext.getPropertyFilters());
this.propertySources.clear();
for(PropertySource ps:configurationContext.getPropertySources()) {
addPropertySources(ps);
}
this.propertyConverters.clear();
this.propertyConverters.putAll(configurationContext.getPropertyConverters());
this.combinationPolicy = configurationContext.getPropertyValueCombinationPolicy();
return this;
}
@Override
public ConfigurationContextBuilder setContext(ConfigurationContext context) {
checkBuilderState();
this.propertyConverters.putAll(context.getPropertyConverters());
for(PropertySource ps:context.getPropertySources()){
this.propertySources.add(ps);
}
this.propertyFilters.addAll(context.getPropertyFilters());
this.combinationPolicy = context.getPropertyValueCombinationPolicy();
return this;
}
@Override
public ConfigurationContextBuilder addPropertySources(PropertySource... sources){
return addPropertySources(Arrays.asList(sources));
}
@Override
public ConfigurationContextBuilder addPropertySources(Collection<PropertySource> sources){
checkBuilderState();
for(PropertySource source:sources) {
if (!this.propertySources.contains(source)) {
this.propertySources.add(source);
}
}
return this;
}
@Override
public ConfigurationContextBuilder addDefaultPropertySources() {
checkBuilderState();
List<PropertySource> propertySources = new ArrayList<>();
addCorePropertyResources(propertySources);
propertySources.addAll(ServiceContextManager.getServiceContext().getServices(PropertySource.class));
for(PropertySourceProvider provider:
ServiceContextManager.getServiceContext().getServices(PropertySourceProvider.class)){
propertySources.addAll(provider.getPropertySources());
}
Collections.sort(propertySources, PropertySourceComparator.getInstance());
return addPropertySources(propertySources);
}
private void addCorePropertyResources(List<PropertySource> propertySources) {
propertySources.add(new EnvironmentPropertySource());
propertySources.add(new JavaConfigurationPropertySource());
propertySources.add(new CLIPropertySource());
propertySources.add(new SystemPropertySource());
}
@Override
public ConfigurationContextBuilder addDefaultPropertyFilters() {
checkBuilderState();
for(PropertyFilter pf:ServiceContextManager.getServiceContext().getServices(PropertyFilter.class)){
addPropertyFilters(pf);
}
return this;
}
@Override
public DefaultConfigurationContextBuilder addDefaultPropertyConverters() {
checkBuilderState();
addCorePropertyConverters();
for(Map.Entry<TypeLiteral, Collection<PropertyConverter>> en:getDefaultPropertyConverters().entrySet()){
for(PropertyConverter pc: en.getValue()) {
addPropertyConverters(en.getKey(), pc);
}
}
return this;
}
private void addCorePropertyConverters() {
addPropertyConverters(TypeLiteral.<BigDecimal>of(BigDecimal.class), new BigDecimalConverter());
addPropertyConverters(TypeLiteral.<BigInteger>of(BigInteger.class), new BigIntegerConverter());
addPropertyConverters(TypeLiteral.<Boolean>of(Boolean.class), new BooleanConverter());
addPropertyConverters(TypeLiteral.<Byte>of(Byte.class), new ByteConverter());
addPropertyConverters(TypeLiteral.<Character>of(Character.class), new CharConverter());
addPropertyConverters(TypeLiteral.<Class<?>>of(Class.class), new ClassConverter());
addPropertyConverters(TypeLiteral.<Currency>of(Currency.class), new CurrencyConverter());
addPropertyConverters(TypeLiteral.<Double>of(Double.class), new DoubleConverter());
addPropertyConverters(TypeLiteral.<File>of(File.class), new FileConverter());
addPropertyConverters(TypeLiteral.<Float>of(Float.class), new FloatConverter());
addPropertyConverters(TypeLiteral.<Integer>of(Integer.class), new IntegerConverter());
addPropertyConverters(TypeLiteral.<Long>of(Long.class), new LongConverter());
addPropertyConverters(TypeLiteral.<Number>of(Number.class), new NumberConverter());
addPropertyConverters(TypeLiteral.<Path>of(Path.class), new PathConverter());
addPropertyConverters(TypeLiteral.<Short>of(Short.class), new ShortConverter());
addPropertyConverters(TypeLiteral.<URI>of(URI.class), new URIConverter());
addPropertyConverters(TypeLiteral.<URL>of(URL.class), new URLConverter());
}
@Override
public ConfigurationContextBuilder removePropertySources(PropertySource... propertySources) {
return removePropertySources(Arrays.asList(propertySources));
}
@Override
public ConfigurationContextBuilder removePropertySources(Collection<PropertySource> propertySources) {
checkBuilderState();
this.propertySources.removeAll(propertySources);
return this;
}
private PropertySource getPropertySource(String name) {
for(PropertySource ps:propertySources){
if(ps.getName().equals(name)){
return ps;
}
}
throw new IllegalArgumentException("No such PropertySource: "+name);
}
@Override
public List<PropertySource> getPropertySources() {
return this.propertySources;
}
@Override
public ConfigurationContextBuilder increasePriority(PropertySource propertySource) {
checkBuilderState();
int index = propertySources.indexOf(propertySource);
if(index<0){
throw new IllegalArgumentException("No such PropertySource: " + propertySource);
}
if(index<(propertySources.size()-1)){
propertySources.remove(propertySource);
propertySources.add(index+1, propertySource);
}
return this;
}
@Override
public ConfigurationContextBuilder decreasePriority(PropertySource propertySource) {
checkBuilderState();
int index = propertySources.indexOf(propertySource);
if(index<0){
throw new IllegalArgumentException("No such PropertySource: " + propertySource);
}
if(index>0){
propertySources.remove(propertySource);
propertySources.add(index-1, propertySource);
}
return this;
}
@Override
public ConfigurationContextBuilder highestPriority(PropertySource propertySource) {
checkBuilderState();
int index = propertySources.indexOf(propertySource);
if(index<0){
throw new IllegalArgumentException("No such PropertySource: " + propertySource);
}
if(index<(propertySources.size()-1)){
propertySources.remove(propertySource);
propertySources.add(propertySource);
}
return this;
}
@Override
public ConfigurationContextBuilder lowestPriority(PropertySource propertySource) {
checkBuilderState();
int index = propertySources.indexOf(propertySource);
if(index<0){
throw new IllegalArgumentException("No such PropertySource: " + propertySource);
}
if(index>0){
propertySources.remove(propertySource);
propertySources.add(0, propertySource);
}
return this;
}
@Override
public ConfigurationContextBuilder addPropertyFilters(PropertyFilter... filters){
return addPropertyFilters(Arrays.asList(filters));
}
@Override
public ConfigurationContextBuilder addPropertyFilters(Collection<PropertyFilter> filters){
checkBuilderState();
for(PropertyFilter f:filters) {
if (!this.propertyFilters.contains(f)) {
this.propertyFilters.add(f);
}
}
return this;
}
@Override
public ConfigurationContextBuilder removePropertyFilters(PropertyFilter... filters) {
return removePropertyFilters(Arrays.asList(filters));
}
@Override
public ConfigurationContextBuilder removePropertyFilters(Collection<PropertyFilter> filters) {
checkBuilderState();
this.propertyFilters.removeAll(filters);
return this;
}
@Override
public <T> ConfigurationContextBuilder removePropertyConverters(TypeLiteral<T> typeToConvert,
PropertyConverter<T>... converters) {
return removePropertyConverters(typeToConvert, Arrays.asList(converters));
}
@Override
public <T> ConfigurationContextBuilder removePropertyConverters(TypeLiteral<T> typeToConvert,
Collection<PropertyConverter<T>> converters) {
Collection<PropertyConverter<?>> subConverters = this.propertyConverters.get(typeToConvert);
if(subConverters!=null) {
subConverters.removeAll(converters);
}
return this;
}
@Override
public ConfigurationContextBuilder removePropertyConverters(TypeLiteral<?> typeToConvert) {
this.propertyConverters.remove(typeToConvert);
return this;
}
@Override
public ConfigurationContextBuilder setPropertyValueCombinationPolicy(PropertyValueCombinationPolicy combinationPolicy){
checkBuilderState();
this.combinationPolicy = Objects.requireNonNull(combinationPolicy);
return this;
}
@Override
public <T> ConfigurationContextBuilder addPropertyConverters(TypeLiteral<T> type, PropertyConverter<T>... propertyConverters){
checkBuilderState();
Objects.requireNonNull(type);
Objects.requireNonNull(propertyConverters);
Collection<PropertyConverter<?>> converters = this.propertyConverters.get(type);
if(converters==null){
converters = new ArrayList<>();
this.propertyConverters.put(type, converters);
}
for(PropertyConverter<T> propertyConverter:propertyConverters) {
if (!converters.contains(propertyConverter)) {
converters.add(propertyConverter);
} else {
LOG.finer("Converter ignored, already registered: " + propertyConverter);
}
}
return this;
}
@Override
public <T> ConfigurationContextBuilder addPropertyConverters(TypeLiteral<T> type, Collection<PropertyConverter<T>> propertyConverters){
checkBuilderState();
Objects.requireNonNull(type);
Objects.requireNonNull(propertyConverters);
Collection<PropertyConverter<?>> converters = this.propertyConverters.get(type);
if(converters==null){
converters = new ArrayList<>();
this.propertyConverters.put(type, converters);
}
for(PropertyConverter<T> propertyConverter:propertyConverters) {
if (!converters.contains(propertyConverter)) {
converters.add(propertyConverter);
} else {
LOG.finer("Converter ignored, already registered: " + propertyConverter);
}
}
return this;
}
private WrappedPropertySource getWrappedPropertySource(PropertySource delegate) {
PropertySource ps = getPropertySource(delegate.getName());
return WrappedPropertySource.of(ps);
}
protected ConfigurationContextBuilder loadDefaults() {
checkBuilderState();
this.combinationPolicy = PropertyValueCombinationPolicy.DEFAULT_OVERRIDING_COLLECTOR;
addDefaultPropertySources();
addDefaultPropertyFilters();
addDefaultPropertyConverters();
return this;
}
private Map<TypeLiteral, Collection<PropertyConverter>> getDefaultPropertyConverters() {
Map<TypeLiteral, Collection<PropertyConverter>> result = new HashMap<>();
for (PropertyConverter conv : ServiceContextManager.getServiceContext().getServices(
PropertyConverter.class)) {
Type type = TypeLiteral.getGenericInterfaceTypeParameters(conv.getClass(), PropertyConverter.class)[0];
TypeLiteral target = TypeLiteral.of(type);
Collection<PropertyConverter> convList = result.get(target);
if (convList == null) {
convList = new ArrayList<>();
result.put(target, convList);
}
convList.add(conv);
}
return result;
}
/**
* Builds a new configuration based on the configuration of this builder instance.
*
* @return a new {@link Configuration configuration instance},
* never {@code null}.
*/
@Override
public ConfigurationContext build() {
checkBuilderState();
built = true;
return new DefaultConfigurationContext(this);
}
@Override
public ConfigurationContextBuilder sortPropertyFilter(Comparator<PropertyFilter> comparator) {
Collections.sort(propertyFilters, comparator);
return this;
}
@Override
public ConfigurationContextBuilder sortPropertySources(Comparator<PropertySource> comparator) {
Collections.sort(propertySources, comparator);
return this;
}
private void checkBuilderState() {
if (built) {
throw new IllegalStateException("Configuration has already been build.");
}
}
@Override
public List<PropertyFilter> getPropertyFilters() {
return propertyFilters;
}
@Override
public Map<TypeLiteral<?>, Collection<PropertyConverter<?>>> getPropertyConverter() {
return Collections.unmodifiableMap(this.propertyConverters);
}
}