blob: d0506387cd4e253a836dd529ebd6758cb926c85f [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.spisupport;
import org.apache.tamaya.Configuration;
import org.apache.tamaya.TypeLiteral;
import org.apache.tamaya.spi.ConfigurationBuilder;
import org.apache.tamaya.spi.ConfigurationContext;
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.ServiceContext;
import org.apache.tamaya.spi.ServiceContextManager;
import org.apache.tamaya.spisupport.propertysource.CLIPropertySource;
import org.apache.tamaya.spisupport.propertysource.EnvironmentPropertySource;
import org.apache.tamaya.spisupport.propertysource.JavaConfigurationPropertySource;
import org.apache.tamaya.spisupport.propertysource.SystemPropertySource;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.*;
import java.util.logging.Logger;
/**
* Default implementation of {@link ConfigurationBuilder}.
*/
public class DefaultConfigurationBuilder implements ConfigurationBuilder {
private static final Logger LOG = Logger.getLogger(DefaultConfigurationBuilder.class.getName());
protected ServiceContext serviceContext = ServiceContextManager.getServiceContext();
protected List<PropertyFilter> propertyFilters = new ArrayList<>();
protected List<PropertySource> propertySources = new ArrayList<>();
protected Map<TypeLiteral<?>, List<PropertyConverter<?>>> propertyConverters = new HashMap<>();
protected MetadataProvider metaDataProvider = serviceContext.create(MetadataProvider.class, DefaultMetaDataProvider::new);
/**
* Flag if the config has already been built.
* Configuration can be built only once
*/
protected boolean built;
/**
* Creates a new builder instance.
*/
public DefaultConfigurationBuilder() {
}
/**
* Creates a new builder instance.
* @param context the configuration context to be used, not null.
*/
public DefaultConfigurationBuilder(ConfigurationContext context) {
this.propertyConverters.putAll(context.getPropertyConverters());
this.propertyFilters.addAll(context.getPropertyFilters());
for(PropertySource ps:context.getPropertySources()) {
addPropertySources(ps);
}
}
/**
* Creates a new builder instance initializing it with the given context.
* @param configuration the configuration to be used, not null.
*/
public DefaultConfigurationBuilder(Configuration configuration) {
this(configuration.getContext());
}
@Override
public ConfigurationBuilder setClassLoader(ClassLoader classLoader) {
setServiceContext(ServiceContextManager.getServiceContext(classLoader));
serviceContext.reset();
return this;
}
@Override
public ClassLoader getClassLoader() {
return serviceContext.getClassLoader();
}
@Override
public ConfigurationBuilder setServiceContext(ServiceContext serviceContext) {
checkBuilderState();
this.serviceContext = Objects.requireNonNull(serviceContext);
return this;
}
/**
* Allows to setCurrent configuration context during unit tests.
* @param configuration the configuration to be used, not null.
*/
public ConfigurationBuilder setConfiguration(Configuration configuration) {
setContext(configuration.getContext());
return this;
}
@Override
public ConfigurationBuilder setContext(ConfigurationContext context) {
checkBuilderState();
//noinspection deprecation
this.propertyFilters.clear();
this.propertyFilters.addAll(context.getPropertyFilters());
this.propertySources.clear();
for(PropertySource ps:context.getPropertySources()) {
addPropertySources(ps);
}
this.propertyConverters.clear();
this.propertyConverters.putAll(context.getPropertyConverters());
return this;
}
@Override
public ConfigurationBuilder setMeta(String property, String key, String value){
this.metaDataProvider.setMeta(property, key, value);
return this;
}
@Override
public ConfigurationBuilder setMeta(String property, Map<String, String> metaData){
this.metaDataProvider.setMeta(property, metaData);
return this;
}
/**
* Adds the given sources as property sources.
*
* @param sources property sources to addPropertyValue.
* @return the current configuration builder.
*/
@Override
public ConfigurationBuilder addPropertySources(Collection<PropertySource> sources){
checkBuilderState();
for(PropertySource source:sources) {
if(this.propertySources.stream()
.filter(ex -> Objects.equals(ex.getName(), source.getName()))
.findAny()
.isPresent()){
LOG.finest(() -> "Omitting already present property source: " + source.getName());
continue;
}
this.propertySources.add(source);
}
return this;
}
public ConfigurationBuilder addDefaultPropertyFilters() {
checkBuilderState();
for(PropertyFilter pf:serviceContext.getServices(PropertyFilter.class)){
addPropertyFilters(pf);
}
return this;
}
public ConfigurationBuilder addDefaultPropertySources() {
checkBuilderState();
List<PropertySource> propertySources = new ArrayList<>();
addCorePropertyResources(propertySources);
for(PropertySource ps: serviceContext.getServices(PropertySource.class)) {
if(!propertySources.contains(ps)){
propertySources.add(ps);
}
}
for(PropertySourceProvider provider:
serviceContext.getServices(PropertySourceProvider.class)){
propertySources.addAll(provider.getPropertySources());
}
Collections.sort(propertySources, PropertySourceComparator.getInstance());
return addPropertySources(propertySources);
}
public ConfigurationBuilder addDefaultPropertyConverters() {
checkBuilderState();
addCorePropertyConverters();
for(Map.Entry<TypeLiteral, Collection<PropertyConverter>> en:getDefaultPropertyConverters().entrySet()){
for(PropertyConverter pc: en.getValue()) {
addPropertyConverters(en.getKey(), pc);
}
}
return this;
}
@Override
public ConfigurationBuilder removePropertySources(Collection<PropertySource> propertySources) {
checkBuilderState();
this.propertySources.removeAll(propertySources);
return this;
}
@Override
public List<PropertySource> getPropertySources() {
return Collections.unmodifiableList(this.propertySources);
}
@Override
public ConfigurationBuilder 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 ConfigurationBuilder 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 ConfigurationBuilder 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 ConfigurationBuilder 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 ConfigurationBuilder addPropertyFilters(Collection<PropertyFilter> filters){
checkBuilderState();
for(PropertyFilter f:filters) {
if (!this.propertyFilters.contains(f)) {
this.propertyFilters.add(f);
}
}
return this;
}
@Override
public ConfigurationBuilder removePropertyFilters(Collection<PropertyFilter> filters) {
checkBuilderState();
this.propertyFilters.removeAll(filters);
return this;
}
@Override
public <T> ConfigurationBuilder 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 ConfigurationBuilder removePropertyConverters(TypeLiteral<?> typeToConvert) {
this.propertyConverters.remove(typeToConvert);
return this;
}
@Override
public <T> ConfigurationBuilder addPropertyConverters(TypeLiteral<T> type, Collection<PropertyConverter<T>> propertyConverters){
checkBuilderState();
Objects.requireNonNull(type);
Objects.requireNonNull(propertyConverters);
List<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.finest("Converter ignored, already registered: " + propertyConverter);
}
}
return this;
}
/**
* 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}.
*/
@Override
public Configuration build() {
Configuration cfg = new DefaultConfiguration(
new DefaultConfigurationContext(
serviceContext,
this.propertyFilters,
this.propertySources,
this.propertyConverters,
this.metaDataProvider));
this.built = true;
return cfg;
}
@Override
public ConfigurationBuilder sortPropertyFilter(Comparator<PropertyFilter> comparator) {
Collections.sort(propertyFilters, comparator);
return this;
}
@Override
public ConfigurationBuilder sortPropertySources(Comparator<PropertySource> comparator) {
Collections.sort(propertySources, comparator);
return this;
}
@Override
public ConfigurationBuilder sortPropertyConverter(Comparator<PropertyConverter> comparator) {
for(List<PropertyConverter<?>> converters:this.propertyConverters.values()) {
Collections.sort(converters, comparator);
}
return this;
}
@Override
public List<PropertyFilter> getPropertyFilters() {
return this.propertyFilters;
}
@Override
public Map<TypeLiteral<?>, List<PropertyConverter<?>>> getPropertyConverter() {
return this.propertyConverters;
}
protected ConfigurationBuilder loadDefaults() {
checkBuilderState();
addDefaultPropertySources();
addDefaultPropertyFilters();
addDefaultPropertyConverters();
return this;
}
protected Map<TypeLiteral, Collection<PropertyConverter>> getDefaultPropertyConverters() {
Map<TypeLiteral, Collection<PropertyConverter>> result = new HashMap<>();
for (PropertyConverter conv : serviceContext.getServices(
PropertyConverter.class)) {
for(Type type:conv.getClass().getGenericInterfaces()){
if(type instanceof ParameterizedType){
ParameterizedType pt = (ParameterizedType)type;
if(PropertyConverter.class.equals(((ParameterizedType) type).getRawType())){
TypeLiteral target = TypeLiteral.of(pt.getActualTypeArguments()[0]);
Collection<PropertyConverter> convList = result.get(target);
if (convList == null) {
convList = new ArrayList<>();
result.put(target, convList);
}
convList.add(conv);
}
}
}
}
return result;
}
protected void addCorePropertyResources(List<PropertySource> propertySources) {
JavaConfigurationPropertySource jps = new JavaConfigurationPropertySource();
jps.init(serviceContext.getClassLoader());
for(PropertySource ps: new PropertySource[]{
new EnvironmentPropertySource(),
jps,
new CLIPropertySource(),
new SystemPropertySource()
}){
if(!propertySources.contains(ps)){
propertySources.add(ps);
}
}
}
@SuppressWarnings("unchecked")
protected void addCorePropertyConverters() {
// should be overridden by subclasses.
}
protected PropertySource getPropertySource(String name) {
for(PropertySource ps:propertySources){
if(ps.getName().equals(name)){
return ps;
}
}
throw new IllegalArgumentException("No such PropertySource: "+name);
}
private void checkBuilderState() {
if (built) {
throw new IllegalStateException("Configuration has already been build.");
}
}
}