Skip to content

Commit bf2cb03

Browse files
author
Soroosh Sarabadani
committed
Test annotation processor works correctly when an intermediary class exists.
1 parent d969187 commit bf2cb03

File tree

6 files changed

+121
-14
lines changed

6 files changed

+121
-14
lines changed

operator-framework/src/main/java/io/javaoperatorsdk/operator/processing/ControllerAnnotationProcessor.java

Lines changed: 35 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818
import javax.tools.JavaFileObject;
1919
import java.io.IOException;
2020
import java.io.PrintWriter;
21+
import java.util.ArrayList;
22+
import java.util.List;
2123
import java.util.Set;
2224

2325
@SupportedAnnotationTypes(
@@ -32,40 +34,36 @@ public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment
3234
= roundEnv.getElementsAnnotatedWith(annotation);
3335
annotatedElements.stream().filter(element -> element instanceof Symbol.ClassSymbol)
3436
.map(e -> (Symbol.ClassSymbol) e)
35-
.forEach(controllerClassSymbol -> generateDoneableClass(controllerClassSymbol));
37+
.forEach(this::generateDoneableClass);
3638
}
3739
return false;
3840
}
3941

4042
private void generateDoneableClass(Symbol.ClassSymbol controllerClassSymbol) {
4143
try {
42-
// TODO: the resourceType retrieval logic is currently very fragile, done for testing purposes and need to be improved to cover all possible conditions
43-
final Type controllerType = controllerClassSymbol
44-
.getInterfaces()
45-
.stream()
46-
.filter(i -> i.toString()
47-
.startsWith(ResourceController.class.getCanonicalName())
48-
)
49-
.findFirst()
50-
.orElseThrow(() -> new Exception("ResourceController is not implemented by " + controllerClassSymbol.toString()));
44+
final TypeMirror resourceType = findResourceType(controllerClassSymbol);
45+
Symbol.ClassSymbol customerResourceSymbol = (Symbol.ClassSymbol) processingEnv
46+
.getElementUtils()
47+
.getTypeElement(resourceType.toString());
5148

52-
final TypeMirror resourceType = controllerType.getTypeArguments().get(0);
53-
Symbol.ClassSymbol customerResourceSymbol = (Symbol.ClassSymbol) processingEnv.getElementUtils().getTypeElement(resourceType.toString());
5449
JavaFileObject builderFile = processingEnv.getFiler()
5550
.createSourceFile(customerResourceSymbol.className() + "Doneable");
51+
5652
try (PrintWriter out = new PrintWriter(builderFile.openWriter())) {
5753
final MethodSpec constructor = MethodSpec.constructorBuilder()
5854
.addModifiers(Modifier.PUBLIC)
5955
.addParameter(TypeName.get(resourceType), "resource")
6056
.addParameter(Function.class, "function")
6157
.addStatement("super(resource,function)")
6258
.build();
59+
6360
final TypeSpec typeSpec = TypeSpec.classBuilder(customerResourceSymbol.name + "Doneable")
6461
.addAnnotation(RegisterForReflection.class)
6562
.superclass(ParameterizedTypeName.get(ClassName.get(CustomResourceDoneable.class), TypeName.get(resourceType)))
6663
.addModifiers(Modifier.PUBLIC)
6764
.addMethod(constructor)
6865
.build();
66+
6967
JavaFile file = JavaFile.builder(customerResourceSymbol.packge().fullname.toString(), typeSpec)
7068
.build();
7169
file.writeTo(out);
@@ -76,4 +74,29 @@ private void generateDoneableClass(Symbol.ClassSymbol controllerClassSymbol) {
7674
ex.printStackTrace();
7775
}
7876
}
77+
78+
private TypeMirror findResourceType(Symbol.ClassSymbol controllerClassSymbol) throws Exception {
79+
final Type controllerType = collectAllInterfaces(controllerClassSymbol)
80+
.stream()
81+
.filter(i -> i.toString()
82+
.startsWith(ResourceController.class.getCanonicalName())
83+
)
84+
.findFirst()
85+
.orElseThrow(() -> new Exception("ResourceController is not implemented by " + controllerClassSymbol.toString()));
86+
87+
final TypeMirror resourceType = controllerType.getTypeArguments().get(0);
88+
return resourceType;
89+
}
90+
91+
private List<Type> collectAllInterfaces(Symbol.ClassSymbol classSymbol) {
92+
List<Type> interfaces = new ArrayList<>(classSymbol.getInterfaces());
93+
Symbol.ClassSymbol superclass = (Symbol.ClassSymbol) processingEnv.getTypeUtils().asElement(classSymbol.getSuperclass());
94+
95+
while (superclass != null) {
96+
interfaces.addAll(superclass.getInterfaces());
97+
superclass = (Symbol.ClassSymbol) processingEnv.getTypeUtils().asElement(superclass.getSuperclass());
98+
}
99+
100+
return interfaces;
101+
}
79102
}

operator-framework/src/test/java/io/javaoperatorsdk/operator/processing/ControllerAnnotationProcessorTest.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,20 @@ public void generateCorrectDoneableClassIfInterfaceIsSecond() {
1717
final JavaFileObject expectedResource = JavaFileObjects.forResource("ControllerImplemented2InterfacesExpected.java");
1818
JavaFileObjectSubject.assertThat(compilation.generatedSourceFiles().get(0)).hasSourceEquivalentTo(expectedResource);
1919
}
20+
21+
@Test
22+
public void generateCorrectDoneableClassIfThereIsAbstractBaseController() {
23+
24+
Compilation compilation = Compiler.javac()
25+
.withProcessors(new ControllerAnnotationProcessor())
26+
.compile(
27+
JavaFileObjects.forResource("AbstractController.java"),
28+
JavaFileObjects.forResource("ControllerImplementedIntermediateAbstractClass.java")
29+
);
30+
CompilationSubject.assertThat(compilation).succeeded();
31+
32+
final JavaFileObject expectedResource = JavaFileObjects.forResource("ControllerImplementedIntermediateAbstractClassExpected.java");
33+
JavaFileObjectSubject.assertThat(compilation.generatedSourceFiles().get(0)).hasSourceEquivalentTo(expectedResource);
34+
35+
}
2036
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package io;
2+
3+
import io.fabric8.kubernetes.client.CustomResource;
4+
import io.fabric8.kubernetes.client.CustomResourceDoneable;
5+
import io.javaoperatorsdk.operator.api.Context;
6+
import io.javaoperatorsdk.operator.api.Controller;
7+
import io.javaoperatorsdk.operator.api.ResourceController;
8+
import io.javaoperatorsdk.operator.api.UpdateControl;
9+
import io.fabric8.kubernetes.api.model.Secret;
10+
import io.fabric8.kubernetes.api.model.SecretBuilder;
11+
import io.fabric8.kubernetes.client.KubernetesClient;
12+
import org.apache.commons.lang3.RandomStringUtils;
13+
import org.slf4j.Logger;
14+
import org.slf4j.LoggerFactory;
15+
16+
17+
import java.io.Serializable;
18+
19+
import static java.lang.String.format;
20+
21+
22+
public abstract class AbstractController implements Serializable, ResourceController<AbstractController.MyCustomResource> {
23+
public static class MyCustomResource extends CustomResource {
24+
25+
}
26+
}

operator-framework/src/test/resources/ControllerImplemented2Interfaces.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,10 @@
1818

1919
import static java.lang.String.format;
2020

21-
@Controller(crdName = "schemas.mysql.sample.javaoperatorsdk")
21+
@Controller(crdName = "test.crd")
2222
public class ControllerImplemented2Interfaces implements Serializable, ResourceController<ControllerImplemented2Interfaces.MyCustomResource> {
2323

2424
public static class MyCustomResource extends CustomResource {
25-
2625
}
2726

2827
@Override
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package io;
2+
3+
import io.fabric8.kubernetes.client.CustomResource;
4+
import io.fabric8.kubernetes.client.CustomResourceDoneable;
5+
import io.javaoperatorsdk.operator.api.Context;
6+
import io.javaoperatorsdk.operator.api.Controller;
7+
import io.javaoperatorsdk.operator.api.ResourceController;
8+
import io.javaoperatorsdk.operator.api.UpdateControl;
9+
import io.fabric8.kubernetes.api.model.Secret;
10+
import io.fabric8.kubernetes.api.model.SecretBuilder;
11+
import io.fabric8.kubernetes.client.KubernetesClient;
12+
import org.apache.commons.lang3.RandomStringUtils;
13+
import org.slf4j.Logger;
14+
import org.slf4j.LoggerFactory;
15+
16+
17+
import java.io.Serializable;
18+
19+
import static java.lang.String.format;
20+
21+
@Controller(crdName = "test.crd")
22+
public class ControllerImplementedIntermediateAbstractClass extends AbstractController implements Serializable {
23+
24+
public UpdateControl<AbstractController.MyCustomResource> createOrUpdateResource(AbstractController.MyCustomResource customResource, Context<AbstractController.MyCustomResource> context) {
25+
return UpdateControl.updateCustomResource(null);
26+
}
27+
28+
public boolean deleteResource(AbstractController.MyCustomResource customResource, Context<AbstractController.MyCustomResource> context) {
29+
return false;
30+
}
31+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package io;
2+
3+
import io.fabric8.kubernetes.api.builder.Function;
4+
import io.fabric8.kubernetes.client.CustomResourceDoneable;
5+
import io.quarkus.runtime.annotations.RegisterForReflection;
6+
7+
@RegisterForReflection
8+
public class MyCustomResourceDoneable extends CustomResourceDoneable<AbstractController.MyCustomResource> {
9+
public MyCustomResourceDoneable(AbstractController.MyCustomResource resource, Function function) {
10+
super(resource, function);
11+
}
12+
}

0 commit comments

Comments
 (0)