Skip to content

Commit 9ab5575

Browse files
authored
Introduce support for an extension to auto register a value type (keycloak#35645)
Closes keycloak#35592 Signed-off-by: stianst <[email protected]>
1 parent 4c7dea5 commit 9ab5575

File tree

11 files changed

+167
-92
lines changed

11 files changed

+167
-92
lines changed

test-framework/core/src/main/java/org/keycloak/test/framework/TestFrameworkExtension.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@ public interface TestFrameworkExtension {
1010

1111
List<Supplier<?, ?>> suppliers();
1212

13+
default List<Class<?>> alwaysEnabledValueTypes() {
14+
return Collections.emptyList();
15+
}
16+
1317
default Map<Class<?>, String> valueTypeAliases() {
1418
return Collections.emptyMap();
1519
}

test-framework/core/src/main/java/org/keycloak/test/framework/server/AbstractInterceptorHelper.java renamed to test-framework/core/src/main/java/org/keycloak/test/framework/injection/AbstractInterceptorHelper.java

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,18 @@
1-
package org.keycloak.test.framework.server;
2-
3-
import org.keycloak.test.framework.injection.InstanceContext;
4-
import org.keycloak.test.framework.injection.Registry;
5-
import org.keycloak.test.framework.injection.RequestedInstance;
6-
import org.keycloak.test.framework.injection.Supplier;
1+
package org.keycloak.test.framework.injection;
72

83
import java.util.HashMap;
94
import java.util.LinkedList;
105
import java.util.List;
116

127
public abstract class AbstractInterceptorHelper<I, V> {
138

9+
private final Registry registry;
1410
private final Class<?> interceptorClass;
1511
private final List<Interception> interceptions = new LinkedList<>();
1612
private final InterceptedBy interceptedBy = new InterceptedBy();
1713

18-
19-
2014
public AbstractInterceptorHelper(Registry registry, Class<I> interceptorClass) {
15+
this.registry = registry;
2116
this.interceptorClass = interceptorClass;
2217

2318
registry.getDeployedInstances().stream().filter(i -> isInterceptor(i.getSupplier())).forEach(i -> interceptions.add(new Interception(i)));
@@ -34,6 +29,7 @@ public boolean sameInterceptors(InstanceContext<?, ?> instanceContext) {
3429
public V intercept(V value, InstanceContext<?, ?> instanceContext) {
3530
for (Interception interception : interceptions) {
3631
value = intercept(value, interception.supplier, interception.existingInstance);
32+
registry.getLogger().logIntercepted(value, interception.supplier);
3733
}
3834
instanceContext.addNote("InterceptedBy", interceptedBy);
3935
return value;
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
package org.keycloak.test.framework.injection;
2+
3+
import org.keycloak.test.framework.TestFrameworkExtension;
4+
import org.keycloak.test.framework.config.Config;
5+
6+
import java.lang.annotation.Annotation;
7+
import java.util.HashSet;
8+
import java.util.LinkedList;
9+
import java.util.List;
10+
import java.util.ServiceLoader;
11+
import java.util.Set;
12+
13+
public class Extensions {
14+
15+
private final RegistryLogger logger;
16+
private final ValueTypeAlias valueTypeAlias;
17+
private final List<Supplier<?, ?>> suppliers;
18+
private final List<Class<?>> alwaysEnabledValueTypes;
19+
20+
public Extensions() {
21+
List<TestFrameworkExtension> extensions = loadExtensions();
22+
valueTypeAlias = loadValueTypeAlias(extensions);
23+
logger = new RegistryLogger(valueTypeAlias);
24+
suppliers = loadSuppliers(extensions, valueTypeAlias);
25+
alwaysEnabledValueTypes = loadAlwaysEnabledValueTypes(extensions);
26+
}
27+
28+
public ValueTypeAlias getValueTypeAlias() {
29+
return valueTypeAlias;
30+
}
31+
32+
public List<Supplier<?, ?>> getSuppliers() {
33+
return suppliers;
34+
}
35+
36+
public List<Class<?>> getAlwaysEnabledValueTypes() {
37+
return alwaysEnabledValueTypes;
38+
}
39+
40+
@SuppressWarnings("unchecked")
41+
public <T> Supplier<T, ?> findSupplierByType(Class<T> typeClass) {
42+
return (Supplier<T, ?>) suppliers.stream().filter(s -> s.getValueType().equals(typeClass)).findFirst().orElse(null);
43+
}
44+
45+
@SuppressWarnings("unchecked")
46+
public <T> Supplier<T, ?> findSupplierByAnnotation(Annotation annotation) {
47+
return (Supplier<T, ?>) suppliers.stream().filter(s -> s.getAnnotationClass().equals(annotation.annotationType())).findFirst().orElse(null);
48+
}
49+
50+
private List<TestFrameworkExtension> loadExtensions() {
51+
List<TestFrameworkExtension> extensions = new LinkedList<>();
52+
ServiceLoader.load(TestFrameworkExtension.class).iterator().forEachRemaining(extensions::add);
53+
return extensions;
54+
}
55+
56+
private ValueTypeAlias loadValueTypeAlias(List<TestFrameworkExtension> extensions) {
57+
ValueTypeAlias valueTypeAlias = new ValueTypeAlias();
58+
extensions.forEach(e -> valueTypeAlias.addAll(e.valueTypeAliases()));
59+
return valueTypeAlias;
60+
}
61+
62+
private List<Supplier<?, ?>> loadSuppliers(List<TestFrameworkExtension> extensions, ValueTypeAlias valueTypeAlias) {
63+
List<Supplier<?, ?>> suppliers = new LinkedList<>();
64+
List<Supplier<?, ?>> skippedSuppliers = new LinkedList<>();
65+
Set<Class<?>> loadedValueTypes = new HashSet<>();
66+
67+
for (TestFrameworkExtension extension : extensions) {
68+
for (var supplier : extension.suppliers()) {
69+
Class<?> valueType = supplier.getValueType();
70+
String requestedSupplier = Config.getSelectedSupplier(valueType, valueTypeAlias);
71+
if (supplier.getAlias().equals(requestedSupplier) || (requestedSupplier == null && !loadedValueTypes.contains(valueType))) {
72+
suppliers.add(supplier);
73+
loadedValueTypes.add(valueType);
74+
} else {
75+
skippedSuppliers.add(supplier);
76+
}
77+
}
78+
}
79+
80+
logger.logSuppliers(suppliers, skippedSuppliers);
81+
82+
return suppliers;
83+
}
84+
85+
private List<Class<?>> loadAlwaysEnabledValueTypes(List<TestFrameworkExtension> extensions) {
86+
return extensions.stream().flatMap(s -> s.alwaysEnabledValueTypes().stream()).toList();
87+
}
88+
89+
}

test-framework/core/src/main/java/org/keycloak/test/framework/injection/Registry.java

Lines changed: 41 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,34 @@
11
package org.keycloak.test.framework.injection;
22

33
import org.junit.jupiter.api.extension.ExtensionContext;
4-
import org.keycloak.test.framework.TestFrameworkExtension;
5-
import org.keycloak.test.framework.config.Config;
64

75
import java.lang.annotation.Annotation;
86
import java.lang.reflect.Field;
97
import java.util.Arrays;
108
import java.util.Comparator;
11-
import java.util.HashSet;
129
import java.util.Iterator;
1310
import java.util.LinkedList;
1411
import java.util.List;
1512
import java.util.Objects;
16-
import java.util.Optional;
17-
import java.util.ServiceLoader;
1813
import java.util.Set;
19-
import java.util.stream.Collectors;
2014

2115
@SuppressWarnings({"rawtypes", "unchecked"})
2216
public class Registry implements ExtensionContext.Store.CloseableResource {
2317

24-
private RegistryLogger logger;
18+
private final RegistryLogger logger;
2519

2620
private ExtensionContext currentContext;
27-
private final List<Supplier<?, ?>> suppliers = new LinkedList<>();
21+
private final Extensions extensions;
2822
private final List<InstanceContext<?, ?>> deployedInstances = new LinkedList<>();
2923
private final List<RequestedInstance<?, ?>> requestedInstances = new LinkedList<>();
3024

3125
public Registry() {
32-
loadSuppliers();
26+
extensions = new Extensions();
27+
logger = new RegistryLogger(extensions.getValueTypeAlias());
28+
}
29+
30+
RegistryLogger getLogger() {
31+
return logger;
3332
}
3433

3534
public ExtensionContext getCurrentContext() {
@@ -48,11 +47,11 @@ public <T> T getDependency(Class<T> typeClass, String ref, InstanceContext depen
4847
return dependency;
4948
} else {
5049
dependency = getRequestedDependency(typeClass, ref, dependent);
51-
if(dependency != null) {
50+
if (dependency != null) {
5251
return dependency;
5352
} else {
5453
dependency = getUnConfiguredDependency(typeClass, ref, dependent);
55-
if(dependency != null) {
54+
if (dependency != null) {
5655
return dependency;
5756
}
5857
}
@@ -100,22 +99,18 @@ private <T> T getRequestedDependency(Class<T> typeClass, String ref, InstanceCon
10099

101100
private <T> T getUnConfiguredDependency(Class<T> typeClass, String ref, InstanceContext dependent) {
102101
InstanceContext dependency;
103-
Optional<Supplier<?, ?>> supplied = suppliers.stream().filter(s -> s.getValueType().equals(typeClass)).findFirst();
104-
if (supplied.isPresent()) {
105-
Supplier<T, ?> supplier = (Supplier<T, ?>) supplied.get();
106-
Annotation defaultAnnotation = DefaultAnnotationProxy.proxy(supplier.getAnnotationClass());
107-
dependency = new InstanceContext(-1, this, supplier, defaultAnnotation, typeClass);
102+
Supplier<?, ?> supplier = extensions.findSupplierByType(typeClass);
103+
Annotation defaultAnnotation = DefaultAnnotationProxy.proxy(supplier.getAnnotationClass());
104+
dependency = new InstanceContext(-1, this, supplier, defaultAnnotation, typeClass);
108105

109-
dependency.registerDependency(dependent);
110-
dependency.setValue(supplier.getValue(dependency));
106+
dependency.registerDependency(dependent);
107+
dependency.setValue(supplier.getValue(dependency));
111108

112-
deployedInstances.add(dependency);
109+
deployedInstances.add(dependency);
113110

114-
logger.logDependencyInjection(dependent, dependency, RegistryLogger.InjectionType.UN_CONFIGURED);
111+
logger.logDependencyInjection(dependent, dependency, RegistryLogger.InjectionType.UN_CONFIGURED);
115112

116-
return (T) dependency.getValue();
117-
}
118-
return null;
113+
return (T) dependency.getValue();
119114
}
120115

121116
public void beforeEach(Object testInstance) {
@@ -127,6 +122,14 @@ public void beforeEach(Object testInstance) {
127122
}
128123

129124
private void findRequestedInstances(Object testInstance) {
125+
List<Class<?>> alwaysEnabledValueTypes = extensions.getAlwaysEnabledValueTypes();
126+
for (Class<?> valueType : alwaysEnabledValueTypes) {
127+
RequestedInstance requestedInstance = createRequestedInstance(null, valueType);
128+
if (requestedInstance != null) {
129+
requestedInstances.add(requestedInstance);
130+
}
131+
}
132+
130133
Class testClass = testInstance.getClass();
131134
RequestedInstance requestedServerInstance = createRequestedInstance(testClass.getAnnotations(), null);
132135
if (requestedServerInstance != null) {
@@ -178,7 +181,7 @@ private void deployRequestedInstances() {
178181
private void injectFields(Object testInstance) {
179182
for (Field f : listFields(testInstance.getClass())) {
180183
InstanceContext<?, ?> instance = getDeployedInstance(f.getType(), f.getAnnotations());
181-
if(instance == null) { // a test class might have fields not meant for injection
184+
if (instance == null) { // a test class might have fields not meant for injection
182185
continue;
183186
}
184187
try {
@@ -221,16 +224,23 @@ public void close() {
221224
}
222225

223226
List<Supplier<?, ?>> getSuppliers() {
224-
return suppliers;
227+
return extensions.getSuppliers();
225228
}
226229

227230
private RequestedInstance<?, ?> createRequestedInstance(Annotation[] annotations, Class<?> valueType) {
228-
for (Annotation a : annotations) {
229-
for (Supplier s : suppliers) {
230-
if (s.getAnnotationClass().equals(a.annotationType())) {
231-
return new RequestedInstance(s, a, valueType);
231+
if (annotations != null) {
232+
for (Annotation annotation : annotations) {
233+
Supplier<?, ?> supplier = extensions.findSupplierByAnnotation(annotation);
234+
if (supplier != null) {
235+
return new RequestedInstance(supplier, annotation, valueType);
232236
}
233237
}
238+
} else {
239+
Supplier<?, ?> supplier = extensions.findSupplierByType(valueType);
240+
if (supplier != null) {
241+
Annotation defaultAnnotation = DefaultAnnotationProxy.proxy(supplier.getAnnotationClass());
242+
return new RequestedInstance(supplier, defaultAnnotation, valueType);
243+
}
234244
}
235245
return null;
236246
}
@@ -241,7 +251,7 @@ public void close() {
241251
Supplier supplier = i.getSupplier();
242252
if (supplier.getAnnotationClass().equals(a.annotationType())
243253
&& valueType.isAssignableFrom(i.getValue().getClass())
244-
&& Objects.equals(supplier.getRef(a), i.getRef()) ) {
254+
&& Objects.equals(supplier.getRef(a), i.getRef())) {
245255
return i;
246256
}
247257
}
@@ -264,7 +274,7 @@ private InstanceContext getDeployedInstance(RequestedInstance requestedInstance)
264274
String requestedRef = requestedInstance.getRef();
265275
Class requestedValueType = requestedInstance.getValueType();
266276
for (InstanceContext<?, ?> i : deployedInstances) {
267-
if(!Objects.equals(i.getRef(), requestedRef)) {
277+
if (!Objects.equals(i.getRef(), requestedRef)) {
268278
continue;
269279
}
270280

@@ -279,47 +289,6 @@ private InstanceContext getDeployedInstance(RequestedInstance requestedInstance)
279289
return null;
280290
}
281291

282-
private void loadSuppliers() {
283-
Iterator<TestFrameworkExtension> extensions = ServiceLoader.load(TestFrameworkExtension.class).iterator();
284-
ValueTypeAlias valueTypeAlias = new ValueTypeAlias();
285-
List<Supplier> tmp = new LinkedList<>();
286-
while (extensions.hasNext()) {
287-
TestFrameworkExtension extension = extensions.next();
288-
tmp.addAll(extension.suppliers());
289-
valueTypeAlias.addAll(extension.valueTypeAliases());
290-
}
291-
292-
logger = new RegistryLogger(valueTypeAlias);
293-
294-
Set<Class> loadedValueTypes = new HashSet<>();
295-
Set<Supplier> skippedSuppliers = new HashSet<>();
296-
297-
for (Supplier supplier : tmp) {
298-
boolean shouldAdd = false;
299-
Class supplierValueType = supplier.getValueType();
300-
301-
if (!loadedValueTypes.contains(supplierValueType)) {
302-
String requestedSupplier = Config.getSelectedSupplier(supplierValueType, valueTypeAlias);
303-
if (requestedSupplier != null) {
304-
if (requestedSupplier.equals(supplier.getAlias())) {
305-
shouldAdd = true;
306-
}
307-
} else {
308-
shouldAdd = true;
309-
}
310-
}
311-
312-
if (shouldAdd) {
313-
suppliers.add(supplier);
314-
loadedValueTypes.add(supplierValueType);
315-
} else {
316-
skippedSuppliers.add(supplier);
317-
}
318-
}
319-
320-
logger.logSuppliers(suppliers, skippedSuppliers);
321-
}
322-
323292
private InstanceContext getDeployedInstance(Class typeClass, String ref) {
324293
return deployedInstances.stream()
325294
.filter(i -> i.getSupplier().getValueType().equals(typeClass) && Objects.equals(i.getRef(), ref))

test-framework/core/src/main/java/org/keycloak/test/framework/injection/RegistryLogger.java

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
import org.jboss.logging.Logger;
44

55
import java.util.List;
6-
import java.util.Set;
76
import java.util.stream.Collectors;
87

98
@SuppressWarnings("rawtypes")
@@ -89,7 +88,7 @@ public void logClose() {
8988
LOGGER.debug("Closing all instances");
9089
}
9190

92-
public void logSuppliers(List<Supplier<?, ?>> suppliers, Set<Supplier> skippedSuppliers) {
91+
public void logSuppliers(List<Supplier<?, ?>> suppliers, List<Supplier<?, ?>> skippedSuppliers) {
9392
if (LOGGER.isDebugEnabled()) {
9493
StringBuilder sb = new StringBuilder();
9594
sb.append("Loaded suppliers:");
@@ -122,6 +121,10 @@ private void appendSupplierInfo(Supplier s, StringBuilder sb) {
122121

123122
}
124123

124+
public void logIntercepted(Object value, Supplier<?, ?> supplier) {
125+
LOGGER.debugv("{0} intercepted by {1}", value.getClass().getSimpleName(), supplier.getClass().getSimpleName());
126+
}
127+
125128
public enum InjectionType {
126129

127130
EXISTING("existing"),

test-framework/core/src/main/java/org/keycloak/test/framework/injection/Supplier.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package org.keycloak.test.framework.injection;
22

33
import java.lang.annotation.Annotation;
4+
import java.util.Collections;
5+
import java.util.Set;
46

57
public interface Supplier<T, S extends Annotation> {
68

@@ -37,4 +39,9 @@ default void onBeforeEach(InstanceContext<T, S> instanceContext) {
3739
default int order() {
3840
return SupplierOrder.DEFAULT;
3941
}
42+
43+
default Set<Class<?>> dependencies() {
44+
return Collections.emptySet();
45+
}
46+
4047
}

test-framework/core/src/main/java/org/keycloak/test/framework/injection/SupplierHelpers.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ public static <T> T getAnnotationField(Annotation annotation, String name, T def
2121
return value != null ? value : defaultValue;
2222
}
2323

24+
@SuppressWarnings("unchecked")
2425
public static <T> T getAnnotationField(Annotation annotation, String name) {
2526
if (annotation != null) {
2627
for (Method m : annotation.annotationType().getMethods()) {

0 commit comments

Comments
 (0)