blob: 0654712b18e286b66ccf27dbd782f05906fb7e9a [file] [log] [blame]
/*
* 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.ServiceContext;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Type;
import java.net.URI;
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 org.apache.tamaya.spi.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());
}
}
};
private final ServiceContext serviceContext;
/**
* Creates a new instance.
* @param serviceContext the Service context, not null.
*/
public PropertyConverterManager(ServiceContext serviceContext) {
this(serviceContext, false);
}
/**
* Creates a new instance.
* @param serviceContext the Service context, not null.
* @param init if true, the converters are loaded eagerly.
*/
public PropertyConverterManager(ServiceContext serviceContext, boolean init) {
this.serviceContext = Objects.requireNonNull(serviceContext);
if (init) {
initConverters();
}
}
/**
* Registers the default converters provided out of the box.
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
protected void initConverters() {
for (PropertyConverter conv : serviceContext.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 converter for the given type is registered or a default one can be created.
*/
public boolean isTargetTypeSupported(TypeLiteral<?> targetType) {
return converters.containsKey(targetType) || transitiveConverters.containsKey(targetType) || createDefaultPropertyConverter(targetType) != null;
}
/**
* Get a mapProperties 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 mapProperties 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 createList of all current registered converters for the given target type.
*
* <p>
* If no converters are registered, the component tries to createObject and register 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 createValue.</li>
* <li>The target type of each explicitly registered converter can also be transitively mapped to
* 1) all directly implemented interfaces, 2) all its superclasses (except Object), 3) all the interfaces
* implemented by its superclasses. This group of transitive converters is returned similarly in the
* order already mentioned, whereas here a priority based on 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 (case sensitive mapping).</li>
* </ul>
* <p>
* The above createList of directly registered mappings are always 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 createList 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() && targetType.getRawType().isArray() &&
!targetType.getRawType().getComponentType().isPrimitive()){
try {
readLock.lock();
addConvertersToList(List.class.cast(this.converters.get(TypeLiteral.of(Object[].class))), 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 {@link 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", "createValue", "instanceOf", "getInstance", "from", "fromString", "parse");
if (factoryMethod != null) {
converter = new DefaultPropertyConverter<>(factoryMethod, targetType.getRawType());
}
if (converter == null) {
Constructor<T> constr;
try {
constr = targetType.getRawType().getDeclaredConstructor(String.class);
} catch (NoSuchMethodException e) {
try {
constr = targetType.getRawType().getDeclaredConstructor(CharSequence.class);
} catch (NoSuchMethodException e2) {
LOG.log(Level.FINEST, "No matching constructor found for " + targetType);
return null;
}
}
Constructor<T> finalConstr = constr;
converter = new PropertyConverter<T>() {
@Override
public T convert(String value, ConversionContext context) {
AccessController.doPrivileged(new PrivilegedAction<Object>() {
@Override
public Object run() {
AccessController.doPrivileged((PrivilegedAction<Object>) () -> {
finalConstr.setAccessible(true);
return null;
});
return null;
}
});
try {
return finalConstr.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 createObject 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) {
// continue, try also with CharSequence
}
try {
m = type.getDeclaredMethod(name, CharSequence.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 converter implementation performing several lookups for String conversion
* options.
* @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);
}
}
}
}