package de.zalando.sprocwrapper.globalobjecttransformer; import java.util.HashMap; import java.util.Map; import java.util.Set; import org.reflections.Reflections; import org.reflections.scanners.SubTypesScanner; import org.reflections.scanners.TypeAnnotationsScanner; import org.reflections.util.ClasspathHelper; import org.reflections.util.ConfigurationBuilder; import org.reflections.util.FilterBuilder; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.common.base.Preconditions; import com.google.common.base.Predicate; import com.google.common.collect.ImmutableMap; import de.zalando.sprocwrapper.globalobjecttransformer.annotation.GlobalObjectMapper; import de.zalando.typemapper.core.fieldMapper.ObjectMapper; public class GlobalObjectTransformerLoader { private static final Logger LOG = LoggerFactory.getLogger(GlobalObjectTransformerLoader.class); private static final String DEFAULT_NAMESPACE = "de.zalando"; private static final String GLOBAL_OBJECT_TRANSFORMER_SEARCH_NAMESPACE = "global.object.transformer.search.namespace"; // you need to set the namespace to a valid value like: org.doodlejump private static volatile String namespaceToScan = null; private static volatile ImmutableMap<Class<?>, ObjectMapper<?>> register; public static <T> ObjectMapper<T> getObjectMapperForClass(final Class<T> genericType) throws InstantiationException, IllegalAccessException { Preconditions.checkNotNull(genericType, "genericType"); // performance improvement. Volatile field is read only once in the commons scenario. ImmutableMap<Class<?>, ObjectMapper<?>> localRegister = register; if (localRegister == null) { synchronized (GlobalObjectTransformerLoader.class) { localRegister = register; if (localRegister == null) { register = localRegister = buildMappers(); } } } return (ObjectMapper<T>) localRegister.get(genericType); } private static ImmutableMap<Class<?>, ObjectMapper<?>> buildMappers() throws InstantiationException, IllegalAccessException { final Map<Class<?>, ObjectMapper<?>> mappers = new HashMap<>(); for (final Class<?> foundGlobalObjectTransformer : findObjectMappers()) { if (ObjectMapper.class.isAssignableFrom(foundGlobalObjectTransformer)) { final ObjectMapper<?> mapper = (ObjectMapper<?>) foundGlobalObjectTransformer.newInstance(); final Class<?> valueTransformerReturnType = mapper.getType(); final ObjectMapper<?> previousMapper = mappers.put(valueTransformerReturnType, mapper); if (previousMapper == null) { LOG.debug("Global Object Mapper [{}] for type [{}] registered. ", foundGlobalObjectTransformer.getSimpleName(), valueTransformerReturnType.getSimpleName()); } else { LOG.error("Found multiple global object mappers for type [{}]. [{}] replaced by [{}]", new Object[] { valueTransformerReturnType, previousMapper.getClass().getSimpleName(), valueTransformerReturnType.getSimpleName() }); } } else { LOG.error("Object mapper [{}] should extend [{}]", foundGlobalObjectTransformer.getSimpleName(), ObjectMapper.class.getSimpleName()); } } return ImmutableMap.copyOf(mappers); } private static Set<Class<?>> findObjectMappers() { final Predicate<String> filter = new Predicate<String>() { @Override public boolean apply(final String input) { return GlobalObjectMapper.class.getCanonicalName().equals(input); } }; final String myNameSpaceToScan = getNameSpace(); final Reflections reflections = new Reflections(new ConfigurationBuilder().filterInputsBy( new FilterBuilder.Include(FilterBuilder.prefix(myNameSpaceToScan))).setUrls( ClasspathHelper.forPackage(myNameSpaceToScan)).setScanners(new TypeAnnotationsScanner() .filterResultsBy(filter), new SubTypesScanner())); final Set<Class<?>> objectMapper = reflections.getTypesAnnotatedWith(GlobalObjectMapper.class); return objectMapper; } private static String getNameSpace() { String myNameSpaceToScan = namespaceToScan; if (myNameSpaceToScan == null) { try { myNameSpaceToScan = System.getenv(GLOBAL_OBJECT_TRANSFORMER_SEARCH_NAMESPACE); } catch (final Exception e) { // ignore - e.g. if a security manager exists and permissions are denied. LOG.warn("Could not get the value of environment variable: {}. Using: {}", new Object[] {GLOBAL_OBJECT_TRANSFORMER_SEARCH_NAMESPACE, namespaceToScan, e}); } if (myNameSpaceToScan == null) { myNameSpaceToScan = DEFAULT_NAMESPACE; } } return myNameSpaceToScan; } /** * Use this static function to set the namespace to scan. * * @param newNamespace the new namespace to be searched for * {@link de.zalando.sprocwrapper.globalvaluetransformer.annotation.GlobalValueTransformer} */ public static void changeNamespaceToScan(final String newNamespace) { namespaceToScan = Preconditions.checkNotNull(newNamespace, "newNamespace"); register = null; } }