Skip to content

Package: SystemRoleFactorySupport

SystemRoleFactorySupport

nameinstructionbranchcomplexitylinemethod
SystemRoleFactorySupport()
M: 0 C: 13
100%
M: 0 C: 0
100%
M: 0 C: 1
100%
M: 0 C: 3
100%
M: 0 C: 1
100%
findAllImplementedInterfacesOf(Class)
M: 0 C: 39
100%
M: 0 C: 4
100%
M: 0 C: 3
100%
M: 0 C: 8
100%
M: 0 C: 1
100%
findContextTypeForRole(Class)
M: 0 C: 14
100%
M: 0 C: 2
100%
M: 0 C: 2
100%
M: 0 C: 2
100%
M: 0 C: 1
100%
findDatumTypesForRole(Class)
M: 0 C: 6
100%
M: 0 C: 0
100%
M: 0 C: 1
100%
M: 0 C: 1
100%
M: 0 C: 1
100%
findRoleImplementationsFor(Class, Class)
M: 0 C: 62
100%
M: 0 C: 4
100%
M: 0 C: 3
100%
M: 0 C: 10
100%
M: 0 C: 1
100%
findRoles(Object, Class)
M: 9 C: 115
93%
M: 1 C: 9
90%
M: 1 C: 5
83%
M: 2 C: 25
93%
M: 0 C: 1
100%
findTypeOf(Object)
M: 14 C: 18
56%
M: 1 C: 3
75%
M: 1 C: 2
67%
M: 3 C: 5
63%
M: 0 C: 1
100%
getBean(Class)
M: 2 C: 0
0%
M: 0 C: 0
100%
M: 1 C: 0
0%
M: 1 C: 0
0%
M: 1 C: 0
0%
getParameterValues(Class[], Class, Object, Optional, Optional)
M: 0 C: 60
100%
M: 0 C: 8
100%
M: 0 C: 5
100%
M: 0 C: 9
100%
M: 0 C: 1
100%
lambda$logRoles$0(Map.Entry)
M: 0 C: 6
100%
M: 0 C: 0
100%
M: 0 C: 1
100%
M: 0 C: 1
100%
M: 0 C: 1
100%
lambda$logRoles$1(Map.Entry)
M: 0 C: 6
100%
M: 0 C: 0
100%
M: 0 C: 1
100%
M: 0 C: 1
100%
M: 0 C: 1
100%
logChanges(OwnerAndRole, Set, Set)
M: 0 C: 19
100%
M: 0 C: 4
100%
M: 0 C: 3
100%
M: 0 C: 6
100%
M: 0 C: 1
100%
logRoles()
M: 0 C: 56
100%
M: 0 C: 2
100%
M: 0 C: 2
100%
M: 0 C: 11
100%
M: 0 C: 1
100%
scan(Collection)
M: 0 C: 63
100%
M: 1 C: 7
88%
M: 1 C: 4
80%
M: 0 C: 10
100%
M: 0 C: 1
100%
static {...}
M: 0 C: 4
100%
M: 0 C: 0
100%
M: 0 C: 1
100%
M: 0 C: 1
100%
M: 0 C: 1
100%

Coverage

