Skip to content

feat: cover more cases when trying to determine generic types #1276

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Jun 9, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ public class AnnotationControllerConfiguration<R extends HasMetadata>
protected final Reconciler<R> reconciler;
private final ControllerConfiguration annotation;
private List<DependentResourceSpec> specs;
private Class<R> resourceClass;

public AnnotationControllerConfiguration(Reconciler<R> reconciler) {
this.reconciler = reconciler;
Expand Down Expand Up @@ -81,7 +82,12 @@ public Set<String> getNamespaces() {
@Override
@SuppressWarnings("unchecked")
public Class<R> getResourceClass() {
return (Class<R>) Utils.getFirstTypeArgumentFromInterface(reconciler.getClass());
if (resourceClass == null) {
resourceClass =
(Class<R>) Utils.getFirstTypeArgumentFromSuperClassOrInterface(reconciler.getClass(),
Reconciler.class);
}
return resourceClass;
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,15 @@ default Optional<Duration> reconciliationMaxInterval() {
return Optional.of(Duration.ofHours(10L));
}

@SuppressWarnings("unused")
default ConfigurationService getConfigurationService() {
return ConfigurationServiceProvider.instance();
}

@SuppressWarnings("unchecked")
@Override
default Class<R> getResourceClass() {
return (Class<R>) Utils.getFirstTypeArgumentFromSuperClassOrInterface(getClass(),
ControllerConfiguration.class);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ default String getLabelSelector() {

@SuppressWarnings("unchecked")
default Class<R> getResourceClass() {
return (Class<R>) Utils.getFirstTypeArgumentFromInterface(getClass());
return (Class<R>) Utils.getFirstTypeArgumentFromSuperClassOrInterface(getClass(),
ResourceConfiguration.class);
}

default Set<String> getNamespaces() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,15 @@
import java.lang.reflect.Type;
import java.text.SimpleDateFormat;
import java.time.Instant;
import java.util.Arrays;
import java.util.Date;
import java.util.Properties;
import java.util.function.Function;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import io.javaoperatorsdk.operator.OperatorException;

public class Utils {

private static final Logger log = LoggerFactory.getLogger(Utils.class);
Expand Down Expand Up @@ -59,6 +61,8 @@ public static Version loadFromProperties() {
builtTime);
}

@SuppressWarnings("unused")
// this is used in the Quarkus extension
public static boolean isValidateCustomResourcesEnvVarSet() {
return System.getProperty(CHECK_CRD_ENV_KEY) != null;
}
Expand Down Expand Up @@ -89,16 +93,55 @@ static boolean getBooleanFromSystemPropsOrDefault(String propertyName, boolean d
}

public static Class<?> getFirstTypeArgumentFromExtendedClass(Class<?> clazz) {
Type type = clazz.getGenericSuperclass();
return (Class<?>) ((ParameterizedType) type).getActualTypeArguments()[0];
try {
Type type = clazz.getGenericSuperclass();
return (Class<?>) ((ParameterizedType) type).getActualTypeArguments()[0];
} catch (Exception e) {
throw new RuntimeException("Couldn't retrieve generic parameter type from "
+ clazz.getSimpleName()
+ " because it doesn't extend a class that is parameterized with the type we want to retrieve",
e);
}
}

public static Class<?> getFirstTypeArgumentFromInterface(Class<?> clazz) {
ParameterizedType type = (ParameterizedType) clazz.getGenericInterfaces()[0];
return (Class<?>) type.getActualTypeArguments()[0];
public static Class<?> getFirstTypeArgumentFromInterface(Class<?> clazz,
Class<?> expectedImplementedInterface) {
return Arrays.stream(clazz.getGenericInterfaces())
.filter(type -> type.getTypeName().startsWith(expectedImplementedInterface.getName())
&& type instanceof ParameterizedType)
.map(ParameterizedType.class::cast)
.findFirst()
.map(t -> (Class<?>) t.getActualTypeArguments()[0])
.orElseThrow(() -> new RuntimeException(
"Couldn't retrieve generic parameter type from " + clazz.getSimpleName()
+ " because it doesn't implement "
+ expectedImplementedInterface.getSimpleName()
+ " directly"));
}

public static <C, T> T valueOrDefault(C annotation, Function<C, T> mapper, T defaultValue) {
return annotation == null ? defaultValue : mapper.apply(annotation);
public static Class<?> getFirstTypeArgumentFromSuperClassOrInterface(Class<?> clazz,
Class<?> expectedImplementedInterface) {
// first check super class if it exists
try {
final Class<?> superclass = clazz.getSuperclass();
if (!superclass.equals(Object.class)) {
try {
return getFirstTypeArgumentFromExtendedClass(clazz);
} catch (Exception e) {
// try interfaces
try {
return getFirstTypeArgumentFromInterface(clazz, expectedImplementedInterface);
} catch (Exception ex) {
// try on the parent
return getFirstTypeArgumentFromSuperClassOrInterface(superclass,
expectedImplementedInterface);
}
}
}
return getFirstTypeArgumentFromInterface(clazz, expectedImplementedInterface);
} catch (Exception e) {
throw new OperatorException(
"Couldn't retrieve generic parameter type from " + clazz.getSimpleName(), e);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import io.fabric8.kubernetes.api.model.HasMetadata;
import io.javaoperatorsdk.operator.api.config.DefaultResourceConfiguration;
import io.javaoperatorsdk.operator.api.config.ResourceConfiguration;
import io.javaoperatorsdk.operator.api.config.Utils;
import io.javaoperatorsdk.operator.api.reconciler.EventSourceContext;
import io.javaoperatorsdk.operator.processing.event.source.SecondaryToPrimaryMapper;
import io.javaoperatorsdk.operator.processing.event.source.informer.Mappers;
Expand Down Expand Up @@ -160,4 +161,10 @@ static <R extends HasMetadata> InformerConfigurationBuilder<R> from(
.withNamespacesInheritedFromController(eventSourceContext);
}

@SuppressWarnings("unchecked")
@Override
default Class<R> getResourceClass() {
return (Class<R>) Utils.getFirstTypeArgumentFromSuperClassOrInterface(getClass(),
InformerConfiguration.class);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,18 @@

import io.javaoperatorsdk.operator.sample.simple.TestCustomResource;

import static org.junit.jupiter.api.Assertions.*;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;

class ControllerConfigurationTest {

@Test
void getCustomResourceClass() {
final ControllerConfiguration<TestCustomResource> lambdasCannotBeUsedToExtractGenericParam =
() -> null;
assertThrows(RuntimeException.class,
lambdasCannotBeUsedToExtractGenericParam::getResourceClass);

final ControllerConfiguration<TestCustomResource> conf = new ControllerConfiguration<>() {
@Override
public String getAssociatedReconcilerClassName() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,12 @@

import org.junit.jupiter.api.Test;

import io.fabric8.kubernetes.api.model.ConfigMap;
import io.fabric8.kubernetes.api.model.HasMetadata;
import io.fabric8.kubernetes.api.model.apps.Deployment;
import io.javaoperatorsdk.operator.api.reconciler.Context;
import io.javaoperatorsdk.operator.api.reconciler.Reconciler;
import io.javaoperatorsdk.operator.api.reconciler.UpdateControl;
import io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResource;
import io.javaoperatorsdk.operator.api.reconciler.dependent.ReconcileResult;
import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependentResource;
Expand Down Expand Up @@ -84,10 +88,28 @@ void getsFirstTypeArgumentFromExtendedClass() {

@Test
void getsFirstTypeArgumentFromInterface() {
assertThat(Utils.getFirstTypeArgumentFromInterface(TestDependentResource.class))
assertThat(Utils.getFirstTypeArgumentFromInterface(TestDependentResource.class,
DependentResource.class))
.isEqualTo(Deployment.class);
}

@Test
void getsFirstTypeArgumentFromInterfaceFromParent() {
assertThat(Utils.getFirstTypeArgumentFromSuperClassOrInterface(ConcreteReconciler.class,
Reconciler.class)).isEqualTo(ConfigMap.class);
}

public abstract static class AbstractReconciler<P extends HasMetadata> implements Reconciler<P> {
}

public static class ConcreteReconciler extends AbstractReconciler<ConfigMap> {
@Override
public UpdateControl<ConfigMap> reconcile(ConfigMap resource, Context<ConfigMap> context)
throws Exception {
return null;
}
}

public static class TestDependentResource
implements DependentResource<Deployment, TestCustomResource> {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ public class RuntimeControllerMetadata {
RECONCILERS_RESOURCE_PATH, Reconciler.class, HasMetadata.class);
}

static <R extends HasMetadata> Class<R> getResourceClass(
Reconciler<R> reconciler) {
@SuppressWarnings("unchecked")
static <R extends HasMetadata> Class<R> getResourceClass(Reconciler<R> reconciler) {
final Class<? extends HasMetadata> resourceClass =
controllerToCustomResourceMappings.get(reconciler.getClass());
if (resourceClass == null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,16 +81,15 @@ void getDependentResources() {

@Test
void missingAnnotationThrowsException() {
Assertions.assertThrows(OperatorException.class, () -> {
new AnnotationControllerConfiguration<>(new MissingAnnotationReconciler());
});
Assertions.assertThrows(OperatorException.class,
() -> new AnnotationControllerConfiguration<>(new MissingAnnotationReconciler()));
}

@SuppressWarnings("rawtypes")
private DependentResourceSpec findByName(
List<DependentResourceSpec> dependentResourceSpecList, String name) {
return dependentResourceSpecList.stream().filter(d -> d.getName().equals(name)).findFirst()
.get();
.orElseThrow();
}

@SuppressWarnings("rawtypes")
Expand Down