You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tamaya.apache.org by an...@apache.org on 2017/12/17 08:00:33 UTC
[5/6] incubator-tamaya git commit: Added full JSR implementation.
http://git-wip-us.apache.org/repos/asf/incubator-tamaya/blob/063f8ada/code/base/src/main/java/org/apache/tamaya/base/configsource/JavaConfigurationConfigSource.java
----------------------------------------------------------------------
diff --git a/code/base/src/main/java/org/apache/tamaya/base/configsource/JavaConfigurationConfigSource.java b/code/base/src/main/java/org/apache/tamaya/base/configsource/JavaConfigurationConfigSource.java
new file mode 100644
index 0000000..6fb3fc8
--- /dev/null
+++ b/code/base/src/main/java/org/apache/tamaya/base/configsource/JavaConfigurationConfigSource.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.base.configsource;
+
+import org.apache.tamaya.spi.ServiceContextManager;
+
+import javax.config.spi.ConfigSource;
+import java.io.IOException;
+import java.net.URL;
+import java.util.*;
+
+import static java.lang.String.format;
+import static java.lang.Thread.currentThread;
+
+/**
+ * Provider which reads all {@value DEFAULT_SIMPLE_PROPERTIES_FILE_NAME} and
+ * {@value DEFAULT_XML_PROPERTIES_FILE_NAME} files found in the
+ * classpath. By setting
+ * {@code tamaya.defaultprops.disable} or {@code tamaya.defaults.disable}
+ * as system or environment property this feature can be disabled.
+ */
+public class JavaConfigurationConfigSource extends BaseConfigSource {
+ /**
+ * Default location in the classpath, where Tamaya looks for simple line based configuration by default.
+ */
+ public static final String DEFAULT_SIMPLE_PROPERTIES_FILE_NAME="META-INF/javaconfig.properties";
+
+ /**
+ * Default location in the classpath, where Tamaya looks for XML based configuration by default.
+ */
+ public static final String DEFAULT_XML_PROPERTIES_FILE_NAME = "META-INF/javaconfig.xml";
+
+ private static final int DEFAULT_ORDINAL = 900;
+
+ private boolean enabled = evaluateEnabled();
+
+ public JavaConfigurationConfigSource(){
+ super("resource:META-INF/javaconfig.*", DEFAULT_ORDINAL);
+ }
+
+ private boolean evaluateEnabled() {
+ String value = System.getProperty("tamaya.defaultprops.disable");
+ if(value==null){
+ value = System.getenv("tamaya.defaultprops.disable");
+ }
+ if(value==null){
+ value = System.getProperty("tamaya.defaults.disable");
+ }
+ if(value==null){
+ value = System.getenv("tamaya.defaults.disable");
+ }
+ if(value==null){
+ return true;
+ }
+ return value.isEmpty() || !Boolean.parseBoolean(value);
+ }
+
+ private List<ConfigSource> getPropertySources() {
+ List<ConfigSource> propertySources = new ArrayList<>();
+ propertySources.addAll(loadPropertySourcesByName(DEFAULT_SIMPLE_PROPERTIES_FILE_NAME));
+ propertySources.addAll(loadPropertySourcesByName(DEFAULT_XML_PROPERTIES_FILE_NAME));
+ Collections.sort(propertySources, ConfigSourceComparator.getInstance());
+ return propertySources;
+ }
+
+ private Collection<? extends ConfigSource> loadPropertySourcesByName(String filename) {
+ List<ConfigSource> propertySources = new ArrayList<>();
+ Enumeration<URL> propertyLocations;
+ try {
+ propertyLocations = ServiceContextManager.getServiceContext()
+ .getResources(filename, currentThread().getContextClassLoader());
+ } catch (IOException e) {
+ String msg = format("Error while searching for %s", filename);
+
+ throw new IllegalStateException(msg, e);
+ }
+
+ while (propertyLocations.hasMoreElements()) {
+ URL currentUrl = propertyLocations.nextElement();
+ SimpleConfigSource sps = new SimpleConfigSource(currentUrl);
+
+ propertySources.add(sps);
+ }
+
+ return propertySources;
+ }
+
+ public boolean isEnabled() {
+ return enabled;
+ }
+
+ public void setEnabled(boolean enabled){
+ this.enabled = enabled;
+ }
+
+
+ @Override
+ public Map<String, String> getProperties() {
+ if (!isEnabled()) {
+ return Collections.emptyMap();
+ }
+ Map<String,String> result = new HashMap<>();
+ for(ConfigSource ps:getPropertySources()){
+ result.putAll(ps.getProperties());
+ }
+ return result;
+ }
+
+ @Override
+ public String toString() {
+ return "JavaConfigPropertySource{" +
+ "enabled=" + enabled +
+ '}';
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-tamaya/blob/063f8ada/code/base/src/main/java/org/apache/tamaya/base/configsource/MapConfigSource.java
----------------------------------------------------------------------
diff --git a/code/base/src/main/java/org/apache/tamaya/base/configsource/MapConfigSource.java b/code/base/src/main/java/org/apache/tamaya/base/configsource/MapConfigSource.java
new file mode 100644
index 0000000..2defa06
--- /dev/null
+++ b/code/base/src/main/java/org/apache/tamaya/base/configsource/MapConfigSource.java
@@ -0,0 +1,98 @@
+/*
+ * 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.base.configsource;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Properties;
+
+/**
+ * Simple PropertySource implementation that just takes a Map and an (optional) priority.
+ * Optionally the entries passed can be mapped to a different rootContext.
+ */
+public class MapConfigSource extends BaseConfigSource {
+
+ /**
+ * The current properties.
+ */
+ private final Map<String, String> props = new HashMap<>();
+
+ /**
+ * Creates a new instance, hereby using the default mechanism for evaluating the property source's
+ * priority.
+ *
+ * @param name unique name of this source.
+ * @param props the properties
+ */
+ public MapConfigSource(String name, Map<String, String> props) {
+ this(name, props, null);
+ }
+
+ /**
+ * Creates a new instance, hereby using the default mechanism for evaluating the property source's
+ * priority, but applying a custom mapping {@code prefix} to the entries provided.
+ *
+ * @param name unique name of this source.
+ * @param props the properties
+ * @param prefix the prefix context mapping, or null (for no mapping).
+ */
+ public MapConfigSource(String name, Map<String, String> props, String prefix) {
+ super(name);
+ if (prefix == null) {
+ for (Map.Entry<String, String> en : props.entrySet()) {
+ this.props.put(en.getKey(), en.getValue());
+ }
+ } else {
+ for (Map.Entry<String, String> en : props.entrySet()) {
+ this.props.put(prefix + en.getKey(), en.getValue());
+ }
+ }
+ }
+
+ /**
+ * Creates a new instance, hereby using the default mechanism for evaluating the property source's
+ * priority, but applying a custom mapping {@code rootContext} to the entries provided.
+ *
+ * @param name unique name of this source.
+ * @param props the properties
+ * @param prefix the prefix context mapping, or null (for no mapping).
+ */
+ public MapConfigSource(String name, Properties props, String prefix) {
+ this(name, getMap(props), prefix);
+ }
+
+ /**
+ * Simple method to convert Properties into a Map instance.
+ * @param props the properties, not null.
+ * @return the corresponding Map instance.
+ */
+ public static Map<String, String> getMap(Properties props) {
+ Map<String, String> result = new HashMap<>();
+ for (Map.Entry en : props.entrySet()) {
+ result.put(en.getKey().toString(), en.getValue().toString());
+ }
+ return result;
+ }
+
+
+ @Override
+ public Map<String, String> getProperties() {
+ return Collections.unmodifiableMap(this.props);
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/incubator-tamaya/blob/063f8ada/code/base/src/main/java/org/apache/tamaya/base/configsource/PropertiesResourceConfigSource.java
----------------------------------------------------------------------
diff --git a/code/base/src/main/java/org/apache/tamaya/base/configsource/PropertiesResourceConfigSource.java b/code/base/src/main/java/org/apache/tamaya/base/configsource/PropertiesResourceConfigSource.java
new file mode 100644
index 0000000..4e974df
--- /dev/null
+++ b/code/base/src/main/java/org/apache/tamaya/base/configsource/PropertiesResourceConfigSource.java
@@ -0,0 +1,109 @@
+/*
+ * 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.base.configsource;
+
+import org.apache.tamaya.spi.ServiceContextManager;
+
+import java.io.InputStream;
+import java.net.URL;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Properties;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * Simple PropertySource, with a fixed ordinal that reads a .properties file from a given URL.
+ */
+public class PropertiesResourceConfigSource extends MapConfigSource {
+ /** The logger used. */
+ private static final Logger LOGGER = Logger.getLogger(PropertiesResourceConfigSource.class.getName());
+
+ /**
+ * Creates a new instance.
+ * @param url the resource URL, not null.
+ */
+ public PropertiesResourceConfigSource(URL url){
+ this(url, null);
+ }
+
+ /**
+ * Creates a new instance.
+ * @param prefix the (optional) prefix context for mapping (prefixing) the properties loaded.
+ * @param url the resource URL, not null.
+ */
+ public PropertiesResourceConfigSource(URL url, String prefix){
+ super(url.toExternalForm(), loadProps(url), prefix);
+ }
+
+ /**
+ * Creates a new instance.
+ * @param prefix the (optional) prefix context for mapping (prefixing) the properties loaded.
+ * @param path the resource path, not null.
+ */
+ public PropertiesResourceConfigSource(String path, String prefix){
+ super(path, loadProps(path, null), prefix);
+ }
+
+ /**
+ * Creates a new instance.
+ * @param prefix the (optional) prefix context for mapping (prefixing) the properties loaded.
+ * @param path the resource path, not null.
+ */
+ public PropertiesResourceConfigSource(String path, String prefix, ClassLoader cl){
+ super(path, loadProps(path, cl), prefix);
+ }
+
+ /**
+ * Loads the properties using the JDK's Property loading mechanism.
+ * @param path the resource classpath, not null.
+ * @return the loaded properties.
+ */
+ private static Map<String, String> loadProps(String path, ClassLoader cl) {
+ if(cl==null){
+ cl = PropertiesResourceConfigSource.class.getClassLoader();
+ }
+ URL url = ServiceContextManager.getServiceContext().getResource(path, cl);
+ return loadProps(url);
+ }
+
+ /**
+ * Loads the properties using the JDK's Property loading mechanism.
+ * @param url the resource URL, not null.
+ * @return the loaded properties.
+ */
+ private static Map<String, String> loadProps(URL url) {
+ Map<String,String> result = new HashMap<>();
+ if(url!=null) {
+ try (InputStream is = url.openStream()) {
+ Properties props = new Properties();
+ props.load(is);
+ for (Map.Entry en : props.entrySet()) {
+ result.put(en.getKey().toString(), en.getValue().toString());
+ }
+ } catch (Exception e) {
+ LOGGER.log(Level.WARNING, "Failed to read properties from " + url, e);
+ }
+ }else{
+ LOGGER.log(Level.WARNING, "No properties found at " + url);
+ }
+ return result;
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/incubator-tamaya/blob/063f8ada/code/base/src/main/java/org/apache/tamaya/base/configsource/SimpleConfigSource.java
----------------------------------------------------------------------
diff --git a/code/base/src/main/java/org/apache/tamaya/base/configsource/SimpleConfigSource.java b/code/base/src/main/java/org/apache/tamaya/base/configsource/SimpleConfigSource.java
new file mode 100644
index 0000000..8d59c4c
--- /dev/null
+++ b/code/base/src/main/java/org/apache/tamaya/base/configsource/SimpleConfigSource.java
@@ -0,0 +1,282 @@
+/*
+ * 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.base.configsource;
+
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Properties;
+import java.util.UUID;
+import java.util.logging.Logger;
+
+/**
+ * Simple implementation of a {@link javax.config.spi.ConfigSource} for
+ * simple property files and XML property files.
+ */
+public class SimpleConfigSource extends BaseConfigSource {
+
+ private static final Logger LOG = Logger.getLogger(SimpleConfigSource.class.getName());
+
+ /**
+ * The current properties.
+ */
+ private Map<String, String> properties = new HashMap<>();
+
+ /**
+ * Creates a new Properties based PropertySource based on the given URL.
+ *
+ * @param propertiesLocation the URL encoded location, not null.
+ */
+ public SimpleConfigSource(File propertiesLocation) {
+ super(propertiesLocation.toString(), 0);
+ try {
+ this.properties = load(propertiesLocation.toURI().toURL());
+ } catch (IOException e) {
+ throw new IllegalArgumentException("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 SimpleConfigSource(URL propertiesLocation) {
+ super(propertiesLocation.toString(), 0);
+ this.properties = load(Objects.requireNonNull(propertiesLocation));
+ }
+
+ /**
+ * Creates a new Properties based PropertySource.
+ *
+ * @param name the property source name, not null.
+ * @param properties the properties, not null
+ * @param defaultOrdinal the default ordinal
+ */
+ public SimpleConfigSource(String name, Map<String, String> properties, int defaultOrdinal){
+ super(name, defaultOrdinal);
+ for(Map.Entry<String,String> en: properties.entrySet()) {
+ this.properties.put(en.getKey(), en.getValue());
+ }
+ }
+
+ /**
+ * 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 SimpleConfigSource(String name, Map<String, String> properties) {
+ this(name, properties, 0);
+ }
+
+ /**
+ * 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 SimpleConfigSource(String name, URL propertiesLocation) {
+ super(name, 0);
+ this.properties = load(propertiesLocation);
+ }
+
+ private SimpleConfigSource(Builder builder) {
+ properties = builder.properties;
+ if(builder.defaultOrdinal!=null){
+ setDefaultOrdinal(builder.defaultOrdinal);
+ }
+ if(builder.ordinal!=null){
+ setOrdinal(builder.ordinal);
+ }
+ setName(builder.name);
+ }
+
+ public static Builder newBuilder() {
+ return new Builder();
+ }
+
+ @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 static 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);
+ }
+ }
+ String source = propertiesFile.toString();
+ for (String key : props.stringPropertyNames()) {
+ properties.put(key, props.getProperty(key));
+ }
+ } catch (IOException e) {
+ throw new IllegalArgumentException("Error loading properties from " + propertiesFile, e);
+ }
+
+ return properties;
+ }
+
+ private static boolean isXMLPropertieFiles(URL url) {
+ return url.getFile().endsWith(".xml");
+ }
+
+
+ /**
+ * {@code SimplePropertySource} builder static inner class.
+ */
+ public static final class Builder {
+ private String name;
+ private Integer defaultOrdinal;
+ private Integer ordinal;
+ private Map<String, String> properties = new HashMap<>();
+
+ private Builder() {
+ }
+
+ /**
+ * Sets the {@code name} to a new UUID and returns a reference to this Builder so that the methods
+ * can be chained together.
+ *
+ * @return a reference to this Builder
+ */
+ public Builder withUuidName() {
+ this.name = UUID.randomUUID().toString();
+ return this;
+ }
+
+ /**
+ * Sets the {@code name} and returns a reference to this Builder so that the methods
+ * can be chained together.
+ *
+ * @param val the {@code name} to set, not null.
+ * @return a reference to this Builder
+ */
+ public Builder withName(String val) {
+ this.name = Objects.requireNonNull(name);
+ return this;
+ }
+
+ /**
+ * Sets the {@code ordinal} and returns a reference to this Builder so that the methods
+ * can be chained together.
+ *
+ * @param val the {@code ordinal} to set
+ * @return a reference to this Builder
+ */
+ public Builder withOrdinal(int val) {
+ this.ordinal = val;
+ return this;
+ }
+
+ /**
+ * Sets the {@code defaultOrdinal} and returns a reference to this Builder so that the methods
+ * can be chained together.
+ *
+ * @param val the {@code defaultOrdinal} to set
+ * @return a reference to this Builder
+ */
+ public Builder withDefaultOrdinal(int val) {
+ this.defaultOrdinal = val;
+ return this;
+ }
+
+ /**
+ * Reads the {@code properties} from the given resource and returns a reference
+ * to this Builder so that the methods can be chained together.
+ *
+ * @param resource the {@code resource} to read
+ * @return a reference to this Builder
+ */
+ public Builder withProperties(URL resource) {
+ this.properties.putAll(load(resource));
+ return this;
+ }
+
+ /**
+ * Reads the {@code properties} from the given resource and returns a reference
+ * to this Builder so that the methods can be chained together.
+ *
+ * @param file the {@code file} to read from (xml or properties format).
+ * @return a reference to this Builder
+ */
+ public Builder withProperties(File file) {
+ try {
+ this.properties.putAll(load(file.toURI().toURL()));
+ } catch (MalformedURLException e) {
+ throw new IllegalArgumentException("Failed to read file: " + file, e);
+ }
+ return this;
+ }
+
+ /**
+ * Sets the {@code properties} and returns a reference to this Builder so that the methods can be chained together.
+ *
+ * @param val the {@code properties} to set
+ * @return a reference to this Builder
+ */
+ public Builder withProperties(Map<String, String> val) {
+ for(Map.Entry<String,String> en: val.entrySet()) {
+ this.properties.put(en.getKey(), en.getValue());
+ }
+ return this;
+ }
+
+ /**
+ * Sets the {@code properties} and returns a reference to this Builder so that the methods can be chained together.
+ *
+ * @param val the {@code properties} to set
+ * @return a reference to this Builder
+ */
+ public Builder withProperty(String key, String val) {
+ this.properties.put(key, val);
+ return this;
+ }
+
+ /**
+ * Returns a {@code SimplePropertySource} built from the parameters previously set.
+ *
+ * @return a {@code SimplePropertySource} built with parameters of this {@code SimplePropertySource.Builder}
+ */
+ public SimpleConfigSource build() {
+ return new SimpleConfigSource(this);
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-tamaya/blob/063f8ada/code/base/src/main/java/org/apache/tamaya/base/configsource/SystemConfigSource.java
----------------------------------------------------------------------
diff --git a/code/base/src/main/java/org/apache/tamaya/base/configsource/SystemConfigSource.java b/code/base/src/main/java/org/apache/tamaya/base/configsource/SystemConfigSource.java
new file mode 100644
index 0000000..0daf5ce
--- /dev/null
+++ b/code/base/src/main/java/org/apache/tamaya/base/configsource/SystemConfigSource.java
@@ -0,0 +1,186 @@
+/*
+ * 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.base.configsource;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Properties;
+
+/**
+ * This {@link javax.config.spi.ConfigSource} manages the system properties. You can disable this feature by
+ * setting {@code tamaya.envprops.disable} or {@code tamaya.defaults.disable}.
+ */
+public class SystemConfigSource extends BaseConfigSource {
+
+ /**
+ * default ordinal used.
+ */
+ 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 volatile int previousHash;
+
+ /**
+ * Prefix that allows system properties to virtually be mapped on specified sub section.
+ */
+ private String prefix;
+
+ /**
+ * If true, this property source does not return any properties. This is useful since this
+ * property source is applied by default, but can be switched off by setting the
+ * {@code tamaya.envprops.disable} system/environment property to {@code true}.
+ */
+ private boolean disabled = false;
+
+ /**
+ * Creates a new instance. Also initializes the {@code prefix} and {@code disabled} properties
+ * from the system-/ environment properties:
+ * <pre>
+ * tamaya.envprops.prefix
+ * tamaya.envprops.disable
+ * </pre>
+ */
+ public SystemConfigSource(){
+ super("system-properties", DEFAULT_ORDINAL);
+ initFromSystemProperties();
+ if(!disabled){
+ cachedProperties = Collections.unmodifiableMap(loadProperties());
+ }
+ }
+
+ /**
+ * Initializes the {@code prefix} and {@code disabled} properties from the system-/
+ * environment properties:
+ * <pre>
+ * tamaya.envprops.prefix
+ * tamaya.envprops.disable
+ * </pre>
+ */
+ private void initFromSystemProperties() {
+ String value = System.getProperty("tamaya.sysprops.prefix");
+ if(value==null){
+ prefix = System.getenv("tamaya.sysprops.prefix");
+ }
+ 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 && !value.isEmpty()) {
+ this.disabled = Boolean.parseBoolean(value);
+ }
+ }
+
+ /**
+ * Creates a new instance using a fixed ordinal value.
+ * @param ordinal the ordinal number.
+ */
+ public SystemConfigSource(int ordinal){
+ this(null, ordinal);
+ }
+
+ /**
+ * Creates a new instance.
+ * @param prefix the prefix to be used, or null.
+ * @param ordinal the ordinal to be used.
+ */
+ public SystemConfigSource(String prefix, int ordinal){
+ this.prefix = prefix;
+ setOrdinal(ordinal);
+ }
+
+ /**
+ * Creates a new instance.
+ * @param prefix the prefix to be used, or null.
+ */
+ public SystemConfigSource(String prefix){
+ this.prefix = prefix;
+ }
+
+
+ private Map<String, String> loadProperties() {
+ Properties sysProps = System.getProperties();
+ previousHash = System.getProperties().hashCode();
+ final String prefix = this.prefix;
+ Map<String, String> entries = new HashMap<>();
+ for (Map.Entry<Object,Object> entry : sysProps.entrySet()) {
+ if(entry.getKey() instanceof String && entry.getValue() instanceof String) {
+ if (prefix == null) {
+ entries.put((String) entry.getKey(), (String) entry.getValue());
+ } else {
+ entries.put(prefix + entry.getKey(), (String) entry.getValue());
+ }
+ }
+ }
+ return entries;
+ }
+
+ @Override
+ public String getName() {
+ if(disabled){
+ return super.getName() + "(disabled)";
+ }
+ return super.getName();
+ }
+
+ @Override
+ public String getValue(String key) {
+ if(disabled){
+ return null;
+ }
+ String prefix = this.prefix;
+ if(prefix==null) {
+ return System.getProperty(key);
+ }
+ return System.getProperty(key.substring(prefix.length()));
+ }
+
+ @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);
+ }
+ return this.cachedProperties;
+ }
+
+ @Override
+ protected String toStringValues() {
+ return super.toStringValues() +
+ " prefix=" + prefix + '\n' +
+ " disabled=" + disabled + '\n';
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-tamaya/blob/063f8ada/code/base/src/main/java/org/apache/tamaya/base/configsource/WrappedConfigSource.java
----------------------------------------------------------------------
diff --git a/code/base/src/main/java/org/apache/tamaya/base/configsource/WrappedConfigSource.java b/code/base/src/main/java/org/apache/tamaya/base/configsource/WrappedConfigSource.java
new file mode 100644
index 0000000..40842d1
--- /dev/null
+++ b/code/base/src/main/java/org/apache/tamaya/base/configsource/WrappedConfigSource.java
@@ -0,0 +1,117 @@
+/*
+ * 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.base.configsource;
+
+import javax.config.spi.ConfigSource;
+import java.util.Map;
+import java.util.Objects;
+
+/**
+ * Property source effectively managed by the configuration context, allowing resetting of ordinal and its
+ * delegate (e.g. in case of refresh).
+ */
+class WrappedConfigSource implements ConfigSource{
+
+ private Integer ordinal;
+ private ConfigSource delegate;
+ private long loaded = System.currentTimeMillis();
+
+ private WrappedConfigSource(ConfigSource delegate) {
+ this(delegate, null);
+ }
+
+ private WrappedConfigSource(ConfigSource delegate, Integer ordinal) {
+ this.delegate = Objects.requireNonNull(delegate);
+ this.ordinal = ordinal;
+ }
+
+ public static WrappedConfigSource of(ConfigSource ps) {
+ if(ps instanceof WrappedConfigSource){
+ return (WrappedConfigSource)ps;
+ }
+ return new WrappedConfigSource(ps);
+ }
+
+ public static WrappedConfigSource of(ConfigSource ps, Integer ordinal) {
+ if(ps instanceof WrappedConfigSource){
+ return new WrappedConfigSource(((WrappedConfigSource)ps).getDelegate(), ordinal);
+ }
+ return new WrappedConfigSource(ps, ordinal);
+ }
+
+ public int getOrdinal() {
+ if(this.ordinal!=null){
+ return this.ordinal;
+ }
+ return ConfigSourceComparator.getOrdinal(delegate);
+ }
+
+ public void setOrdinal(Integer ordinal) {
+ this.ordinal = ordinal;
+ }
+
+ public void setDelegate(ConfigSource delegate) {
+ this.delegate = Objects.requireNonNull(delegate);
+ this.loaded = System.currentTimeMillis();
+ }
+
+ @Override
+ public String getName() {
+ return delegate.getName();
+ }
+
+ @Override
+ public String getValue(String key) {
+ return delegate.getValue(key);
+ }
+
+ @Override
+ public Map<String, String> getProperties() {
+ return delegate.getProperties();
+ }
+
+ public ConfigSource getDelegate() {
+ return delegate;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof WrappedConfigSource)) return false;
+
+ WrappedConfigSource that = (WrappedConfigSource) o;
+
+ return getDelegate().getName().equals(that.getDelegate().getName());
+ }
+
+ @Override
+ public int hashCode() {
+ return getDelegate().getName().hashCode();
+ }
+
+ @Override
+ public String toString() {
+ return "WrappedPropertySource{" +
+ "name=" + getName() +
+ ", ordinal=" + getOrdinal() +
+ ", loadedAt=" + loaded +
+ ", delegate-class=" + delegate.getClass().getName() +
+ '}';
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-tamaya/blob/063f8ada/code/base/src/main/java/org/apache/tamaya/base/configsource/package-info.java
----------------------------------------------------------------------
diff --git a/code/base/src/main/java/org/apache/tamaya/base/configsource/package-info.java b/code/base/src/main/java/org/apache/tamaya/base/configsource/package-info.java
new file mode 100644
index 0000000..26e6d85
--- /dev/null
+++ b/code/base/src/main/java/org/apache/tamaya/base/configsource/package-info.java
@@ -0,0 +1,23 @@
+/*
+ * 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.
+ */
+
+/**
+ * Contains default implementations for config sources.
+ */
+package org.apache.tamaya.base.configsource;
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/incubator-tamaya/blob/063f8ada/code/base/src/main/java/org/apache/tamaya/base/convert/ConversionContext.java
----------------------------------------------------------------------
diff --git a/code/base/src/main/java/org/apache/tamaya/base/convert/ConversionContext.java b/code/base/src/main/java/org/apache/tamaya/base/convert/ConversionContext.java
new file mode 100644
index 0000000..6f88211
--- /dev/null
+++ b/code/base/src/main/java/org/apache/tamaya/base/convert/ConversionContext.java
@@ -0,0 +1,253 @@
+/*
+ * 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.base.convert;
+
+import org.apache.tamaya.spi.TypeLiteral;
+
+import javax.config.Config;
+import javax.config.spi.Converter;
+import java.lang.reflect.AnnotatedElement;
+import java.lang.reflect.Type;
+import java.util.*;
+
+/**
+ * A conversion context containing all the required values for implementing conversion. Use the included #Builder
+ * for creating new instances of. This class is thread-safe to use. Adding supported formats is synchronized.
+ * @see Converter
+ */
+public class ConversionContext {
+
+ private final Config configuration;
+ private final String key;
+ private final Type targetType;
+ private final AnnotatedElement annotatedElement;
+ private final List<String> supportedFormats = new ArrayList<>();
+
+ private static ThreadLocal<ConversionContext> INSTANCE = new ThreadLocal<>();
+
+ public static ConversionContext getContext(){
+ return INSTANCE.get();
+ }
+
+ public static void setContext(ConversionContext context){
+ INSTANCE.set(Objects.requireNonNull(context));
+ }
+
+ public static void reset() {
+ INSTANCE.remove();
+ }
+
+ /**
+ * Private constructor used from builder.
+ * @param builder the builder, not {@code null}.
+ */
+ protected ConversionContext(Builder builder){
+ this.key = Objects.requireNonNull(builder.key);
+ this.annotatedElement = builder.annotatedElement;
+ this.targetType = Objects.requireNonNull(builder.targetType, "Target type required.");
+ this.supportedFormats.addAll(builder.supportedFormats);
+ this.configuration = builder.configuration;
+ }
+
+ /**
+ * Get the key accessed. This information is very useful to evaluate additional metadata needed to determine/
+ * control further aspects of the conversion.
+ * @return the key. This may be null in case where a default value has to be converted and no unique underlying
+ * key/value configuration is present.
+ */
+ public final String getKey(){
+ return key;
+ }
+
+ /**
+ * Get the target type required.
+ * @return the target type required.
+ */
+ public final Type getTargetType(){
+ return targetType;
+ }
+
+ /**
+ * Get the annotated element, if conversion is performed using injection mechanisms.
+ * @return the annotated element, or {@code null}.
+ */
+ public final AnnotatedElement getAnnotatedElement(){
+ return annotatedElement;
+ }
+
+ /**
+ * Get the configuration, which is targeted.
+ * @return the current configuration context, or {@code null}.
+ */
+ public final Config getConfiguration(){
+ return configuration;
+ }
+
+ /**
+ * Allows to add information on the supported/tried formats, which can be shown to the user, especially when
+ * conversion failed. Adding of formats is synchronized, all formats are added in order to the overall list.
+ * This means formats should be passed in order of precedence.
+ * @param converterType the converters, which implements the formats provided.
+ * @param formatDescriptors the format descriptions in a human readable form, e.g. as regular expressions.
+ */
+ public final void addSupportedFormats(@SuppressWarnings("rawtypes") Class<? extends Converter> converterType, String... formatDescriptors){
+ synchronized (supportedFormats){
+ for(String format: formatDescriptors) {
+ supportedFormats.add(format + " (" + converterType.getSimpleName() + ")");
+ }
+ }
+ }
+
+ /**
+ * Get the supported/tried formats in precedence order. The contents of this method depends on the
+ * {@link Converter} instances involved in a conversion.
+ * @return the supported/tried formats, never {@code null}.
+ */
+ public final List<String> getSupportedFormats(){
+ synchronized (supportedFormats){
+ return new ArrayList<>(supportedFormats);
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "ConversionContext{" +
+ "configuration=" + configuration +
+ ", key='" + key + '\'' +
+ ", targetType=" + targetType +
+ ", annotatedElement=" + annotatedElement +
+ ", supportedFormats=" + supportedFormats +
+ '}';
+ }
+
+
+ /**
+ * Builder to create new instances of {@link ConversionContext}.
+ */
+ public static final class Builder{
+ /** The backing configuration. */
+ private Config configuration;
+ /** The accessed key, or null. */
+ private String key;
+ /** The target type. */
+ private Type targetType;
+ /** The injection target (only set with injection used). */
+ private AnnotatedElement annotatedElement;
+ /** The ordered list of formats tried. */
+ private final Set<String> supportedFormats = new HashSet<>();
+
+ /**
+ * Creates a new Builder instance.
+ * @param key the requested key, may be null.
+ * @param targetType the target type
+ */
+ public Builder(String key, Type targetType) {
+ this(null, key, targetType);
+ }
+
+ /**
+ * Creates a new Builder instance.
+ * @param configuration the configuration, not {@code null}.
+ * @param key the requested key, may be {@code null}.
+ * @param targetType the target type
+ */
+ public Builder(Config configuration, String key, Type targetType){
+ this.key = Objects.requireNonNull(key, "Key required");
+ this.configuration = configuration;
+ this.targetType = Objects.requireNonNull(targetType, "Target type required.");
+ }
+
+ /**
+ * Sets the key.
+ * @param key the key, not {@code null}.
+ * @return the builder instance, for chaining
+ */
+ public Builder setKey(String key){
+ this.key = Objects.requireNonNull(key);
+ return this;
+ }
+
+ /**
+ * Sets the configuration.
+ * @param configuration the configuration, not {@code null}
+ * @return the builder instance, for chaining
+ */
+ public Builder setConfiguration(Config configuration){
+ this.configuration = configuration;
+ return this;
+ }
+
+ /**
+ * Sets the annotated element, when configuration is injected.
+ * @param annotatedElement the annotated element, not {@code null}
+ * @return the builder instance, for chaining
+ */
+ public Builder setAnnotatedElement(AnnotatedElement annotatedElement){
+ this.annotatedElement = Objects.requireNonNull(annotatedElement);
+ return this;
+ }
+
+ /**
+ * Sets the target type explicitly. This is required in some rare cases, e.g. injection of {@code Provider}
+ * instances, where the provider's result type must be produced.
+ * @param targetType the
+ * @return the builder for chaining.
+ */
+ public Builder setTargetType(Type targetType) {
+ this.targetType = Objects.requireNonNull(targetType);
+ return this;
+ }
+
+ /**
+ * Add the formats provided by a {@link Converter}. This method should be called by each converters
+ * performing/trying conversion, so the user can be given feedback on the supported formats on failure.
+ * @param converterType the converters type, not {@code null}.
+ * @param formatDescriptors the formats supported in a human readable form, e.g. as regular expressions.
+ * @return the builder instance, for chaining
+ */
+ public Builder addSupportedFormats(@SuppressWarnings("rawtypes") Class<? extends Converter> converterType, String... formatDescriptors){
+ for(String format: formatDescriptors) {
+ supportedFormats.add(format + " (" + converterType.getSimpleName() + ")");
+ }
+ return this;
+ }
+
+ /**
+ * Builds a new context instance.
+ * @return a new context, never null.
+ */
+ public ConversionContext build(){
+ ConversionContext ctx = new ConversionContext(this);
+ INSTANCE.set(ctx);
+ return ctx;
+ }
+
+ @Override
+ public String toString() {
+ return "Builder{" +
+ "configuration=" + configuration +
+ ", key='" + key + '\'' +
+ ", targetType=" + targetType +
+ ", annotatedElement=" + annotatedElement +
+ ", supportedFormats=" + supportedFormats +
+ '}';
+ }
+
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-tamaya/blob/063f8ada/code/base/src/main/java/org/apache/tamaya/base/convert/ConverterManager.java
----------------------------------------------------------------------
diff --git a/code/base/src/main/java/org/apache/tamaya/base/convert/ConverterManager.java b/code/base/src/main/java/org/apache/tamaya/base/convert/ConverterManager.java
new file mode 100644
index 0000000..b7a0888
--- /dev/null
+++ b/code/base/src/main/java/org/apache/tamaya/base/convert/ConverterManager.java
@@ -0,0 +1,630 @@
+/*
+ * 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.base.convert;
+
+import org.apache.tamaya.base.FormatUtils;
+import org.apache.tamaya.base.PriorityServiceComparator;
+import org.apache.tamaya.spi.PropertyConverter;
+import org.apache.tamaya.spi.ServiceContext;
+import org.apache.tamaya.spi.ServiceContextManager;
+
+import javax.config.Config;
+import javax.config.spi.Converter;
+import java.lang.reflect.*;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.util.*;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * Manager that deals with {@link Converter} instances.
+ * This class is thread-safe.
+ */
+public class ConverterManager {
+ /**
+ * The logger used.
+ */
+ private static final Logger LOG = Logger.getLogger(ConverterManager.class.getName());
+ /**
+ * The registered converters.
+ */
+ private final Map<Type, List<Converter>> converters = new ConcurrentHashMap<>();
+ /**
+ * The transitive converters.
+ */
+ private final Map<Type, List<Converter>> transitiveConverters = new ConcurrentHashMap<>();
+
+ private ClassLoader classloader = ServiceContext.defaultClassLoader();
+
+ private static final Comparator<Object> PRIORITY_COMPARATOR = new Comparator<Object>() {
+
+ @Override
+ public int compare(Object o1, Object o2) {
+ int prio = PriorityServiceComparator.getPriority(o1) - PriorityServiceComparator.getPriority(o2);
+ if (prio < 0) {
+ return 1;
+ } else if (prio > 0) {
+ return -1;
+ } else {
+ return o1.getClass().getSimpleName().compareTo(o2.getClass().getSimpleName());
+ }
+ }
+ };
+
+ /**
+ * Get the classloader used for instance creation.
+ * @return the classloader, never null.
+ */
+ public ClassLoader getClassloader(){
+ return classloader;
+ }
+
+ /**
+ * Sets the classloader to use for loading of instances.
+ * @param ClassLoader the classloader, not null.
+ * @return this instance for chaining.
+ */
+ public ConverterManager setClassloader(ClassLoader ClassLoader){
+ this.classloader = Objects.requireNonNull(classloader);
+ return this;
+ }
+
+ /**
+ * Checks the current implemented generic interfaces and evaluates the given single type parameter.
+ *
+ * @param clazz the class to check, not {@code null}.
+ * @param interfaceType the interface type to be checked, not {@code null}.
+ * @return the generic type parameters, or an empty array, if it cannot be evaluated.
+ */
+ private Type[] getGenericInterfaceTypeParameters(Class<?> clazz, Class<?> interfaceType) {
+ Objects.requireNonNull(clazz, "Class parameter must be given.");
+ Objects.requireNonNull(interfaceType, "Interface parameter must be given.");
+
+ for (Type type : clazz.getGenericInterfaces()) {
+ if (type instanceof ParameterizedType) {
+ ParameterizedType parameterizedType = (ParameterizedType) type;
+ if(parameterizedType.getRawType().equals(interfaceType)){
+ return parameterizedType.getActualTypeArguments();
+ }
+ }
+ }
+ return new Type[0];
+ }
+
+ /**
+ * This method can be used for adding {@link PropertyConverter}s.
+ * Converters are added at the end after any existing converters.
+ * For converters already registered for the current target type the
+ * method has no effect.
+ *
+ * @param typeToConvert the type for which the converters is for
+ * @param converters the converters to add for this type
+ * @return this builder, for chaining, never null.
+ */
+ public <T> ConverterManager addConverter(Type typeToConvert, Converter<T>... converters) {
+ return addConverter(typeToConvert, Arrays.asList(converters));
+ }
+
+ /**
+ * This method can be used for adding {@link PropertyConverter}s.
+ * Converters are added at the end after any existing converters.
+ * For converters already registered for the current target type the
+ * method has no effect.
+ *
+ * @param typeToConvert the type for which the converters is for
+ * @param converters the converters to add for this type
+ * @return this builder, for chaining, never null.
+ */
+ public <T> ConverterManager addConverter(Type typeToConvert, Collection<Converter<T>> converters) {
+ Objects.requireNonNull(converters);
+ List<Converter> converterList = List.class.cast(this.converters.get(typeToConvert));
+ if(converterList==null){
+ converterList = new ArrayList<>();
+ }else{
+ converterList = new ArrayList<>(converterList);
+ }
+ for(Converter converter:converters) {
+ if (!converterList.contains(converter)) {
+ converterList.add(converter);
+ }
+ }
+ Collections.sort(converterList, PRIORITY_COMPARATOR);
+ this.converters.put(typeToConvert, Collections.unmodifiableList(converterList));
+ addTransitiveConverters(typeToConvert, Collection.class.cast(converters));
+ return this;
+ }
+
+ private ConverterManager addTransitiveConverters(Type typeToConvert, Collection<Converter> converters) {
+ // evaluate transitive closure for all inherited supertypes and implemented interfaces
+ // direct implemented interfaces
+ if(typeToConvert instanceof Class) {
+ Class targetClass = (Class) typeToConvert;
+ for (Class<?> ifaceType : targetClass.getInterfaces()) {
+ List<Converter> converterList = List.class.cast(this.transitiveConverters.get(typeToConvert));
+ if(converterList==null){
+ converterList = new ArrayList<>();
+ }else{
+ converterList = new ArrayList<>(converterList);
+ }
+ for(Converter converter:converters){
+ if(!converterList.contains(converter)){
+ converterList.add(converter);
+ }
+ }
+ Collections.sort(converterList, PRIORITY_COMPARATOR);
+ this.transitiveConverters.put(ifaceType, Collections.unmodifiableList(converterList));
+ }
+ Class<?> superClass = targetClass.getSuperclass();
+ while (superClass != null && !superClass.equals(Object.class)) {
+ List<Converter> converterList = List.class.cast(this.transitiveConverters.get(superClass));
+ if(converterList==null){
+ converterList = new ArrayList<>();
+ }else{
+ converterList = new ArrayList<>(converterList);
+ }
+ for(Converter converter:converters){
+ if(!converterList.contains(converter)){
+ converterList.add(converter);
+ }
+ }
+ Collections.sort(converterList, PRIORITY_COMPARATOR);
+ this.transitiveConverters.put(superClass, Collections.unmodifiableList(converterList));
+ addTransitiveConverters(superClass, converters);
+ superClass = superClass.getSuperclass();
+ }
+ }
+ return this;
+ }
+
+ public ConverterManager addConverters(Converter... converters){
+ return addConverters(Arrays.asList(converters));
+ }
+
+ public ConverterManager addConverters(Collection<Converter> converters){
+ for(Converter conv:converters) {
+ addConverter(conv);
+ }
+ return this;
+ }
+
+ public ConverterManager addConverter(Converter conv) {
+ for (Type type : conv.getClass().getGenericInterfaces()) {
+ if (type instanceof ParameterizedType) {
+ ParameterizedType pt = (ParameterizedType) type;
+ if (Converter.class.equals(((ParameterizedType) type).getRawType())) {
+ Type target = pt.getActualTypeArguments()[0];
+ addConverter(target, conv);
+ }
+ }
+ }
+ return this;
+ }
+
+ public ConverterManager addDiscoveredConverters() {
+ addCoreConverters();
+ for(Map.Entry<Type, Collection<Converter<?>>> en:getDefaultConverters().entrySet()){
+ for(Converter pc: en.getValue()) {
+ addConverter(en.getKey(), pc);
+ }
+ }
+ return this;
+ }
+
+ protected Map<Type, Collection<Converter<?>>> getDefaultConverters() {
+ Map<Type, Collection<Converter<?>>> result = new HashMap<>();
+ for (Converter<?> conv : ServiceContextManager.getServiceContext().getServices(
+ Converter.class, classloader)) {
+ addConverter(conv);
+ }
+ return result;
+ }
+
+ protected ConverterManager addCoreConverters() {
+ // should be overridden by subclasses.
+ return this;
+ }
+
+ /**
+ * Removes the given PropertyConverter instances for the given type,
+ * if existing.
+ *
+ * @param typeToConvert the type which the converters is for
+ * @param converters the converters to remove
+ * @return this builder, for chaining, never null.
+ */
+ public ConverterManager removeConverters(Type typeToConvert,
+ @SuppressWarnings("unchecked") Converter... converters){
+ return removeConverters(typeToConvert, Arrays.asList(converters));
+ }
+
+ /**
+ * Removes the given PropertyConverter instances for the given type,
+ * if existing.
+ *
+ * @param typeToConvert the type which the converters is for
+ * @param converters the converters to remove
+ * @return this builder, for chaining, never null.
+ */
+ public ConverterManager removeConverters(Type typeToConvert,
+ Collection<Converter> converters){
+ Objects.requireNonNull(converters);
+ List<Converter> converterList = List.class.cast(this.converters.get(typeToConvert));
+ if(converterList!=null){
+ converterList = new ArrayList<>(converterList);
+ converterList.removeAll(converters);
+ }
+ Collections.sort(converterList, PRIORITY_COMPARATOR);
+ this.converters.put(typeToConvert, Collections.unmodifiableList(converterList));
+ return this;
+ }
+
+ /**
+ * Removes all converters for the given type, which actually renders a given type
+ * unsupported for type conversion.
+ *
+ * @param typeToConvert the type which the converters is for
+ * @return this builder, for chaining, never null.
+ */
+ public ConverterManager removeConverters(Type typeToConvert){
+ this.converters.remove(typeToConvert);
+ this.transitiveConverters.remove(typeToConvert);
+ return this;
+ }
+
+ /**
+ * Removes all contained items.
+ * @return this instance for chaining.
+ */
+ public ConverterManager clear() {
+ this.converters.clear();
+ this.transitiveConverters.clear();
+ return this;
+ }
+
+ /**
+ * Allows to evaluate if a given target type is supported.
+ *
+ * @param targetType the target type, not {@code null}.
+ * @return true, if a converters for the given type is registered, or a default one can be created.
+ */
+ public boolean isTargetTypeSupported(Type targetType) {
+ return converters.containsKey(targetType) || transitiveConverters.containsKey(targetType) || createDefaultPropertyConverter(targetType) != null;
+ }
+
+ /**
+ * Get a map of all property converters currently registered. This will not contain the converters that
+ * may be created, when an instance is adapted, which provides a String constructor or compatible
+ * factory methods taking a single String instance.
+ *
+ * @return the current map of instantiated and registered converters.
+ * @see #createDefaultPropertyConverter(Type)
+ */
+ public Map<Type, List<Converter>> getConverters() {
+ return new HashMap<>(this.converters);
+ }
+
+ /**
+ * Get the list of all current registered converters for the given target type.
+ * If not converters are registered, they component tries to create and addSources a dynamic
+ * converters based on String constructor or static factory methods available.
+ * 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
+ * primarily for converting a value.</li>
+ * <li>The target type of each explicitly registered converters 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 {@code null}.
+ * @return the ordered list of converters (may be empty for not convertible types).
+ * @see #createDefaultPropertyConverter(Type)
+ */
+ public List<Converter> getConverters(Type targetType) {
+ List<Converter> converterList = new ArrayList<>();
+ addConvertersToList(List.class.cast(this.converters.get(targetType)), converterList);
+ addConvertersToList(List.class.cast(this.transitiveConverters.get(targetType)), converterList);
+
+ // handling of java.lang wrapper classes
+ Type boxedType = mapBoxedType(targetType);
+ if (boxedType != null) {
+ addConvertersToList(List.class.cast(this.converters.get(boxedType)), converterList);
+ }
+ if (converterList.isEmpty() && !String.class.equals(targetType)) {
+ // adding any converters created on the fly, e.g. for enum types.
+ Converter defaultConverter = createDefaultPropertyConverter(targetType);
+ if (defaultConverter != null) {
+ addConverter(targetType, defaultConverter);
+ addConvertersToList(List.class.cast(this.converters.get(targetType)), converterList);
+ }
+ }
+ // check for parametrized types, ignoring param type
+ // direct mapped converters
+ if(targetType!=null) {
+ addConvertersToList(List.class.cast(this.converters.get(
+ targetType)), converterList);
+ }
+ return converterList;
+ }
+
+ public Object convertValue(String key, String value, Type type, Config config) {
+ if (value != null) {
+ List<Converter> converters = getConverters(type);
+ org.apache.tamaya.base.convert.ConversionContext context = new org.apache.tamaya.base.convert.ConversionContext.Builder(config, key, type)
+ .build();
+ ConversionContext.setContext(context);
+ for (Converter converter : converters) {
+ try {
+ Object t = converter.convert(value);
+ if (t != null) {
+ return t;
+ }
+ } catch (Exception e) {
+ LOG.log(Level.FINEST, "PropertyConverter: " + converter + " failed to convert value: " + value, e);
+ }
+ }
+ // if the target type is a String, we can return the value, no conversion required.
+ if(type.equals(String.class)){
+ return value;
+ }
+ // unsupported type, throw an exception
+ throw new IllegalStateException("Unparseable config value for type: " + type.getTypeName() + ": " + key +
+ ", supported formats: " + context.getSupportedFormats());
+ }
+ return null;
+ }
+
+ private <T> void addConvertersToList(Collection<Converter> converters, List<Converter> converterList) {
+ if (converters != null) {
+ for(Converter<T> conv:converters) {
+ if(!converterList.contains(conv)) {
+ converterList.add(conv);
+ }
+ }
+ }
+ }
+
+ /**
+ * Maps native types to the corresponding boxed types.
+ *
+ * @param parameterType the native type.
+ * @param <T> the type
+ * @return the boxed type, or null.
+ */
+ @SuppressWarnings("unchecked")
+ private <T> Type mapBoxedType(Type parameterType) {
+ if (parameterType == int.class) {
+ return Integer.class;
+ }
+ if (parameterType == short.class) {
+ return Short.class;
+ }
+ if (parameterType == byte.class) {
+ return Byte.class;
+ }
+ if (parameterType == long.class) {
+ return Long.class;
+ }
+ if (parameterType == boolean.class) {
+ return Boolean.class;
+ }
+ if (parameterType == char.class) {
+ return Character.class;
+ }
+ if (parameterType == float.class) {
+ return Float.class;
+ }
+ if (parameterType == double.class) {
+ return Double.class;
+ }
+ if (parameterType == int[].class) {
+ return Integer[].class;
+ }
+ if (parameterType == short[].class) {
+ return Short[].class;
+ }
+ if (parameterType == byte[].class) {
+ return Byte[].class;
+ }
+ if (parameterType == long[].class) {
+ return Long[].class;
+ }
+ if (parameterType == boolean.class) {
+ return Boolean.class;
+ }
+ if (parameterType == char[].class) {
+ return Character[].class;
+ }
+ if (parameterType == float[].class) {
+ return Float[].class;
+ }
+ if (parameterType == double[].class) {
+ return Double[].class;
+ }
+ return null;
+ }
+
+ /**
+ * Creates a dynamic PropertyConverter for the given target type.
+ *
+ * @param targetType the target type
+ * @return a new converters, or null.
+ */
+ protected Converter createDefaultPropertyConverter(final Type targetType) {
+ if(!(targetType instanceof Class)){
+ return null;
+ }
+ Class targetClass = (Class)targetType;
+ if (Enum.class.isAssignableFrom(targetClass)) {
+ return new EnumConverter<>(targetClass);
+ }
+ Converter converter = null;
+ final Method factoryMethod = getFactoryMethod(targetClass, "of", "valueOf", "instanceOf", "getInstance", "from", "fromString", "parse");
+ if (factoryMethod != null) {
+ converter = new DefaultPropertyConverter<>(factoryMethod, targetClass);
+ }
+ if (converter == null) {
+ final Constructor constr;
+ try {
+ constr = targetClass.getDeclaredConstructor(String.class);
+ } catch (NoSuchMethodException e) {
+ LOG.log(Level.FINEST, "No matching constrctor for " + targetType, e);
+ return null;
+ }
+ converter = new Converter() {
+ @Override
+ public Object convert(String value) {
+ AccessController.doPrivileged(new PrivilegedAction<Object>() {
+ @Override
+ public Object run() {
+ AccessController.doPrivileged(new PrivilegedAction<Object>() {
+ @Override
+ public Object run() {
+ constr.setAccessible(true);
+ return null;
+ }
+ });
+ return null;
+ }
+ });
+ try {
+ return constr.newInstance(value);
+ } catch (Exception e) {
+ LOG.log(Level.SEVERE, "Error creating new PropertyConverter instance " + targetType, e);
+ }
+ return null;
+ }
+ };
+ }
+ return converter;
+ }
+
+ /**
+ * Tries to evaluate a factory method that can be used to create an instance based on a String.
+ *
+ * @param type the target type
+ * @param methodNames the possible static method names
+ * @return the first method found, or null.
+ */
+ private Method getFactoryMethod(Class<?> type, String... methodNames) {
+ Method m;
+ for (String name : methodNames) {
+ try {
+ m = type.getDeclaredMethod(name, String.class);
+ return m;
+ } catch (NoSuchMethodException | RuntimeException e) {
+ LOG.finest("No such factory method found on type: " + type.getName() + ", methodName: " + name);
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (!(o instanceof ConverterManager)) {
+ return false;
+ }
+ ConverterManager that = (ConverterManager) o;
+ return converters.equals(that.converters);
+
+ }
+
+ @Override
+ public int hashCode() {
+ return converters.hashCode();
+ }
+
+ /**
+ * Default converters imüöementation perfoming several lookups for String converion
+ * option.
+ * @param <T>
+ */
+ private static class DefaultPropertyConverter<T> implements Converter<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.getContext().addSupportedFormats(getClass(), "<String -> "+factoryMethod.toGenericString());
+
+ if (!Modifier.isStatic(factoryMethod.getModifiers())) {
+ throw new IllegalArgumentException(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 IllegalArgumentException("Failed to decode '" + value + "'", e);
+ }
+ }
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder b = new StringBuilder();
+ b.append("Converters\n");
+ b.append("----------\n");
+ b.append(" CLASS TYPE INFO\n\n");
+ for(Map.Entry<Type, List<Converter>> converterEntry: getConverters().entrySet()){
+ for(Converter converter: converterEntry.getValue()){
+ b.append(" ");
+ FormatUtils.appendFormatted(b, converter.getClass().getSimpleName(), 30);
+ if(converterEntry.getKey() instanceof ParameterizedType){
+ ParameterizedType pt = (ParameterizedType)converterEntry.getKey();
+ FormatUtils.appendFormatted(b, pt.getRawType().getTypeName(), 30);
+ }else{
+ FormatUtils.appendFormatted(b, converterEntry.getKey().getTypeName(), 30);
+ }
+ b.append(FormatUtils.removeNewLines(converter.toString()));
+ b.append('\n');
+ }
+ }
+ return b.toString();
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-tamaya/blob/063f8ada/code/base/src/main/java/org/apache/tamaya/base/convert/EnumConverter.java
----------------------------------------------------------------------
diff --git a/code/base/src/main/java/org/apache/tamaya/base/convert/EnumConverter.java b/code/base/src/main/java/org/apache/tamaya/base/convert/EnumConverter.java
new file mode 100644
index 0000000..c0f727b
--- /dev/null
+++ b/code/base/src/main/java/org/apache/tamaya/base/convert/EnumConverter.java
@@ -0,0 +1,81 @@
+/*
+ * 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.base.convert;
+
+//import org.osgi.service.component.annotations.Component;
+
+import javax.config.spi.Converter;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.Locale;
+import java.util.Objects;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * Converter, converting from String to tge given enum type.
+ */
+//@Component(service = Converter.class)
+public class EnumConverter<T> implements Converter<T> {
+ private final Logger LOG = Logger.getLogger(EnumConverter.class.getName());
+ private Class<T> enumType;
+ private Method factory;
+
+ public EnumConverter(Class<T> enumType) {
+ if (!Enum.class.isAssignableFrom(enumType)) {
+ throw new IllegalArgumentException("Not an Enum: " + enumType.getName());
+ }
+ this.enumType = Objects.requireNonNull(enumType);
+ try {
+ this.factory = enumType.getMethod("valueOf", String.class);
+ } catch (NoSuchMethodException e) {
+ throw new IllegalArgumentException("Uncovertible enum type without valueOf method found, please provide a custom " +
+ "PropertyConverter for: " + enumType.getName());
+ }
+ }
+
+ @Override
+ public T convert(String value) {
+ ConversionContext.getContext().addSupportedFormats(getClass(),"<enumValue>");
+ try {
+ return (T) factory.invoke(null, value);
+ } catch (InvocationTargetException | IllegalAccessException e) {
+ LOG.log(Level.FINEST, "Invalid enum value '" + value + "' for " + enumType.getName(), e);
+ }
+ try {
+ return (T) factory.invoke(null, value.toUpperCase(Locale.ENGLISH));
+ } catch (InvocationTargetException | IllegalAccessException e) {
+ LOG.log(Level.FINEST, "Invalid enum value '" + value + "' for " + enumType.getName(), e);
+ }
+ return null;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof EnumConverter)) return false;
+ EnumConverter<?> that = (EnumConverter<?>) o;
+ return Objects.equals(enumType, that.enumType);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(enumType);
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-tamaya/blob/063f8ada/code/base/src/main/java/org/apache/tamaya/base/convert/package-info.java
----------------------------------------------------------------------
diff --git a/code/base/src/main/java/org/apache/tamaya/base/convert/package-info.java b/code/base/src/main/java/org/apache/tamaya/base/convert/package-info.java
new file mode 100644
index 0000000..c63629a
--- /dev/null
+++ b/code/base/src/main/java/org/apache/tamaya/base/convert/package-info.java
@@ -0,0 +1,23 @@
+/*
+ * 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.
+ */
+
+/**
+ * Default implementations for converters and converter management.
+ */
+package org.apache.tamaya.base.convert;
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/incubator-tamaya/blob/063f8ada/code/base/src/main/java/org/apache/tamaya/base/filter/FilterComparator.java
----------------------------------------------------------------------
diff --git a/code/base/src/main/java/org/apache/tamaya/base/filter/FilterComparator.java b/code/base/src/main/java/org/apache/tamaya/base/filter/FilterComparator.java
new file mode 100644
index 0000000..dd2bd49
--- /dev/null
+++ b/code/base/src/main/java/org/apache/tamaya/base/filter/FilterComparator.java
@@ -0,0 +1,72 @@
+/*
+ * 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.base.filter;
+
+import org.apache.tamaya.spi.Filter;
+
+import javax.annotation.Priority;
+import java.io.Serializable;
+import java.util.Comparator;
+
+/**
+ * Comparator for PropertyFilters based on their priority annotations.
+ */
+public final class FilterComparator implements Comparator<Filter>, Serializable {
+
+ private static final long serialVersionUID = 1L;
+
+ private static final FilterComparator INSTANCE = new FilterComparator();
+
+ /**
+ * Get the shared instance of the comparator.
+ * @return the shared instance, never null.
+ */
+ public static FilterComparator getInstance(){
+ return INSTANCE;
+ }
+
+ private FilterComparator(){}
+
+ /**
+ * 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(Filter filter1, Filter 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(Filter filter1, Filter filter2) {
+ return comparePropertyFilters(filter1, filter2);
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-tamaya/blob/063f8ada/code/base/src/main/java/org/apache/tamaya/base/filter/FilterContext.java
----------------------------------------------------------------------
diff --git a/code/base/src/main/java/org/apache/tamaya/base/filter/FilterContext.java b/code/base/src/main/java/org/apache/tamaya/base/filter/FilterContext.java
new file mode 100644
index 0000000..6769445
--- /dev/null
+++ b/code/base/src/main/java/org/apache/tamaya/base/filter/FilterContext.java
@@ -0,0 +1,144 @@
+/*
+ * 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.base.filter;
+
+import org.apache.tamaya.spi.ConfigValue;
+import org.apache.tamaya.spi.Filter;
+
+import javax.config.Config;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+
+/**
+ * A filter context containing all the required values for implementing filtering.
+ *
+ * @see Filter
+ */
+public final class FilterContext {
+ /** The key. */
+ private final ConfigValue property;
+
+ private Map<String,String> configEntries = new HashMap<>();
+
+ /** The current context. */
+ private final Config config;
+
+ private boolean singlePropertyScoped;
+
+ private static ThreadLocal<FilterContext> INSTANCE = new ThreadLocal<>();
+
+ public static FilterContext getContext(){
+ return INSTANCE.get();
+ }
+
+ public static void setContext(FilterContext context){
+ INSTANCE.set(Objects.requireNonNull(context));
+ }
+
+ /**
+ * Creates a new FilterContext, for filtering of a multi value access
+ * using {@link Config#getPropertyNames()} .
+ *
+ * @param value the value under evaluation, not {@code null}.
+ * @param configEntries the raw configuration data available in the
+ * current evaluation context, not {@code null}.
+ * @param config the current config, not {@code null}.
+ */
+ public FilterContext(ConfigValue value, Map<String,String> configEntries, Config config) {
+ Objects.requireNonNull(value, "Value must not be null.");
+ Objects.requireNonNull(configEntries, "Initial configuration entries must be not null.");
+ Objects.requireNonNull(config, "config must be not null.");
+
+ this.singlePropertyScoped = false;
+ this.property = Objects.requireNonNull(value);
+ this.configEntries.putAll(configEntries);
+ this.config = config;
+ }
+
+ /**
+ * Creates a new FilterContext, for filtering of a single value access
+ * using {@link Config#getPropertyNames()}.
+ * @param value the value under evaluation, not {@code null}.
+ * @param config the current config, not {@code null}.
+ */
+ public FilterContext(ConfigValue value, Config config) {
+ this.config = config;
+ this.property = Objects.requireNonNull(value, "Value must not be null.");
+ this.singlePropertyScoped = true;
+ }
+
+ /**
+ * Get the current context.
+ * @return the current context, not {@code null}.
+ */
+ public Config getConfig(){
+ return config;
+ }
+
+ /**
+ * Get the property value under evaluation. This information is very useful to evaluate additional metadata needed to determine/
+ * control further aspects of the conversion.
+ *
+ * @return the key. This may be null in case where a default value has to be converted and no unique underlying
+ * key/value configuration is present.
+ */
+ public ConfigValue getProperty() {
+ return property;
+ }
+
+ /**
+ * Method that determines if filtering is done for a single property accessed, or as part of call to
+ * {@code getProperties()}.
+ * @return true, if its scoped to a single property accessed.
+ */
+ public boolean isSinglePropertyScoped(){
+ return singlePropertyScoped;
+ }
+
+ /**
+ * This map contains the following keys:
+ * <ul>
+ * <li>the original value <b>before</b> any filters were applied on it.</li>
+ * <li>all values starting with an {@code _<key>.}, for example {@code a.value}
+ * may have a map set with {@code a.value} (oringinal value), {@code _a.value.origin,
+ * _a.value.type, etc}. The exact contents is determine by the {@link javax.config.spi.ConfigSource}s
+ * active.</li>
+ * </ul>
+ * Also important to know is that this map given contains all the evaluated raw entries, regardless
+ * of the filters that are later applied. This ensures that met-information required by one filter is
+ * not hidden by another filter, because of an invalid filter ordering. In other words filters may remove
+ * key/value pairs, e.g. fir security reasons, by returning {@code null}, but the values in the raw map
+ * passed as input to the filter process will not be affected by any such removal (but the final properties
+ * returned are affected, of course).
+ *
+ * Finally, when a single property is accessed, e.g. by calling {@code Configuration.get(String)}.
+ *
+ * @return the configuration instance, or null.
+ */
+ public Map<String, String> getConfigEntries() {
+ return configEntries;
+ }
+
+ @Override
+ public String toString() {
+ return "FilterContext{property='" + property + "', configEntries=" + configEntries.keySet() + '}';
+ }
+
+}