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 package it.tidalwave.role.spi;
27
28 import java.lang.reflect.InvocationTargetException;
29 import javax.annotation.Nonnull;
30 import java.util.ArrayList;
31 import java.util.Collection;
32 import java.util.HashSet;
33 import java.util.List;
34 import java.util.Map;
35 import java.util.Optional;
36 import java.util.Set;
37 import java.util.SortedSet;
38 import java.util.TreeSet;
39 import it.tidalwave.util.ContextManager;
40 import it.tidalwave.util.annotation.VisibleForTesting;
41 import it.tidalwave.role.impl.MultiMap;
42 import it.tidalwave.role.impl.OwnerAndRole;
43 import it.tidalwave.dci.annotation.DciRole;
44 import lombok.extern.slf4j.Slf4j;
45 import static java.util.Comparator.*;
46 import static it.tidalwave.util.ShortNames.*;
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64 @Slf4j
65 public abstract class SystemRoleFactorySupport implements SystemRoleFactory
66 {
67 @VisibleForTesting final MultiMap<OwnerAndRole, Class<?>> roleMapByOwnerAndRole = new MultiMap<>();
68
69
70 @VisibleForTesting final Set<OwnerAndRole> alreadyScanned = new HashSet<>();
71
72
73
74
75 @Override @Nonnull
76 public synchronized <T> List<T> findRoles (@Nonnull final Object datum, @Nonnull final Class<? extends T> roleType)
77 {
78 log.trace("findRoles({}, {})", shortId(datum), shortName(roleType));
79 final Class<?> datumType = findTypeOf(datum);
80 final List<T> roles = new ArrayList<>();
81 final var roleImplementationTypes = findRoleImplementationsFor(datumType, roleType);
82
83 outer: for (final var roleImplementationType : roleImplementationTypes)
84 {
85 for (final var constructor : roleImplementationType.getDeclaredConstructors())
86 {
87 log.trace(">>>> trying constructor {}", constructor);
88 final var parameterTypes = constructor.getParameterTypes();
89 Optional<?> context = Optional.empty();
90 final var contextType = findContextTypeForRole(roleImplementationType);
91
92 if (contextType.isPresent())
93 {
94
95 final var contextManager = ContextManager.getInstance();
96 log.trace(">>>> contexts: {}", shortIds(contextManager.getContexts()));
97 context = contextManager.findContextOfType(contextType.get());
98
99 if (context.isEmpty())
100 {
101 log.trace(">>>> role {} discarded, can't find context: {}",
102 shortName(roleImplementationType), shortName(contextType.get()));
103 continue outer;
104 }
105 }
106
107 try
108 {
109 final var params = getParameterValues(parameterTypes, datumType, datum, contextType, context);
110 roles.add(roleType.cast(constructor.newInstance(params)));
111 break;
112 }
113 catch (InstantiationException | IllegalAccessException
114 | IllegalArgumentException | InvocationTargetException e)
115 {
116 log.error("Could not instantiate role of type " + roleImplementationType, e);
117 }
118 }
119 }
120
121 if (log.isTraceEnabled())
122 {
123 log.trace(">>>> findRoles() returning: {}", shortIds(roles));
124 }
125
126 return roles;
127 }
128
129
130
131
132
133
134
135
136
137
138
139 @Nonnull
140 private Object[] getParameterValues (@Nonnull final Class<?>[] parameterTypes,
141 @Nonnull final Class<?> datumClass,
142 @Nonnull final Object datum,
143 @Nonnull final Optional<Class<?>> contextClass,
144 @Nonnull final Optional<?> context)
145 {
146 final var values = new ArrayList<>();
147
148 for (final var parameterType : parameterTypes)
149 {
150 if (parameterType.isAssignableFrom(datumClass))
151 {
152 values.add(datum);
153 }
154 else if (contextClass.isPresent() && parameterType.isAssignableFrom(contextClass.get()))
155 {
156 values.add(context.orElse(null));
157 }
158 else
159 {
160
161 values.add(getBean(parameterType).orElse(null));
162 }
163 }
164
165 log.trace(">>>> constructor parameters: {}", values);
166 return values.toArray();
167 }
168
169
170
171
172
173
174
175
176
177
178
179
180 @Nonnull
181 @VisibleForTesting synchronized <T> Set<Class<? extends T>> findRoleImplementationsFor (
182 @Nonnull final Class<?> datumType,
183 @Nonnull final Class<T> roleType)
184 {
185 final var datumAndRole = new OwnerAndRole(datumType, roleType);
186
187 if (!alreadyScanned.contains(datumAndRole))
188 {
189 alreadyScanned.add(datumAndRole);
190 final var before = new HashSet<>(roleMapByOwnerAndRole.getValues(datumAndRole));
191
192 for (final var superDatumAndRole : datumAndRole.getSuper())
193 {
194 roleMapByOwnerAndRole.addAll(datumAndRole, roleMapByOwnerAndRole.getValues(superDatumAndRole));
195 }
196
197 final var after = new HashSet<>(roleMapByOwnerAndRole.getValues(datumAndRole));
198 logChanges(datumAndRole, before, after);
199 }
200
201 return (Set<Class<? extends T>>)(Set)roleMapByOwnerAndRole.getValues(datumAndRole);
202 }
203
204
205
206
207
208
209 protected synchronized void scan (@Nonnull final Collection<Class<?>> roleImplementationTypes)
210 {
211 log.debug("scan({})", shortNames(roleImplementationTypes));
212
213 for (final var roleImplementationType : roleImplementationTypes)
214 {
215 for (final var datumType : findDatumTypesForRole(roleImplementationType))
216 {
217 for (final var roleType : findAllImplementedInterfacesOf(roleImplementationType))
218 {
219 if (!"org.springframework.beans.factory.aspectj.ConfigurableObject".equals(roleType.getName()))
220 {
221 roleMapByOwnerAndRole.add(new OwnerAndRole(datumType, roleType), roleImplementationType);
222 }
223 }
224 }
225 }
226
227 logRoles();
228 }
229
230
231
232
233
234
235
236
237 @Nonnull
238 @VisibleForTesting static SortedSet<Class<?>> findAllImplementedInterfacesOf (@Nonnull final Class<?> clazz)
239 {
240 final SortedSet<Class<?>> interfaces = new TreeSet<>(comparing(Class::getName));
241 interfaces.addAll(List.of(clazz.getInterfaces()));
242
243 for (final var interface_ : interfaces)
244 {
245 interfaces.addAll(findAllImplementedInterfacesOf(interface_));
246 }
247
248 if (clazz.getSuperclass() != null)
249 {
250 interfaces.addAll(findAllImplementedInterfacesOf(clazz.getSuperclass()));
251 }
252
253 return interfaces;
254 }
255
256
257
258
259
260
261
262
263 @Nonnull
264 protected <T> Optional<T> getBean (@Nonnull final Class<T> beanType)
265 {
266 return Optional.empty();
267 }
268
269
270
271
272
273
274
275 @Nonnull
276 protected Optional<Class<?>> findContextTypeForRole (@Nonnull final Class<?> roleImplementationType)
277 {
278 final var contextClass = roleImplementationType.getAnnotation(DciRole.class).context();
279 return (contextClass == DciRole.NoContext.class) ? Optional.empty() : Optional.of(contextClass);
280 }
281
282
283
284
285
286
287
288 @Nonnull
289 protected Class<?>[] findDatumTypesForRole (@Nonnull final Class<?> roleImplementationType)
290 {
291 return roleImplementationType.getAnnotation(DciRole.class).datumType();
292 }
293
294
295
296 private void logChanges (@Nonnull final OwnerAndRole ownerAndRole,
297 @Nonnull final Set<Class<?>> before,
298 @Nonnull final Set<Class<?>> after)
299 {
300 after.removeAll(before);
301
302 if (!after.isEmpty())
303 {
304 log.debug(">>>>>>> added implementations: {} -> {}", ownerAndRole, shortNames(after));
305
306 if (log.isTraceEnabled())
307 {
308 logRoles();
309 }
310 }
311 }
312
313
314
315 public void logRoles()
316 {
317 log.debug("Configured roles:");
318
319 final var entries = new ArrayList<>(roleMapByOwnerAndRole.entrySet());
320 entries.sort(comparing((Map.Entry<OwnerAndRole, Set<Class<?>>> e) -> e.getKey().getOwnerClass().getName())
321 .thenComparing(e -> e.getKey().getRoleClass().getName()));
322
323 for (final var entry : entries)
324 {
325 log.debug(">>>> {}: {} -> {}",
326 shortName(entry.getKey().getOwnerClass()),
327 shortName(entry.getKey().getRoleClass()),
328 shortNames(entry.getValue()));
329 }
330 }
331
332
333
334
335
336
337
338
339 @Nonnull
340 @VisibleForTesting static <T> Class<T> findTypeOf (@Nonnull final T object)
341 {
342 var ownerClass = object.getClass();
343
344 if (ownerClass.toString().contains("MockitoMock"))
345 {
346 ownerClass = ownerClass.getInterfaces()[0];
347
348 if (log.isTraceEnabled())
349 {
350 log.trace(">>>> owner is a mock {} implementing {}",
351 shortName(ownerClass), shortNames(List.of(ownerClass.getInterfaces())));
352 log.trace(">>>> owner class replaced with {}", shortName(ownerClass));
353 }
354 }
355
356 return (Class<T>)ownerClass;
357 }
358 }