http://git-wip-us.apache.org/repos/asf/incubator-tamaya-extensions/blob/3aca9112/modules/mutable-config/src/test/java/org/apache/tamaya/mutableconfig/internal/WritableXmlPropertiesSource.java ---------------------------------------------------------------------- diff --git a/modules/mutable-config/src/test/java/org/apache/tamaya/mutableconfig/internal/WritableXmlPropertiesSource.java b/modules/mutable-config/src/test/java/org/apache/tamaya/mutableconfig/internal/WritableXmlPropertiesSource.java new file mode 100644 index 0000000..d6aa7ec --- /dev/null +++ b/modules/mutable-config/src/test/java/org/apache/tamaya/mutableconfig/internal/WritableXmlPropertiesSource.java @@ -0,0 +1,49 @@ +/* + * 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.mutableconfig.internal; + +import org.apache.tamaya.mutableconfig.propertysources.MutableXmlPropertiesPropertySource; + +import java.io.File; +import java.io.IOException; + +/** + * Writable test property source based on the {@link MutableXmlPropertiesPropertySource}. + */ +public class WritableXmlPropertiesSource extends MutableXmlPropertiesPropertySource { + + public static File target = createFile(); + + private static File createFile() { + try { + return File.createTempFile("writableProps",".xml"); + } catch (IOException e) { + e.printStackTrace(); + throw new IllegalStateException("Cannot init test.", e); + } + } + + /** + * Creates a new Properties based PropertySource based on the given URL. + */ + public WritableXmlPropertiesSource() throws IOException { + super(target, 200); + } + +}
http://git-wip-us.apache.org/repos/asf/incubator-tamaya-extensions/blob/3aca9112/modules/mutable-config/src/test/resources/META-INF/services/org.apache.tamaya.spi.PropertySource ---------------------------------------------------------------------- diff --git a/modules/mutable-config/src/test/resources/META-INF/services/org.apache.tamaya.spi.PropertySource b/modules/mutable-config/src/test/resources/META-INF/services/org.apache.tamaya.spi.PropertySource new file mode 100644 index 0000000..609b9fe --- /dev/null +++ b/modules/mutable-config/src/test/resources/META-INF/services/org.apache.tamaya.spi.PropertySource @@ -0,0 +1,20 @@ +# +# 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 current 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. +# +org.apache.tamaya.mutableconfig.internal.WritablePropertiesSource +org.apache.tamaya.mutableconfig.internal.WritableXmlPropertiesSource http://git-wip-us.apache.org/repos/asf/incubator-tamaya-extensions/blob/3aca9112/modules/spi-support/pom.xml ---------------------------------------------------------------------- diff --git a/modules/spi-support/pom.xml b/modules/spi-support/pom.xml index c324481..edc8df3 100644 --- a/modules/spi-support/pom.xml +++ b/modules/spi-support/pom.xml @@ -23,7 +23,7 @@ under the License. <parent> <groupId>org.apache.tamaya.ext</groupId> - <artifactId>tamaya-extensions</artifactId> + <artifactId>tamaya-extensions-all</artifactId> <version>0.3-incubating-SNAPSHOT</version> <relativePath>..</relativePath> </parent> @@ -56,6 +56,10 @@ under the License. <groupId>junit</groupId> <artifactId>junit</artifactId> </dependency> + <dependency> + <groupId>org.hamcrest</groupId> + <artifactId>java-hamcrest</artifactId> + </dependency> </dependencies> <build> http://git-wip-us.apache.org/repos/asf/incubator-tamaya-extensions/blob/3aca9112/modules/spi-support/src/main/java/org/apache/tamaya/spisupport/CLIPropertySource.java ---------------------------------------------------------------------- diff --git a/modules/spi-support/src/main/java/org/apache/tamaya/spisupport/CLIPropertySource.java b/modules/spi-support/src/main/java/org/apache/tamaya/spisupport/CLIPropertySource.java new file mode 100644 index 0000000..ec0a532 --- /dev/null +++ b/modules/spi-support/src/main/java/org/apache/tamaya/spisupport/CLIPropertySource.java @@ -0,0 +1,101 @@ +/* + * 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 java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; + +/** + * PropertySource that allows to add the programs main arguments as configuration entries. Unix syntax using '--' and + * '-' params is supported. + */ +public class CLIPropertySource extends BasePropertySource{ + + /** The original main arguments. */ + private static String[] args = new String[0]; + + /** The map of parsed main arguments. */ + private static Map<String,String> mainArgs; + + /** Initializes the initial state. */ + static{ + initMainArgs(args); + } + + + /** + * Creates a new instance. + */ + public CLIPropertySource(){} + + /** + * Configure the main arguments, hereby parsing and mapping the main arguments into + * configuration propertiesi as key-value pairs. + * @param args the main arguments, not null. + */ + public static void initMainArgs(String... args){ + CLIPropertySource.args = Objects.requireNonNull(args); + // TODO is there a way to figure out the args? + String argsProp = System.getProperty("main.args"); + if(argsProp!=null){ + CLIPropertySource.args = argsProp.split("\\s"); + } + Map<String,String> result = null; + if(CLIPropertySource.args==null){ + result = Collections.emptyMap(); + }else{ + result = new HashMap<>(); + String prefix = System.getProperty("main.args.prefix"); + if(prefix==null){ + prefix=""; + } + String key = null; + for(String arg:CLIPropertySource.args){ + if(arg.startsWith("--")){ + arg = arg.substring(2); + int index = arg.indexOf("="); + if(index>0){ + key = arg.substring(0,index).trim(); + result.put(prefix+key, arg.substring(index+1).trim()); + key = null; + }else{ + result.put(prefix+arg, arg); + } + }else if(arg.startsWith("-")){ + key = arg.substring(1); + }else{ + if(key!=null){ + result.put(prefix+key, arg); + key = null; + }else{ + result.put(prefix+arg, arg); + } + } + } + } + CLIPropertySource.mainArgs = Collections.unmodifiableMap(result); + } + + @Override + public Map<String, String> getProperties() { + return Collections.unmodifiableMap(mainArgs); + } +} http://git-wip-us.apache.org/repos/asf/incubator-tamaya-extensions/blob/3aca9112/modules/spi-support/src/main/java/org/apache/tamaya/spisupport/DefaultConfiguration.java ---------------------------------------------------------------------- diff --git a/modules/spi-support/src/main/java/org/apache/tamaya/spisupport/DefaultConfiguration.java b/modules/spi-support/src/main/java/org/apache/tamaya/spisupport/DefaultConfiguration.java index 52a0d11..56c6e3d 100644 --- a/modules/spi-support/src/main/java/org/apache/tamaya/spisupport/DefaultConfiguration.java +++ b/modules/spi-support/src/main/java/org/apache/tamaya/spisupport/DefaultConfiguration.java @@ -75,7 +75,7 @@ public class DefaultConfiguration implements Configuration { if(configData==null){ return null; } - return PropertyFiltering.applyFilter(key, configData.getConfigEntries(), configurationContext); + return PropertyFilterManager.applyFilter(key, configData.getConfigEntries(), configurationContext); } /** @@ -124,7 +124,7 @@ public class DefaultConfiguration implements Configuration { */ @Override public Map<String, String> getProperties() { - return PropertyFiltering.applyFilters(evaluateUnfilteredMap(), configurationContext); + return PropertyFilterManager.applyFilters(evaluateUnfilteredMap(), configurationContext); } /** http://git-wip-us.apache.org/repos/asf/incubator-tamaya-extensions/blob/3aca9112/modules/spi-support/src/main/java/org/apache/tamaya/spisupport/DefaultConfigurationContext.java ---------------------------------------------------------------------- diff --git a/modules/spi-support/src/main/java/org/apache/tamaya/spisupport/DefaultConfigurationContext.java b/modules/spi-support/src/main/java/org/apache/tamaya/spisupport/DefaultConfigurationContext.java index 76f254b..fca6527 100644 --- a/modules/spi-support/src/main/java/org/apache/tamaya/spisupport/DefaultConfigurationContext.java +++ b/modules/spi-support/src/main/java/org/apache/tamaya/spisupport/DefaultConfigurationContext.java @@ -1,41 +1,36 @@ /* * 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 + * 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 + * 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. + * 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.ConfigurationProvider; import org.apache.tamaya.TypeLiteral; 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 javax.annotation.Priority; -import java.io.Serializable; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; -import java.util.Comparator; import java.util.List; import java.util.Map; import java.util.concurrent.locks.Lock; @@ -73,70 +68,54 @@ public class DefaultConfigurationContext implements ConfigurationContext { * Lock for internal synchronization. */ private final ReentrantReadWriteLock propertySourceLock = new ReentrantReadWriteLock(); - - /** Comparator used for ordering property sources. */ - private final PropertySourceComparator propertySourceComparator = new PropertySourceComparator(); - - /** Comparator used for ordering property filters. */ - private final PropertyFilterComparator propertyFilterComparator = new PropertyFilterComparator(); - + /** + * Lock for internal synchronization. + */ + private final ReentrantReadWriteLock propertyFilterLock = new ReentrantReadWriteLock(); /** - * The first time the Configuration system gets invoked we do initialize - * all our {@link PropertySource}s and - * {@link PropertyFilter}s which are known at startup. + * Creates an empty Configuration context. */ - public DefaultConfigurationContext() { - List<PropertySource> propertySources = new ArrayList<>(); + protected DefaultConfigurationContext() { + this(new DefaultConfigurationContextBuilder()); + } + DefaultConfigurationContext(DefaultConfigurationContextBuilder builder) { + List<PropertySource> propertySources = new ArrayList<>(); // first we load all PropertySources which got registered via java.util.ServiceLoader - propertySources.addAll(ServiceContextManager.getServiceContext().getServices(PropertySource.class)); - - // after that we add all PropertySources which get dynamically registered via their PropertySourceProviders - propertySources.addAll(evaluatePropertySourcesFromProviders()); - + propertySources.addAll(builder.propertySources); // now sort them according to their ordinal values - Collections.sort(propertySources, new PropertySourceComparator()); - immutablePropertySources = Collections.unmodifiableList(propertySources); LOG.info("Registered " + immutablePropertySources.size() + " property sources: " + immutablePropertySources); // as next step we pick up the PropertyFilters pretty much the same way - List<PropertyFilter> propertyFilters = new ArrayList<>(); - propertyFilters.addAll(ServiceContextManager.getServiceContext().getServices(PropertyFilter.class)); - Collections.sort(propertyFilters, new PropertyFilterComparator()); + List<PropertyFilter> propertyFilters = new ArrayList<>(builder.getPropertyFilters()); immutablePropertyFilters = Collections.unmodifiableList(propertyFilters); LOG.info("Registered " + immutablePropertyFilters.size() + " property filters: " + immutablePropertyFilters); - immutablePropertyFilters = Collections.unmodifiableList(propertyFilters); - LOG.info("Registered " + immutablePropertyFilters.size() + " property filters: " + - immutablePropertyFilters); - propertyValueCombinationPolicy = ServiceContextManager.getServiceContext().getService(PropertyValueCombinationPolicy.class); - if(propertyValueCombinationPolicy==null) { + // Finally add the converters + for(Map.Entry<TypeLiteral<?>, Collection<PropertyConverter<?>>> en:builder.getPropertyConverter().entrySet()) { + for (PropertyConverter converter : en.getValue()) { + this.propertyConverterManager.register(en.getKey(), converter); + } + } + LOG.info("Registered " + propertyConverterManager.getPropertyConverters().size() + " property converters: " + + propertyConverterManager.getPropertyConverters()); + + propertyValueCombinationPolicy = builder.combinationPolicy; + if(propertyValueCombinationPolicy==null){ + propertyValueCombinationPolicy = ServiceContextManager.getServiceContext().getService(PropertyValueCombinationPolicy.class); + } + if(propertyValueCombinationPolicy==null){ propertyValueCombinationPolicy = PropertyValueCombinationPolicy.DEFAULT_OVERRIDING_COLLECTOR; } LOG.info("Using PropertyValueCombinationPolicy: " + propertyValueCombinationPolicy); } - /** - * Pick up all {@link PropertySourceProvider}s and return all the - * {@link PropertySource}s they like to register. - */ - private Collection<? extends PropertySource> evaluatePropertySourcesFromProviders() { - List<PropertySource> propertySources = new ArrayList<>(); - Collection<PropertySourceProvider> propertySourceProviders = ServiceContextManager.getServiceContext().getServices(PropertySourceProvider.class); - for (PropertySourceProvider propertySourceProvider : propertySourceProviders) { - Collection<PropertySource> sources = propertySourceProvider.getPropertySources(); - LOG.finer("PropertySourceProvider " + propertySourceProvider.getClass().getName() + - " provided the following property sources: " + sources); - propertySources.addAll(sources); - } - - return propertySources; - } + @Deprecated @Override public void addPropertySources(PropertySource... propertySourcesToAdd) { Lock writeLock = propertySourceLock.writeLock(); @@ -152,77 +131,119 @@ public class DefaultConfigurationContext implements ConfigurationContext { } } - /** - * Comparator used for comparing PropertySources. - */ - private static class PropertySourceComparator implements Comparator<PropertySource>, Serializable { - - private static final long serialVersionUID = 1L; - - /** - * Order property source reversely, the most important come first. - * - * @param source1 the first PropertySource - * @param source2 the second PropertySource - * @return the comparison result. - */ - private int comparePropertySources(PropertySource source1, PropertySource source2) { - if (source1.getOrdinal() < source2.getOrdinal()) { - return -1; - } else if (source1.getOrdinal() > source2.getOrdinal()) { - return 1; - } else { - return source1.getClass().getName().compareTo(source2.getClass().getName()); - } + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof DefaultConfigurationContext)){ + return false; } - @Override - public int compare(PropertySource source1, PropertySource source2) { - return comparePropertySources(source1, source2); + DefaultConfigurationContext that = (DefaultConfigurationContext) o; + + if (!propertyConverterManager.equals(that.propertyConverterManager)) { + return false; + } + if (!immutablePropertySources.equals(that.immutablePropertySources)) { + return false; + } + if (!immutablePropertyFilters.equals(that.immutablePropertyFilters)) { + return false; } + return getPropertyValueCombinationPolicy().equals(that.getPropertyValueCombinationPolicy()); + } - /** - * Comparator used for comparing PropertyFilters. - */ - private static class PropertyFilterComparator implements Comparator<PropertyFilter>, Serializable{ - - private static final long serialVersionUID = 1L; - - /** - * Compare 2 filters for ordering the filter chain. - * - * @param filter1 the first filter - * @param filter2 the second filter - * @return the comparison result - */ - private int comparePropertyFilters(PropertyFilter filter1, PropertyFilter filter2) { - Priority prio1 = filter1.getClass().getAnnotation(Priority.class); - Priority prio2 = filter2.getClass().getAnnotation(Priority.class); - int ord1 = prio1 != null ? prio1.value() : 0; - int ord2 = prio2 != null ? prio2.value() : 0; - - if (ord1 < ord2) { - return -1; - } else if (ord1 > ord2) { - return 1; - } else { - return filter1.getClass().getName().compareTo(filter2.getClass().getName()); + @Override + public int hashCode() { + int result = propertyConverterManager.hashCode(); + result = 31 * result + immutablePropertySources.hashCode(); + result = 31 * result + immutablePropertyFilters.hashCode(); + result = 31 * result + getPropertyValueCombinationPolicy().hashCode(); + return result; + } + + @Override + public String toString() { + StringBuilder b = new StringBuilder("ConfigurationContext{\n"); + b.append(" Property Sources\n"); + b.append(" ----------------\n"); + b.append(" CLASS NAME ORDINAL SCANNABLE SIZE\n"); + for(PropertySource ps:getPropertySources()){ + b.append(" "); + appendFormatted(b, ps.getClass().getSimpleName(), 30); + appendFormatted(b, ps.getName(), 70); + appendFormatted(b, String.valueOf(ps.getOrdinal()), 8); + appendFormatted(b, String.valueOf(ps.isScannable()), 10); + if(ps.isScannable()) { + appendFormatted(b, String.valueOf(ps.getProperties().size()), 8); + }else{ + appendFormatted(b, "-", 8); + } + b.append('\n'); + } + b.append("\n"); + b.append(" Property Filters\n"); + b.append(" ----------------\n"); + b.append(" CLASS INFO\n"); + for(PropertyFilter filter:getPropertyFilters()){ + b.append(" "); + appendFormatted(b, filter.getClass().getSimpleName(), 30); + b.append(removeNewLines(filter.toString())); + b.append('\n'); + } + b.append("\n\n"); + b.append(" Property Converters\n"); + b.append(" -------------------\n"); + b.append(" CLASS TYPE INFO\n"); + for(Map.Entry<TypeLiteral<?>, List<PropertyConverter<?>>> converterEntry:getPropertyConverters().entrySet()){ + for(PropertyConverter converter: converterEntry.getValue()){ + b.append(" "); + appendFormatted(b, converter.getClass().getSimpleName(), 30); + appendFormatted(b, converterEntry.getKey().getRawType().getSimpleName(), 30); + b.append(removeNewLines(converter.toString())); + b.append('\n'); } } + b.append('}'); + return b.toString(); + } - @Override - public int compare(PropertyFilter filter1, PropertyFilter filter2) { - return comparePropertyFilters(filter1, filter2); + private void appendFormatted(StringBuilder b, String text, int length) { + int padding; + if(text.length() <= (length)){ + b.append(text); + padding = length - text.length(); + }else{ + b.append(text.substring(0, length-1)); + padding = 1; + } + for(int i=0;i<padding;i++){ + b.append(' '); } } + private String removeNewLines(String s) { + return s.replace('\n', ' ').replace('\r', ' '); + } + @Override public List<PropertySource> getPropertySources() { return immutablePropertySources; } @Override + public PropertySource getPropertySource(String name) { + for(PropertySource ps:getPropertySources()){ + if(name.equals(ps.getName())){ + return ps; + } + } + return null; + } + + @Override public <T> void addPropertyConverter(TypeLiteral<T> typeToConvert, PropertyConverter<T> propertyConverter) { propertyConverterManager.register(typeToConvert, propertyConverter); LOG.info("Added PropertyConverter: " + propertyConverter.getClass().getName()); @@ -250,7 +271,7 @@ public class DefaultConfigurationContext implements ConfigurationContext { @Override public ConfigurationContextBuilder toBuilder() { - return ConfigurationProvider.getConfigurationContextBuilder().setContext(this); + return new DefaultConfigurationContextBuilder(this); } } http://git-wip-us.apache.org/repos/asf/incubator-tamaya-extensions/blob/3aca9112/modules/spi-support/src/main/java/org/apache/tamaya/spisupport/DefaultConfigurationContextBuilder.java ---------------------------------------------------------------------- diff --git a/modules/spi-support/src/main/java/org/apache/tamaya/spisupport/DefaultConfigurationContextBuilder.java b/modules/spi-support/src/main/java/org/apache/tamaya/spisupport/DefaultConfigurationContextBuilder.java new file mode 100644 index 0000000..edf8173 --- /dev/null +++ b/modules/spi-support/src/main/java/org/apache/tamaya/spisupport/DefaultConfigurationContextBuilder.java @@ -0,0 +1,410 @@ +/* + * 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.TypeLiteral; +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.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.logging.Logger; + +/** + * Default implementation of {@link ConfigurationContextBuilder}. + */ +public class DefaultConfigurationContextBuilder implements ConfigurationContextBuilder { + + private static final Logger LOG = Logger.getLogger(DefaultConfigurationContextBuilder.class.getName()); + + public static final Comparator<PropertySource> DEFAULT_PROPERTYSOURCE_COMPARATOR = new PropertySourceComparator(); + public static final Comparator<?> DEFAULT_PROPERTYFILTER_COMPARATOR = new PriorityServiceComparator(); + + 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; + } + + protected DefaultConfigurationContextBuilder loadDefaultPropertyFilters() { + checkBuilderState(); + for(PropertyFilter pf:ServiceContextManager.getServiceContext().getServices(PropertyFilter.class)){ + addPropertyFilters(pf); + } + return this; + } + + protected DefaultConfigurationContextBuilder loadDefaultPropertySources() { + checkBuilderState(); + for(PropertySource ps:ServiceContextManager.getServiceContext().getServices(PropertySource.class)){ + addPropertySources(ps); + } + for(PropertySourceProvider pv:ServiceContextManager.getServiceContext().getServices(PropertySourceProvider.class)){ + for(PropertySource ps:pv.getPropertySources()){ + addPropertySources(ps); + } + } + return this; + } + + protected DefaultConfigurationContextBuilder loadDefaultPropertyConverters() { + checkBuilderState(); + for(Map.Entry<TypeLiteral, Collection<PropertyConverter>> en:getDefaultPropertyConverters().entrySet()){ + for(PropertyConverter pc: en.getValue()) { + addPropertyConverters(en.getKey(), pc); + } + } + return this; + } + + @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.warning("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.warning("Converter ignored, already registered: " + propertyConverter); + } + } + return this; + } + + protected ConfigurationContextBuilder loadDefaults() { + checkBuilderState(); + this.combinationPolicy = PropertyValueCombinationPolicy.DEFAULT_OVERRIDING_COLLECTOR; + loadDefaultPropertySources(); + loadDefaultPropertyFilters(); + loadDefaultPropertyConverters(); + return this; + } + + + private Map<TypeLiteral, Collection<PropertyConverter>> getDefaultPropertyConverters() { + Map<TypeLiteral, Collection<PropertyConverter>> result = new HashMap<>(); + for (PropertyConverter conv : ServiceContextManager.getServiceContext().getServices( + PropertyConverter.class)) { + TypeLiteral target = TypeLiteral.of(TypeLiteral.of(conv.getClass()).getType()); + 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); + } +} http://git-wip-us.apache.org/repos/asf/incubator-tamaya-extensions/blob/3aca9112/modules/spi-support/src/main/java/org/apache/tamaya/spisupport/EnvironmentPropertySource.java ---------------------------------------------------------------------- diff --git a/modules/spi-support/src/main/java/org/apache/tamaya/spisupport/EnvironmentPropertySource.java b/modules/spi-support/src/main/java/org/apache/tamaya/spisupport/EnvironmentPropertySource.java new file mode 100644 index 0000000..dca8060 --- /dev/null +++ b/modules/spi-support/src/main/java/org/apache/tamaya/spisupport/EnvironmentPropertySource.java @@ -0,0 +1,102 @@ +/* + * 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.spi.PropertySource; +import org.apache.tamaya.spi.PropertyValue; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.logging.Logger; + +/** + * This {@link PropertySource} provides all Properties which are set + * via + * {@code export myprop=myval} on UNIX Systems or + * {@code set myprop=myval} on Windows. You can disable this feature by setting {@code tamaya.envprops.disable} + * or {@code tamaya.defaults.disable}. + */ +public class EnvironmentPropertySource implements PropertySource { + + private static final Logger LOG = Logger.getLogger(EnvironmentPropertySource.class.getName()); + + /** + * default ordinal for {@link org.apache.tamaya.core.propertysource.EnvironmentPropertySource} + */ + public static final int DEFAULT_ORDINAL = 300; + + private final boolean disabled = evaluateDisabled(); + + private boolean evaluateDisabled() { + String value = System.getProperty("tamaya.envprops.disable"); + if(value==null){ + value = System.getenv("tamaya.envprops.disable"); + } + if(value==null){ + value = System.getProperty("tamaya.defaults.disable"); + } + if(value==null){ + value = System.getenv("tamaya.defaults.disable"); + } + if(value==null){ + return false; + } + return value.isEmpty() || Boolean.parseBoolean(value); + } + + @Override + public int getOrdinal() { + return DEFAULT_ORDINAL; + } + + @Override + public String getName() { + if(disabled){ + return "environment-properties(disabled)"; + } + return "environment-properties"; + } + + @Override + public PropertyValue get(String key) { + if(disabled){ + return null; + } + return PropertyValue.of(key, System.getenv(key), getName()); + } + + @Override + public Map<String, String> getProperties() { + if(disabled){ + return Collections.emptyMap(); + } + Map<String, String> entries = new HashMap<>(System.getenv()); + for (Map.Entry<String, String> entry : System.getenv().entrySet()) { + entries.put("_" + entry.getKey() + ".source", getName()); + } + return entries; + } + + @Override + public boolean isScannable() { + return true; + } + +} http://git-wip-us.apache.org/repos/asf/incubator-tamaya-extensions/blob/3aca9112/modules/spi-support/src/main/java/org/apache/tamaya/spisupport/PropertyConverterManager.java ---------------------------------------------------------------------- diff --git a/modules/spi-support/src/main/java/org/apache/tamaya/spisupport/PropertyConverterManager.java b/modules/spi-support/src/main/java/org/apache/tamaya/spisupport/PropertyConverterManager.java index 2be6313..1f1a2a9 100644 --- a/modules/spi-support/src/main/java/org/apache/tamaya/spisupport/PropertyConverterManager.java +++ b/modules/spi-support/src/main/java/org/apache/tamaya/spisupport/PropertyConverterManager.java @@ -1,20 +1,20 @@ /* * 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 + * 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 + * 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. + * 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; @@ -24,24 +24,18 @@ import org.apache.tamaya.spi.ConversionContext; import org.apache.tamaya.spi.PropertyConverter; import org.apache.tamaya.spi.ServiceContextManager; -import javax.annotation.Priority; import java.lang.reflect.Constructor; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.lang.reflect.Type; import java.security.AccessController; import java.security.PrivilegedAction; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; +import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; +import java.util.logging.Level; import java.util.logging.Logger; /** @@ -70,7 +64,7 @@ public class PropertyConverterManager { @Override public int compare(Object o1, Object o2) { - int prio = getPriority(o1) - getPriority(o2); + int prio = PriorityServiceComparator.getPriority(o1) - PriorityServiceComparator.getPriority(o2); if (prio < 0) { return 1; } else if (prio > 0) { @@ -82,21 +76,6 @@ public class PropertyConverterManager { }; /** - * Checks the given instance for a @Priority annotation. If present the annotation's value s evaluated. If no such - * annotation is present, a default priority is returned (1); - * @param o the instance, not null. - * @return a priority, by default 1. - */ - public static int getPriority(Object o){ - int prio = 1; - Priority priority = o.getClass().getAnnotation(Priority.class); - if (priority != null) { - prio = priority.value(); - } - return prio; - } - - /** * Constructor. */ public PropertyConverterManager() { @@ -132,11 +111,16 @@ public class PropertyConverterManager { try { writeLock.lock(); List converters = List.class.cast(this.converters.get(targetType)); + if(converters!=null && converters.contains(converter)){ + return; + } List<PropertyConverter<?>> newConverters = new ArrayList<>(); if (converters != null) { newConverters.addAll(converters); } - newConverters.add(converter); + if(!newConverters.contains(converter)) { + newConverters.add(converter); + } Collections.sort(newConverters, PRIORITY_COMPARATOR); this.converters.put(targetType, Collections.unmodifiableList(newConverters)); // evaluate transitive closure for all inherited supertypes and implemented interfaces @@ -185,8 +169,7 @@ public class PropertyConverterManager { * @return true, if a converter for the given type is registered, or a default one can be created. */ public boolean isTargetTypeSupported(TypeLiteral<?> targetType) { - return converters.containsKey(targetType) || transitiveConverters.containsKey(targetType) - || createDefaultPropertyConverter(targetType) != null; + return converters.containsKey(targetType) || transitiveConverters.containsKey(targetType) || createDefaultPropertyConverter(targetType) != null; } /** @@ -211,29 +194,25 @@ public class PropertyConverterManager { * Get the list of all current registered converters for the given target type. * If not converters are registered, they component tries to create and register a dynamic * converter based on String costructor or static factory methods available. - * - * <p>The converters provided are of the following type and returned in the following order:</p> - * + * The converters provided are of the following type and returned in the following order: * <ul> - * <li>Converters mapped explicitly to the required target type are returned first, ordered - * by decreasing priority. This means, if explicit converters are registered these are used - * primarly for converting a value.</li> - * <li>The target type of each explicitly registered converter also can be transitively mapped to - * 1) all directly implemented interfaces, 2) all its superclasses (except Object), 3) all the interfaces - * implemented by its superclasses. These groups of transitive converters is returned similarly in the - * order as mentioned, whereas also here a priority based decreasing ordering is applied.</li> - * <li>java.lang wrapper classes and native types are automatically mapped.</li> - * <li>If no explicit converters are registered, for Enum types a default implementation is provided that - * compares the configuration values with the different enum members defined (cases sensitive mapping).</li> + * <li>Converters mapped explicitly to the required target type are returned first, ordered + * by decreasing priority. This means, if explicit converters are registered these are used + * primarly for converting a value.</li> + * <li>The target type of each explicitly registered converter also can be transitively mapped to + * 1) all directly implemented interfaces, 2) all its superclasses (except Object), 3) all the interfaces + * implemented by its superclasses. These groups of transitive converters is returned similarly in the + * order as mentioned, whereas also here a priority based decreasing ordering is applied.</li> + * <li>java.lang wrapper classes and native types are automatically mapped.</li> + * <li>If no explicit converters are registered, for Enum types a default implementation is provided that + * compares the configuration values with the different enum members defined (cases sensitive mapping).</li> * </ul> - * - * + * <p> * So given that list above directly registered mappings always are tried first, before any transitive mapping * should be used. Also in all cases @Priority annotations are honored for ordering of the converters in place. * Transitive conversion is supported for all directly implemented interfaces (including inherited ones) and * the inheritance hierarchy (exception Object). Superinterfaces of implemented interfaces are ignored. * - * * @param targetType the target type, not null. * @param <T> the type class * @return the ordered list of converters (may be empty for not convertible types). @@ -246,55 +225,58 @@ public class PropertyConverterManager { // direct mapped converters try { readLock.lock(); - converters = List.class.cast(this.converters.get(targetType)); - } finally { - readLock.unlock(); - } - if (converters != null) { - converterList.addAll(converters); - } - // transitive converter - try { - readLock.lock(); - converters = List.class.cast(this.transitiveConverters.get(targetType)); + addConvertersToList(List.class.cast(this.converters.get(targetType)), converterList); + addConvertersToList(List.class.cast(this.transitiveConverters.get(targetType)), converterList); } finally { readLock.unlock(); } - if (converters != null) { - converterList.addAll(converters); - } - // handling of java.ui.lang wrapper classes + // handling of java.lang wrapper classes TypeLiteral<T> boxedType = mapBoxedType(targetType); if (boxedType != null) { try { readLock.lock(); - converters = List.class.cast(this.converters.get(boxedType)); + addConvertersToList(List.class.cast(this.converters.get(boxedType)), converterList); } finally { readLock.unlock(); } - if (converters != null) { - converterList.addAll(converters); - } } - if (converterList.isEmpty()) { + if (converterList.isEmpty() && !TypeLiteral.of(String.class).equals(targetType)) { // adding any converters created on the fly, e.g. for enum types. PropertyConverter<T> defaultConverter = createDefaultPropertyConverter(targetType); if (defaultConverter != null) { register(targetType, defaultConverter); try { readLock.lock(); - converters = List.class.cast(this.converters.get(targetType)); + addConvertersToList(List.class.cast(this.converters.get(targetType)), converterList); } finally { readLock.unlock(); } } - if (converters != null) { - converterList.addAll(converters); + } + // check for parametrized types, ignoring param type + // direct mapped converters + if(targetType.getType()!=null) { + try { + readLock.lock(); + addConvertersToList(List.class.cast(this.converters.get( + TypeLiteral.of(targetType.getRawType()))), converterList); + } finally { + readLock.unlock(); } } return converterList; } + private <T> void addConvertersToList(Collection<PropertyConverter<T>> converters, List<PropertyConverter<T>> converterList) { + if (converters != null) { + for(PropertyConverter<T> conv:converters) { + if(!converterList.contains(conv)) { + converterList.add(conv); + } + } + } + } + /** * Maps native types to the corresponding boxed types. * @@ -302,7 +284,6 @@ public class PropertyConverterManager { * @param <T> the type * @return the boxed type, or null. */ - @SuppressWarnings("all") private <T> TypeLiteral<T> mapBoxedType(TypeLiteral<T> targetType) { Type parameterType = targetType.getType(); if (parameterType == int.class) { @@ -370,46 +351,40 @@ public class PropertyConverterManager { PropertyConverter<T> converter = null; final Method factoryMethod = getFactoryMethod(targetType.getRawType(), "of", "valueOf", "instanceOf", "getInstance", "from", "fromString", "parse"); if (factoryMethod != null) { + converter = new DefaultPropertyConverter<>(factoryMethod, targetType.getRawType()); + } + if (converter == null) { + final Constructor<T> constr; + try { + constr = targetType.getRawType().getDeclaredConstructor(String.class); + } catch (NoSuchMethodException e) { + LOG.log(Level.FINEST, "No matching constrctor for " + targetType, e); + return null; + } converter = new PropertyConverter<T>() { - @Override - public T convert(String value, ConversionContext context) { - try { - if (!Modifier.isStatic(factoryMethod.getModifiers())) { - throw new ConfigException(factoryMethod.toGenericString() + - " is not a static method. Only static " + - "methods can be used as factory methods."); - } + @Override + public T convert(String value, ConversionContext context) { AccessController.doPrivileged(new PrivilegedAction<Object>() { + @Override public Object run() { - factoryMethod.setAccessible(true); + AccessController.doPrivileged(new PrivilegedAction<Object>() { + @Override + public Object run() { + constr.setAccessible(true); + return null; + } + }); return null; } }); - Object invoke = factoryMethod.invoke(null, value); - return targetType.getRawType().cast(invoke); - } catch (Exception e) { - throw new ConfigException("Failed to decode '" + value + "'", e); - } - } - }; - } - if (converter == null) { - try { - final Constructor<T> constr = targetType.getRawType().getDeclaredConstructor(String.class); - converter = new PropertyConverter<T>() { - @Override - public T convert(String value, ConversionContext context) { try { - constr.setAccessible(true); return constr.newInstance(value); } catch (Exception e) { - throw new ConfigException("Failed to decode '" + value + "'", e); + LOG.log(Level.SEVERE, "Error creating new PropertyConverter instance " + targetType, e); } + return null; } }; - } catch (Exception e) { - LOG.finest("Failed to construct instance of type: " + targetType.getRawType().getName() + ": " + e); - } } return converter; } @@ -434,4 +409,61 @@ public class PropertyConverterManager { return null; } + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof PropertyConverterManager)) { + return false; + } + PropertyConverterManager that = (PropertyConverterManager) o; + return converters.equals(that.converters); + + } + + @Override + public int hashCode() { + return converters.hashCode(); + } + + /** + * Default converter imüöementation perfoming several lookups for String converion + * option. + * @param <T> + */ + private static class DefaultPropertyConverter<T> implements PropertyConverter<T> { + + private final Method factoryMethod; + private final Class<T> targetType; + + DefaultPropertyConverter(Method factoryMethod, Class<T> targetType){ + this.factoryMethod = Objects.requireNonNull(factoryMethod); + this.targetType = Objects.requireNonNull(targetType); + } + + @Override + public T convert(String value, ConversionContext context) { + context.addSupportedFormats(getClass(), "<String -> "+factoryMethod.toGenericString()); + + if (!Modifier.isStatic(factoryMethod.getModifiers())) { + throw new ConfigException(factoryMethod.toGenericString() + + " is not a static method. Only static " + + "methods can be used as factory methods."); + } + try { + AccessController.doPrivileged(new PrivilegedAction<Object>() { + @Override + public Object run() { + factoryMethod.setAccessible(true); + return null; + } + }); + Object invoke = factoryMethod.invoke(null, value); + return targetType.cast(invoke); + } catch (Exception e) { + throw new ConfigException("Failed to decode '" + value + "'", e); + } + } + } } http://git-wip-us.apache.org/repos/asf/incubator-tamaya-extensions/blob/3aca9112/modules/spi-support/src/main/java/org/apache/tamaya/spisupport/PropertyFilterComparator.java ---------------------------------------------------------------------- diff --git a/modules/spi-support/src/main/java/org/apache/tamaya/spisupport/PropertyFilterComparator.java b/modules/spi-support/src/main/java/org/apache/tamaya/spisupport/PropertyFilterComparator.java new file mode 100644 index 0000000..a7a5c66 --- /dev/null +++ b/modules/spi-support/src/main/java/org/apache/tamaya/spisupport/PropertyFilterComparator.java @@ -0,0 +1,60 @@ +/* + * 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.spi.PropertyFilter; + +import javax.annotation.Priority; +import java.io.Serializable; +import java.util.Comparator; + +/** + * Comparator for PropertyFilters based on their priority annotations. + */ +public class PropertyFilterComparator implements Comparator<PropertyFilter>, Serializable { + + private static final long serialVersionUID = 1L; + + /** + * Compare 2 filters for ordering the filter chain. + * + * @param filter1 the first filter + * @param filter2 the second filter + * @return the comparison result + */ + private int comparePropertyFilters(PropertyFilter filter1, PropertyFilter filter2) { + Priority prio1 = filter1.getClass().getAnnotation(Priority.class); + Priority prio2 = filter2.getClass().getAnnotation(Priority.class); + int ord1 = prio1 != null ? prio1.value() : 0; + int ord2 = prio2 != null ? prio2.value() : 0; + + if (ord1 < ord2) { + return -1; + } else if (ord1 > ord2) { + return 1; + } else { + return filter1.getClass().getName().compareTo(filter2.getClass().getName()); + } + } + + @Override + public int compare(PropertyFilter filter1, PropertyFilter filter2) { + return comparePropertyFilters(filter1, filter2); + } +} http://git-wip-us.apache.org/repos/asf/incubator-tamaya-extensions/blob/3aca9112/modules/spi-support/src/main/java/org/apache/tamaya/spisupport/PropertyFilterManager.java ---------------------------------------------------------------------- diff --git a/modules/spi-support/src/main/java/org/apache/tamaya/spisupport/PropertyFilterManager.java b/modules/spi-support/src/main/java/org/apache/tamaya/spisupport/PropertyFilterManager.java new file mode 100644 index 0000000..611722a --- /dev/null +++ b/modules/spi-support/src/main/java/org/apache/tamaya/spisupport/PropertyFilterManager.java @@ -0,0 +1,131 @@ +/* + * 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.spi.ConfigurationContext; +import org.apache.tamaya.spi.FilterContext; +import org.apache.tamaya.spi.PropertyFilter; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Implementation of the Configuration API. This class uses the current {@link ConfigurationContext} to evaluate the + * chain of {@link org.apache.tamaya.spi.PropertySource} and {@link PropertyFilter} + * instance to evaluate the current Configuration. + */ +public final class PropertyFilterManager { + /** + * The logger. + */ + private static final Logger LOG = Logger.getLogger(PropertyFilterManager.class.getName()); + /** + * The maximal number of filter cycles performed before aborting. + */ + private static final int MAX_FILTER_LOOPS = 10; + + /** + * Private singleton constructor. + */ + private PropertyFilterManager(){} + + public static String applyFilter(String key, Map<String,String> configData, ConfigurationContext configurationContext) { + // Apply filters to values, prevent values filtered to null! + String unfilteredValue = configData.get(key); + for (int i = 0; i < MAX_FILTER_LOOPS; i++) { + boolean changed = false; + // Apply filters to values, prevent values filtered to null! + for (PropertyFilter filter : configurationContext.getPropertyFilters()) { + String newValue = filter.filterProperty(unfilteredValue, new FilterContext(key, configData, true)); + if (newValue != null && !newValue.equals(unfilteredValue)) { + changed = true; + if (LOG.isLoggable(Level.FINEST)) { + LOG.finest("Filter - " + key + ": " + unfilteredValue + " -> " + newValue + " by " + filter); + } + } else if (unfilteredValue != null && !unfilteredValue.equals(newValue)) { + changed = true; + if (LOG.isLoggable(Level.FINEST)) { + LOG.finest("Filter - " + key + ": " + unfilteredValue + " -> " + newValue + " by " + filter); + } + } + unfilteredValue = newValue; + } + if (!changed) { + LOG.finest("Finishing filter loop, no changes detected."); + break; + } else { + if (i == (MAX_FILTER_LOOPS - 1)) { + if (LOG.isLoggable(Level.WARNING)) { + LOG.warning("Maximal filter loop count reached, aborting filter evaluation after cycles: " + i); + } + } else { + LOG.finest("Repeating filter loop, changes detected."); + } + } + } + return unfilteredValue; + } + + public static Map<String, String> applyFilters(Map<String, String> inputMap, ConfigurationContext configurationContext) { + Map<String, String> resultMap = new HashMap<>(inputMap); + // Apply filters to values, prevent values filtered to null! + for (int i = 0; i < MAX_FILTER_LOOPS; i++) { + AtomicInteger changes = new AtomicInteger(); + for (PropertyFilter filter : configurationContext.getPropertyFilters()) { + for (Map.Entry<String, String> entry : inputMap.entrySet()) { + final String k = entry.getKey(); + final String v = entry.getValue(); + + String newValue = filter.filterProperty(v, new FilterContext(k, inputMap, false)); + if (newValue != null && !newValue.equals(v)) { + changes.incrementAndGet(); + LOG.finest("Filter - " + k + ": " + v + " -> " + newValue + " by " + filter); + } else if (v != null && !v.equals(newValue)) { + changes.incrementAndGet(); + LOG.finest("Filter - " + k + ": " + v + " -> " + newValue + " by " + filter); + } + // Remove null values + if (null != newValue) { + resultMap.put(k, newValue); + }else{ + resultMap.remove(k); + } + } + } + if (changes.get() == 0) { + LOG.finest("Finishing filter loop, no changes detected."); + break; + } else { + if (i == (MAX_FILTER_LOOPS - 1)) { + if (LOG.isLoggable(Level.WARNING)) { + LOG.warning("Maximal filter loop count reached, aborting filter evaluation after cycles: " + i); + } + } else { + LOG.finest("Repeating filter loop, changes detected: " + changes.get()); + } + changes.set(0); + } + } + return resultMap; + } + +} http://git-wip-us.apache.org/repos/asf/incubator-tamaya-extensions/blob/3aca9112/modules/spi-support/src/main/java/org/apache/tamaya/spisupport/SimplePropertySource.java ---------------------------------------------------------------------- diff --git a/modules/spi-support/src/main/java/org/apache/tamaya/spisupport/SimplePropertySource.java b/modules/spi-support/src/main/java/org/apache/tamaya/spisupport/SimplePropertySource.java new file mode 100644 index 0000000..6c15e35 --- /dev/null +++ b/modules/spi-support/src/main/java/org/apache/tamaya/spisupport/SimplePropertySource.java @@ -0,0 +1,151 @@ +/* + * 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.ConfigException; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Properties; +import java.util.logging.Logger; + +/** + * Simple implementation of a {@link org.apache.tamaya.spi.PropertySource} for + * simple property files and XML property files. + */ +public class SimplePropertySource extends BasePropertySource { + + private static final Logger LOG = Logger.getLogger(SimplePropertySource.class.getName()); + + /** + * The property source name. + */ + private String name; + + /** + * The current properties. + */ + private Map<String, String> properties; + + /** + * Creates a new Properties based PropertySource based on the given URL. + * + * @param propertiesLocation the URL encoded location, not null. + */ + public SimplePropertySource(File propertiesLocation) { + super(0); + try { + this.name = propertiesLocation.toString(); + this.properties = load(propertiesLocation.toURI().toURL()); + } catch (IOException e) { + throw new ConfigException("Failed to load properties from " + propertiesLocation, e); + } + } + + /** + * Creates a new Properties based PropertySource based on the given URL. + * + * @param propertiesLocation the URL encoded location, not null. + */ + public SimplePropertySource(URL propertiesLocation) { + super(0); + this.properties = load(Objects.requireNonNull(propertiesLocation)); + this.name = propertiesLocation.toString(); + } + + /** + * Creates a new Properties based PropertySource based on the given properties map. + * + * @param name the name, not null. + * @param properties the properties, not null. + */ + public SimplePropertySource(String name, Map<String, String> properties) { + super(0); + this.properties = new HashMap<>(properties); + this.name = Objects.requireNonNull(name); + } + + /** + * Creates a new Properties based PropertySource based on the given URL. + * + * @param name The property source name + * @param propertiesLocation the URL encoded location, not null. + */ + public SimplePropertySource(String name, URL propertiesLocation) { + super(0); + this.properties = load(propertiesLocation); + this.name = Objects.requireNonNull(name); + } + + @Override + public String getName() { + return name; + } + + @Override + public Map<String, String> getProperties() { + return this.properties; + } + + /** + * loads the Properties from the given URL + * + * @param propertiesFile {@link URL} to load Properties from + * @return loaded {@link Properties} + * @throws IllegalStateException in case of an error while reading properties-file + */ + private Map<String, String> load(URL propertiesFile) { + boolean isXML = isXMLPropertieFiles(propertiesFile); + + Map<String, String> properties = new HashMap<>(); + try (InputStream stream = propertiesFile.openStream()) { + Properties props = new Properties(); + if (stream != null) { + if (isXML) { + props.loadFromXML(stream); + } else { + props.load(stream); + } + } + + for (String key : props.stringPropertyNames()) { + properties.put(key, props.getProperty(key)); + if (getName() == null){ + LOG.warning("No property source name found for " + this +", ommitting source meta-entries."); + } else { + properties.put("_" + key + ".source", getName()); + } + } + } catch (IOException e) { + throw new ConfigException("Error loading properties from " + propertiesFile, e); + } + + return properties; + } + + private boolean isXMLPropertieFiles(URL url) { + return url.getFile().endsWith(".xml"); + } + +} http://git-wip-us.apache.org/repos/asf/incubator-tamaya-extensions/blob/3aca9112/modules/spi-support/src/main/java/org/apache/tamaya/spisupport/SystemPropertySource.java ---------------------------------------------------------------------- diff --git a/modules/spi-support/src/main/java/org/apache/tamaya/spisupport/SystemPropertySource.java b/modules/spi-support/src/main/java/org/apache/tamaya/spisupport/SystemPropertySource.java new file mode 100644 index 0000000..5f48738 --- /dev/null +++ b/modules/spi-support/src/main/java/org/apache/tamaya/spisupport/SystemPropertySource.java @@ -0,0 +1,125 @@ +/* + * 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.spi.PropertySource; +import org.apache.tamaya.spi.PropertyValue; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Properties; + +/** + * This {@link PropertySource} manages the system properties. You can disable this feature by + * setting {@code tamaya.envprops.disable} or {@code tamaya.defaults.disable}. + */ +public class SystemPropertySource implements PropertySource { + + /** + * default ordinal for {@link org.apache.tamaya.core.propertysource.SystemPropertySource} + */ + public static final int DEFAULT_ORDINAL = 1000; + + private volatile Map<String,String> cachedProperties; + + /** + * previous System.getProperties().hashCode() + * so we can check if we need to reload + */ + private int previousHash; + + private final boolean disabled = evaluateDisabled(); + + private boolean evaluateDisabled() { + String value = System.getProperty("tamaya.sysprops.disable"); + if(value==null){ + value = System.getenv("tamaya.sysprops.disable"); + } + if(value==null){ + value = System.getProperty("tamaya.defaults.disable"); + } + if(value==null){ + value = System.getenv("tamaya.defaults.disable"); + } + if(value==null){ + return false; + } + return value.isEmpty() || Boolean.parseBoolean(value); + } + + + + public SystemPropertySource() { + cachedProperties = loadProperties(); + previousHash = System.getProperties().hashCode(); + } + + private Map<String, String> loadProperties() { + Map<String,String> props = new HashMap<>(); + Properties sysProps = System.getProperties(); + for(String name: sysProps.stringPropertyNames()) { + props.put(name,sysProps.getProperty(name)); + props.put("_"+name+".source",getName()); + } + return props; + } + + @Override + public int getOrdinal() { + return DEFAULT_ORDINAL; + } + + @Override + public String getName() { + if(disabled){ + return "system-properties(disabled)"; + } + return "system-properties"; + } + + @Override + public PropertyValue get(String key) { + if(disabled){ + return null; + } + return PropertyValue.of(key, System.getProperty(key), getName()); + } + + @Override + public Map<String, String> getProperties() { + if(disabled){ + return Collections.emptyMap(); + } + // only need to reload and fill our map if something has changed + // synchronization was removed, Instance was marked as volatile. In the worst case it + // is reloaded twice, but the values will be the same. + if (previousHash != System.getProperties().hashCode()) { + Map<String, String> properties = loadProperties(); + this.cachedProperties = Collections.unmodifiableMap(properties); + previousHash = System.getProperties().hashCode(); + } + return this.cachedProperties; + } + + @Override + public boolean isScannable() { + return true; + } +} http://git-wip-us.apache.org/repos/asf/incubator-tamaya-extensions/blob/3aca9112/modules/spi-support/src/test/java/org/apache/tamaya/spisupport/A.java ---------------------------------------------------------------------- diff --git a/modules/spi-support/src/test/java/org/apache/tamaya/spisupport/A.java b/modules/spi-support/src/test/java/org/apache/tamaya/spisupport/A.java new file mode 100644 index 0000000..4101f1e --- /dev/null +++ b/modules/spi-support/src/test/java/org/apache/tamaya/spisupport/A.java @@ -0,0 +1,29 @@ +/* + * 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; + +/** + * Test class for testing transitively evaluated property converters. + */ +class A implements AutoCloseable{ + @Override + public void close() throws Exception { + + } +} http://git-wip-us.apache.org/repos/asf/incubator-tamaya-extensions/blob/3aca9112/modules/spi-support/src/test/java/org/apache/tamaya/spisupport/B.java ---------------------------------------------------------------------- diff --git a/modules/spi-support/src/test/java/org/apache/tamaya/spisupport/B.java b/modules/spi-support/src/test/java/org/apache/tamaya/spisupport/B.java new file mode 100644 index 0000000..584b923 --- /dev/null +++ b/modules/spi-support/src/test/java/org/apache/tamaya/spisupport/B.java @@ -0,0 +1,29 @@ +/* + * 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; + +/** + * Test class for testing transitively evaluated property converters. + */ +public class B extends A implements Runnable{ + @Override + public void run() { + + } +}