mirror of
https://github.com/keycloak/keycloak.git
synced 2026-01-10 15:32:05 -03:30
Introduce support for an extension to auto register a value type (#35645)
Closes #35592 Signed-off-by: stianst <stianst@gmail.com>
This commit is contained in:
parent
4c7dea5d70
commit
9ab5575959
@ -10,6 +10,10 @@ public interface TestFrameworkExtension {
|
||||
|
||||
List<Supplier<?, ?>> suppliers();
|
||||
|
||||
default List<Class<?>> alwaysEnabledValueTypes() {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
default Map<Class<?>, String> valueTypeAliases() {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
|
||||
@ -1,9 +1,4 @@
|
||||
package org.keycloak.test.framework.server;
|
||||
|
||||
import org.keycloak.test.framework.injection.InstanceContext;
|
||||
import org.keycloak.test.framework.injection.Registry;
|
||||
import org.keycloak.test.framework.injection.RequestedInstance;
|
||||
import org.keycloak.test.framework.injection.Supplier;
|
||||
package org.keycloak.test.framework.injection;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
@ -11,13 +6,13 @@ import java.util.List;
|
||||
|
||||
public abstract class AbstractInterceptorHelper<I, V> {
|
||||
|
||||
private final Registry registry;
|
||||
private final Class<?> interceptorClass;
|
||||
private final List<Interception> interceptions = new LinkedList<>();
|
||||
private final InterceptedBy interceptedBy = new InterceptedBy();
|
||||
|
||||
|
||||
|
||||
public AbstractInterceptorHelper(Registry registry, Class<I> interceptorClass) {
|
||||
this.registry = registry;
|
||||
this.interceptorClass = interceptorClass;
|
||||
|
||||
registry.getDeployedInstances().stream().filter(i -> isInterceptor(i.getSupplier())).forEach(i -> interceptions.add(new Interception(i)));
|
||||
@ -34,6 +29,7 @@ public abstract class AbstractInterceptorHelper<I, V> {
|
||||
public V intercept(V value, InstanceContext<?, ?> instanceContext) {
|
||||
for (Interception interception : interceptions) {
|
||||
value = intercept(value, interception.supplier, interception.existingInstance);
|
||||
registry.getLogger().logIntercepted(value, interception.supplier);
|
||||
}
|
||||
instanceContext.addNote("InterceptedBy", interceptedBy);
|
||||
return value;
|
||||
@ -0,0 +1,89 @@
|
||||
package org.keycloak.test.framework.injection;
|
||||
|
||||
import org.keycloak.test.framework.TestFrameworkExtension;
|
||||
import org.keycloak.test.framework.config.Config;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.ServiceLoader;
|
||||
import java.util.Set;
|
||||
|
||||
public class Extensions {
|
||||
|
||||
private final RegistryLogger logger;
|
||||
private final ValueTypeAlias valueTypeAlias;
|
||||
private final List<Supplier<?, ?>> suppliers;
|
||||
private final List<Class<?>> alwaysEnabledValueTypes;
|
||||
|
||||
public Extensions() {
|
||||
List<TestFrameworkExtension> extensions = loadExtensions();
|
||||
valueTypeAlias = loadValueTypeAlias(extensions);
|
||||
logger = new RegistryLogger(valueTypeAlias);
|
||||
suppliers = loadSuppliers(extensions, valueTypeAlias);
|
||||
alwaysEnabledValueTypes = loadAlwaysEnabledValueTypes(extensions);
|
||||
}
|
||||
|
||||
public ValueTypeAlias getValueTypeAlias() {
|
||||
return valueTypeAlias;
|
||||
}
|
||||
|
||||
public List<Supplier<?, ?>> getSuppliers() {
|
||||
return suppliers;
|
||||
}
|
||||
|
||||
public List<Class<?>> getAlwaysEnabledValueTypes() {
|
||||
return alwaysEnabledValueTypes;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> Supplier<T, ?> findSupplierByType(Class<T> typeClass) {
|
||||
return (Supplier<T, ?>) suppliers.stream().filter(s -> s.getValueType().equals(typeClass)).findFirst().orElse(null);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> Supplier<T, ?> findSupplierByAnnotation(Annotation annotation) {
|
||||
return (Supplier<T, ?>) suppliers.stream().filter(s -> s.getAnnotationClass().equals(annotation.annotationType())).findFirst().orElse(null);
|
||||
}
|
||||
|
||||
private List<TestFrameworkExtension> loadExtensions() {
|
||||
List<TestFrameworkExtension> extensions = new LinkedList<>();
|
||||
ServiceLoader.load(TestFrameworkExtension.class).iterator().forEachRemaining(extensions::add);
|
||||
return extensions;
|
||||
}
|
||||
|
||||
private ValueTypeAlias loadValueTypeAlias(List<TestFrameworkExtension> extensions) {
|
||||
ValueTypeAlias valueTypeAlias = new ValueTypeAlias();
|
||||
extensions.forEach(e -> valueTypeAlias.addAll(e.valueTypeAliases()));
|
||||
return valueTypeAlias;
|
||||
}
|
||||
|
||||
private List<Supplier<?, ?>> loadSuppliers(List<TestFrameworkExtension> extensions, ValueTypeAlias valueTypeAlias) {
|
||||
List<Supplier<?, ?>> suppliers = new LinkedList<>();
|
||||
List<Supplier<?, ?>> skippedSuppliers = new LinkedList<>();
|
||||
Set<Class<?>> loadedValueTypes = new HashSet<>();
|
||||
|
||||
for (TestFrameworkExtension extension : extensions) {
|
||||
for (var supplier : extension.suppliers()) {
|
||||
Class<?> valueType = supplier.getValueType();
|
||||
String requestedSupplier = Config.getSelectedSupplier(valueType, valueTypeAlias);
|
||||
if (supplier.getAlias().equals(requestedSupplier) || (requestedSupplier == null && !loadedValueTypes.contains(valueType))) {
|
||||
suppliers.add(supplier);
|
||||
loadedValueTypes.add(valueType);
|
||||
} else {
|
||||
skippedSuppliers.add(supplier);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
logger.logSuppliers(suppliers, skippedSuppliers);
|
||||
|
||||
return suppliers;
|
||||
}
|
||||
|
||||
private List<Class<?>> loadAlwaysEnabledValueTypes(List<TestFrameworkExtension> extensions) {
|
||||
return extensions.stream().flatMap(s -> s.alwaysEnabledValueTypes().stream()).toList();
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,35 +1,34 @@
|
||||
package org.keycloak.test.framework.injection;
|
||||
|
||||
import org.junit.jupiter.api.extension.ExtensionContext;
|
||||
import org.keycloak.test.framework.TestFrameworkExtension;
|
||||
import org.keycloak.test.framework.config.Config;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.Arrays;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.ServiceLoader;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@SuppressWarnings({"rawtypes", "unchecked"})
|
||||
public class Registry implements ExtensionContext.Store.CloseableResource {
|
||||
|
||||
private RegistryLogger logger;
|
||||
private final RegistryLogger logger;
|
||||
|
||||
private ExtensionContext currentContext;
|
||||
private final List<Supplier<?, ?>> suppliers = new LinkedList<>();
|
||||
private final Extensions extensions;
|
||||
private final List<InstanceContext<?, ?>> deployedInstances = new LinkedList<>();
|
||||
private final List<RequestedInstance<?, ?>> requestedInstances = new LinkedList<>();
|
||||
|
||||
public Registry() {
|
||||
loadSuppliers();
|
||||
extensions = new Extensions();
|
||||
logger = new RegistryLogger(extensions.getValueTypeAlias());
|
||||
}
|
||||
|
||||
RegistryLogger getLogger() {
|
||||
return logger;
|
||||
}
|
||||
|
||||
public ExtensionContext getCurrentContext() {
|
||||
@ -48,11 +47,11 @@ public class Registry implements ExtensionContext.Store.CloseableResource {
|
||||
return dependency;
|
||||
} else {
|
||||
dependency = getRequestedDependency(typeClass, ref, dependent);
|
||||
if(dependency != null) {
|
||||
if (dependency != null) {
|
||||
return dependency;
|
||||
} else {
|
||||
dependency = getUnConfiguredDependency(typeClass, ref, dependent);
|
||||
if(dependency != null) {
|
||||
if (dependency != null) {
|
||||
return dependency;
|
||||
}
|
||||
}
|
||||
@ -100,22 +99,18 @@ public class Registry implements ExtensionContext.Store.CloseableResource {
|
||||
|
||||
private <T> T getUnConfiguredDependency(Class<T> typeClass, String ref, InstanceContext dependent) {
|
||||
InstanceContext dependency;
|
||||
Optional<Supplier<?, ?>> supplied = suppliers.stream().filter(s -> s.getValueType().equals(typeClass)).findFirst();
|
||||
if (supplied.isPresent()) {
|
||||
Supplier<T, ?> supplier = (Supplier<T, ?>) supplied.get();
|
||||
Annotation defaultAnnotation = DefaultAnnotationProxy.proxy(supplier.getAnnotationClass());
|
||||
dependency = new InstanceContext(-1, this, supplier, defaultAnnotation, typeClass);
|
||||
Supplier<?, ?> supplier = extensions.findSupplierByType(typeClass);
|
||||
Annotation defaultAnnotation = DefaultAnnotationProxy.proxy(supplier.getAnnotationClass());
|
||||
dependency = new InstanceContext(-1, this, supplier, defaultAnnotation, typeClass);
|
||||
|
||||
dependency.registerDependency(dependent);
|
||||
dependency.setValue(supplier.getValue(dependency));
|
||||
dependency.registerDependency(dependent);
|
||||
dependency.setValue(supplier.getValue(dependency));
|
||||
|
||||
deployedInstances.add(dependency);
|
||||
deployedInstances.add(dependency);
|
||||
|
||||
logger.logDependencyInjection(dependent, dependency, RegistryLogger.InjectionType.UN_CONFIGURED);
|
||||
logger.logDependencyInjection(dependent, dependency, RegistryLogger.InjectionType.UN_CONFIGURED);
|
||||
|
||||
return (T) dependency.getValue();
|
||||
}
|
||||
return null;
|
||||
return (T) dependency.getValue();
|
||||
}
|
||||
|
||||
public void beforeEach(Object testInstance) {
|
||||
@ -127,6 +122,14 @@ public class Registry implements ExtensionContext.Store.CloseableResource {
|
||||
}
|
||||
|
||||
private void findRequestedInstances(Object testInstance) {
|
||||
List<Class<?>> alwaysEnabledValueTypes = extensions.getAlwaysEnabledValueTypes();
|
||||
for (Class<?> valueType : alwaysEnabledValueTypes) {
|
||||
RequestedInstance requestedInstance = createRequestedInstance(null, valueType);
|
||||
if (requestedInstance != null) {
|
||||
requestedInstances.add(requestedInstance);
|
||||
}
|
||||
}
|
||||
|
||||
Class testClass = testInstance.getClass();
|
||||
RequestedInstance requestedServerInstance = createRequestedInstance(testClass.getAnnotations(), null);
|
||||
if (requestedServerInstance != null) {
|
||||
@ -178,7 +181,7 @@ public class Registry implements ExtensionContext.Store.CloseableResource {
|
||||
private void injectFields(Object testInstance) {
|
||||
for (Field f : listFields(testInstance.getClass())) {
|
||||
InstanceContext<?, ?> instance = getDeployedInstance(f.getType(), f.getAnnotations());
|
||||
if(instance == null) { // a test class might have fields not meant for injection
|
||||
if (instance == null) { // a test class might have fields not meant for injection
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
@ -221,16 +224,23 @@ public class Registry implements ExtensionContext.Store.CloseableResource {
|
||||
}
|
||||
|
||||
List<Supplier<?, ?>> getSuppliers() {
|
||||
return suppliers;
|
||||
return extensions.getSuppliers();
|
||||
}
|
||||
|
||||
private RequestedInstance<?, ?> createRequestedInstance(Annotation[] annotations, Class<?> valueType) {
|
||||
for (Annotation a : annotations) {
|
||||
for (Supplier s : suppliers) {
|
||||
if (s.getAnnotationClass().equals(a.annotationType())) {
|
||||
return new RequestedInstance(s, a, valueType);
|
||||
if (annotations != null) {
|
||||
for (Annotation annotation : annotations) {
|
||||
Supplier<?, ?> supplier = extensions.findSupplierByAnnotation(annotation);
|
||||
if (supplier != null) {
|
||||
return new RequestedInstance(supplier, annotation, valueType);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Supplier<?, ?> supplier = extensions.findSupplierByType(valueType);
|
||||
if (supplier != null) {
|
||||
Annotation defaultAnnotation = DefaultAnnotationProxy.proxy(supplier.getAnnotationClass());
|
||||
return new RequestedInstance(supplier, defaultAnnotation, valueType);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@ -241,7 +251,7 @@ public class Registry implements ExtensionContext.Store.CloseableResource {
|
||||
Supplier supplier = i.getSupplier();
|
||||
if (supplier.getAnnotationClass().equals(a.annotationType())
|
||||
&& valueType.isAssignableFrom(i.getValue().getClass())
|
||||
&& Objects.equals(supplier.getRef(a), i.getRef()) ) {
|
||||
&& Objects.equals(supplier.getRef(a), i.getRef())) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
@ -264,7 +274,7 @@ public class Registry implements ExtensionContext.Store.CloseableResource {
|
||||
String requestedRef = requestedInstance.getRef();
|
||||
Class requestedValueType = requestedInstance.getValueType();
|
||||
for (InstanceContext<?, ?> i : deployedInstances) {
|
||||
if(!Objects.equals(i.getRef(), requestedRef)) {
|
||||
if (!Objects.equals(i.getRef(), requestedRef)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -279,47 +289,6 @@ public class Registry implements ExtensionContext.Store.CloseableResource {
|
||||
return null;
|
||||
}
|
||||
|
||||
private void loadSuppliers() {
|
||||
Iterator<TestFrameworkExtension> extensions = ServiceLoader.load(TestFrameworkExtension.class).iterator();
|
||||
ValueTypeAlias valueTypeAlias = new ValueTypeAlias();
|
||||
List<Supplier> tmp = new LinkedList<>();
|
||||
while (extensions.hasNext()) {
|
||||
TestFrameworkExtension extension = extensions.next();
|
||||
tmp.addAll(extension.suppliers());
|
||||
valueTypeAlias.addAll(extension.valueTypeAliases());
|
||||
}
|
||||
|
||||
logger = new RegistryLogger(valueTypeAlias);
|
||||
|
||||
Set<Class> loadedValueTypes = new HashSet<>();
|
||||
Set<Supplier> skippedSuppliers = new HashSet<>();
|
||||
|
||||
for (Supplier supplier : tmp) {
|
||||
boolean shouldAdd = false;
|
||||
Class supplierValueType = supplier.getValueType();
|
||||
|
||||
if (!loadedValueTypes.contains(supplierValueType)) {
|
||||
String requestedSupplier = Config.getSelectedSupplier(supplierValueType, valueTypeAlias);
|
||||
if (requestedSupplier != null) {
|
||||
if (requestedSupplier.equals(supplier.getAlias())) {
|
||||
shouldAdd = true;
|
||||
}
|
||||
} else {
|
||||
shouldAdd = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (shouldAdd) {
|
||||
suppliers.add(supplier);
|
||||
loadedValueTypes.add(supplierValueType);
|
||||
} else {
|
||||
skippedSuppliers.add(supplier);
|
||||
}
|
||||
}
|
||||
|
||||
logger.logSuppliers(suppliers, skippedSuppliers);
|
||||
}
|
||||
|
||||
private InstanceContext getDeployedInstance(Class typeClass, String ref) {
|
||||
return deployedInstances.stream()
|
||||
.filter(i -> i.getSupplier().getValueType().equals(typeClass) && Objects.equals(i.getRef(), ref))
|
||||
|
||||
@ -3,7 +3,6 @@ package org.keycloak.test.framework.injection;
|
||||
import org.jboss.logging.Logger;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
@ -89,7 +88,7 @@ class RegistryLogger {
|
||||
LOGGER.debug("Closing all instances");
|
||||
}
|
||||
|
||||
public void logSuppliers(List<Supplier<?, ?>> suppliers, Set<Supplier> skippedSuppliers) {
|
||||
public void logSuppliers(List<Supplier<?, ?>> suppliers, List<Supplier<?, ?>> skippedSuppliers) {
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("Loaded suppliers:");
|
||||
@ -122,6 +121,10 @@ class RegistryLogger {
|
||||
|
||||
}
|
||||
|
||||
public void logIntercepted(Object value, Supplier<?, ?> supplier) {
|
||||
LOGGER.debugv("{0} intercepted by {1}", value.getClass().getSimpleName(), supplier.getClass().getSimpleName());
|
||||
}
|
||||
|
||||
public enum InjectionType {
|
||||
|
||||
EXISTING("existing"),
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
package org.keycloak.test.framework.injection;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
|
||||
public interface Supplier<T, S extends Annotation> {
|
||||
|
||||
@ -37,4 +39,9 @@ public interface Supplier<T, S extends Annotation> {
|
||||
default int order() {
|
||||
return SupplierOrder.DEFAULT;
|
||||
}
|
||||
|
||||
default Set<Class<?>> dependencies() {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -21,6 +21,7 @@ public class SupplierHelpers {
|
||||
return value != null ? value : defaultValue;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T> T getAnnotationField(Annotation annotation, String name) {
|
||||
if (annotation != null) {
|
||||
for (Method m : annotation.annotationType().getMethods()) {
|
||||
|
||||
@ -10,7 +10,7 @@ import org.keycloak.test.framework.injection.RequestedInstance;
|
||||
import org.keycloak.test.framework.injection.Supplier;
|
||||
import org.keycloak.test.framework.injection.SupplierHelpers;
|
||||
import org.keycloak.test.framework.injection.SupplierOrder;
|
||||
import org.keycloak.test.framework.server.AbstractInterceptorHelper;
|
||||
import org.keycloak.test.framework.injection.AbstractInterceptorHelper;
|
||||
import org.keycloak.test.framework.server.KeycloakServer;
|
||||
|
||||
public class RealmSupplier implements Supplier<ManagedRealm, InjectRealm> {
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
package org.keycloak.test.framework.server;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.test.framework.injection.AbstractInterceptorHelper;
|
||||
import org.keycloak.test.framework.annotations.KeycloakIntegrationTest;
|
||||
import org.keycloak.test.framework.config.Config;
|
||||
import org.keycloak.test.framework.database.TestDatabase;
|
||||
|
||||
@ -12,6 +12,11 @@ public class RemoteTestFrameworkExtension implements TestFrameworkExtension {
|
||||
return List.of(
|
||||
new TimeOffsetSupplier(),
|
||||
new RemoteProvidersSupplier()
|
||||
);
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Class<?>> alwaysEnabledValueTypes() {
|
||||
return List.of(RemoteProviders.class);
|
||||
}
|
||||
}
|
||||
|
||||
@ -7,11 +7,11 @@ import org.keycloak.test.framework.injection.RequestedInstance;
|
||||
import org.keycloak.test.framework.injection.Supplier;
|
||||
import org.keycloak.test.framework.injection.SupplierOrder;
|
||||
import org.keycloak.test.framework.remote.RemoteProviders;
|
||||
import org.keycloak.test.framework.server.KeycloakServerConfigBuilder;
|
||||
import org.keycloak.test.framework.server.KeycloakServerConfigInterceptor;
|
||||
import org.keycloak.test.framework.server.KeycloakUrls;
|
||||
|
||||
public class TimeOffsetSupplier implements Supplier<TimeOffSet, InjectTimeOffSet>, KeycloakServerConfigInterceptor<TimeOffSet, InjectTimeOffSet> {
|
||||
import java.util.Set;
|
||||
|
||||
public class TimeOffsetSupplier implements Supplier<TimeOffSet, InjectTimeOffSet> {
|
||||
@Override
|
||||
public Class<InjectTimeOffSet> getAnnotationClass() {
|
||||
return InjectTimeOffSet.class;
|
||||
@ -22,6 +22,11 @@ public class TimeOffsetSupplier implements Supplier<TimeOffSet, InjectTimeOffSet
|
||||
return TimeOffSet.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<Class<?>> dependencies() {
|
||||
return Set.of(HttpClient.class, RemoteProviders.class, KeycloakUrls.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TimeOffSet getValue(InstanceContext<TimeOffSet, InjectTimeOffSet> instanceContext) {
|
||||
var httpClient = instanceContext.getDependency(HttpClient.class);
|
||||
@ -55,11 +60,6 @@ public class TimeOffsetSupplier implements Supplier<TimeOffSet, InjectTimeOffSet
|
||||
@Override
|
||||
public int order() {
|
||||
return SupplierOrder.BEFORE_KEYCLOAK_SERVER;
|
||||
// Implementing the KeycloakServerConfigInterceptor is a workaround for RemoteProvidersSupplier to work
|
||||
}
|
||||
|
||||
@Override
|
||||
public KeycloakServerConfigBuilder intercept(KeycloakServerConfigBuilder serverConfig, InstanceContext<TimeOffSet, InjectTimeOffSet> instanceContext) {
|
||||
return serverConfig;
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user