http://git-wip-us.apache.org/repos/asf/incubator-tamaya/blob/7917a9f3/code/spi-support/src/main/java/org/apache/tamaya/spisupport/PropertyConverterManager.java ---------------------------------------------------------------------- diff --git a/code/spi-support/src/main/java/org/apache/tamaya/spisupport/PropertyConverterManager.java b/code/spi-support/src/main/java/org/apache/tamaya/spisupport/PropertyConverterManager.java new file mode 100644 index 0000000..921cac6 --- /dev/null +++ b/code/spi-support/src/main/java/org/apache/tamaya/spisupport/PropertyConverterManager.java @@ -0,0 +1,471 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.tamaya.spisupport; + +import org.apache.tamaya.ConfigException; +import org.apache.tamaya.TypeLiteral; +import org.apache.tamaya.spi.ConversionContext; +import org.apache.tamaya.spi.PropertyConverter; +import org.apache.tamaya.spi.ServiceContextManager; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.lang.reflect.Type; +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Manager that deals with {@link PropertyConverter} instances. + * This class is thread-safe. + */ +public class PropertyConverterManager { + /** + * The logger used. + */ + private static final Logger LOG = Logger.getLogger(PropertyConverterManager.class.getName()); + /** + * The registered converters. + */ + private final Map<TypeLiteral<?>, List<PropertyConverter<?>>> converters = new ConcurrentHashMap<>(); + /** + * The transitive converters. + */ + private final Map<TypeLiteral<?>, List<PropertyConverter<?>>> transitiveConverters = new ConcurrentHashMap<>(); + /** + * The lock used. + */ + private final ReadWriteLock lock = new ReentrantReadWriteLock(); + + 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()); + } + } + }; + + /** + * Constructor. + */ + public PropertyConverterManager() { + this(false); + } + + public PropertyConverterManager(boolean init) { + if (init) { + initConverters(); + } + } + + /** + * Registers the default converters provided out of the box. + */ + @SuppressWarnings({ "rawtypes", "unchecked" }) + protected void initConverters() { + for (PropertyConverter conv : ServiceContextManager.getServiceContext().getServices(PropertyConverter.class)) { + Type type = TypeLiteral.getGenericInterfaceTypeParameters(conv.getClass(), PropertyConverter.class)[0]; + register(TypeLiteral.of(type), conv); + } + } + + /** + * Registers a new converters instance. + * + * @param targetType the target type, not {@code null}. + * @param converter the converters, not {@code null}. + * @param <T> the type. + */ + @SuppressWarnings("unchecked") + public <T> void register(TypeLiteral<T> targetType, PropertyConverter<T> converter) { + Objects.requireNonNull(converter); + Lock writeLock = lock.writeLock(); + try { + writeLock.lock(); + List<PropertyConverter<?>> converters = List.class.cast(this.converters.get(targetType)); + if(converters!=null && converters.contains(converter)){ + return; + } + List<PropertyConverter<?>> newConverters = new ArrayList<>(); + if (converters != null) { + newConverters.addAll(converters); + } + if(!newConverters.contains(converter)) { + newConverters.add(converter); + } + Collections.sort(newConverters, PRIORITY_COMPARATOR); + this.converters.put(targetType, Collections.unmodifiableList(newConverters)); + // evaluate transitive closure for all inherited supertypes and implemented interfaces + // direct implemented interfaces + for (Class<?> ifaceType : targetType.getRawType().getInterfaces()) { + converters = List.class.cast(this.transitiveConverters.get(TypeLiteral.of(ifaceType))); + newConverters = new ArrayList<>(); + if (converters != null) { + newConverters.addAll(converters); + } + newConverters.add(converter); + Collections.sort(newConverters, PRIORITY_COMPARATOR); + this.transitiveConverters.put(TypeLiteral.of(ifaceType), Collections.unmodifiableList(newConverters)); + } + Class<?> superClass = targetType.getRawType().getSuperclass(); + while (superClass != null && !superClass.equals(Object.class)) { + converters = List.class.cast(this.transitiveConverters.get(TypeLiteral.of(superClass))); + newConverters = new ArrayList<>(); + if (converters != null) { + newConverters.addAll(converters); + } + newConverters.add(converter); + Collections.sort(newConverters, PRIORITY_COMPARATOR); + this.transitiveConverters.put(TypeLiteral.of(superClass), Collections.unmodifiableList(newConverters)); + for (Class<?> ifaceType : superClass.getInterfaces()) { + converters = List.class.cast(this.transitiveConverters.get(TypeLiteral.of(ifaceType))); + newConverters = new ArrayList<>(); + if (converters != null) { + newConverters.addAll(converters); + } + newConverters.add(converter); + Collections.sort(newConverters, PRIORITY_COMPARATOR); + this.transitiveConverters.put(TypeLiteral.of(ifaceType), Collections.unmodifiableList(newConverters)); + } + superClass = superClass.getSuperclass(); + } + } finally { + writeLock.unlock(); + } + } + + /** + * 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(TypeLiteral<?> 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(org.apache.tamaya.TypeLiteral) + */ + public Map<TypeLiteral<?>, List<PropertyConverter<?>>> getPropertyConverters() { + Lock readLock = lock.readLock(); + try { + readLock.lock(); + return new HashMap<>(this.converters); + } finally { + readLock.unlock(); + } + } + + /** + * Get the list of all current registered converters for the given target type. + * If not converters are registered, they component tries to create and register a dynamic + * 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}. + * @param <T> the type class + * @return the ordered list of converters (may be empty for not convertible types). + * @see #createDefaultPropertyConverter(org.apache.tamaya.TypeLiteral) + */ + public <T> List<PropertyConverter<T>> getPropertyConverters(TypeLiteral<T> targetType) { + Lock readLock = lock.readLock(); + List<PropertyConverter<T>> converterList = new ArrayList<>(); + // direct mapped converters + try { + readLock.lock(); + addConvertersToList(List.class.cast(this.converters.get(targetType)), converterList); + addConvertersToList(List.class.cast(this.transitiveConverters.get(targetType)), converterList); + } finally { + readLock.unlock(); + } + // handling of java.lang wrapper classes + TypeLiteral<T> boxedType = mapBoxedType(targetType); + if (boxedType != null) { + try { + readLock.lock(); + addConvertersToList(List.class.cast(this.converters.get(boxedType)), converterList); + } finally { + readLock.unlock(); + } + } + if (converterList.isEmpty() && !TypeLiteral.of(String.class).equals(targetType)) { + // adding any converters created on the fly, e.g. for enum types. + PropertyConverter<T> defaultConverter = createDefaultPropertyConverter(targetType); + if (defaultConverter != null) { + register(targetType, defaultConverter); + try { + readLock.lock(); + addConvertersToList(List.class.cast(this.converters.get(targetType)), converterList); + } finally { + readLock.unlock(); + } + } + } + // check for parametrized types, ignoring param type + // direct mapped converters + if(targetType.getType()!=null) { + try { + readLock.lock(); + addConvertersToList(List.class.cast(this.converters.get( + TypeLiteral.of(targetType.getRawType()))), converterList); + } finally { + readLock.unlock(); + } + } + return converterList; + } + + private <T> void addConvertersToList(Collection<PropertyConverter<T>> converters, List<PropertyConverter<T>> converterList) { + if (converters != null) { + for(PropertyConverter<T> conv:converters) { + if(!converterList.contains(conv)) { + converterList.add(conv); + } + } + } + } + + /** + * Maps native types to the corresponding boxed types. + * + * @param targetType the native type. + * @param <T> the type + * @return the boxed type, or null. + */ + @SuppressWarnings("unchecked") + private <T> TypeLiteral<T> mapBoxedType(TypeLiteral<T> targetType) { + Type parameterType = targetType.getType(); + if (parameterType == int.class) { + return TypeLiteral.class.cast(TypeLiteral.of(Integer.class)); + } + if (parameterType == short.class) { + return TypeLiteral.class.cast(TypeLiteral.of(Short.class)); + } + if (parameterType == byte.class) { + return TypeLiteral.class.cast(TypeLiteral.of(Byte.class)); + } + if (parameterType == long.class) { + return TypeLiteral.class.cast(TypeLiteral.of(Long.class)); + } + if (parameterType == boolean.class) { + return TypeLiteral.class.cast(TypeLiteral.of(Boolean.class)); + } + if (parameterType == char.class) { + return TypeLiteral.class.cast(TypeLiteral.of(Character.class)); + } + if (parameterType == float.class) { + return TypeLiteral.class.cast(TypeLiteral.of(Float.class)); + } + if (parameterType == double.class) { + return TypeLiteral.class.cast(TypeLiteral.of(Double.class)); + } + if (parameterType == int[].class) { + return TypeLiteral.class.cast(TypeLiteral.of(Integer[].class)); + } + if (parameterType == short[].class) { + return TypeLiteral.class.cast(TypeLiteral.of(Short[].class)); + } + if (parameterType == byte[].class) { + return TypeLiteral.class.cast(TypeLiteral.of(Byte[].class)); + } + if (parameterType == long[].class) { + return TypeLiteral.class.cast(TypeLiteral.of(Long[].class)); + } + if (parameterType == boolean.class) { + return TypeLiteral.class.cast(TypeLiteral.of(Boolean.class)); + } + if (parameterType == char[].class) { + return TypeLiteral.class.cast(TypeLiteral.of(Character[].class)); + } + if (parameterType == float[].class) { + return TypeLiteral.class.cast(TypeLiteral.of(Float[].class)); + } + if (parameterType == double[].class) { + return TypeLiteral.class.cast(TypeLiteral.of(Double[].class)); + } + return null; + } + + /** + * Creates a dynamic PropertyConverter for the given target type. + * + * @param targetType the target type + * @param <T> the type class + * @return a new converters, or null. + */ + protected <T> PropertyConverter<T> createDefaultPropertyConverter(final TypeLiteral<T> targetType) { + if (Enum.class.isAssignableFrom(targetType.getRawType())) { + return new EnumConverter<>(targetType.getRawType()); + } + PropertyConverter<T> converter = null; + final Method factoryMethod = getFactoryMethod(targetType.getRawType(), "of", "valueOf", "instanceOf", "getInstance", "from", "fromString", "parse"); + if (factoryMethod != null) { + converter = new DefaultPropertyConverter<>(factoryMethod, targetType.getRawType()); + } + if (converter == null) { + final Constructor<T> constr; + try { + constr = targetType.getRawType().getDeclaredConstructor(String.class); + } catch (NoSuchMethodException e) { + LOG.log(Level.FINEST, "No matching constrctor for " + targetType, e); + return null; + } + converter = new PropertyConverter<T>() { + @Override + public T convert(String value, ConversionContext context) { + 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 PropertyConverterManager)) { + return false; + } + PropertyConverterManager that = (PropertyConverterManager) 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 PropertyConverter<T> { + + private final Method factoryMethod; + private final Class<T> targetType; + + DefaultPropertyConverter(Method factoryMethod, Class<T> targetType){ + this.factoryMethod = Objects.requireNonNull(factoryMethod); + this.targetType = Objects.requireNonNull(targetType); + } + + @Override + public T convert(String value, ConversionContext context) { + context.addSupportedFormats(getClass(), "<String -> "+factoryMethod.toGenericString()); + + if (!Modifier.isStatic(factoryMethod.getModifiers())) { + throw new ConfigException(factoryMethod.toGenericString() + + " is not a static method. Only static " + + "methods can be used as factory methods."); + } + try { + AccessController.doPrivileged(new PrivilegedAction<Object>() { + @Override + public Object run() { + factoryMethod.setAccessible(true); + return null; + } + }); + Object invoke = factoryMethod.invoke(null, value); + return targetType.cast(invoke); + } catch (Exception e) { + throw new ConfigException("Failed to decode '" + value + "'", e); + } + } + } +}
http://git-wip-us.apache.org/repos/asf/incubator-tamaya/blob/7917a9f3/code/spi-support/src/main/java/org/apache/tamaya/spisupport/PropertyFilterComparator.java ---------------------------------------------------------------------- diff --git a/code/spi-support/src/main/java/org/apache/tamaya/spisupport/PropertyFilterComparator.java b/code/spi-support/src/main/java/org/apache/tamaya/spisupport/PropertyFilterComparator.java new file mode 100644 index 0000000..20eef63 --- /dev/null +++ b/code/spi-support/src/main/java/org/apache/tamaya/spisupport/PropertyFilterComparator.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.spisupport; + +import org.apache.tamaya.spi.PropertyFilter; + +import javax.annotation.Priority; +import java.io.Serializable; +import java.util.Comparator; + +/** + * Comparator for PropertyFilters based on their priority annotations. + */ +public final class PropertyFilterComparator implements Comparator<PropertyFilter>, Serializable { + + private static final long serialVersionUID = 1L; + + private static final PropertyFilterComparator INSTANCE = new PropertyFilterComparator(); + + /** + * Get the shared instance of the comparator. + * @return the shared instance, never null. + */ + public static PropertyFilterComparator getInstance(){ + return INSTANCE; + } + + private PropertyFilterComparator(){} + + /** + * Compare 2 filters for ordering the filter chain. + * + * @param filter1 the first filter + * @param filter2 the second filter + * @return the comparison result + */ + private int comparePropertyFilters(PropertyFilter filter1, PropertyFilter filter2) { + Priority prio1 = filter1.getClass().getAnnotation(Priority.class); + Priority prio2 = filter2.getClass().getAnnotation(Priority.class); + int ord1 = prio1 != null ? prio1.value() : 0; + int ord2 = prio2 != null ? prio2.value() : 0; + + if (ord1 < ord2) { + return -1; + } else if (ord1 > ord2) { + return 1; + } else { + return filter1.getClass().getName().compareTo(filter2.getClass().getName()); + } + } + + @Override + public int compare(PropertyFilter filter1, PropertyFilter filter2) { + return comparePropertyFilters(filter1, filter2); + } +} http://git-wip-us.apache.org/repos/asf/incubator-tamaya/blob/7917a9f3/code/spi-support/src/main/java/org/apache/tamaya/spisupport/PropertyFiltering.java ---------------------------------------------------------------------- diff --git a/code/spi-support/src/main/java/org/apache/tamaya/spisupport/PropertyFiltering.java b/code/spi-support/src/main/java/org/apache/tamaya/spisupport/PropertyFiltering.java new file mode 100644 index 0000000..20f1aaf --- /dev/null +++ b/code/spi-support/src/main/java/org/apache/tamaya/spisupport/PropertyFiltering.java @@ -0,0 +1,124 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.tamaya.spisupport; + +import org.apache.tamaya.spi.ConfigurationContext; +import org.apache.tamaya.spi.FilterContext; +import org.apache.tamaya.spi.PropertyFilter; +import org.apache.tamaya.spi.PropertyValue; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Implementation of the Configuration API. This class uses the current {@link ConfigurationContext} to evaluate the + * chain of {@link org.apache.tamaya.spi.PropertySource} and {@link PropertyFilter} + * instance to evaluate the current Configuration. + */ +public final class PropertyFiltering{ + /** + * The logger. + */ + private static final Logger LOG = Logger.getLogger(PropertyFiltering.class.getName()); + /** + * The maximal number of filter cycles performed before aborting. + */ + private static final int MAX_FILTER_LOOPS = 10; + + /** + * Private singleton constructor. + */ + private PropertyFiltering(){} + + /** + * Filters a single value. + * @param value the raw value, not {@code null}. + * @param context the context + * @return the filtered value, including {@code null}. + */ + public static PropertyValue applyFilter(PropertyValue value, ConfigurationContext context) { + FilterContext filterContext = new FilterContext(value, context); + return filterValue(filterContext); + } + + /** + * Filters all properties. + * @param rawProperties the unfiltered properties, not {@code null}. + * @param context the context + * @return the filtered value, inclusing null. + */ + public static Map<String, PropertyValue> applyFilters(Map<String, PropertyValue> rawProperties, ConfigurationContext context) { + Map<String, PropertyValue> result = new HashMap<>(); + // Apply filters to values, prevent values filtered to null! + for (Map.Entry<String, PropertyValue> entry : rawProperties.entrySet()) { + FilterContext filterContext = new FilterContext(entry.getValue(), rawProperties, context); + PropertyValue filtered = filterValue(filterContext); + if(filtered!=null){ + result.put(filtered.getKey(), filtered); + } + } + return result; + } + + /** + * Basic filter logic. + * @param context the filter context, not {@code null}. + * @return the filtered value. + */ + private static PropertyValue filterValue(FilterContext context) { + PropertyValue inputValue = context.getProperty(); + PropertyValue filteredValue = inputValue; + + for (int i = 0; i < MAX_FILTER_LOOPS; i++) { + int changes = 0; + for (PropertyFilter filter : context.getContext().getPropertyFilters()) { + filteredValue = filter.filterProperty(inputValue, context); + 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; + } + +} http://git-wip-us.apache.org/repos/asf/incubator-tamaya/blob/7917a9f3/code/spi-support/src/main/java/org/apache/tamaya/spisupport/PropertySourceComparator.java ---------------------------------------------------------------------- diff --git a/code/spi-support/src/main/java/org/apache/tamaya/spisupport/PropertySourceComparator.java b/code/spi-support/src/main/java/org/apache/tamaya/spisupport/PropertySourceComparator.java new file mode 100644 index 0000000..d572335 --- /dev/null +++ b/code/spi-support/src/main/java/org/apache/tamaya/spisupport/PropertySourceComparator.java @@ -0,0 +1,122 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.tamaya.spisupport; + +import org.apache.tamaya.spi.PropertySource; +import org.apache.tamaya.spi.PropertyValue; + +import javax.annotation.Priority; +import java.io.Serializable; +import java.lang.reflect.Method; +import java.util.Comparator; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Comparator for ordering of PropertySources based on their ordinal method and class name. + */ +public class PropertySourceComparator implements Comparator<PropertySource>, Serializable { + /** serial version UID. */ + private static final long serialVersionUID = 1L; + + private static final Logger LOG = Logger.getLogger(PropertySourceComparator.class.getName()); + + private static final PropertySourceComparator INSTANCE = new PropertySourceComparator(); + + private String alternativeOrdinalKey; + + /** Singleton constructor. */ + private PropertySourceComparator(){} + + /** + * Get the shared instance of the comparator. + * @return the shared instance, never null. + */ + public static PropertySourceComparator getInstance(){ + return INSTANCE; + } + + + /** + * Order property source reversely, the most important comes first. + * + * @param source1 the first PropertySource + * @param source2 the second PropertySource + * @return the comparison result. + */ + private int comparePropertySources(PropertySource source1, PropertySource source2) { + if (getOrdinal(source1) < getOrdinal(source2)) { + return -1; + } else if (getOrdinal(source1) > getOrdinal(source2)) { + return 1; + } else { + return source1.getClass().getName().compareTo(source2.getClass().getName()); + } + } + + /** + * Evaluates an ordinal value from a {@link PropertySource}, Hereby the ordinal of type {@code int} + * is evaluated as follows: + * <ol> + * <li>It evaluates the {@code String} value for {@link PropertySource#TAMAYA_ORDINAL} and tries + * to convert it to an {@code int} value, using {@link Integer#parseInt(String)}.</li> + * <li>It tries to find and evaluate a method {@code int getOrdinal()}.</li> + * <li>It tries to find and evaluate a static field {@code int ORDINAL}.</li> + * <li>It tries to find and evaluate a class level {@link Priority} annotation.</li> + * <li>It uses the default priority ({@code 0}.</li> + * </ol> + * @param propertySource the property source, not {@code null}. + * @return the ordinal value to compare the property source. + */ + public static int getOrdinal(PropertySource propertySource) { + return getOrdinal(propertySource, null); + } + + public static int getOrdinal(PropertySource propertySource, String alternativeOrdinalKey) { + if(alternativeOrdinalKey!=null) { + PropertyValue ordinalValue = propertySource.get(alternativeOrdinalKey); + if (ordinalValue != null) { + try { + return Integer.parseInt(ordinalValue.getValue().trim()); + } catch (Exception e) { + LOG.finest("Failed to parse ordinal from " + alternativeOrdinalKey + + " in " + propertySource.getName() + ": " + ordinalValue.getValue()); + } + } + } + return propertySource.getOrdinal(); + } + + /** + * Overrides/adds the key to evaluate/override a property sources ordinal. + * @param ordinalKey sets the alternative ordinal key, if null default + * behaviour will be active. + * @return the instance for chaining. + */ + public PropertySourceComparator setOrdinalKey(String ordinalKey) { + this.alternativeOrdinalKey = ordinalKey; + return this; + } + + @Override + public int compare(PropertySource source1, PropertySource source2) { + return comparePropertySources(source1, source2); + } + +} http://git-wip-us.apache.org/repos/asf/incubator-tamaya/blob/7917a9f3/code/spi-support/src/main/java/org/apache/tamaya/spisupport/ReflectionUtil.java ---------------------------------------------------------------------- diff --git a/code/spi-support/src/main/java/org/apache/tamaya/spisupport/ReflectionUtil.java b/code/spi-support/src/main/java/org/apache/tamaya/spisupport/ReflectionUtil.java new file mode 100644 index 0000000..390c8d8 --- /dev/null +++ b/code/spi-support/src/main/java/org/apache/tamaya/spisupport/ReflectionUtil.java @@ -0,0 +1,42 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.tamaya.spisupport; + +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; + + +/** + * Small utility class used by other parts. + */ +public final class ReflectionUtil { + + private ReflectionUtil(){} + + public static ParameterizedType getParametrizedType(Class<?> clazz) { + Type[] genericTypes = clazz.getGenericInterfaces(); + for (Type type : genericTypes) { + if (type instanceof ParameterizedType) { + return (ParameterizedType) type; + } + + } + return null; + } +} http://git-wip-us.apache.org/repos/asf/incubator-tamaya/blob/7917a9f3/code/spi-support/src/main/java/org/apache/tamaya/spisupport/RegexPropertyFilter.java ---------------------------------------------------------------------- diff --git a/code/spi-support/src/main/java/org/apache/tamaya/spisupport/RegexPropertyFilter.java b/code/spi-support/src/main/java/org/apache/tamaya/spisupport/RegexPropertyFilter.java new file mode 100644 index 0000000..1f8cce9 --- /dev/null +++ b/code/spi-support/src/main/java/org/apache/tamaya/spisupport/RegexPropertyFilter.java @@ -0,0 +1,84 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.tamaya.spisupport; + +import org.apache.tamaya.spi.FilterContext; +import org.apache.tamaya.spi.PropertyFilter; +import org.apache.tamaya.spi.PropertyValue; + +import java.util.Arrays; +import java.util.List; + +/** + * Predicate filtering using a regex expression operating on the key. It allows either + * to define the target keys to be selected (includes), or to be excluded (excludes). + */ +public final class RegexPropertyFilter implements PropertyFilter{ + /** The expression used to include entries that match. */ + private List<String> includes; + /** The expression used to exclude entries that match. */ + private List<String> excludes; + + /** + * Sets the regex expression to be applied on the key to filter the corresponding entry + * if matching. + * @param expressions the regular expression for inclusion, not null. + */ + public void setIncludes(String... expressions){ + this.includes = Arrays.asList(expressions); + } + + /** + * Sets the regex expression to be applied on the key to remove the corresponding entries + * if matching. + * @param expressions the regular expressions for exclusion, not null. + */ + public void setExcludes(String... expressions){ + this.excludes= Arrays.asList(expressions); + } + + @Override + public PropertyValue filterProperty(PropertyValue valueToBeFiltered, FilterContext context) { + if(includes!=null){ + for(String expression:includes){ + if(context.getProperty().getKey().matches(expression)){ + return valueToBeFiltered; + } + } + return null; + } + if(excludes!=null){ + for(String expression:excludes){ + if(context.getProperty().getKey().matches(expression)){ + return null; + } + } + } + return valueToBeFiltered; + } + + @Override + public String toString() { + return "RegexPropertyFilter{" + + "includes='" + includes + '\'' + + "excludes='" + excludes + '\'' + + '}'; + } + +} http://git-wip-us.apache.org/repos/asf/incubator-tamaya/blob/7917a9f3/code/spi-support/src/main/java/org/apache/tamaya/spisupport/propertysource/BasePropertySource.java ---------------------------------------------------------------------- diff --git a/code/spi-support/src/main/java/org/apache/tamaya/spisupport/propertysource/BasePropertySource.java b/code/spi-support/src/main/java/org/apache/tamaya/spisupport/propertysource/BasePropertySource.java new file mode 100644 index 0000000..54481ac --- /dev/null +++ b/code/spi-support/src/main/java/org/apache/tamaya/spisupport/propertysource/BasePropertySource.java @@ -0,0 +1,173 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.tamaya.spisupport.propertysource; + +import org.apache.tamaya.spi.PropertySource; +import org.apache.tamaya.spi.PropertyValue; +import org.apache.tamaya.spi.PropertyValueBuilder; + +import java.util.Map; +import java.util.Objects; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Abstract {@link org.apache.tamaya.spi.PropertySource} that allows to set a default ordinal that will be used, if no + * ordinal is provided with the config. + */ +public abstract class BasePropertySource implements PropertySource{ + /** default ordinal that will be used, if no ordinal is provided with the config. */ + private int defaultOrdinal; + /** Used if the ordinal has been set explicitly. */ + private volatile Integer ordinal; + /** The name of the property source. */ + private String name; + + /** + * Constructor. + * @param name the (unique) property source name, not {@code null}. + */ + protected BasePropertySource(String name){ + this.name = Objects.requireNonNull(name); + this.defaultOrdinal = 0; + } + + /** + * Constructor. + * @param defaultOrdinal default ordinal that will be used, if no ordinal is provided with the config. + */ + protected BasePropertySource(int defaultOrdinal){ + this.name = getClass().getSimpleName(); + this.defaultOrdinal = defaultOrdinal; + } + + /** + * Constructor. + * @param name the (unique) property source name, not {@code null}. + * @param defaultOrdinal default ordinal that will be used, if no ordinal is provided with the config. + */ + protected BasePropertySource(String name, int defaultOrdinal){ + this.name = Objects.requireNonNull(name); + this.defaultOrdinal = defaultOrdinal; + } + + + /** + * Constructor, using a default ordinal of 0. + */ + protected BasePropertySource(){ + this(0); + } + + @Override + public String getName() { + return name; + } + + /** + * Sets the property source's (unique) name. + * @param name the name, not {@code null}. + */ + public void setName(String name){ + this.name = Objects.requireNonNull(name); + } + + /** + * Allows to set the ordinal of this property source explcitly. This will override any evaluated + * ordinal, or default ordinal. To reset an explcit ordinal call {@code setOrdinal(null);}. + * @param ordinal the explicit ordinal, or {@code null}. + */ + public void setOrdinal(Integer ordinal){ + this.ordinal = ordinal; + } + + /** + * Allows to set the ordinal of this property source explcitly. This will override any evaluated + * ordinal, or default ordinal. To reset an explcit ordinal call {@code setOrdinal(null);}. + * @param defaultOrdinal the default ordinal, or {@code null}. + */ + public void setDefaultOrdinal(Integer defaultOrdinal){ + this.defaultOrdinal = defaultOrdinal; + } + + public int getOrdinal() { + Integer ordinal = this.ordinal; + if(ordinal!=null){ + Logger.getLogger(getClass().getName()).finest( + "Using explicit ordinal '"+ordinal+"' for property source: " + getName()); + return ordinal; + } + PropertyValue configuredOrdinal = get(TAMAYA_ORDINAL); + if(configuredOrdinal!=null){ + try { + return Integer.parseInt(configuredOrdinal.getValue()); + } catch (Exception e) { + Logger.getLogger(getClass().getName()).log(Level.WARNING, + "Configured ordinal is not an int number: " + configuredOrdinal, e); + } + } + return getDefaultOrdinal(); + } + + /** + * Returns the default ordinal used, when no ordinal is set, or the ordinal was not parseable to an int value. + * @return the default ordinal used, by default 0. + */ + public int getDefaultOrdinal(){ + return defaultOrdinal; + } + + @Override + public PropertyValue get(String key) { + Map<String,PropertyValue> properties = getProperties(); + PropertyValue val = properties.get(key); + if(val==null){ + return null; + } + return val; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + BasePropertySource that = (BasePropertySource) o; + + return name.equals(that.name); + } + + @Override + public int hashCode() { + return name.hashCode(); + } + + @Override + public String toString() { + return getClass().getSimpleName() + "{" + + toStringValues() + + '}'; + } + + protected String toStringValues() { + return " defaultOrdinal=" + defaultOrdinal + '\n' + + " ordinal=" + ordinal + '\n' + + " name='" + name + '\'' + '\n'; + } +} http://git-wip-us.apache.org/repos/asf/incubator-tamaya/blob/7917a9f3/code/spi-support/src/main/java/org/apache/tamaya/spisupport/propertysource/BuildablePropertySource.java ---------------------------------------------------------------------- diff --git a/code/spi-support/src/main/java/org/apache/tamaya/spisupport/propertysource/BuildablePropertySource.java b/code/spi-support/src/main/java/org/apache/tamaya/spisupport/propertysource/BuildablePropertySource.java new file mode 100644 index 0000000..fbea188 --- /dev/null +++ b/code/spi-support/src/main/java/org/apache/tamaya/spisupport/propertysource/BuildablePropertySource.java @@ -0,0 +1,231 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.tamaya.spisupport.propertysource; + +import org.apache.tamaya.spi.PropertySource; +import org.apache.tamaya.spi.PropertyValue; + +import java.util.*; + +/** + * A Buildable property source. + */ +public class BuildablePropertySource implements PropertySource{ + + private int ordinal; + private String name = "PropertySource-"+UUID.randomUUID().toString(); + private Map<String,PropertyValue> properties = new HashMap<>(); + + @Override + public int getOrdinal() { + return ordinal; + } + + @Override + public String getName() { + return name; + } + + @Override + public PropertyValue get(String key) { + return properties.get(key); + } + + @Override + public Map<String, PropertyValue> getProperties() { + return Collections.unmodifiableMap(properties); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + BuildablePropertySource that = (BuildablePropertySource) o; + + return name.equals(that.name); + } + + @Override + public boolean isScannable() { + return true; + } + + @Override + public int hashCode() { + return name.hashCode(); + } + + @Override + public String toString() { + return "BuildablePropertySource{" + + "ordinal=" + ordinal + + ", name='" + name + '\'' + + ", properties=" + properties + + '}'; + } + + /** + * Builder builder. + * + * @return the builder + */ + public static Builder builder() { + return new Builder(); + } + + + /** + * The type Builder. + */ + public static final class Builder { + private int ordinal; + private String source = "<on-the-fly-build>"; + private String name = "PropertySource-"+ UUID.randomUUID().toString(); + private Map<String,PropertyValue> properties = new HashMap<>(); + + private Builder() { + } + + /** + * With ordinal builder. + * + * @param ordinal the ordinal + * @return the builder + */ + public Builder withOrdinal(int ordinal) { + this.ordinal = ordinal; + return this; + } + + /** + * With source builder. + * + * @param source the source + * @return the builder + */ + public Builder withSource(String source) { + this.source = Objects.requireNonNull(source); + return this; + } + + /** + * With name builder. + * + * @param name the name + * @return the builder + */ + public Builder withName(String name) { + this.name = Objects.requireNonNull(name); + return this; + } + + /** + * With simple property builder. + * + * @param key the key + * @param value the value + * @return the builder + */ + public Builder withSimpleProperty(String key, String value) { + return withProperties(PropertyValue.of(key, value, this.source)); + } + + /** + * With simple property builder. + * + * @param key the key + * @param value the value + * @param source the source + * @return the builder + */ + public Builder withSimpleProperty(String key, String value, String source) { + return withProperties(PropertyValue.of(key, value, source)); + } + + /** + * With properties builder. + * + * @param values the values + * @return the builder + */ + public Builder withProperties(PropertyValue... values) { + for(PropertyValue val:values){ + this.properties.put(val.getKey(), val); + } + return this; + } + + /** + * With properties builder. + * + * @param properties the properties + * @return the builder + */ + public Builder withProperties(Map<String, PropertyValue> properties) { + this.properties = Objects.requireNonNull(properties); + return this; + } + + /** + * With properties builder. + * + * @param properties the properties + * @param source the source + * @return the builder + */ + public Builder withProperties(Map<String, String> properties, String source) { + this.properties.putAll(PropertyValue.map(properties, source)); + return this; + } + + /** + * With simple properties builder. + * + * @param properties the properties + * @return the builder + */ + public Builder withSimpleProperties(Map<String, String> properties) { + this.properties.putAll(PropertyValue.map(properties, this.source)); + return this; + } + + /** + * But builder. + * + * @return the builder + */ + public Builder but() { + return builder().withOrdinal(ordinal).withName(name).withProperties(properties); + } + + /** + * Build buildable property source. + * + * @return the buildable property source + */ + public BuildablePropertySource build() { + BuildablePropertySource buildablePropertySource = new BuildablePropertySource(); + buildablePropertySource.name = this.name; + buildablePropertySource.properties = this.properties; + buildablePropertySource.ordinal = this.ordinal; + return buildablePropertySource; + } + } +} http://git-wip-us.apache.org/repos/asf/incubator-tamaya/blob/7917a9f3/code/spi-support/src/main/java/org/apache/tamaya/spisupport/propertysource/BuildablePropertySourceProvider.java ---------------------------------------------------------------------- diff --git a/code/spi-support/src/main/java/org/apache/tamaya/spisupport/propertysource/BuildablePropertySourceProvider.java b/code/spi-support/src/main/java/org/apache/tamaya/spisupport/propertysource/BuildablePropertySourceProvider.java new file mode 100644 index 0000000..1becb50 --- /dev/null +++ b/code/spi-support/src/main/java/org/apache/tamaya/spisupport/propertysource/BuildablePropertySourceProvider.java @@ -0,0 +1,114 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.tamaya.spisupport.propertysource; + +import org.apache.tamaya.spi.PropertySource; +import org.apache.tamaya.spi.PropertySourceProvider; + +import java.util.*; + +/** + * A Buildable property source. + */ +public class BuildablePropertySourceProvider implements PropertySourceProvider{ + + private List<PropertySource> sources = new ArrayList<>(); + + @Override + public Collection<PropertySource> getPropertySources() { + return Collections.unmodifiableCollection(sources); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + BuildablePropertySourceProvider that = (BuildablePropertySourceProvider) o; + + return sources.equals(that.sources); + } + + @Override + public int hashCode() { + return sources.hashCode(); + } + + @Override + public String toString() { + return "BuildablePropertySourceProvider{" + + "sources=" + sources + + '}'; + } + + /** + * Builder builder. + * + * @return the builder + */ + public static Builder builder() { + return new Builder(); + } + + + + + /** + * The type Builder. + */ + public static final class Builder { + private List<PropertySource> sources = new ArrayList<>(); + + private Builder() { + } + + /** + * With propertySources. + * + * @param propertySources the propertySources + * @return the builder + */ + public Builder withPropertySourcs(PropertySource... propertySources) { + this.sources.addAll(Arrays.asList(propertySources)); + return this; + } + + /** + * With property sources builder. + * + * @param sources the property sources + * @return the builder + */ + public Builder withPropertySourcs(Collection<PropertySource> sources) { + this.sources.addAll(sources); + return this; + } + + /** + * Build buildable property source. + * + * @return the buildable property source + */ + public BuildablePropertySourceProvider build() { + BuildablePropertySourceProvider buildablePropertySource = new BuildablePropertySourceProvider(); + buildablePropertySource.sources.addAll(this.sources); + return buildablePropertySource; + } + } +} http://git-wip-us.apache.org/repos/asf/incubator-tamaya/blob/7917a9f3/code/spi-support/src/main/java/org/apache/tamaya/spisupport/propertysource/CLIPropertySource.java ---------------------------------------------------------------------- diff --git a/code/spi-support/src/main/java/org/apache/tamaya/spisupport/propertysource/CLIPropertySource.java b/code/spi-support/src/main/java/org/apache/tamaya/spisupport/propertysource/CLIPropertySource.java new file mode 100644 index 0000000..a83722f --- /dev/null +++ b/code/spi-support/src/main/java/org/apache/tamaya/spisupport/propertysource/CLIPropertySource.java @@ -0,0 +1,137 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.tamaya.spisupport.propertysource; + +import org.apache.tamaya.spi.PropertyValue; + +import java.util.*; + +/** + * PropertySource that allows to add the programs main arguments as configuration entries. Unix syntax using '--' and + * '-' params is supported. + */ +public class CLIPropertySource extends BasePropertySource { + + /** The original main arguments. */ + private static String[] args = new String[0]; + + /** The map of parsed main arguments. */ + private static Map<String,PropertyValue> mainArgs; + + /** Initializes the initial state. */ + static{ + initMainArgs(args); + } + + /** + * Creates a new instance. + */ + public CLIPropertySource(){ + this((String[])null); + } + + /** + * Creates a new instance, allows optionally to pass the main arguments. + * @param args the args, or null. + */ + public CLIPropertySource(String... args){ + super("CLI"); + if(args!=null){ + initMainArgs(args); + } + } + + /** + * Creates a new instance, allows optionally to pass the main arguments. + * @param args the args, or null. + * @param ordinal the ordinal to be applied. + */ + public CLIPropertySource(int ordinal, String... args){ + if(args!=null){ + initMainArgs(args); + } + setOrdinal(ordinal); + } + + + + /** + * Configure the main arguments, hereby parsing and mapping the main arguments into + * configuration propertiesi as key-value pairs. + * @param args the main arguments, not null. + */ + public static void initMainArgs(String... args){ + CLIPropertySource.args = Objects.requireNonNull(args); + // TODO is there a way to figure out the args? + String argsProp = System.getProperty("main.args"); + if(argsProp!=null){ + CLIPropertySource.args = argsProp.split("\\s"); + } + Map<String,String> result = null; + if(CLIPropertySource.args==null){ + result = Collections.emptyMap(); + }else{ + result = new HashMap<>(); + String prefix = System.getProperty("main.args.prefix"); + if(prefix==null){ + prefix=""; + } + String key = null; + for(String arg:CLIPropertySource.args){ + if(arg.startsWith("--")){ + arg = arg.substring(2); + int index = arg.indexOf("="); + if(index>0){ + key = arg.substring(0,index).trim(); + result.put(prefix+key, arg.substring(index+1).trim()); + key = null; + }else{ + result.put(prefix+arg, arg); + } + }else if(arg.startsWith("-")){ + key = arg.substring(1); + }else{ + if(key!=null){ + result.put(prefix+key, arg); + key = null; + }else{ + result.put(prefix+arg, arg); + } + } + } + } + Map<String,PropertyValue> finalProps = new HashMap<>(); + for(Map.Entry<String,String> en:result.entrySet()) { + finalProps.put(en.getKey(), + PropertyValue.of(en.getKey(), en.getValue(), "main-args")); + } + CLIPropertySource.mainArgs = Collections.unmodifiableMap(finalProps); + } + + @Override + public Map<String, PropertyValue> getProperties() { + return Collections.unmodifiableMap(mainArgs); + } + + @Override + protected String toStringValues() { + return super.toStringValues() + + " args=" + Arrays.toString(args) + '\n'; + } +} http://git-wip-us.apache.org/repos/asf/incubator-tamaya/blob/7917a9f3/code/spi-support/src/main/java/org/apache/tamaya/spisupport/propertysource/EnvironmentPropertySource.java ---------------------------------------------------------------------- diff --git a/code/spi-support/src/main/java/org/apache/tamaya/spisupport/propertysource/EnvironmentPropertySource.java b/code/spi-support/src/main/java/org/apache/tamaya/spisupport/propertysource/EnvironmentPropertySource.java new file mode 100644 index 0000000..bb9f6b8 --- /dev/null +++ b/code/spi-support/src/main/java/org/apache/tamaya/spisupport/propertysource/EnvironmentPropertySource.java @@ -0,0 +1,287 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.tamaya.spisupport.propertysource; + +import org.apache.tamaya.spi.PropertyValue; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +/** + * <p>{@link PropertySource} to access environment variables via Tamaya + * which are set via {@code export VARIABLE=value} on UNIX systems or + * {@code set VARIABLE=value} on Windows systems.</p> + * + * <p>Using the {@linkplain EnvironmentPropertySource} without any + * additional configuration gives access to all existing environment + * variables available to the Java process Tamaya is running in.</p> + * + * <h1>Simple usage example</h1> + * + * <pre> + * $ export OPS_MODE=production + * $ export COLOR=false + * $ java -jar application.jar + * </pre> + * + * <p>To access {@code OPS_MODE} and {@code COLOR} with the following code + * fragment could be used:</p> + * + * <pre> + * PropertySource ps = new EnvironmentPropertySource(); + * PropertyValue opsMode = ps.get("OPS_MODE"); + * PropertyValue color = ps.get("COLOR"); + * </pre> + * + * <h1>Application specific environmet variables with prefix</h1> + * + * <p>Given the case where to instances of the same application are running on + * a single machine but need different values for the environment variable + * {@code CUSTOMER}. The {@linkplain EnvironmentPropertySource} allows you + * to prefix the environment variable with an application specific prefix + * and to access it by the non-prefixed variable name.</p> + * + * <pre> + * $ export CUSTOMER=none + * $ export a81.CUSTOMER=moon + * $ export b78.CUSTOMER=luna + * </pre> + * + * <p>Given an environment with these tree variables the application running + * for the customer called Moon could be started with the following command:</p> + * + * <pre> + * $ java -Dtamaya.envprops.prefix=a81 -jar application.jar + * </pre> + * + * <p>The application specific value can now be accessed from the code of the + * application like this:</p> + * + * <pre> + * PropertySource ps = new EnvironmentPropertySource(); + * PropertyValue pv = ps.get("CUSTOMER"); + * System.out.println(pv.getValue()); + * </pre> + * + * <p>The output of application would be {@code moon}.</p> + * + * <h1>Disabling the access to environment variables</h1> + * + * <p>The access to environment variables could be simply + * disabled by the setting the systemproperty {@code tamaya.envprops.disable} + * or {@code tamaya.defaults.disable} to {@code true}.</p> + */ +public class EnvironmentPropertySource extends BasePropertySource { + private static final String TAMAYA_ENVPROPS_PREFIX = "tamaya.envprops.prefix"; + private static final String TAMAYA_ENVPROPS_DISABLE = "tamaya.envprops.disable"; + private static final String TAMAYA_DEFAULT_DISABLE = "tamaya.defaults.disable"; + + /** + * Default ordinal for {@link org.apache.tamaya.spisupport.propertysource.EnvironmentPropertySource} + */ + public static final int DEFAULT_ORDINAL = 300; + + /** + * Prefix that allows environment 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; + + private SystemPropertiesProvider propertiesProvider = new SystemPropertiesProvider(); + + /** + * 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 EnvironmentPropertySource(){ + initFromSystemProperties(); + } + + /** + * 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.envprops.prefix"); + if(value==null){ + prefix = System.getenv("tamaya.envprops.prefix"); + } + value = System.getProperty("tamaya.envprops.disable"); + if(value==null){ + value = System.getenv("tamaya.envprops.disable"); + } + if(value==null){ + value = System.getProperty("tamaya.defaults.disable"); + } + if(value==null){ + value = System.getenv("tamaya.defaults.disable"); + } + if(value!=null && !value.isEmpty()) { + this.disabled = Boolean.parseBoolean(value); + } + } + + /** + * Creates a new instance using a fixed ordinal value. + * @param ordinal the ordinal number. + */ + public EnvironmentPropertySource(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 EnvironmentPropertySource(String prefix, int ordinal){ + super("environment-properties"); + this.prefix = prefix; + setOrdinal(ordinal); + } + + /** + * Creates a new instance. + * @param prefix the prefix to be used, or null. + */ + public EnvironmentPropertySource(String prefix){ + this.prefix = prefix; + } + + @Override + public int getDefaultOrdinal() { + return DEFAULT_ORDINAL; + } + + @Override + public String getName() { + if (isDisabled()) { + return "environment-properties(disabled)"; + } + return "environment-properties"; + } + + @Override + public PropertyValue get(String key) { + if (isDisabled()) { + return null; + } + + String effectiveKey = hasPrefix() ? getPrefix() + "." + key + : key; + + String value = getPropertiesProvider().getenv(effectiveKey); + + return PropertyValue.of(key, value, getName()); + } + + private boolean hasPrefix() { + return null != prefix; + } + + @Override + public Map<String, PropertyValue> getProperties() { + if(disabled){ + return Collections.emptyMap(); + } + String prefix = this.prefix; + if(prefix==null) { + Map<String, PropertyValue> entries = new HashMap<>(System.getenv().size()); + for (Map.Entry<String, String> entry : System.getenv().entrySet()) { + entries.put(entry.getKey(), PropertyValue.of(entry.getKey(), entry.getValue(), getName())); + } + return entries; + }else{ + Map<String, PropertyValue> entries = new HashMap<>(System.getenv().size()); + for (Map.Entry<String, String> entry : System.getenv().entrySet()) { + entries.put(prefix + entry.getKey(), PropertyValue.of(prefix + entry.getKey(), entry.getValue(), getName())); + } + return entries; + } + } + + + @Override + protected String toStringValues() { + return super.toStringValues() + + " prefix=" + prefix + '\n' + + " disabled=" + disabled + '\n'; + } + + void setPropertiesProvider(SystemPropertiesProvider spp) { + propertiesProvider = spp; + initFromSystemProperties(); + } + + SystemPropertiesProvider getPropertiesProvider() { + return propertiesProvider; + } + + public String getPrefix() { + return prefix; + } + + public boolean isDisabled() { + return disabled; + } + + /** + * <p>Provides access to the system properties used to configure + * {@linkplain EnvironmentPropertySource}.</p> + * + * <p>This implementation delegates all property lookups + * to {@linkplain System#getProperty(String)}.</p> + */ + static class SystemPropertiesProvider { + String getEnvPropsPrefix() { + return System.getenv(TAMAYA_ENVPROPS_PREFIX); + } + + String getEnvPropsDisable() { + return System.getenv(TAMAYA_ENVPROPS_DISABLE); + } + + String getDefaultsDisable() { + return System.getenv(TAMAYA_DEFAULT_DISABLE); + } + + String getenv(String name) { + return System.getenv(name); + } + + Map<String, String> getenv() { + return System.getenv(); + } + } +} http://git-wip-us.apache.org/repos/asf/incubator-tamaya/blob/7917a9f3/code/spi-support/src/main/java/org/apache/tamaya/spisupport/propertysource/JavaConfigurationPropertySource.java ---------------------------------------------------------------------- diff --git a/code/spi-support/src/main/java/org/apache/tamaya/spisupport/propertysource/JavaConfigurationPropertySource.java b/code/spi-support/src/main/java/org/apache/tamaya/spisupport/propertysource/JavaConfigurationPropertySource.java new file mode 100644 index 0000000..92f520e --- /dev/null +++ b/code/spi-support/src/main/java/org/apache/tamaya/spisupport/propertysource/JavaConfigurationPropertySource.java @@ -0,0 +1,134 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.tamaya.spisupport.propertysource; + +import org.apache.tamaya.ConfigException; +import org.apache.tamaya.spi.PropertySource; +import org.apache.tamaya.spi.PropertyValue; +import org.apache.tamaya.spi.ServiceContextManager; +import org.apache.tamaya.spisupport.PropertySourceComparator; + +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 JavaConfigurationPropertySource extends BasePropertySource { + /** + * 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/javaconfiguration.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/javaconfiguration.xml"; + + private static final int DEFAULT_ORDINAL = 900; + + private boolean enabled = evaluateEnabled(); + + public JavaConfigurationPropertySource(){ + super("resource:META-INF/javaconfiguration.*", 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<PropertySource> getPropertySources() { + List<PropertySource> propertySources = new ArrayList<>(); + propertySources.addAll(loadPropertySourcesByName(DEFAULT_SIMPLE_PROPERTIES_FILE_NAME)); + propertySources.addAll(loadPropertySourcesByName(DEFAULT_XML_PROPERTIES_FILE_NAME)); + Collections.sort(propertySources, PropertySourceComparator.getInstance()); + return propertySources; + } + + private Collection<? extends PropertySource> loadPropertySourcesByName(String filename) { + List<PropertySource> 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 ConfigException(msg, e); + } + + while (propertyLocations.hasMoreElements()) { + URL currentUrl = propertyLocations.nextElement(); + SimplePropertySource sps = new SimplePropertySource(currentUrl); + + propertySources.add(sps); + } + + return propertySources; + } + + public boolean isEnabled() { + return enabled; + } + + public void setEnabled(boolean enabled){ + this.enabled = enabled; + } + + + @Override + public Map<String, PropertyValue> getProperties() { + if (!isEnabled()) { + return Collections.emptyMap(); + } + Map<String,PropertyValue> result = new HashMap<>(); + for(PropertySource ps:getPropertySources()){ + result.putAll(ps.getProperties()); + } + return result; + } + + @Override + public String toString() { + return "JavaConfigurationPropertySource{" + + "enabled=" + enabled + + '}'; + } +} http://git-wip-us.apache.org/repos/asf/incubator-tamaya/blob/7917a9f3/code/spi-support/src/main/java/org/apache/tamaya/spisupport/propertysource/MapPropertySource.java ---------------------------------------------------------------------- diff --git a/code/spi-support/src/main/java/org/apache/tamaya/spisupport/propertysource/MapPropertySource.java b/code/spi-support/src/main/java/org/apache/tamaya/spisupport/propertysource/MapPropertySource.java new file mode 100644 index 0000000..0cabb35 --- /dev/null +++ b/code/spi-support/src/main/java/org/apache/tamaya/spisupport/propertysource/MapPropertySource.java @@ -0,0 +1,102 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.tamaya.spisupport.propertysource; + +import org.apache.tamaya.spi.PropertyValue; + +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 MapPropertySource extends BasePropertySource { + + /** + * The current properties. + */ + private final Map<String, PropertyValue> 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 MapPropertySource(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 MapPropertySource(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(), + PropertyValue.of(en.getKey(), en.getValue(), name)); + } + } else { + for (Map.Entry<String, String> en : props.entrySet()) { + this.props.put(prefix + en.getKey(), + PropertyValue.of(prefix + en.getKey(), en.getValue(), name)); + } + } + } + + /** + * 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 MapPropertySource(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, PropertyValue> getProperties() { + return Collections.unmodifiableMap(this.props); + } + +} http://git-wip-us.apache.org/repos/asf/incubator-tamaya/blob/7917a9f3/code/spi-support/src/main/java/org/apache/tamaya/spisupport/propertysource/PropertiesResourcePropertySource.java ---------------------------------------------------------------------- diff --git a/code/spi-support/src/main/java/org/apache/tamaya/spisupport/propertysource/PropertiesResourcePropertySource.java b/code/spi-support/src/main/java/org/apache/tamaya/spisupport/propertysource/PropertiesResourcePropertySource.java new file mode 100644 index 0000000..27b6e4b --- /dev/null +++ b/code/spi-support/src/main/java/org/apache/tamaya/spisupport/propertysource/PropertiesResourcePropertySource.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.spisupport.propertysource; + +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 PropertiesResourcePropertySource extends MapPropertySource { + /** The logger used. */ + private static final Logger LOGGER = Logger.getLogger(PropertiesResourcePropertySource.class.getName()); + + /** + * Creates a new instance. + * @param url the resource URL, not null. + */ + public PropertiesResourcePropertySource(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 PropertiesResourcePropertySource(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 PropertiesResourcePropertySource(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 PropertiesResourcePropertySource(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 = PropertiesResourcePropertySource.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; + } + +}