diff --git a/docs/src/main/asciidoc/resteasy-reactive.adoc b/docs/src/main/asciidoc/resteasy-reactive.adoc index a4bcee4e7dd4d..b749c3ec8053c 100644 --- a/docs/src/main/asciidoc/resteasy-reactive.adoc +++ b/docs/src/main/asciidoc/resteasy-reactive.adoc @@ -1643,7 +1643,7 @@ Assuming a `Record` looks like: ---- public class Record { - // id is a mandatory field for the generation of web links + // the class must contain/inherit either and `id` field or an `@Id` annotated field private int id; public Record() { diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive-links/deployment/src/main/java/io/quarkus/resteasy/reactive/links/deployment/LinksProcessor.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive-links/deployment/src/main/java/io/quarkus/resteasy/reactive/links/deployment/LinksProcessor.java index 00bbea81524ff..2066c8c497d4a 100644 --- a/extensions/resteasy-reactive/quarkus-resteasy-reactive-links/deployment/src/main/java/io/quarkus/resteasy/reactive/links/deployment/LinksProcessor.java +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive-links/deployment/src/main/java/io/quarkus/resteasy/reactive/links/deployment/LinksProcessor.java @@ -4,10 +4,12 @@ import static org.jboss.resteasy.reactive.common.processor.ResteasyReactiveDotNames.OBJECT_NAME; import java.util.HashSet; +import java.util.LinkedList; import java.util.List; import java.util.Set; import java.util.function.Function; import java.util.function.Supplier; +import java.util.stream.Collectors; import org.jboss.jandex.AnnotationInstance; import org.jboss.jandex.ClassInfo; @@ -146,6 +148,9 @@ private RuntimeValue implementPathParameterValueGetter for (LinkInfo linkInfo : linkInfos) { String entityType = linkInfo.getEntityType(); DotName className = DotName.createSimple(entityType); + + validateClassHasFieldId(index, entityType); + for (String parameterName : linkInfo.getPathParameters()) { FieldInfoSupplier byParamName = new FieldInfoSupplier(c -> c.field(parameterName), className, index); @@ -153,7 +158,6 @@ private RuntimeValue implementPathParameterValueGetter // We later map that getter's accessor with an entity type. // If a field is inside a parent class, the getter accessor will be mapped to each subclass which // has REST links that need access to that field. - FieldInfo fieldInfo = byParamName.get(); if ((fieldInfo == null) && parameterName.equals("id")) { // this is a special case where we want to go through the fields of the class @@ -188,14 +192,49 @@ private RuntimeValue implementPathParameterValueGetter entityType, getterMetadata.getFieldName(), getterMetadata.getGetterAccessorName()); } } - } - getterAccessorsContainerRecorder.validateContainer(getterAccessorsContainer); } return getterAccessorsContainer; } + /** + * Validates if the given classname contains a field `id` or annotated with `@Id` + * + * @throws IllegalStateException if the classname does not contain any sort of field identifier + */ + private void validateClassHasFieldId(IndexView index, String entityType) { + // create a new independent class name that we can override + DotName className = DotName.createSimple(entityType); + List classFields = index.getClassByName(className).fields(); + List classAnnotations = index.getClassByName(className).annotations(); + + LinkedList allFields = new LinkedList<>(classFields); + LinkedList allAnnotations = new LinkedList<>(classAnnotations); + + // also get fields and annotations from all super classes + DotName superClassName = index.getClassByName(className).superName(); + while (superClassName != null) { + List allSuperFields = index.getClassByName(superClassName).fields(); + List allSuperAnnotations = index.getClassByName(superClassName).annotations(); + allFields.addAll(allSuperFields); + allAnnotations.addAll(allSuperAnnotations); + + className = superClassName; + superClassName = index.getClassByName(className).superName(); + } + + List fieldsNamedId = allFields.stream().filter(f -> f.name().equals("id")).collect(Collectors.toList()); + List fieldsAnnotatedWithId = allAnnotations.stream() + .filter(a -> a.name().toString().endsWith("persistence.Id")) + .collect(Collectors.toList()); + + if (fieldsNamedId.isEmpty() && fieldsAnnotatedWithId.isEmpty()) { + throw new IllegalStateException("Cannot generate web links for the class " + entityType + + " because is either missing an `id` field or a field with an `@Id` annotation"); + } + } + /** * Implement a field getter inside a class and create an accessor class which knows how to access it. */ diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive-links/deployment/src/test/java/io/quarkus/resteasy/reactive/links/deployment/AbstractEntity.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive-links/deployment/src/test/java/io/quarkus/resteasy/reactive/links/deployment/AbstractEntity.java index 861ad58348303..7ebfdec550a3b 100644 --- a/extensions/resteasy-reactive/quarkus-resteasy-reactive-links/deployment/src/test/java/io/quarkus/resteasy/reactive/links/deployment/AbstractEntity.java +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive-links/deployment/src/test/java/io/quarkus/resteasy/reactive/links/deployment/AbstractEntity.java @@ -1,8 +1,6 @@ package io.quarkus.resteasy.reactive.links.deployment; -public abstract class AbstractEntity { - - private int id; +public abstract class AbstractEntity extends AbstractId { private String slug; @@ -10,18 +8,10 @@ public AbstractEntity() { } protected AbstractEntity(int id, String slug) { - this.id = id; + super(id); this.slug = slug; } - public int getId() { - return id; - } - - public void setId(int id) { - this.id = id; - } - public String getSlug() { return slug; } diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive-links/deployment/src/test/java/io/quarkus/resteasy/reactive/links/deployment/AbstractId.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive-links/deployment/src/test/java/io/quarkus/resteasy/reactive/links/deployment/AbstractId.java new file mode 100644 index 0000000000000..163d7dcb90731 --- /dev/null +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive-links/deployment/src/test/java/io/quarkus/resteasy/reactive/links/deployment/AbstractId.java @@ -0,0 +1,21 @@ +package io.quarkus.resteasy.reactive.links.deployment; + +public abstract class AbstractId { + + private int id; + + public AbstractId() { + } + + protected AbstractId(int id) { + this.id = id; + } + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } +} \ No newline at end of file diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive-links/deployment/src/test/java/io/quarkus/resteasy/reactive/links/deployment/HalLinksWithJacksonTest.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive-links/deployment/src/test/java/io/quarkus/resteasy/reactive/links/deployment/HalLinksWithJacksonTest.java index 16ac0de033a17..b37da8dcb601f 100644 --- a/extensions/resteasy-reactive/quarkus-resteasy-reactive-links/deployment/src/test/java/io/quarkus/resteasy/reactive/links/deployment/HalLinksWithJacksonTest.java +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive-links/deployment/src/test/java/io/quarkus/resteasy/reactive/links/deployment/HalLinksWithJacksonTest.java @@ -12,7 +12,7 @@ public class HalLinksWithJacksonTest extends AbstractHalLinksTest { @RegisterExtension static final QuarkusProdModeTest TEST = new QuarkusProdModeTest() .withApplicationRoot((jar) -> jar - .addClasses(AbstractEntity.class, TestRecord.class, TestResource.class)) + .addClasses(AbstractId.class, AbstractEntity.class, TestRecord.class, TestResource.class)) .setForcedDependencies(List.of( Dependency.of("io.quarkus", "quarkus-resteasy-reactive-jackson", Version.getVersion()), Dependency.of("io.quarkus", "quarkus-hal", Version.getVersion()))) diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive-links/deployment/src/test/java/io/quarkus/resteasy/reactive/links/deployment/HalLinksWithJsonbTest.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive-links/deployment/src/test/java/io/quarkus/resteasy/reactive/links/deployment/HalLinksWithJsonbTest.java index 45d6663f60ac7..778847fbdbdea 100644 --- a/extensions/resteasy-reactive/quarkus-resteasy-reactive-links/deployment/src/test/java/io/quarkus/resteasy/reactive/links/deployment/HalLinksWithJsonbTest.java +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive-links/deployment/src/test/java/io/quarkus/resteasy/reactive/links/deployment/HalLinksWithJsonbTest.java @@ -12,7 +12,7 @@ public class HalLinksWithJsonbTest extends AbstractHalLinksTest { @RegisterExtension static final QuarkusProdModeTest TEST = new QuarkusProdModeTest() .withApplicationRoot((jar) -> jar - .addClasses(AbstractEntity.class, TestRecord.class, TestResource.class)) + .addClasses(AbstractId.class, AbstractEntity.class, TestRecord.class, TestResource.class)) .setForcedDependencies(List.of( Dependency.of("io.quarkus", "quarkus-resteasy-reactive-jsonb", Version.getVersion()), Dependency.of("io.quarkus", "quarkus-hal", Version.getVersion()))) diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive-links/deployment/src/test/java/io/quarkus/resteasy/reactive/links/deployment/RestLinksInjectionTest.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive-links/deployment/src/test/java/io/quarkus/resteasy/reactive/links/deployment/RestLinksInjectionTest.java index e7009c35a5c62..f8610662664fe 100644 --- a/extensions/resteasy-reactive/quarkus-resteasy-reactive-links/deployment/src/test/java/io/quarkus/resteasy/reactive/links/deployment/RestLinksInjectionTest.java +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive-links/deployment/src/test/java/io/quarkus/resteasy/reactive/links/deployment/RestLinksInjectionTest.java @@ -19,7 +19,7 @@ public class RestLinksInjectionTest { @RegisterExtension static final QuarkusUnitTest TEST = new QuarkusUnitTest() .withApplicationRoot((jar) -> jar - .addClasses(AbstractEntity.class, TestRecord.class, TestResource.class)); + .addClasses(AbstractId.class, AbstractEntity.class, TestRecord.class, TestResource.class)); @TestHTTPResource("records") String recordsUrl; diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive-links/deployment/src/test/java/io/quarkus/resteasy/reactive/links/deployment/RestLinksWithFailureInjectionTest.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive-links/deployment/src/test/java/io/quarkus/resteasy/reactive/links/deployment/RestLinksWithFailureInjectionTest.java index 14cfdf4b73379..72791f897f1b6 100644 --- a/extensions/resteasy-reactive/quarkus-resteasy-reactive-links/deployment/src/test/java/io/quarkus/resteasy/reactive/links/deployment/RestLinksWithFailureInjectionTest.java +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive-links/deployment/src/test/java/io/quarkus/resteasy/reactive/links/deployment/RestLinksWithFailureInjectionTest.java @@ -16,8 +16,9 @@ public class RestLinksWithFailureInjectionTest { .withApplicationRoot(jar -> jar.addClasses(TestRecordNoId.class, TestResourceNoId.class)).assertException(t -> { Throwable rootCause = ExceptionUtil.getRootCause(t); assertThat(rootCause).isInstanceOf(IllegalStateException.class) - .hasMessageContaining("Cannot generate web links for the class(es) " + - "io.quarkus.resteasy.reactive.links.deployment.TestRecordNoId because it does not contain an `id` field"); + .hasMessageContaining("Cannot generate web links for the class " + + "io.quarkus.resteasy.reactive.links.deployment.TestRecordNoId because is either " + + "missing an `id` field or a field with an `@Id` annotation"); }); @Test diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive-links/runtime/src/main/java/io/quarkus/resteasy/reactive/links/runtime/GetterAccessorsContainer.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive-links/runtime/src/main/java/io/quarkus/resteasy/reactive/links/runtime/GetterAccessorsContainer.java index 107df3b95c630..db1f858272912 100644 --- a/extensions/resteasy-reactive/quarkus-resteasy-reactive-links/runtime/src/main/java/io/quarkus/resteasy/reactive/links/runtime/GetterAccessorsContainer.java +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive-links/runtime/src/main/java/io/quarkus/resteasy/reactive/links/runtime/GetterAccessorsContainer.java @@ -2,7 +2,6 @@ import java.util.HashMap; import java.util.Map; -import java.util.Set; /** * Utility class that allows us to easily find a {@code GetterAccessor} based on a type and a field name. @@ -24,13 +23,4 @@ public void put(String className, String fieldName, GetterAccessor getterAccesso getterAccessorsByField.put(fieldName, getterAccessor); } } - - public Set classNameSet() { - return getterAccessors.keySet(); - } - - public boolean containsClassNameAndFieldName(String className, String fieldName) { - return getterAccessors.containsKey(className) && - getterAccessors.get(className).containsKey(fieldName); - } } diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive-links/runtime/src/main/java/io/quarkus/resteasy/reactive/links/runtime/GetterAccessorsContainerRecorder.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive-links/runtime/src/main/java/io/quarkus/resteasy/reactive/links/runtime/GetterAccessorsContainerRecorder.java index 7b5708012de8b..6424690f18537 100644 --- a/extensions/resteasy-reactive/quarkus-resteasy-reactive-links/runtime/src/main/java/io/quarkus/resteasy/reactive/links/runtime/GetterAccessorsContainerRecorder.java +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive-links/runtime/src/main/java/io/quarkus/resteasy/reactive/links/runtime/GetterAccessorsContainerRecorder.java @@ -30,34 +30,4 @@ public void addAccessor(RuntimeValue container, String throw new RuntimeException("Failed to initialize " + accessorName + ": " + e.getMessage()); } } - - /** - * Validates if all classes the container contain an accessor for the field name id - * - * @throws IllegalStateException if no accessor for classname.id is found - */ - public void validateContainer(RuntimeValue container) { - boolean shouldThrow = false; - StringBuilder builder = new StringBuilder(); - String mandatoryField = "id"; - - var allClassNames = container.getValue().classNameSet(); - if (!allClassNames.isEmpty()) { - builder.append("Cannot generate web links for the class(es) "); - } - - for (String className : allClassNames) { - boolean containsIdField = container.getValue().containsClassNameAndFieldName(className, mandatoryField); - if (!containsIdField) { - shouldThrow = true; - builder.append(className); - builder.append(" "); - } - } - - if (shouldThrow) { - builder.append("because it does not contain an `id` field"); - throw new IllegalStateException(builder.toString()); - } - } }