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() + '}'; + } + +}