1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34 package info.magnolia.config.resolver;
35
36 import static java.util.function.Function.identity;
37 import static java.util.stream.Collectors.toMap;
38
39 import info.magnolia.init.MagnoliaConfigurationProperties;
40 import info.magnolia.jcr.node2bean.TypeDescriptor;
41
42 import java.lang.annotation.Annotation;
43 import java.lang.reflect.InvocationTargetException;
44 import java.lang.reflect.Method;
45 import java.util.Map;
46 import java.util.Optional;
47 import java.util.function.Function;
48
49 import javax.inject.Inject;
50 import javax.inject.Singleton;
51
52 import info.magnolia.objectfactory.annotation.Multibinding;
53 import info.magnolia.transformer.TypeResolver;
54
55 import org.apache.commons.lang3.StringUtils;
56 import org.reflections.Reflections;
57 import org.slf4j.Logger;
58 import org.slf4j.LoggerFactory;
59
60
61
62
63
64
65
66
67
68
69
70 @Singleton
71 @Multibinding
72 public final class AnnotationProcessingTypeResolver implements TypeResolver {
73
74 private static final Logger log = LoggerFactory.getLogger(AnnotationProcessingTypeResolver.class);
75
76 private final Map<Class, AliasTypeResolver> delegates;
77 private static final String TYPE_PROPERTY = "$type";
78
79 @Inject
80 public AnnotationProcessingTypeResolver(MagnoliaConfigurationProperties configurationProperties) {
81 Object[] whitelistedPackages = StringUtils.stripAll(StringUtils.split(configurationProperties.getProperty(MagnoliaConfigurationProperties.MAGNOLIA_DEFINITIONS_CLASSPATH), ","));
82 final Reflections reflections = new Reflections(whitelistedPackages);
83
84 this.delegates = reflections.getTypesAnnotatedWith(TypeAlias.class).stream()
85 .filter(Class::isAnnotation)
86 .map(annotation -> new AliasTypeResolver(
87 annotation.getAnnotation(TypeAlias.class).type(),
88 resolveRelatedTypes((Class<? extends Annotation>) annotation, reflections)))
89 .collect(toMap(AliasTypeResolver::baseType, Function.identity()));
90 }
91
92 private Map<String, Class> resolveRelatedTypes(Class<? extends Annotation> annotation, Reflections reflections) {
93 return reflections
94 .getTypesAnnotatedWith(annotation, true)
95 .stream()
96 .collect(toMap(type -> getAlias(type.getAnnotation(annotation)), identity()));
97 }
98
99 @Override
100 public Optional<Class<?>> resolveType(TypeDescriptor typeDescriptor, Map<String, Object> properties) {
101 return delegates.values().stream()
102 .map(value -> value.resolveType(typeDescriptor, properties))
103 .filter(Optional::isPresent)
104 .findFirst()
105 .orElse(Optional.empty());
106 }
107
108 private String getAlias(Annotation annotation) {
109 try {
110 final Method valueMethod = annotation.getClass().getMethod("value");
111 valueMethod.setAccessible(true);
112 return (String) valueMethod.invoke(annotation);
113 } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
114 throw new RuntimeException("Failed to resolve alias", e);
115 }
116 }
117
118 private final class AliasTypeResolver implements TypeResolver {
119
120 private final Class<?> baseType;
121 private final Map<String, Class> aliasedTypes;
122
123 AliasTypeResolver(Class<?> baseType, Map<String, Class> aliasedTypes) {
124 this.baseType = baseType;
125 this.aliasedTypes = aliasedTypes;
126 }
127
128 Class<?> baseType() {
129 return baseType;
130 }
131
132 @Override
133 public Optional<Class<?>> resolveType(TypeDescriptor typeDescriptor, Map<String, Object> properties) {
134 if (supportsType(typeDescriptor) && properties.containsKey(TYPE_PROPERTY)) {
135 Optional result = Optional.of(properties.get(TYPE_PROPERTY)).map(aliasedTypes::get);
136 if (result.isPresent()) {
137 return result;
138 } else {
139 log.warn("Encountered the '{}' property but failed to resolved the type from its value: [{}]. Available types: [{}]", TYPE_PROPERTY, properties.get(TYPE_PROPERTY), String.join(",", aliasedTypes.keySet()));
140 }
141 }
142 return Optional.empty();
143 }
144
145 @Override
146 public boolean supportsType(TypeDescriptor typeDescriptor) {
147 return typeDescriptor != null && baseType.isAssignableFrom(typeDescriptor.getType());
148 }
149 }
150 }