diff --git a/core/deployment/src/main/java/io/quarkus/deployment/steps/ReflectiveHierarchyStep.java b/core/deployment/src/main/java/io/quarkus/deployment/steps/ReflectiveHierarchyStep.java index 90f64325cdfe3..1187de34273b6 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/steps/ReflectiveHierarchyStep.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/steps/ReflectiveHierarchyStep.java @@ -31,6 +31,7 @@ import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassFinalFieldsWritablePredicateBuildItem; import io.quarkus.deployment.builditem.nativeimage.ReflectiveHierarchyBuildItem; import io.quarkus.deployment.builditem.nativeimage.ReflectiveHierarchyIgnoreWarningBuildItem; +import io.quarkus.deployment.util.JandexUtil; public class ReflectiveHierarchyStep { @@ -123,17 +124,19 @@ private void addReflectiveHierarchy(CombinedIndexBuildItem combinedIndexBuildIte return; } - addClassTypeHierarchy(combinedIndexBuildItem, reflectiveHierarchyBuildItem, source, type.name(), + addClassTypeHierarchy(combinedIndexBuildItem, reflectiveHierarchyBuildItem, source, type.name(), type.name(), processedReflectiveHierarchies, unindexedClasses, finalFieldsWritable, reflectiveClass); for (ClassInfo subclass : combinedIndexBuildItem.getIndex().getAllKnownSubclasses(type.name())) { addClassTypeHierarchy(combinedIndexBuildItem, reflectiveHierarchyBuildItem, source, subclass.name(), + subclass.name(), processedReflectiveHierarchies, unindexedClasses, finalFieldsWritable, reflectiveClass); } for (ClassInfo subclass : combinedIndexBuildItem.getIndex().getAllKnownImplementors(type.name())) { addClassTypeHierarchy(combinedIndexBuildItem, reflectiveHierarchyBuildItem, source, subclass.name(), + subclass.name(), processedReflectiveHierarchies, unindexedClasses, finalFieldsWritable, reflectiveClass); } @@ -146,6 +149,7 @@ private void addReflectiveHierarchy(CombinedIndexBuildItem combinedIndexBuildIte ParameterizedType parameterizedType = (ParameterizedType) type; if (!reflectiveHierarchyBuildItem.getIgnoreTypePredicate().test(parameterizedType.name())) { addClassTypeHierarchy(combinedIndexBuildItem, reflectiveHierarchyBuildItem, source, parameterizedType.name(), + parameterizedType.name(), processedReflectiveHierarchies, unindexedClasses, finalFieldsWritable, reflectiveClass); } @@ -161,6 +165,7 @@ private void addClassTypeHierarchy(CombinedIndexBuildItem combinedIndexBuildItem ReflectiveHierarchyBuildItem reflectiveHierarchyBuildItem, String source, DotName name, + DotName initialName, Set processedReflectiveHierarchies, Map> unindexedClasses, Predicate finalFieldsWritable, @@ -198,7 +203,7 @@ private void addClassTypeHierarchy(CombinedIndexBuildItem combinedIndexBuildItem return; } - addClassTypeHierarchy(combinedIndexBuildItem, reflectiveHierarchyBuildItem, source, info.superName(), + addClassTypeHierarchy(combinedIndexBuildItem, reflectiveHierarchyBuildItem, source, info.superName(), initialName, processedReflectiveHierarchies, unindexedClasses, finalFieldsWritable, reflectiveClass); for (FieldInfo field : info.fields()) { @@ -210,7 +215,24 @@ private void addClassTypeHierarchy(CombinedIndexBuildItem combinedIndexBuildItem // also skip the outer class elements (unfortunately, we don't have a way to test for synthetic fields in Jandex) continue; } - addReflectiveHierarchy(combinedIndexBuildItem, reflectiveHierarchyBuildItem, source, field.type(), + Type fieldType = field.type(); + if ((field.type().kind() == Kind.TYPE_VARIABLE) && (info.typeParameters().size() == 1)) { + // handle the common case where the super type has a generic type in the class signature which + // is completely resolved by the sub type + // this could be made to handle more complex cases, but it is unlikely we will have to do so + if (field.type().asTypeVariable().identifier().equals(info.typeParameters().get(0).identifier())) { + try { + List types = JandexUtil.resolveTypeParameters(initialName, info.name(), + combinedIndexBuildItem.getIndex()); + if (types.size() == 1) { + fieldType = types.get(0); + } + } catch (IllegalArgumentException ignored) { + + } + } + } + addReflectiveHierarchy(combinedIndexBuildItem, reflectiveHierarchyBuildItem, source, fieldType, processedReflectiveHierarchies, unindexedClasses, finalFieldsWritable, reflectiveClass); } diff --git a/integration-tests/resteasy-jackson/src/main/java/io/quarkus/it/resteasy/jackson/generics/BaseClass.java b/integration-tests/resteasy-jackson/src/main/java/io/quarkus/it/resteasy/jackson/generics/BaseClass.java new file mode 100644 index 0000000000000..f16d39aa02609 --- /dev/null +++ b/integration-tests/resteasy-jackson/src/main/java/io/quarkus/it/resteasy/jackson/generics/BaseClass.java @@ -0,0 +1,23 @@ +package io.quarkus.it.resteasy.jackson.generics; + +public class BaseClass { + + private String baseVariable; + private T data; + + public String getBaseVariable() { + return this.baseVariable; + } + + public void setBaseVariable(String baseVariable) { + this.baseVariable = baseVariable; + } + + public T getData() { + return this.data; + } + + public void setData(T data) { + this.data = data; + } +} diff --git a/integration-tests/resteasy-jackson/src/main/java/io/quarkus/it/resteasy/jackson/generics/ExtendedClass.java b/integration-tests/resteasy-jackson/src/main/java/io/quarkus/it/resteasy/jackson/generics/ExtendedClass.java new file mode 100644 index 0000000000000..aab1dcdb9fe59 --- /dev/null +++ b/integration-tests/resteasy-jackson/src/main/java/io/quarkus/it/resteasy/jackson/generics/ExtendedClass.java @@ -0,0 +1,14 @@ +package io.quarkus.it.resteasy.jackson.generics; + +public class ExtendedClass extends BaseClass { + + private String extendedVariable; + + public String getExtendedVariable() { + return this.extendedVariable; + } + + public void setExtendedVariable(String extendedVariable) { + this.extendedVariable = extendedVariable; + } +} diff --git a/integration-tests/resteasy-jackson/src/main/java/io/quarkus/it/resteasy/jackson/generics/GenericsResource.java b/integration-tests/resteasy-jackson/src/main/java/io/quarkus/it/resteasy/jackson/generics/GenericsResource.java new file mode 100644 index 0000000000000..6b4862df1ece7 --- /dev/null +++ b/integration-tests/resteasy-jackson/src/main/java/io/quarkus/it/resteasy/jackson/generics/GenericsResource.java @@ -0,0 +1,22 @@ +package io.quarkus.it.resteasy.jackson.generics; + +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; + +@Path("/generics") +public class GenericsResource { + + @GET + @Produces(MediaType.APPLICATION_JSON) + public ExtendedClass testExtendedClass() { + final ExtendedClass c = new ExtendedClass(); + c.setBaseVariable("myBaseVariable"); + c.setExtendedVariable("myExtendedVariable"); + final MyData d = new MyData(); + d.setDataVariable("myData"); + c.setData(d); + return c; + } +} diff --git a/integration-tests/resteasy-jackson/src/main/java/io/quarkus/it/resteasy/jackson/generics/MyData.java b/integration-tests/resteasy-jackson/src/main/java/io/quarkus/it/resteasy/jackson/generics/MyData.java new file mode 100644 index 0000000000000..a1300cf7e95e1 --- /dev/null +++ b/integration-tests/resteasy-jackson/src/main/java/io/quarkus/it/resteasy/jackson/generics/MyData.java @@ -0,0 +1,14 @@ +package io.quarkus.it.resteasy.jackson.generics; + +public class MyData { + + private String dataVariable; + + public String getDataVariable() { + return this.dataVariable; + } + + public void setDataVariable(String dataVariable) { + this.dataVariable = dataVariable; + } +} diff --git a/integration-tests/resteasy-jackson/src/test/java/io/quarkus/it/resteasy/jackson/GenericsResourceIT.java b/integration-tests/resteasy-jackson/src/test/java/io/quarkus/it/resteasy/jackson/GenericsResourceIT.java new file mode 100644 index 0000000000000..6c6f7da676e50 --- /dev/null +++ b/integration-tests/resteasy-jackson/src/test/java/io/quarkus/it/resteasy/jackson/GenericsResourceIT.java @@ -0,0 +1,7 @@ +package io.quarkus.it.resteasy.jackson; + +import io.quarkus.test.junit.QuarkusIntegrationTest; + +@QuarkusIntegrationTest +public class GenericsResourceIT extends GenericsResourceTest { +} diff --git a/integration-tests/resteasy-jackson/src/test/java/io/quarkus/it/resteasy/jackson/GenericsResourceTest.java b/integration-tests/resteasy-jackson/src/test/java/io/quarkus/it/resteasy/jackson/GenericsResourceTest.java new file mode 100644 index 0000000000000..8cd4c0a604881 --- /dev/null +++ b/integration-tests/resteasy-jackson/src/test/java/io/quarkus/it/resteasy/jackson/GenericsResourceTest.java @@ -0,0 +1,25 @@ +package io.quarkus.it.resteasy.jackson; + +import static io.restassured.RestAssured.given; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; + +import io.quarkus.it.resteasy.jackson.generics.ExtendedClass; +import io.quarkus.test.junit.QuarkusTest; + +@QuarkusTest +public class GenericsResourceTest { + + @Test + public void testExtendedEndpoint() { + ExtendedClass response = given() + .when().get("/generics") + .then() + .statusCode(200) + .extract().body().as(ExtendedClass.class); + assertEquals("myBaseVariable", response.getBaseVariable()); + assertEquals("myExtendedVariable", response.getExtendedVariable()); + assertEquals("myData", response.getData().getDataVariable()); + } +}