Skip to content

Commit

Permalink
Testsuite PoC - Refactor registry and suppliers around annotations, a…
Browse files Browse the repository at this point in the history
…nd make lookup of dependency by ref more generic (keycloak#32161)

Signed-off-by: stianst <[email protected]>
  • Loading branch information
stianst authored Aug 19, 2024
1 parent 799201f commit 841e279
Show file tree
Hide file tree
Showing 19 changed files with 175 additions and 118 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,6 @@
@KeycloakIntegrationTest
public class OAuthClientTest {

@InjectRealm
ManagedRealm realm; // Need to specify realm as otherwise there's a bug when annotation is not present

@InjectUser(config = UserConfig.class)
ManagedUser user;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@

LifeCycle lifecycle() default LifeCycle.CLASS;

String ref() default "default";
String ref() default "";

String realmRef() default "default";
String realmRef() default "";
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,5 @@

LifeCycle lifecycle() default LifeCycle.CLASS;

String ref() default "default";
String ref() default "";
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@

LifeCycle lifecycle() default LifeCycle.CLASS;

String ref() default "default";
String ref() default "";

String realmRef() default "default";
String realmRef() default "";
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package org.keycloak.test.framework.injection;

public interface AnnotationFields {

String CONFIG = "config";
String LIFECYCLE = "lifecycle";
String REF = "ref";

String REALM_REF = "realmRef";

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package org.keycloak.test.framework.injection;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class DefaultAnnotationProxy implements InvocationHandler {

private final Class<?> annotationClass;

public <S> DefaultAnnotationProxy(Class<?> annotationClass) {
this.annotationClass = annotationClass;
}

public static <S> S proxy(Class<S> annotationClass) {
return (S) Proxy.newProxyInstance(DefaultAnnotationProxy.class.getClassLoader(), new Class<?>[]{annotationClass}, new DefaultAnnotationProxy(annotationClass));
}

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (method.getName().equals("annotationType")) {
return annotationClass;
} else {
return annotationClass.getMethod(method.getName()).getDefaultValue();
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -14,36 +14,25 @@ public class InstanceContext<T, A extends Annotation> {
private final Set<InstanceContext<T, A>> dependencies = new HashSet<>();
private T value;
private Class<? extends T> requestedValueType;
private final Class<?> config;
private LifeCycle lifeCycle;
private final String ref;
private final String realmRef;
private final Map<String, Object> notes = new HashMap<>();

public InstanceContext(Registry registry, Supplier<T, A> supplier, A annotation, Class<? extends T> requestedValueType) {
this.registry = registry;
this.supplier = supplier;
this.annotation = annotation;
this.requestedValueType = requestedValueType;
this.config = (Class<?>) supplier.getAnnotationElementValue(annotation, SupplierHelpers.CONFIG);
this.lifeCycle = (LifeCycle) supplier.getAnnotationElementValue(annotation, SupplierHelpers.LIFECYCLE);
this.ref = (String) supplier.getAnnotationElementValue(annotation, SupplierHelpers.REF);
this.realmRef = (String) supplier.getAnnotationElementValue(annotation, SupplierHelpers.REALM_REF);
this.lifeCycle = supplier.getLifeCycle(annotation);
this.ref = StringUtil.convertEmptyToNull(supplier.getRef(annotation));
}

public InstanceContext(Registry registry, Supplier<T, A> supplier, Class<? extends T> requestedValueType, String ref, Class<?> config) {
this.registry = registry;
this.supplier = supplier;
this.annotation = null;
this.requestedValueType = requestedValueType;
this.config = config;
this.lifeCycle = supplier.getDefaultLifecycle();
this.ref = ref;
this.realmRef = "";
public <D> D getDependency(Class<D> typeClazz) {
return getDependency(typeClazz, null);
}

public <D> D getDependency(Class<D> typeClazz) {
return registry.getDependency(typeClazz, this);
public <D> D getDependency(Class<D> typeClazz, String ref) {
return registry.getDependency(typeClazz, ref, this);
}

public Registry getRegistry() {
Expand All @@ -66,10 +55,6 @@ public Class<? extends T> getRequestedValueType() {
return requestedValueType;
}

public Class<?> getConfig() {
return config;
}

public LifeCycle getLifeCycle() {
return lifeCycle;
}
Expand All @@ -78,10 +63,6 @@ public String getRef() {
return ref;
}

public String getRealmRef() {
return realmRef;
}

public A getAnnotation() {
return annotation;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
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;
Expand Down Expand Up @@ -39,17 +40,18 @@ public void setCurrentContext(ExtensionContext currentContext) {
this.currentContext = currentContext;
}

public <T> T getDependency(Class<T> typeClass, InstanceContext dependent) {
public <T> T getDependency(Class<T> typeClass, String ref, InstanceContext dependent) {
ref = StringUtil.convertEmptyToNull(ref);
T dependency;
dependency = getDeployedDependency(typeClass, dependent);
dependency = getDeployedDependency(typeClass, ref, dependent);
if (dependency != null) {
return dependency;
} else {
dependency = getRequestedDependency(typeClass, dependent);
dependency = getRequestedDependency(typeClass, ref, dependent);
if(dependency != null) {
return dependency;
} else {
dependency = getUnConfiguredDependency(typeClass, dependent);
dependency = getUnConfiguredDependency(typeClass, ref, dependent);
if(dependency != null) {
return dependency;
}
Expand All @@ -59,14 +61,8 @@ public <T> T getDependency(Class<T> typeClass, InstanceContext dependent) {
throw new RuntimeException("Dependency not found: " + typeClass);
}

private <T> T getDeployedDependency(Class<T> typeClass, InstanceContext dependent) {
InstanceContext dependency;
if(!dependent.getRealmRef().equals("")) {
dependency = getDeployedInstance(typeClass, dependent.getRealmRef());
} else {
dependency = getDeployedInstance(typeClass);
}

private <T> T getDeployedDependency(Class<T> typeClass, String ref, InstanceContext dependent) {
InstanceContext dependency = getDeployedInstance(typeClass, ref);
if (dependency != null) {
dependency.registerDependency(dependent);

Expand All @@ -81,16 +77,10 @@ private <T> T getDeployedDependency(Class<T> typeClass, InstanceContext dependen
return null;
}

private <T> T getRequestedDependency(Class<T> typeClass, InstanceContext dependent) {
InstanceContext dependency;
RequestedInstance requestedDependency;
if(!dependent.getRealmRef().equals("")) {
requestedDependency = getRequestedInstance(typeClass, dependent.getRealmRef());
} else {
requestedDependency = getRequestedInstance(typeClass);
}
private <T> T getRequestedDependency(Class<T> typeClass, String ref, InstanceContext dependent) {
RequestedInstance requestedDependency = getRequestedInstance(typeClass, ref);
if (requestedDependency != null) {
dependency = new InstanceContext<Object, Annotation>(this, requestedDependency.getSupplier(), requestedDependency.getAnnotation(), requestedDependency.getValueType());
InstanceContext dependency = new InstanceContext<Object, Annotation>(this, requestedDependency.getSupplier(), requestedDependency.getAnnotation(), requestedDependency.getValueType());
dependency.setValue(requestedDependency.getSupplier().getValue(dependency));
dependency.registerDependency(dependent);
deployedInstances.add(dependency);
Expand All @@ -108,16 +98,13 @@ private <T> T getRequestedDependency(Class<T> typeClass, InstanceContext depende
return null;
}

private <T> T getUnConfiguredDependency(Class<T> typeClass, InstanceContext dependent) {
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();
if(!dependent.getRealmRef().equals("")) {
dependency = new InstanceContext(this, supplier, typeClass, dependent.getRealmRef(), DefaultRealmConfig.class);
} else {
dependency = new InstanceContext(this, supplier, null, typeClass);
}
Annotation defaultAnnotation = DefaultAnnotationProxy.proxy(supplier.getAnnotationClass());
dependency = new InstanceContext(this, supplier, defaultAnnotation, typeClass);

dependency.registerDependency(dependent);
dependency.setValue(supplier.getValue(dependency));
Expand Down Expand Up @@ -258,7 +245,7 @@ public void close() {
Supplier supplier = i.getSupplier();
if (supplier.getAnnotationClass().equals(a.annotationType())
&& valueType.isAssignableFrom(i.getValue().getClass())
&& supplier.getAnnotationElementValue(a, SupplierHelpers.REF).equals(i.getRef()) ) {
&& Objects.equals(supplier.getRef(a), i.getRef()) ) {
return i;
}
}
Expand All @@ -284,7 +271,7 @@ private InstanceContext getDeployedInstance(RequestedInstance requestedInstance)
String requestedRef = requestedInstance.getRef();
Class requestedValueType = requestedInstance.getValueType();
for (InstanceContext<?, ?> i : deployedInstances) {
if(!i.getRef().equals(requestedRef)) {
if(!Objects.equals(i.getRef(), requestedRef)) {
continue;
}

Expand Down Expand Up @@ -345,20 +332,16 @@ private void loadSuppliers() {
}
}

private InstanceContext getDeployedInstance(Class typeClass) {
return deployedInstances.stream().filter(i -> i.getSupplier().getValueType().equals(typeClass)).findFirst().orElse(null);
}

private InstanceContext getDeployedInstance(Class typeClass, String realmRef) {
return deployedInstances.stream().filter(i -> i.getSupplier().getValueType().equals(typeClass)).filter(j -> j.getRef().equals(realmRef)).findFirst().orElse(null);
}

private RequestedInstance getRequestedInstance(Class typeClass) {
return requestedInstances.stream().filter(i -> i.getSupplier().getValueType().equals(typeClass)).findFirst().orElse(null);
private InstanceContext getDeployedInstance(Class typeClass, String ref) {
return deployedInstances.stream()
.filter(i -> i.getSupplier().getValueType().equals(typeClass) && Objects.equals(i.getRef(), ref))
.findFirst().orElse(null);
}

private RequestedInstance getRequestedInstance(Class typeClass, String realmRef) {
return requestedInstances.stream().filter(i -> i.getSupplier().getValueType().equals(typeClass)).filter(j -> j.getRef().equals(realmRef)).findFirst().orElse(null);
private RequestedInstance getRequestedInstance(Class typeClass, String ref) {
return requestedInstances.stream()
.filter(i -> i.getSupplier().getValueType().equals(typeClass) && Objects.equals(i.getRef(), ref))
.findFirst().orElse(null);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ public RequestedInstance(Supplier<T, A> supplier, A annotation, Class<? extends
this.supplier = supplier;
this.annotation = annotation;
this.valueType = valueType;
this.lifeCycle = (LifeCycle) supplier.getAnnotationElementValue(annotation, SupplierHelpers.LIFECYCLE);
this.ref = (String) supplier.getAnnotationElementValue(annotation, SupplierHelpers.REF);
this.lifeCycle = supplier.getLifeCycle(annotation);
this.ref = StringUtil.convertEmptyToNull(supplier.getRef(annotation));
}

public Supplier<T, A> getSupplier() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package org.keycloak.test.framework.injection;

public class StringUtil {

public static String convertEmptyToNull(String s) {
return s != null && s.isEmpty() ? null : s;
}

}
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
package org.keycloak.test.framework.injection;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Optional;

public interface Supplier<T, S extends Annotation> {

Expand All @@ -13,32 +10,12 @@ public interface Supplier<T, S extends Annotation> {

T getValue(InstanceContext<T, S> instanceContext);

default Object getAnnotationElementValue(S annotation, String annotationAttribute) {
if (annotation != null) {
Optional<Method> annotationMethod = Arrays.stream(annotation.annotationType().getMethods()).filter(m -> m.getName().equals(annotationAttribute)).findFirst();
if (annotationMethod.isPresent()) {
try {
return annotationMethod.get().invoke(annotation);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
return getAnnotationElementValue(annotationAttribute);
default String getRef(S annotation) {
return StringUtil.convertEmptyToNull(SupplierHelpers.getAnnotationField(annotation, AnnotationFields.REF));
}

default Object getAnnotationElementValue(String annotationAttribute) {
switch (annotationAttribute) {
case SupplierHelpers.LIFECYCLE -> {
return this.getDefaultLifecycle();
}
case SupplierHelpers.REF, SupplierHelpers.REALM_REF -> {
return "";
}
default -> {
return null;
}
}
default LifeCycle getLifeCycle(S annotation) {
return SupplierHelpers.getAnnotationField(annotation, AnnotationFields.LIFECYCLE, getDefaultLifecycle());
}

default LifeCycle getDefaultLifecycle() {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
package org.keycloak.test.framework.injection;

import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;

public class SupplierHelpers {

public static final String CONFIG = "config";
public static final String LIFECYCLE = "lifecycle";
public static final String REF = "ref";
public static final String REALM_REF = "realmRef";

public static <T> T getInstance(Class<T> clazz) {
try {
Constructor<T> declaredConstructor = clazz.getDeclaredConstructor();
Expand All @@ -18,4 +15,29 @@ public static <T> T getInstance(Class<T> clazz) {
throw new RuntimeException(e);
}
}

public static <T> T getAnnotationField(Annotation annotation, String name, T defaultValue) {
T value = getAnnotationField(annotation, name);
return value != null ? value : defaultValue;
}

public static <T> T getAnnotationField(Annotation annotation, String name) {
if (annotation != null) {
for (Method m : annotation.annotationType().getMethods()) {
if (m.getName().equals(name)) {
try {
return (T) m.invoke(annotation);
} catch (Exception e){
throw new RuntimeException(e);
}
}
}
}
return null;
}

public static String createName(InstanceContext<?, ?> instanceContext) {
return instanceContext.getRef() != null ? instanceContext.getRef() : "default";
}

}
Loading

0 comments on commit 841e279

Please sign in to comment.