http://git-wip-us.apache.org/repos/asf/incubator-tamaya/blob/063f8ada/code/base/src/main/java/org/apache/tamaya/base/filter/FilterManager.java ---------------------------------------------------------------------- diff --git a/code/base/src/main/java/org/apache/tamaya/base/filter/FilterManager.java b/code/base/src/main/java/org/apache/tamaya/base/filter/FilterManager.java new file mode 100644 index 0000000..32c8f3f --- /dev/null +++ b/code/base/src/main/java/org/apache/tamaya/base/filter/FilterManager.java @@ -0,0 +1,273 @@ +/* + * 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.base.FormatUtils; +import org.apache.tamaya.spi.Filter; +import org.apache.tamaya.spi.ConfigValue; +import org.apache.tamaya.spi.ServiceContext; +import org.apache.tamaya.spi.ServiceContextManager; + +import javax.config.Config; +import java.util.*; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Implementation of the Configuration API. This class uses the current {@link javax.config.Config} to evaluate the + * chain of {@link javax.config.spi.ConfigSource} and {@link Filter} + * instance to evaluate the current Configuration. + */ +public class FilterManager { + /** + * The logger. + */ + private static final Logger LOG = Logger.getLogger(FilterManager.class.getName()); + /** + * The maximal number of filter cycles performed before aborting. + */ + private static final int MAX_FILTER_LOOPS = 10; + + private List<Filter> filters = new ArrayList<>(); + + private ClassLoader classloader = ServiceContext.defaultClassLoader(); + + /** + * Create a new filter manager. + */ + public FilterManager(){ + } + + /** + * Create a new filter manager. + * @param filters the filters to be used, not null. + */ + public FilterManager(List<Filter> filters){ + this.filters.addAll(filters); + LOG.info("Registered " + filters.size() + " config filter: " + filters); + } + + /** + * 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 FilterManager setClassloader(ClassLoader ClassLoader){ + this.classloader = Objects.requireNonNull(classloader); + return this; + } + + /** + * Get the current list of filters. + * @return the list of filters. + */ + public List<Filter> getFilters(){ + return Collections.unmodifiableList(filters); + } + + /** + * Adds the given Filter instances, hereby the instances are added + * to the end of the list with highest priority. The ordering of existing + * property filters remains unchanged. To sort the + * filters call {@link #sortFilter}. + * + * @param filters the filters to add + * @return this instance, for chaining, never null. + */ + public FilterManager addFilter(Filter... filters) { + return addFilter(Arrays.asList(filters)); + } + + /** + * Adds the given Filter instances, hereby the instances are added + * to the end of the list with highest priority. The ordering of existing + * property filters remains unchanged. To sort the + * filters call {@link #sortFilter}. + * + * @param filters the filters to add + * @return this instance, for chaining, never null. + */ + public FilterManager addFilter(Collection<Filter> filters) { + Objects.requireNonNull(filters); + for(Filter filter:filters) { + if (!this.filters.contains(filter)) { + this.filters.add(filter); + } + } + return this; + } + + /** + * Removes the given PropertyFilter instances, if existing. The order of the remaining + * filters is preserved. + * + * @param filters the filter to remove + * @return this builder, for chaining, never null. + */ + public FilterManager removeFilters(Filter... filters){ + return removeFilters(Arrays.asList(filters)); + } + + /** + * Removes the given PropertyFilter instances, if existing. The order of the remaining + * filters is preserved. + * + * @param filters the filter to remove + * @return this builder, for chaining, never null. + */ + public FilterManager removeFilters(Collection<Filter> filters){ + Objects.requireNonNull(filters); + this.filters.removeAll(filters); + return this; + } + + /** + * Add all registered (default) property filters to the context built. + * @return this builder, for chaining, never null. + */ + public FilterManager addDefaultFilters() { + for(Filter pf: ServiceContextManager.getServiceContext().getServices(Filter.class, classloader)){ + addFilter(pf); + } + return this; + } + + public FilterManager sortFilter(Comparator<Filter> comparator) { + Collections.sort(filters, comparator); + return this; + } + + /** + * Removes all contained items. + * @return this instance for chaining. + */ + public FilterManager clear() { + this.filters.clear(); + return this; + } + + /** + * Filters a single value. + * @param value the raw value, not {@code null}. + * @return the filtered value, including {@code null}. + */ + public ConfigValue filterValue(ConfigValue value) { + FilterContext filterContext = new FilterContext(value, null); + return filterValue(filterContext); + } + + /** + * Filters a single value. + * @param value the raw value, not {@code null}. + * @param config the config + * @return the filtered value, including {@code null}. + */ + public ConfigValue filterValue(ConfigValue value, Config config) { + FilterContext filterContext = new FilterContext(value, config); + return filterValue(filterContext); + } + + /** + * Filters all properties. + * @param rawProperties the unfiltered properties, not {@code null}. + * @param config the config + * @return the filtered value, inclusing null. + */ + public Map<String, String> applyFilters(Map<String, String> rawProperties, Config config) { + Map<String, String> result = new HashMap<>(); + // Apply filters to values, prevent values filtered to null! + for (Map.Entry<String, String> entry : rawProperties.entrySet()) { + FilterContext filterContext = new FilterContext(ConfigValue.of(entry.getKey(), rawProperties), config); + ConfigValue filtered = filterValue(filterContext); + if(filtered!=null){ + result.putAll(filtered.asMap()); + } + } + return result; + } + + /** + * Basic filter logic. + * @param context the filter context, not {@code null}. + * @return the filtered value. + */ + private ConfigValue filterValue(FilterContext context) { + ConfigValue inputValue = context.getProperty(); + ConfigValue filteredValue = inputValue; + for (int i = 0; i < MAX_FILTER_LOOPS; i++) { + int changes = 0; + for (Filter filter :filters) { + filteredValue = filter.filterProperty(inputValue); + if (filteredValue != null && !filteredValue.equals(inputValue)) { + changes++; + LOG.finest("Filter - " + inputValue + " -> " + filteredValue + " by " + filter); + } + if(filteredValue==null){ + LOG.finest("Filter removed entry - " + inputValue + ": " + filter); + break; + }else{ + inputValue = filteredValue; + } + } + if (changes == 0) { + LOG.finest("Finishing filter loop, no changes detected."); + break; + } else if (filteredValue == null) { + break; + } else { + if (i == (MAX_FILTER_LOOPS - 1)) { + if (LOG.isLoggable(Level.WARNING)) { + LOG.warning("Maximal filter loop count reached, aborting filter evaluation after cycles: " + i); + } + } else { + LOG.finest("Repeating filter loop, changes detected: " + changes); + } + } + } + return filteredValue; + } + + @Override + public String toString() { + StringBuilder b = new StringBuilder(); + b.append("Filters\n"); + b.append("-------\n"); + if(filters.isEmpty()){ + b.append(" No filters loaded.\n\n"); + }else { + b.append(" CLASS INFO\n\n"); + for (Filter filter : filters) { + b.append(" "); + FormatUtils.appendFormatted(b, filter.getClass().getSimpleName(), 30); + b.append(FormatUtils.removeNewLines(filter.toString())); + b.append('\n'); + } + 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/filter/package-info.java ---------------------------------------------------------------------- diff --git a/code/base/src/main/java/org/apache/tamaya/base/filter/package-info.java b/code/base/src/main/java/org/apache/tamaya/base/filter/package-info.java new file mode 100644 index 0000000..7880500 --- /dev/null +++ b/code/base/src/main/java/org/apache/tamaya/base/filter/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 filter management and evaluation. + */ +package org.apache.tamaya.base.filter; \ 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/package-info.java ---------------------------------------------------------------------- diff --git a/code/base/src/main/java/org/apache/tamaya/base/package-info.java b/code/base/src/main/java/org/apache/tamaya/base/package-info.java new file mode 100644 index 0000000..820476e --- /dev/null +++ b/code/base/src/main/java/org/apache/tamaya/base/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 config and related artifacts. + */ +package org.apache.tamaya.base; \ 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/spi/ConfigContext.java ---------------------------------------------------------------------- diff --git a/code/base/src/main/java/org/apache/tamaya/spi/ConfigContext.java b/code/base/src/main/java/org/apache/tamaya/spi/ConfigContext.java new file mode 100644 index 0000000..7c77e12 --- /dev/null +++ b/code/base/src/main/java/org/apache/tamaya/spi/ConfigContext.java @@ -0,0 +1,154 @@ +/* + * 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.spi; + +import javax.config.spi.ConfigSource; +import javax.config.spi.Converter; +import java.lang.reflect.Type; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +/** + * Central SPI for programmatically dealing with the setup of the configuration system. + * This includes adding and enlisting {@link javax.config.spi.ConfigSource}s, + * managing {@link javax.config.spi.Converter}s, ConfigFilters, etc. + */ +public interface ConfigContext { + + /** + * This method returns the current list of registered PropertySources ordered via their ordinal. + * PropertySources with a lower ordinal come last. The PropertySource with the + * highest ordinal comes first. + * If two PropertySources have the same ordinal number they will get sorted + * using their class name just to ensure the user at least gets the same ordering + * after a JVM restart, hereby names before are added last. + * PropertySources are loaded when this method is called the first time, which basically is + * when the first time configuration is accessed. + * + * @return a sorted list of registered PropertySources. The returned list need not be modifiable + */ + List<ConfigSource> getSources(); + +// /** +// * Access a {@link ConfigSource} using its (unique) name. +// * @param name the propoerty source's name, not {@code null}. +// * @return the propoerty source found, or {@code null}. +// */ +// default ConfigSource getSource(String name) { +// for(ConfigSource ps: getSources()){ +// if(name.equals(ps.getName())){ +// return ps; +// } +// } +// return null; +// } + + /** + * Access the current PropertyFilter instances. + * @return the list of registered PropertyFilters, never null. + */ + List<Filter> getFilters(); + + /** + * <p> + * This method returns the Map of registered PropertyConverters + * per type. + * The List for each type is ordered via their {@link javax.annotation.Priority} and + * cladd name. + * </p> + * + * <p>A simplified scenario could be like:</p> + * <pre> + * { + * Date.class -> {StandardDateConverter, TimezoneDateConverter, MyCustomDateConverter } + * Boolean.class -> {StandardBooleanConverter, FrenchBooleanConverter} + * Integer.class -> {DynamicDefaultConverter} + * } + * </pre> + * + * @return map with sorted list of registered PropertySources per type. + */ + Map<Type, List<Converter>> getConverters(); + + /** + * <p> + * This method returns the registered PropertyConverters for a given type. + * The List for each type is ordered via their {@link javax.annotation.Priority}. + * </p> + * + * <p> + * PropertyConverters with a higher Priority come first. The PropertyConverter with the + * lowest Priority comes last. + * If two PropertyConverter have the same ordinal number they will get sorted + * using their class name just to ensure the user at least gets the same ordering + * after a JVM restart. + * </p> + * + * <p> + * Additionally if a PropertyProvider is accessed, which is not registered the implementation + * should try to figure out, if there could be a default implementation as follows:</p> + * <ol> + * <li>Look for static factory methods: {@code of(String), valueOf(String), getInstance(String), + * instanceOf(String), fomr(String)}</li> + * <li>Look for a matching constructor: {@code T(String)}.</li> + * </ol> + * + * <p> + * If a correspoding factory method or constructor could be found, a corresponding + * PropertyConverter should be created and registered automatically for the given + * type. + * </p> + * + * <p> The scenario could be like:</p> + * + * <pre> + * { + * Date.class -> {MyCustomDateConverter,StandardDateConverter, TimezoneDateConverter} + * Boolean.class -> {StandardBooleanConverter, FrenchBooleanConverter} + * Integer.class -> {DynamicDefaultConverter} + * } + * </pre> + * + * <p> + * The converters returned for a type should be used as a chain, whereas the result of the + * first converters that is able to convert the configured value, is taken as the chain's result. + * No more converters are called after a converters has successfully converted the input into + * the required target type. + * </p> + * + * @param type type of the desired converters + * @return a sorted list of registered PropertySources per type, or null. + */ + default List<Converter> getConverters(Type type){ + return Optional.ofNullable(getConverters().get(type)).orElse(Collections.emptyList()); + } + + /** + * Access the {@link ConfigValueCombinationPolicy} used to evaluate the final + * property values. + * @return the {@link ConfigValueCombinationPolicy} used, never null. + */ + default ConfigValueCombinationPolicy getConfigValueCombinationPolicy(){ + return ConfigValueCombinationPolicy.DEFAULT_OVERRIDING_POLICY; + } + +} + http://git-wip-us.apache.org/repos/asf/incubator-tamaya/blob/063f8ada/code/base/src/main/java/org/apache/tamaya/spi/ConfigValue.java ---------------------------------------------------------------------- diff --git a/code/base/src/main/java/org/apache/tamaya/spi/ConfigValue.java b/code/base/src/main/java/org/apache/tamaya/spi/ConfigValue.java new file mode 100644 index 0000000..fedcef5 --- /dev/null +++ b/code/base/src/main/java/org/apache/tamaya/spi/ConfigValue.java @@ -0,0 +1,209 @@ +/* + * 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.spi; + +import java.io.Serializable; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; + +/** + * Class modelling the result of a request for a property value. A property value is basically identified by its key. + * There might be reasons, where one want to further analyze, which PropertySources provided a value and which not, so + * it is possible to create a PropertyValue with a null value. Nevertheless in all cases the provider source (typically + * the name of the PropertySource) must be set. + */ +public final class ConfigValue implements Serializable{ + private static final long serialVersionUID = 1L; + /** The requested key. */ + private String key; + /** The value. */ + private String value; + /** Additional metadata provided by the provider. */ + private String metaEntry; + + ConfigValue(ConfigValueBuilder builder){ + this.key = Objects.requireNonNull(builder.key); + this.value = Objects.requireNonNull(builder.value); + this.metaEntry = builder.metaEntry; + } + + /** + * Creates a new instance + * @param key the key, not {@code null}. + * @param value the value, not {@code null}. + * @param metaEntry the source, typically the name of the {@link javax.config.spi.ConfigSource} providing + * the value, not {@code null}. + */ + private ConfigValue(String key, String value, String metaEntry){ + this.key = Objects.requireNonNull(key, "Key is required."); + this.value = Objects.requireNonNull(value, "Value is required."); + this.metaEntry = metaEntry; + } + + /** + * The requested key. + * @return the, key never {@code null}. + */ + public String getKey() { + return key; + } + + /** + * The value. + * @return the value, in case a value is null it is valid to return {#code null} as result for + * {@link javax.config.spi.ConfigSource#getValue(String)}}. + */ + public String getValue() { + return this.value; + } + + /** + * Creates a full configuration map for this key, value pair and all its meta context data. This map + * is also used for subsequent processing, like value filtering. + * @return the property value entry map. + */ + public String getMetaEntry() { + return metaEntry; + } + + /** + * Creates a new builder instance. + * @param key the key, not {@code null}. + * @return a new builder instance. + */ + public static ConfigValueBuilder builder(String key){ + Objects.requireNonNull(key, "Key must be given."); + return new ConfigValueBuilder(key); + } + + /** + * Creates a new builder instance. + * @param key the key, not {@code null}. + * @param value the property value, not {@code null}. + * @return a new builder instance. + */ + public static ConfigValueBuilder builder(String key, String value) { + Objects.requireNonNull(key, "Key must be given."); + Objects.requireNonNull(value, "Value must be given"); + return new ConfigValueBuilder(key, value); + } + + + /** + * Creates a new PropertyValue without any metadata.. + * @param key the key, not {@code null}. + * @param value the value. + * @param metaEntry the metaEntry, typically the name of the {@link javax.config.spi.ConfigSource} + * providing the value, not {@code null}. + * @return a new property value instance, or {@code null}, + * if the value passed is {@code null}.. + */ + public static ConfigValue of(String key, String value, String metaEntry) { + if (value==null) { + return null; + } + return new ConfigValue(key, value, metaEntry); + } + + /** + * Creates a new builder instance based on this item. + * @return a new builder, never null. + */ + public ConfigValueBuilder toBuilder() { + return new ConfigValueBuilder(this.getKey()) + .setValue(this.getValue()) + .setMetaEntry(this.metaEntry); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof ConfigValue)) return false; + ConfigValue that = (ConfigValue) o; + return Objects.equals(getKey(), that.getKey()) && + Objects.equals(getValue(), that.getValue()) && + Objects.equals(getMetaEntry(), that.getMetaEntry()); + } + + @Override + public int hashCode() { + return Objects.hash(getKey(), getValue(), + getMetaEntry()); + } + + @Override + public String toString() { + return "PropertyValue{" + + "key='" + key + '\'' + + ", value='" + value + '\'' + + ", metaEntry='" + metaEntry + '\'' + + '}'; + } + + /** + * Maps a map of {@code Map<String,String>} to a {@code Map<String,PropertyValue>}. + * @param config the String based map, not {@code null}. + * @return the corresponding value based map. + */ + public static Map<String,ConfigValue> map(Map<String, String> config) { + Map<String,ConfigValue> result = new HashMap<>(config.size()); + for(Map.Entry<String,String> en:config.entrySet()){ + result.put(en.getKey(), ConfigValue.of(en.getKey(), en.getValue(), config.get(en.getKey()+"[meta]"))); + } + return result; + } + + /** + * Maps a map of {@code Map<String,String>} to a {@code Map<String,PropertyValue>}. + * + * @param config the String based map, not {@code null}. + * @param source the source name, not {@code null}. + * @param metaData additional metadata, not {@code null}. + * @return the corresponding value based map. + */ + public static Map<String,ConfigValue> map(Map<String, String> config, String source, + Map<String,String> metaData) { + Objects.requireNonNull(config, "Config must be given."); + Objects.requireNonNull(source, "Source must be given."); + Objects.requireNonNull(metaData, "Meta data must be given."); + + Map<String,ConfigValue> result = new HashMap<>(config.size()); + + for(Map.Entry<String,String> en:config.entrySet()){ + ConfigValue value = new ConfigValueBuilder(en.getKey(), source).setValue(en.getValue()) + .addMetaEntries(metaData).build(); + result.put(en.getKey(), value); + } + return result; + } + + public Map<? extends String, ? extends String> asMap() { + Map<String,String> map = new HashMap<>(); + map.put(key, value); + map.put(key+"[meta]", this.metaEntry); + return map; + } + + public static ConfigValue of(String key, Map<String, String> rawProperties) { + String value = rawProperties.get(key); + String meta = rawProperties.get(key+"[meta]"); + return new ConfigValue(key, value, meta); + } +} http://git-wip-us.apache.org/repos/asf/incubator-tamaya/blob/063f8ada/code/base/src/main/java/org/apache/tamaya/spi/ConfigValueBuilder.java ---------------------------------------------------------------------- diff --git a/code/base/src/main/java/org/apache/tamaya/spi/ConfigValueBuilder.java b/code/base/src/main/java/org/apache/tamaya/spi/ConfigValueBuilder.java new file mode 100644 index 0000000..d1c2d60 --- /dev/null +++ b/code/base/src/main/java/org/apache/tamaya/spi/ConfigValueBuilder.java @@ -0,0 +1,179 @@ +/* + * 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.spi; + +import java.io.IOException; +import java.io.StringWriter; +import java.util.*; + +/** + * Builder to create a {@link ConfigValue} instance. + */ +public class ConfigValueBuilder { + /** The key accessed. */ + String key; + /** The property value. */ + String value; + /** additional metadata entries (optional). */ + String metaEntry; + + /** + * Create a new builder instance, for a given set of parameters. + * Before calling build at least a {@link #value} and its {@link #metaEntry} + * must be set. + */ + ConfigValueBuilder(String key){ + this.key = Objects.requireNonNull(key); + } + + /** + * Create a new builder instance, for a given set of parameters. + * @param key to access a property value, not {@code null}. + * @param value property value. + */ + ConfigValueBuilder(String key, String value) { + this.key = Objects.requireNonNull(key); + this.value = Objects.requireNonNull(value); + } + + /** + * Create a new builder instance, for a given set of parameters. + * + * @param key to access a property value. + * @param value the value, not {@code null}. If a value is {@code null} + * {@link javax.config.spi.ConfigSource#getValue(String)} should return {@code null}. + * @param metaEntry property metaEntry. + */ + ConfigValueBuilder(String key, String value, String metaEntry) { + this.key = Objects.requireNonNull(key); + this.value = value; + this.metaEntry = Objects.requireNonNull(metaEntry); + } + + /** + * Replaces/sets the context data. + * @param metaEntry the context data to be applied. + * @return the builder for chaining. + */ + public ConfigValueBuilder setMetaEntry(String metaEntry) { + this.metaEntry = metaEntry; + return this; + } + + /** + * Add an additional context data information. + * @param key the context data key, not {@code null}. + * @param value the context value, not {@code null} (will be converted to String). + * @return the builder for chaining. + */ + public ConfigValueBuilder addMetaEntry(String key, String value) { + Objects.requireNonNull(key, "Meta key must be given."); + Objects.requireNonNull(value, "Meta value must be given."); + if(metaEntry==null){ + metaEntry = key+"="+value; + }else{ + metaEntry = "\n" + key+"="+value; + } + return this; + } + + /** + * Adds the context data given. + * @param metaEntries the context data to be applied, not {@code null}. + * @return the builder for chaining. + */ + public ConfigValueBuilder addMetaEntries(Map<String, String> metaEntries) { + Properties props = new Properties(); + props.putAll(metaEntries); + StringWriter stringWriter = new StringWriter(); + try { + props.store(stringWriter, null); + stringWriter.flush(); + if(metaEntry==null){ + metaEntry = stringWriter.toString(); + }else{ + metaEntry += '\n' + stringWriter.toString(); + } + } catch (IOException e) { + e.printStackTrace(); + } + return this; + } + + /** + * Adds the context data given as JSON object. + * @param meta the context data in JSON format, not {@code null}. + * @return the builder for chaining. + */ + public ConfigValueBuilder addMetaEntry(String meta) { + if(metaEntry==null){ + metaEntry = meta; + }else{ + metaEntry += '\n' + meta; + } + return this; + } + + /** + * Get the value's context data. + * @return the context data, not {@code null}. + */ + public String getMetaEntry() { + return metaEntry; + } + + /** + * Sets a new key. + * @param key the new key, not {@code null}. + * @return the builder for chaining. + */ + public ConfigValueBuilder setKey(String key) { + this.key = Objects.requireNonNull(key); + return this; + } + + /** + * Sets a new value. + * @param value the new value, not {@code null}. + * @return the builder for chaining. + */ + public ConfigValueBuilder setValue(String value) { + this.value = Objects.requireNonNull(value, "Value must be given."); + + return this; + } + + /** + * Creates a new immutable {@link ConfigValue}. + * @return a new immutable {@link ConfigValue}, never {@code null}. + */ + public ConfigValue build(){ + return new ConfigValue(this); + } + + @Override + public String toString() { + return "PropertyValueBuilder{" + + "key='" + key + '\'' + + "value='" + value + '\'' + + ", metaEntry=" + metaEntry + + '}'; + } + +} http://git-wip-us.apache.org/repos/asf/incubator-tamaya/blob/063f8ada/code/base/src/main/java/org/apache/tamaya/spi/Experimental.java ---------------------------------------------------------------------- diff --git a/code/base/src/main/java/org/apache/tamaya/spi/Experimental.java b/code/base/src/main/java/org/apache/tamaya/spi/Experimental.java new file mode 100644 index 0000000..9831820 --- /dev/null +++ b/code/base/src/main/java/org/apache/tamaya/spi/Experimental.java @@ -0,0 +1,32 @@ +/* + * 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.spi; + +import java.lang.annotation.*; + +/** + * This is a simple annotation for flaging out functionality or features the Tamaya team is not sure if it is already + * stabilized, so use it with some caution. + */ +@Retention(RetentionPolicy.RUNTIME) +@Inherited +@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.FIELD, + ElementType.TYPE}) +public @interface Experimental { +} http://git-wip-us.apache.org/repos/asf/incubator-tamaya/blob/063f8ada/code/base/src/main/java/org/apache/tamaya/spi/Filter.java ---------------------------------------------------------------------- diff --git a/code/base/src/main/java/org/apache/tamaya/spi/Filter.java b/code/base/src/main/java/org/apache/tamaya/spi/Filter.java new file mode 100644 index 0000000..9fdad86 --- /dev/null +++ b/code/base/src/main/java/org/apache/tamaya/spi/Filter.java @@ -0,0 +1,50 @@ +/* + * 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.spi; + + +/** + * <p>Interface for filtering the current map of properties during the evaluation of the chain of PropertySources. + * Filters can be registered using the {@link org.apache.tamaya.spi.ServiceContext}. The ordinal + * hereby is defined by the corresponding {@code @Priority} annotation.</p> + * <p>Filters </p> + */ +@FunctionalInterface +public interface Filter { + + /** + * <p>Maps the current {@code valueToBeFiltered} value to a new value. The resulting value will be used as the result + * passed to the user.</p> + * <p>If a filter is currently not available, it should just pass the input map to the method's + * output.</p> + * <p>Returning {@code null} will remove the entry.</p> + * <h3>Implementation specification</h3> + * Implementations of this class must be + * <ul> + * <li>reentrant</li> + * <li>thread-safe</li> + * </ul> + * @param value the value to be filtered, which also can be {@code null} if removed by another filter. + * @return the filtered value, or {@code null} if the value should be removed alltogether. + * @see ConfigValue + * @see ConfigValueBuilder + */ + ConfigValue filterProperty(ConfigValue value); + +} http://git-wip-us.apache.org/repos/asf/incubator-tamaya/blob/063f8ada/code/base/src/main/java/org/apache/tamaya/spi/ServiceContext.java ---------------------------------------------------------------------- diff --git a/code/base/src/main/java/org/apache/tamaya/spi/ServiceContext.java b/code/base/src/main/java/org/apache/tamaya/spi/ServiceContext.java new file mode 100644 index 0000000..ff0bb9f --- /dev/null +++ b/code/base/src/main/java/org/apache/tamaya/spi/ServiceContext.java @@ -0,0 +1,147 @@ +/* + * 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.spi; + +import java.io.IOException; +import java.net.URL; +import java.util.Enumeration; +import java.util.List; + + +/** + * This class models the component that is managing the lifecycle current the + * services used by the Configuration API. + */ +public interface ServiceContext { + + /** + * @return ordinal of the ServiceContext. The one with the highest ordinal will be taken. + */ + int ordinal(); + + static ClassLoader defaultClassLoader(){ + ClassLoader classloader = Thread.currentThread().getContextClassLoader(); + if(classloader==null){ + classloader = ServiceContextManager.class.getClassLoader(); + } + return classloader; + } + + /** + * Access a service singleton via its type. + * If multiple implementations for the very serviceType exist then + * the one with the highest {@link javax.annotation.Priority} will be used. + * + * @param <T> the type of the service type. + * @param serviceType the service type. + * @return The instance to be used, or {@code null} + * @throws IllegalArgumentException if there are multiple service implementations with the maximum priority. + */ + default <T> T getService(Class<T> serviceType){ + return getService(serviceType, defaultClassLoader()); + } + + /** + * Access a service singleton via its type. + * If multiple implementations for the very serviceType exist then + * the one with the highest {@link javax.annotation.Priority} will be used. + * + * @param <T> the type of the service type. + * @param serviceType the service type. + * @param classLoader the class loader to be considered. + * @return The instance to be used, or {@code null} + * @throws IllegalArgumentException if there are multiple service implementations with the maximum priority. + */ + <T> T getService(Class<T> serviceType, ClassLoader classLoader); + + /** + * Factory method to create a type, hereby a new instance is created on each access. + * If multiple implementations for the very serviceType exist then + * the one with the highest {@link javax.annotation.Priority} will be used as the base + * for creating subsequent instances. + * + * @param <T> the type of the service type. + * @param serviceType the service type. + * @return The new instance to be used, or {@code null} + * @throws IllegalArgumentException if there are multiple service implementations with the maximum priority. + */ + default <T> T create(Class<T> serviceType){ + return create(serviceType, defaultClassLoader()); + } + + /** + * Factory method to create a type, hereby a new instance is created on each access. + * If multiple implementations for the very serviceType exist then + * the one with the highest {@link javax.annotation.Priority} will be used as the base + * for creating subsequent instances. + * + * @param <T> the type of the service type. + * @param serviceType the service type. + * @param classLoader the class loader to be considered. + * @return The new instance to be used, or {@code null} + * @throws IllegalArgumentException if there are multiple service implementations with the maximum priority. + */ + <T> T create(Class<T> serviceType, ClassLoader classLoader); + + + /** + * Access a list current services, given its type. The bootstrap mechanism should + * order the instance for precedence, hereby the most significant should be + * first in order. + * + * @param serviceType + * the service type. + * @param <T> the type of the list element returned by this method + * @return The instance to be used, never {@code null} + */ + default <T> List<T> getServices(Class<T> serviceType){ + return getServices(serviceType, defaultClassLoader()); + } + + /** + * Access a list current services, given its type. The bootstrap mechanism should + * order the instance for precedence, hereby the most significant should be + * first in order. + * + * @param serviceType + * the service type. + * @param <T> the type of the list element returned by this method + * @return The instance to be used, never {@code null} + */ + <T> List<T> getServices(Class<T> serviceType, ClassLoader classLoader); + + /** + * Loads resources from the current runtime context. This method allows to use runtime + * specific code to load resources, e.g. within OSGI environments. + * @param resource the resource, not {@code null}. + * @param cl the desired classloader context, if null, the current thread context classloader is used. + * @return the resources found + * @throws IOException if load fails. + */ + Enumeration<URL> getResources(String resource, ClassLoader cl) throws IOException; + + /** + * Loads a resource from the current runtime context. This method allows to use runtime + * specific code to load a resource, e.g. within OSGI environments. + * @param resource the resource, not {@code null}. + * @param cl the desired classloader context, if null, the current thread context classloader is used. + * @return the resource found, or {@code null}. + */ + URL getResource(String resource, ClassLoader cl); +} http://git-wip-us.apache.org/repos/asf/incubator-tamaya/blob/063f8ada/code/base/src/main/java/org/apache/tamaya/spi/ServiceContextManager.java ---------------------------------------------------------------------- diff --git a/code/base/src/main/java/org/apache/tamaya/spi/ServiceContextManager.java b/code/base/src/main/java/org/apache/tamaya/spi/ServiceContextManager.java new file mode 100644 index 0000000..ddfd425 --- /dev/null +++ b/code/base/src/main/java/org/apache/tamaya/spi/ServiceContextManager.java @@ -0,0 +1,118 @@ +/* + * 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.spi; + +import java.util.Objects; +import java.util.ServiceLoader; +import java.util.logging.Level; +import java.util.logging.Logger; + + +/** + * This singleton provides access to the services available in the current {@link ServiceContext}. The + * behaviour can be adapted, by calling {@link ServiceContextManager#set(ServiceContext)} before accessing any + * services. + */ +public final class ServiceContextManager { + + /** + * The logger used. + */ + private static final Logger LOG = Logger.getLogger(ServiceContextManager.class.getName()); + + /** + * The ServiceProvider used. + */ + private static volatile ServiceContext serviceContextProviderDelegate; + + /** + * Private singletons constructor. + */ + private ServiceContextManager() { + } + + /** + * Load the {@link ServiceContext} to be used. + * + * @return {@link ServiceContext} to be used for loading the services. + */ + private static ServiceContext loadDefaultServiceProvider() { + ServiceContext highestServiceContext = null; + try { + int highestOrdinal = 0; + for (ServiceContext serviceContext : ServiceLoader.load(ServiceContext.class)) { + if (highestServiceContext == null) { + highestServiceContext = serviceContext; + } else if (serviceContext.ordinal() > highestOrdinal) { + highestServiceContext = serviceContext; + highestOrdinal = serviceContext.ordinal(); + } + } + } catch (Exception e) { + throw new IllegalStateException("ServiceContext not loadable", e); + } + if (highestServiceContext == null) { + throw new IllegalStateException("No ServiceContext found"); + } + LOG.info("Using Service Context of type: " + highestServiceContext.getClass().getName()); + return highestServiceContext; + } + + /** + * Replace the current {@link ServiceContext} in use. + * + * @param serviceContextProvider the new {@link ServiceContext}, not {@code null}. + * @return the currently used context after setting it. + */ + public static ServiceContext set(ServiceContext serviceContextProvider) { + Objects.requireNonNull(serviceContextProvider); + ServiceContext currentContext = ServiceContextManager.serviceContextProviderDelegate; + + synchronized (ServiceContextManager.class) { + if (ServiceContextManager.serviceContextProviderDelegate == null) { + ServiceContextManager.serviceContextProviderDelegate = serviceContextProvider; + LOG.log(Level.INFO, "Using ServiceProvider: " + serviceContextProvider.getClass().getName()); + } else { + LOG.log(Level.WARNING, "Replacing ServiceProvider " + + ServiceContextManager.serviceContextProviderDelegate.getClass().getName() + + " with: " + serviceContextProvider.getClass().getName()); + ServiceContextManager.serviceContextProviderDelegate = serviceContextProvider; + } + } + + return currentContext; + } + + /** + * Ge {@link ServiceContext}. If necessary the {@link ServiceContext} will be laziliy loaded. + * + * @return the {@link ServiceContext} used. + */ + public static ServiceContext getServiceContext(){ + if (serviceContextProviderDelegate == null) { + synchronized (ServiceContextManager.class) { + if (serviceContextProviderDelegate == null) { + serviceContextProviderDelegate = loadDefaultServiceProvider(); + } + } + } + return serviceContextProviderDelegate; + } + +} http://git-wip-us.apache.org/repos/asf/incubator-tamaya/blob/063f8ada/code/base/src/main/java/org/apache/tamaya/spi/StandaloneConfigContextBuilder.java ---------------------------------------------------------------------- diff --git a/code/base/src/main/java/org/apache/tamaya/spi/StandaloneConfigContextBuilder.java b/code/base/src/main/java/org/apache/tamaya/spi/StandaloneConfigContextBuilder.java new file mode 100644 index 0000000..f8cd841 --- /dev/null +++ b/code/base/src/main/java/org/apache/tamaya/spi/StandaloneConfigContextBuilder.java @@ -0,0 +1,407 @@ +///* +// * 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.spi; +// +//import org.apache.tamaya.base.configsource.ConfigSourceComparator; +//import org.apache.tamaya.base.configsource.CLIConfigSource; +//import org.apache.tamaya.base.configsource.EnvironmentConfigSource; +//import org.apache.tamaya.base.configsource.JavaConfigurationConfigSource; +//import org.apache.tamaya.base.configsource.SystemConfigSource; +// +//import javax.config.spi.ConfigSource; +//import javax.config.spi.ConfigSourceProvider; +//import javax.config.spi.Converter; +//import java.lang.reflect.ParameterizedType; +//import java.lang.reflect.Type; +//import java.util.*; +//import java.util.logging.Logger; +// +///** +// * Default implementation of {@link StandaloneConfigContextBuilder}. +// */ +//public final class StandaloneConfigContextBuilder { +// +// private static final Logger LOG = Logger.getLogger(StandaloneConfigContextBuilder.class.getName()); +// +// protected List<Filter> filters = new ArrayList<>(); +// protected List<ConfigSource> propertySources = new ArrayList<>(); +// protected ConfigValueCombinationPolicy combinationPolicy = ConfigValueCombinationPolicy.DEFAULT_OVERRIDING_POLICY; +// protected Map<Type, Collection<Converter>> propertyConverters = new HashMap<>(); +// +// /** +// * Flag if the config has already been built. +// * Configuration can be built only once +// */ +// private boolean built; +// private ClassLoader classLoader; +// +// /** +// * Creates a new builder instance. +// */ +// public StandaloneConfigContextBuilder() { +// } +// +// /** +// * Creates a new builder instance initializing it with the given context. +// * @param context the context to be used, not null. +// */ +// public StandaloneConfigContextBuilder(ConfigContext context) { +// this.propertyConverters.putAll(context.getConverters()); +// this.filters.addAll(context.getFilters()); +// for(ConfigSource ps:context.getSources()) { +// withSources(ps); +// } +// this.combinationPolicy = context.getConfigValueCombinationPolicy(); +// } +// +// /** +// * Allows to reset configuration context during unit tests. +// */ +// public final StandaloneConfigContextBuilder reset() { +// checkBuilderState(); +// this.filters.clear(); +// this.propertySources.clear(); +// this.propertyConverters.clear(); +// this.combinationPolicy = ConfigValueCombinationPolicy.DEFAULT_OVERRIDING_POLICY; +// return this; +// } +// +// +// public StandaloneConfigContextBuilder withContext(ConfigContext context) { +// checkBuilderState(); +// this.propertyConverters.putAll(context.getConverters()); +// for(ConfigSource ps:context.getSources()){ +// this.propertySources.add(ps); +// } +// this.filters.addAll(context.getFilters()); +// this.combinationPolicy = context.getConfigValueCombinationPolicy(); +// return this; +// } +// +// public final StandaloneConfigContextBuilder withSources(ConfigSource... sources){ +// return withSources(Arrays.asList(sources)); +// } +// +// public StandaloneConfigContextBuilder withSources(Collection<ConfigSource> sources){ +// checkBuilderState(); +// for(ConfigSource source:sources) { +// if (!this.propertySources.contains(source)) { +// this.propertySources.add(source); +// } +// } +// return this; +// } +// +// +// public StandaloneConfigContextBuilder addDiscoveredSources() { +// checkBuilderState(); +// List<ConfigSource> propertySources = new ArrayList<>(); +//// addDiscoveredSources(propertySources); +// for(ConfigSource ps: ServiceContextManager.getServiceContext().getServices(ConfigSource.class, classLoader)) { +// if(!propertySources.contains(ps)){ +// propertySources.add(ps); +// } +// } +// +// for(ConfigSourceProvider provider: +// ServiceContextManager.getServiceContext().getServices(ConfigSourceProvider.class, classLoader)){ +// for(ConfigSource src: provider.getConfigSources(classLoader)){ +// propertySources.add(src); +// } +// } +// Collections.sort(propertySources, ConfigSourceComparator.getInstance()); +// return withSources(propertySources); +// } +// +// private StandaloneConfigContextBuilder addDiscoveredSources(List<ConfigSource> propertySources) { +// for(ConfigSource ps: new ConfigSource[]{ +// new EnvironmentConfigSource(), +// new JavaConfigurationConfigSource(), +// new CLIConfigSource(), +// new SystemConfigSource() +// }){ +// if(!propertySources.contains(ps)){ +// propertySources.add(ps); +// } +// } +// return this; +// } +// +// public StandaloneConfigContextBuilder addDiscoveredPropertyFilters() { +// checkBuilderState(); +// for(Filter pf:ServiceContextManager.getServiceContext().getServices(Filter.class, classLoader)){ +// withFilters(pf); +// } +// return this; +// } +// +// public StandaloneConfigContextBuilder addDiscoveredConverters() { +// checkBuilderState(); +// addDiscoveredConverters(); +// for(Map.Entry<Type, Collection<Converter>> en: getDefaultConverters().entrySet()){ +// for(Converter pc: en.getValue()) { +// withConverters(en.getKey(), pc); +// } +// } +// return this; +// } +// +// @SuppressWarnings("unchecked") +// public void addDiscoveredConverters() { +// // should be overridden by subclasses. +// } +// +// public final StandaloneConfigContextBuilder removeSources(ConfigSource... propertySources) { +// return removeSources(Arrays.asList(propertySources)); +// } +// +// public StandaloneConfigContextBuilder removeSources(Collection<ConfigSource> propertySources) { +// checkBuilderState(); +// this.propertySources.removeAll(propertySources); +// return this; +// } +// +// protected ConfigSource getSource(String name) { +// for(ConfigSource ps:propertySources){ +// if(ps.getName().equals(name)){ +// return ps; +// } +// } +// throw new IllegalArgumentException("No such PropertySource: "+name); +// } +// +// public List<ConfigSource> getSources() { +// return Collections.unmodifiableList(this.propertySources); +// } +// +// public StandaloneConfigContextBuilder increasePriority(ConfigSource propertySource) { +// checkBuilderState(); +// int index = propertySources.indexOf(propertySource); +// if(index<0){ +// throw new IllegalArgumentException("No such PropertySource: " + propertySource); +// } +// if(index<(propertySources.size()-1)){ +// propertySources.remove(propertySource); +// propertySources.add(index+1, propertySource); +// } +// return this; +// } +// +// public StandaloneConfigContextBuilder decreasePriority(ConfigSource propertySource) { +// checkBuilderState(); +// int index = propertySources.indexOf(propertySource); +// if(index<0){ +// throw new IllegalArgumentException("No such PropertySource: " + propertySource); +// } +// if(index>0){ +// propertySources.remove(propertySource); +// propertySources.add(index-1, propertySource); +// } +// return this; +// } +// +// public StandaloneConfigContextBuilder highestPriority(ConfigSource propertySource) { +// checkBuilderState(); +// int index = propertySources.indexOf(propertySource); +// if(index<0){ +// throw new IllegalArgumentException("No such PropertySource: " + propertySource); +// } +// if(index<(propertySources.size()-1)){ +// propertySources.remove(propertySource); +// propertySources.add(propertySource); +// } +// return this; +// } +// +// public StandaloneConfigContextBuilder lowestPriority(ConfigSource propertySource) { +// checkBuilderState(); +// int index = propertySources.indexOf(propertySource); +// if(index<0){ +// throw new IllegalArgumentException("No such PropertySource: " + propertySource); +// } +// if(index>0){ +// propertySources.remove(propertySource); +// propertySources.add(0, propertySource); +// } +// return this; +// } +// +// public final StandaloneConfigContextBuilder withFilters(Filter... filters){ +// return withFilters(Arrays.asList(filters)); +// } +// +// public final StandaloneConfigContextBuilder withFilters(Collection<Filter> filters){ +// checkBuilderState(); +// for(Filter f:filters) { +// if (!this.filters.contains(f)) { +// this.filters.add(f); +// } +// } +// return this; +// } +// +// public final StandaloneConfigContextBuilder removeFilters(Filter... filters) { +// return removeFilters(Arrays.asList(filters)); +// } +// +// public final StandaloneConfigContextBuilder removeFilters(Collection<Filter> filters) { +// checkBuilderState(); +// this.filters.removeAll(filters); +// return this; +// } +// +// +// public final <T> StandaloneConfigContextBuilder removeConverters(Type typeToConvert, +// @SuppressWarnings("unchecked") Converter<T>... converters) { +// return removeConverters(typeToConvert, Arrays.asList(converters)); +// } +// +// public final <T> StandaloneConfigContextBuilder removeConverters(Type typeToConvert, +// Collection<Converter<T>> converters) { +// Collection<Converter> subConverters = this.propertyConverters.get(typeToConvert); +// if(subConverters!=null) { +// subConverters.removeAll(converters); +// } +// return this; +// } +// +// public final StandaloneConfigContextBuilder removeConverters(TypeLiteral<?> typeToConvert) { +// this.propertyConverters.remove(typeToConvert); +// return this; +// } +// +// +// public final StandaloneConfigContextBuilder withPropertyValueCombinationPolicy(ConfigValueCombinationPolicy combinationPolicy){ +// checkBuilderState(); +// this.combinationPolicy = Objects.requireNonNull(combinationPolicy); +// return this; +// } +// +// +// public <T> StandaloneConfigContextBuilder withConverters(Type type, Converter<T>... propertyConverters){ +// checkBuilderState(); +// Objects.requireNonNull(type); +// Objects.requireNonNull(propertyConverters); +// Collection<Converter> converters = this.propertyConverters.get(type); +// if(converters==null){ +// converters = new ArrayList<>(); +// this.propertyConverters.put(type, converters); +// } +// for(Converter<T> propertyConverter:propertyConverters) { +// if (!converters.contains(propertyConverter)) { +// converters.add(propertyConverter); +// } else { +// LOG.warning("Converter ignored, already registered: " + propertyConverter); +// } +// } +// return this; +// } +// +// public <T> StandaloneConfigContextBuilder withConverters(Type type, Collection<Converter<T>> propertyConverters){ +// checkBuilderState(); +// Objects.requireNonNull(type); +// Objects.requireNonNull(propertyConverters); +// Collection<Converter> converters = this.propertyConverters.get(type); +// if(converters==null){ +// converters = new ArrayList<>(); +// this.propertyConverters.put(type, converters); +// } +// for(Converter<T> propertyConverter:propertyConverters) { +// if (!converters.contains(propertyConverter)) { +// converters.add(propertyConverter); +// } else { +// LOG.warning("Converter ignored, already registered: " + propertyConverter); +// } +// } +// return this; +// } +// +// protected StandaloneConfigContextBuilder loadDefaults() { +// checkBuilderState(); +// this.combinationPolicy = ConfigValueCombinationPolicy.DEFAULT_OVERRIDING_COLLECTOR; +// addDiscoveredSources(); +// addDiscoveredPropertyFilters(); +// addDiscoveredConverters(); +// return this; +// } +// +// +// protected Map<Type, Collection<Converter>> getDefaultConverters() { +// Map<Type, Collection<Converter>> result = new HashMap<>(); +// for (Converter conv : ServiceContextManager.getServiceContext().getServices( +// Converter.class, classLoader)) { +// 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]; +// Collection<Converter> convList = result.get(target); +// if (convList == null) { +// convList = new ArrayList<>(); +// result.put(target, convList); +// } +// convList.add(conv); +// } +// } +// } +// } +// return result; +// } +// +// +// /** +// * Builds a new configuration based on the configuration of this builder instance. +// * +// * @return a new {@link javax.config.Config configuration instance}, +// * never {@code null}. +// */ +// public ConfigContext build() { +// checkBuilderState(); +// built = true; +// return new StandaloneConfigContext(this); +// } +// +// public StandaloneConfigContextBuilder sortFilter(Comparator<Filter> comparator) { +// Collections.sort(filters, comparator); +// return this; +// } +// +// public StandaloneConfigContextBuilder sortSources(Comparator<ConfigSource> comparator) { +// Collections.sort(propertySources, comparator); +// return this; +// } +// +// private void checkBuilderState() { +// if (built) { +// throw new IllegalStateException("Configuration has already been build."); +// } +// } +// +// public List<Filter> getFilters() { +// return filters; +// } +// +// public Map<Type, Collection<Converter>> getConverter() { +// return Collections.unmodifiableMap(this.propertyConverters); +// } +// +// public void setClassLoader(ClassLoader classLoader) { +// this.classLoader = classLoader; +// } +//} http://git-wip-us.apache.org/repos/asf/incubator-tamaya/blob/063f8ada/code/base/src/main/java/org/apache/tamaya/spi/TamayaConfigBuilder.java ---------------------------------------------------------------------- diff --git a/code/base/src/main/java/org/apache/tamaya/spi/TamayaConfigBuilder.java b/code/base/src/main/java/org/apache/tamaya/spi/TamayaConfigBuilder.java new file mode 100644 index 0000000..e48a6bd --- /dev/null +++ b/code/base/src/main/java/org/apache/tamaya/spi/TamayaConfigBuilder.java @@ -0,0 +1,333 @@ +/* + * 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.spi; + +import org.apache.tamaya.base.DefaultConfigBuilder; + +import javax.config.spi.ConfigBuilder; +import javax.config.spi.ConfigSource; +import javax.config.spi.Converter; +import java.lang.reflect.Type; +import java.util.Collection; +import java.util.Comparator; +import java.util.List; +import java.util.Map; + +/** + * A builder for creating new or adapting instances of {@link javax.config.Config}. + * Builders can be obtained in exactly two ways: + * <ol> + * <li>By accessing an empty builder instance from + * {@link javax.config.spi.ConfigProviderResolver#getBuilder()}.</li> + * </ol> + */ +public interface TamayaConfigBuilder extends ConfigBuilder, ConfigContextSupplier{ + + /** + * Create a new empty configuration builder. + * @return + */ + static TamayaConfigBuilder create() { + return new DefaultConfigBuilder(); + } + + static TamayaConfigBuilder create(ConfigContextSupplier contextSupplier){ + return new DefaultConfigBuilder(contextSupplier.getConfigContext()); + } + + static TamayaConfigBuilder from(ConfigBuilder configBuilder){ + if(configBuilder instanceof TamayaConfigBuilder) { + return (TamayaConfigBuilder) configBuilder; + }else if(configBuilder instanceof ConfigContextSupplier){ + return create((ConfigContextSupplier)configBuilder); + } + throw new IllegalArgumentException("Builder must implement ConfigContextSupplier."); + } + + /** + * This method can be used for programmatically adding {@link ConfigSource}s. + * Hereby the property source is added to the tail of property sources with + * lowest priority regardless of its current ordinal value. To sort the property + * sources based on their ordinals call {@link #sortSources}. + * + * @param propertySources the PropertySources to add + * @return this builder, for chaining, never null. + * @throws IllegalArgumentException If a property source with a given name already + * exists. + */ + TamayaConfigBuilder withSources(Collection<ConfigSource> propertySources); + + /** + * Removes the given property sources, if existing. The existing order of property + * sources is preserved. + * + * @param propertySources the property sources to remove, not {@code null}. + * @return the builder for chaining. + */ + TamayaConfigBuilder removeSources(ConfigSource... propertySources); + + /** + * Removes the given property sources, if existing. The existing order of property + * sources is preserved. + * + * @param propertySources the property sources to remove, not {@code null}. + * @return the builder for chaining. + */ + TamayaConfigBuilder removeSources(Collection<ConfigSource> propertySources); + + /** + * Increases the priority of the given property source, by moving it towards the end + * of the chain of property sources. If the property source given is already at the end + * this method has no effect. This operation does not change any ordinal values. + * + * @param propertySource the property source to be incresed regarding its significance. + * @return the builder for chaining. + * @throws IllegalArgumentException If no such property source exists in the current + * chain. + */ + TamayaConfigBuilder increasePriority(ConfigSource propertySource); + + /** + * Decreases the priority of the given property source, by moving it towards the start + * of the chain of property sources. If the property source given is already the first + * this method has no effect. This operation does not change any ordinal values. + * + * @param propertySource the property source to be decresed regarding its significance. + * @return the builder for chaining. + * @throws IllegalArgumentException If no such property source exists in the current + * chain. + */ + TamayaConfigBuilder decreasePriority(ConfigSource propertySource); + + /** + * Increases the priority of the given property source to be maximal, by moving it to + * the tail of the of property source chain. If the property source given is + * already the last item this method has no effect. This operation does not change + * any ordinal values. + * + * @param propertySource the property source to be maximized regarding its significance. + * @return the builder for chaining. + * @throws IllegalArgumentException If no such property source exists in the current + * chain. + */ + TamayaConfigBuilder highestPriority(ConfigSource propertySource); + + /** + * Decreases the priority of the given property source to be minimal, by moving it to + * the start of the chain of property source chain. If the property source given is + * already the first item this method has no effect. This operation does not change + * any ordinal values. + * + * @param propertySource the property source to be minimized regarding its significance. + * @return the builder for chaining. + * @throws IllegalArgumentException If no such property source exists in the current + * chain. + */ + TamayaConfigBuilder lowestPriority(ConfigSource propertySource); + + /** + * Access the current chain of property sources. Items at the end of the list have + * precedence/more significance. + * + * @return the property source chain, never {@code null}. + */ + List<ConfigSource> getSources(); + + /** + * Sorts the current registered property sources using the given comparator. + * + * NOTE: property sources at the beginning have minimal significance. + * + * @param comparator the comparator to be used, not {@code null}. + * @return this instance for chaining. + */ + TamayaConfigBuilder sortSources(Comparator<ConfigSource> comparator); + + /** + * Adds the given PropertyFilter instances, hereby the instances are added + * to the end of the list with highest priority. The ordering of existing + * property filters remains unchanged. To sort the property + * filters call {@link #sortFilter}. + * + * @param filters the filters to add + * @return this builder, for chaining, never null. + */ + TamayaConfigBuilder withFilters(Filter... filters); + + /** + * Adds the given PropertyFilter instances, hereby the instances are added + * to the end of the list with highest priority. The ordering of existing + * property filters remains unchanged. To sort the property + * filters call {@link #sortFilter}. + * + * @param filters the filters to add + * @return this builder, for chaining, never null. + */ + TamayaConfigBuilder withFilters(Collection<Filter> filters); + + /** + * Add all registered (default) property filters to the context built. + * @return this builder, for chaining, never null. + */ + TamayaConfigBuilder addDiscoveredFilters(); + + /** + * Removes the given PropertyFilter instances, if existing. The order of the remaining + * filters is preserved. + * + * @param filters the filter to remove + * @return this builder, for chaining, never null. + */ + TamayaConfigBuilder removeFilters(Filter... filters); + + /** + * Removes the given PropertyFilter instances, if existing. The order of the remaining + * filters is preserved. + * + * @param filters the filter to remove + * @return this builder, for chaining, never null. + */ + TamayaConfigBuilder removeFilters(Collection<Filter> filters); + + /** + * Access the current chain of property filters. Items at the end of the list have + * precedence/more significance. + * + * @return the property source chain, never {@code null}. + */ + List<Filter> getFilters(); + + /** + * This method can be used for adding {@link Converter}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 converters the converters to add for this type + * @return this builder, for chaining, never null. + */ + TamayaConfigBuilder withConverters(Collection<Converter<?>> converters); + + /** + * This method can be used for adding {@link Converter}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 + * @param <T> the target type. + * @return this builder, for chaining, never null. + */ + <T> TamayaConfigBuilder withConverters(Class<T> typeToConvert, Converter<T>... converters); + + /** + * This method can be used for adding {@link Converter}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 + * @param <T> the target type. + * @return this builder, for chaining, never null. + */ + <T> TamayaConfigBuilder withConverters(Class<T> typeToConvert, Collection<Converter<T>> 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 + * @param <T> the target type. + * @return this builder, for chaining, never null. + */ + <T> TamayaConfigBuilder removeConverters(Class<T> typeToConvert, Converter<T>... 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 + * @param <T> the target type. + * @return this builder, for chaining, never null. + */ + <T> TamayaConfigBuilder removeConverters(Class<T> typeToConvert, Collection<Converter<T>> converters); + + /** + * 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. + */ + <T> TamayaConfigBuilder removeConverters(Class<T> typeToConvert); + + /** + * Access the current registered property converters. + * + * @return the current registered property converters. + */ + Map<Type, List<Converter>> getConverter(); + + /** + * Sets the {@link ConfigValueCombinationPolicy} used to evaluate the final + * property values. + * + * @param policy the {@link ConfigValueCombinationPolicy} used, not {@code null}. + * @return this builder, for chaining, never null. + */ + TamayaConfigBuilder withPropertyValueCombinationPolicy(ConfigValueCombinationPolicy policy); + + /** + * Sorts the current registered property filters using the given comparator. + * + * NOTE: property filters at the beginning have minimal significance. + * + * @param comparator the comparator to be used, not {@code null}. + * @return this instance for chaining. + */ + TamayaConfigBuilder sortFilter(Comparator<Filter> comparator); + + @Override + TamayaConfigBuilder addDefaultSources(); + + @Override + TamayaConfigBuilder addDiscoveredSources(); + + @Override + TamayaConfigBuilder addDiscoveredConverters(); + + @Override + TamayaConfigBuilder forClassLoader(ClassLoader loader); + + @Override + TamayaConfigBuilder withSources(ConfigSource... sources); + + @Override + TamayaConfigBuilder withConverters(Converter<?>... converters); + + @Override + <T> TamayaConfigBuilder withConverter(Class<T> type, Converter<T> converter); + +} + http://git-wip-us.apache.org/repos/asf/incubator-tamaya/blob/063f8ada/code/base/src/main/java/org/apache/tamaya/spi/TypeLiteral.java ---------------------------------------------------------------------- diff --git a/code/base/src/main/java/org/apache/tamaya/spi/TypeLiteral.java b/code/base/src/main/java/org/apache/tamaya/spi/TypeLiteral.java new file mode 100644 index 0000000..0139e85 --- /dev/null +++ b/code/base/src/main/java/org/apache/tamaya/spi/TypeLiteral.java @@ -0,0 +1,225 @@ +/* + * 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.spi; + +import java.io.Serializable; +import java.lang.reflect.GenericArrayType; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.Objects; + + +/** + * <p>Class for instantiation of objects that represent parameterized types + * with current parameters.</p> + * + * <p>An object that represents a parameterized type may be obtained by + * subclassing <tt>TypeLiteral</tt>.</p> + * + * <pre> + * TypeLiteral<List<Integer>> stringListType = new TypeLiteral<List<Integer>>() {}; + * </pre> + * + * @param <T> the type, including all type parameters + */ +public class TypeLiteral<T> implements Serializable { + + private static final long serialVersionUID = 1L; + private static final Type[] EMPTY_TYPE_ARRAY = new Type[0]; + /** The current defined type. */ + private final Type definedType; + + /** + * Constructor. + * @param definedType the defined type. + */ + public TypeLiteral(Type definedType) { + Objects.requireNonNull(definedType, "Type must be given"); + + this.definedType = definedType; + } + + /** + * Constructor only for directly implemeting a TypeLiteral hereby dynamically implementing a generic interface. + */ + public TypeLiteral() { + this.definedType = getDefinedType(this.getClass()); + } + + /** + * Creates a new TypeLiteral based on a given type. + * + * @param type the type , not {@code null}. + * @param <R> the literal generic type. + * @return the corresponding TypeLiteral, never {@code null}. + */ + public static <R> TypeLiteral<R> of(Type type) { + Objects.requireNonNull(type, "Type must be given."); + + return new TypeLiteral<R>(type); + } + + /** + * 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. + */ + public static 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 EMPTY_TYPE_ARRAY; + } + + /** + * Method that checks the class's type for a generic interface implementation type. + * + * @param type the type, not {@code null}. + * @return the generic type parameter of the given single type generic interfaceType, or an empty array. + */ + public static Type[] getTypeParameters(Type type) { + Objects.requireNonNull(type, "Type must be given."); + + if (type instanceof ParameterizedType) { + ParameterizedType parameterizedType = (ParameterizedType) type; + return parameterizedType.getActualTypeArguments(); + } + return EMPTY_TYPE_ARRAY; + } + + public final Type getType() { + return definedType; + } + + /** + * Returns basic raw Java type. + * + * @return the actual type represented by this object + */ + @SuppressWarnings("unchecked") + public final Class<T> getRawType() { + Class<T> rawType; + + if (this.definedType instanceof ParameterizedType) { + ParameterizedType pt = (ParameterizedType) this.definedType; + rawType = (Class<T>) pt.getRawType(); + } else if (this.definedType instanceof GenericArrayType) { + rawType = (Class<T>) Object[].class; + } else if (this.definedType instanceof Class) { + rawType = (Class<T>) this.definedType; + } else { + throw new RuntimeException("Illegal type for the Type Literal Class"); + } + + return rawType; + } + + /** + * Returns actual type arguments, if present. + * + * @return the actual type represented by defined class, or an empty array. + */ + public final Type[] getActualTypeArguments() { + if (this.definedType instanceof ParameterizedType) { + ParameterizedType pt = (ParameterizedType) this.definedType; + return pt.getActualTypeArguments(); + } + return new Type[0]; + } + + + protected Type getDefinedType(Class<?> clazz) { + Type type; + + if (clazz == null) { + throw new RuntimeException("Class parameter clazz can not be null"); + } + + Type superClazz = clazz.getGenericSuperclass(); + + if (superClazz instanceof ParameterizedType) { + ParameterizedType pt = (ParameterizedType) superClazz; + Type[] actualArgs = pt.getActualTypeArguments(); + + if (actualArgs.length == 1) { + type = actualArgs[0]; + + } else { + throw new RuntimeException("More than one parametric type"); + } + + } else if (superClazz.equals(Object.class)) { + throw new RuntimeException("Super class must be parametrized type"); + } else { + type = getDefinedType((Class<?>) superClazz); + } + + return type; + } + + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((definedType == null) ? 0 : definedType.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + TypeLiteral<?> other = (TypeLiteral<?>) obj; + if (definedType == null) { + if (other.definedType != null) { + return false; + } + } else if (!definedType.equals(other.definedType)) { + return false; + } + return true; + } + + + @Override + public String toString() { + return "TypeLiteral{" + + "type=" + definedType + + '}'; + } + +}