1: /*
2: * *************************************************************************************************************************************************************
3: *
4: * TheseFoolishThings: Miscellaneous utilities
5: * http://tidalwave.it/projects/thesefoolishthings
6: *
7: * Copyright (C) 2009 - 2024 by Tidalwave s.a.s. (http://tidalwave.it)
8: *
9: * *************************************************************************************************************************************************************
10: *
11: * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License.
12: * You may obtain a copy of the License at
13: *
14: * http://www.apache.org/licenses/LICENSE-2.0
15: *
16: * 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
17: * CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
18: *
19: * *************************************************************************************************************************************************************
20: *
21: * git clone https://bitbucket.org/tidalwave/thesefoolishthings-src
22: * git clone https://github.com/tidalwave-it/thesefoolishthings-src
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: * A basic implementation of a {@link SystemRoleFactory}. This class must be specialized to:
51: *
52: * <ol>
53: * <li>discover roles (see {@link #scan(java.util.Collection)}</li>
54: * <li>associate roles to a datum (see {@link #findDatumTypesForRole(java.lang.Class)}</li>
55: * <li>associate roles to contexts (see {@link #findContextTypeForRole(java.lang.Class)}</li>
56: * <li>eventually retrieve beans to inject in created roles (see {@link #getBean(java.lang.Class)}</li>
57: * </ol>
58: *
59: * Specializations might use annotations or configuration files to accomplish these tasks.
60: *
61: * @author Fabrizio Giudici
62: *
63: **************************************************************************************************************************************************************/
64: @Slf4j
65: public abstract class SystemRoleFactorySupport implements SystemRoleFactory
66: {
67: @VisibleForTesting final MultiMap<OwnerAndRole, Class<?>> roleMapByOwnerAndRole = new MultiMap<>();
68:
69: // FIXME: use ConcurrentHashMap
70: @VisibleForTesting final Set<OwnerAndRole> alreadyScanned = new HashSet<>();
71:
72: /***********************************************************************************************************************************************************
73: * {@inheritDoc}
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: // With DI frameworks such as Spring it's better to avoid eager initializations of references
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: * Prepare the constructor parameters out of the given expected types. Parameters will be eventually made of the
131: * given datum, context, and other objects returned by {@link #getBean(java.lang.Class)}.
132: *
133: * @param parameterTypes the expected types
134: * @param datumClass the type of the datum
135: * @param datum the datum
136: * @param contextClass the type of the context
137: * @param context the context
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 // generic injection
159: {
160: // FIXME: it's injecting null, but perhaps should it throw exception?
161: values.add(getBean(parameterType).orElse(null));
162: }
163: }
164:
165: log.trace(">>>> constructor parameters: {}", values);
166: return values.toArray();
167: }
168:
169: /***********************************************************************************************************************************************************
170: * Finds the role implementations for the given owner type and role type. This method might discover new
171: * implementations that weren't found during the initial scan, since the initial scan can't go down in a
172: * hierarchy; that is, given a Base class or interface with some associated roles, it can't associate those roles
173: * to subclasses (or implementations) of Base. Now we can navigate up the hierarchy and complete the picture.
174: * Each new discovered role is added into the map, so the next time scanning will be faster.
175: *
176: * @param datumType the type of the datum
177: * @param roleType the type of the role to find
178: * @return the types of role implementations
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: * Scans all the given role implementation classes and build a map of roles by owner class.
206: *
207: * @param roleImplementationTypes the types of role implementations to scan
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: * Finds all the interfaces implemented by a given class, including those eventually implemented by superclasses
232: * and interfaces that are indirectly implemented (e.g. C implements I1, I1 extends I2).
233: *
234: * @param clazz the class to inspect
235: * @return the implemented interfaces
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: * Retrieves an extra bean.
258: *
259: * @param <T> the static type of the bean
260: * @param beanType the dynamic type of the bean
261: * @return the bean
262: **********************************************************************************************************************************************************/
263: @Nonnull
264: protected <T> Optional<T> getBean (@Nonnull final Class<T> beanType)
265: {
266: return Optional.empty();
267: }
268:
269: /***********************************************************************************************************************************************************
270: * Returns the type of the context associated to the given role implementation type.
271: *
272: * @param roleImplementationType the role type
273: * @return the context type
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: * Returns the valid datum types for the given role implementation type.
284: *
285: * @param roleImplementationType the role type
286: * @return the datum types
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()) // yes, trace
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: * Returns the type of an object, taking care of mocks created by Mockito, for which the implemented interface is
334: * returned.
335: *
336: * @param object the object
337: * @return the object type
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]; // 1st is the original class, 2nd is CGLIB proxy
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: }