From 1f85a3ceffcba5ade65bea827b715efe6a0e3709 Mon Sep 17 00:00:00 2001 From: Jose Date: Tue, 25 Jul 2023 14:41:17 +0200 Subject: [PATCH] Allow to exclude classes in REST Data with Panache with annotations Quarkus enables the inclusion or exclusion of Jakarta REST Resources, Providers and Features directly thanks to build time conditions in the same that it does for CDI beans. Thus, the REST Data with Panache interfaces can be annotated with profile conditions (`@io.quarkus.arc.profile.IfBuildProfile` or `@io.quarkus.arc.profile.UnlessBuildProfile`) and/or with property conditions (`io.quarkus.arc.properties.IfBuildProperty` or `io.quarkus.arc.properties.UnlessBuildProperty`) to indicate to Quarkus at build time under which conditions the generated Jakarta REST classes should be included. In the following example, Quarkus will include the generated resource from the `PeopleResource` interface if and only if the build profile `app1` has been enabled. ```java @IfBuildProfile("app1") public interface PeopleResource extends PanacheEntityResource { } ``` === Using a runtime property IMPORTANT: This option is only available when using the RESTEasy Reactive Quarkus extension. Quarkus can also conditionally disable the generated Jakarta REST Resources based on the value of runtime properties using the `@io.quarkus.resteasy.reactive.server.EndpointDisabled` annotation. In the following example, Quarkus will exclude the generated resource from the `PeopleResource` interface at runtime if the application has `some.property` configured to `"disable"`. ```java @EndpointDisabled(name = "some.property", stringValue = "disable") public interface PeopleResource extends PanacheEntityResource { } ``` Fix https://github.com/quarkusio/quarkus/issues/34938 --- docs/src/main/asciidoc/rest-data-panache.adoc | 32 ++++++++++++ ...ildConditionsWithResourceDisabledTest.java | 24 +++++++++ ...uildConditionsWithResourceEnabledTest.java | 25 +++++++++ .../panache/deployment/build/Collection.java | 15 ++++++ .../deployment/build/CollectionsResource.java | 8 +++ ...ildConditionsWithResourceDisabledTest.java | 25 +++++++++ ...uildConditionsWithResourceEnabledTest.java | 25 +++++++++ .../panache/deployment/build/Collection.java | 15 ++++++ .../deployment/build/CollectionsResource.java | 8 +++ .../panache/deployment/RestDataProcessor.java | 52 +++++++++++++------ .../ResourcePropertiesProvider.java | 4 +- 11 files changed, 216 insertions(+), 17 deletions(-) create mode 100644 extensions/panache/hibernate-orm-rest-data-panache/deployment/src/test/java/io/quarkus/hibernate/orm/rest/data/panache/deployment/build/BuildConditionsWithResourceDisabledTest.java create mode 100644 extensions/panache/hibernate-orm-rest-data-panache/deployment/src/test/java/io/quarkus/hibernate/orm/rest/data/panache/deployment/build/BuildConditionsWithResourceEnabledTest.java create mode 100644 extensions/panache/hibernate-orm-rest-data-panache/deployment/src/test/java/io/quarkus/hibernate/orm/rest/data/panache/deployment/build/Collection.java create mode 100644 extensions/panache/hibernate-orm-rest-data-panache/deployment/src/test/java/io/quarkus/hibernate/orm/rest/data/panache/deployment/build/CollectionsResource.java create mode 100644 extensions/panache/hibernate-reactive-rest-data-panache/deployment/src/test/java/io/quarkus/hibernate/reactive/rest/data/panache/deployment/build/BuildConditionsWithResourceDisabledTest.java create mode 100644 extensions/panache/hibernate-reactive-rest-data-panache/deployment/src/test/java/io/quarkus/hibernate/reactive/rest/data/panache/deployment/build/BuildConditionsWithResourceEnabledTest.java create mode 100644 extensions/panache/hibernate-reactive-rest-data-panache/deployment/src/test/java/io/quarkus/hibernate/reactive/rest/data/panache/deployment/build/Collection.java create mode 100644 extensions/panache/hibernate-reactive-rest-data-panache/deployment/src/test/java/io/quarkus/hibernate/reactive/rest/data/panache/deployment/build/CollectionsResource.java diff --git a/docs/src/main/asciidoc/rest-data-panache.adoc b/docs/src/main/asciidoc/rest-data-panache.adoc index 6b4beea9dca76..2b06557157124 100644 --- a/docs/src/main/asciidoc/rest-data-panache.adoc +++ b/docs/src/main/asciidoc/rest-data-panache.adoc @@ -597,3 +597,35 @@ Both responses would also contain these headers: * Link: < http://example.com/people?page=1&size=2 >; rel="next" A `previous` link header (and hal link) would not be included, because the previous page does not exist. + +== Include/Exclude Jakarta REST classes + +=== Using Build time conditions + +Quarkus enables the inclusion or exclusion of Jakarta REST Resources, Providers and Features directly thanks to build time conditions in the same that it does for CDI beans. +Thus, the REST Data with Panache interfaces can be annotated with profile conditions (`@io.quarkus.arc.profile.IfBuildProfile` or `@io.quarkus.arc.profile.UnlessBuildProfile`) and/or with property conditions (`io.quarkus.arc.properties.IfBuildProperty` or `io.quarkus.arc.properties.UnlessBuildProperty`) to indicate to Quarkus at build time under which conditions the generated Jakarta REST classes should be included. + +In the following example, Quarkus will include the generated resource from the `PeopleResource` interface if and only if the build profile `app1` has been enabled. + +[source,java] +---- +@IfBuildProfile("app1") +public interface PeopleResource extends PanacheEntityResource { +} +---- + + +=== Using a runtime property + +IMPORTANT: This option is only available when using the RESTEasy Reactive Quarkus extension. + +Quarkus can also conditionally disable the generated Jakarta REST Resources based on the value of runtime properties using the `@io.quarkus.resteasy.reactive.server.EndpointDisabled` annotation. + +In the following example, Quarkus will exclude the generated resource from the `PeopleResource` interface at runtime if the application has `some.property` configured to `"disable"`. + +[source,java] +---- +@EndpointDisabled(name = "some.property", stringValue = "disable") +public interface PeopleResource extends PanacheEntityResource { +} +---- \ No newline at end of file diff --git a/extensions/panache/hibernate-orm-rest-data-panache/deployment/src/test/java/io/quarkus/hibernate/orm/rest/data/panache/deployment/build/BuildConditionsWithResourceDisabledTest.java b/extensions/panache/hibernate-orm-rest-data-panache/deployment/src/test/java/io/quarkus/hibernate/orm/rest/data/panache/deployment/build/BuildConditionsWithResourceDisabledTest.java new file mode 100644 index 0000000000000..9010ee4a3ea35 --- /dev/null +++ b/extensions/panache/hibernate-orm-rest-data-panache/deployment/src/test/java/io/quarkus/hibernate/orm/rest/data/panache/deployment/build/BuildConditionsWithResourceDisabledTest.java @@ -0,0 +1,24 @@ +package io.quarkus.hibernate.orm.rest.data.panache.deployment.build; + +import static io.restassured.RestAssured.given; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; + +public class BuildConditionsWithResourceDisabledTest { + @RegisterExtension + static final QuarkusUnitTest TEST = new QuarkusUnitTest() + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) + .addClasses(Collection.class, CollectionsResource.class)); + + @Test + void shouldResourceNotBeFound() { + given().accept("application/json") + .when().get("/collections") + .then().statusCode(404); + } +} diff --git a/extensions/panache/hibernate-orm-rest-data-panache/deployment/src/test/java/io/quarkus/hibernate/orm/rest/data/panache/deployment/build/BuildConditionsWithResourceEnabledTest.java b/extensions/panache/hibernate-orm-rest-data-panache/deployment/src/test/java/io/quarkus/hibernate/orm/rest/data/panache/deployment/build/BuildConditionsWithResourceEnabledTest.java new file mode 100644 index 0000000000000..11ebf24d90d6f --- /dev/null +++ b/extensions/panache/hibernate-orm-rest-data-panache/deployment/src/test/java/io/quarkus/hibernate/orm/rest/data/panache/deployment/build/BuildConditionsWithResourceEnabledTest.java @@ -0,0 +1,25 @@ +package io.quarkus.hibernate.orm.rest.data.panache.deployment.build; + +import static io.restassured.RestAssured.given; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; + +public class BuildConditionsWithResourceEnabledTest { + @RegisterExtension + static final QuarkusUnitTest TEST = new QuarkusUnitTest() + .overrideConfigKey("collections.enabled", "true") + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) + .addClasses(Collection.class, CollectionsResource.class)); + + @Test + void shouldResourceBeFound() { + given().accept("application/json") + .when().get("/collections") + .then().statusCode(200); + } +} diff --git a/extensions/panache/hibernate-orm-rest-data-panache/deployment/src/test/java/io/quarkus/hibernate/orm/rest/data/panache/deployment/build/Collection.java b/extensions/panache/hibernate-orm-rest-data-panache/deployment/src/test/java/io/quarkus/hibernate/orm/rest/data/panache/deployment/build/Collection.java new file mode 100644 index 0000000000000..8d7e69a1dccac --- /dev/null +++ b/extensions/panache/hibernate-orm-rest-data-panache/deployment/src/test/java/io/quarkus/hibernate/orm/rest/data/panache/deployment/build/Collection.java @@ -0,0 +1,15 @@ +package io.quarkus.hibernate.orm.rest.data.panache.deployment.build; + +import jakarta.persistence.Entity; +import jakarta.persistence.Id; + +import io.quarkus.hibernate.orm.panache.PanacheEntityBase; + +@Entity +public class Collection extends PanacheEntityBase { + + @Id + public String id; + + public String name; +} diff --git a/extensions/panache/hibernate-orm-rest-data-panache/deployment/src/test/java/io/quarkus/hibernate/orm/rest/data/panache/deployment/build/CollectionsResource.java b/extensions/panache/hibernate-orm-rest-data-panache/deployment/src/test/java/io/quarkus/hibernate/orm/rest/data/panache/deployment/build/CollectionsResource.java new file mode 100644 index 0000000000000..d9b3159230bba --- /dev/null +++ b/extensions/panache/hibernate-orm-rest-data-panache/deployment/src/test/java/io/quarkus/hibernate/orm/rest/data/panache/deployment/build/CollectionsResource.java @@ -0,0 +1,8 @@ +package io.quarkus.hibernate.orm.rest.data.panache.deployment.build; + +import io.quarkus.arc.properties.IfBuildProperty; +import io.quarkus.hibernate.orm.rest.data.panache.PanacheEntityResource; + +@IfBuildProperty(name = "collections.enabled", stringValue = "true") +public interface CollectionsResource extends PanacheEntityResource { +} diff --git a/extensions/panache/hibernate-reactive-rest-data-panache/deployment/src/test/java/io/quarkus/hibernate/reactive/rest/data/panache/deployment/build/BuildConditionsWithResourceDisabledTest.java b/extensions/panache/hibernate-reactive-rest-data-panache/deployment/src/test/java/io/quarkus/hibernate/reactive/rest/data/panache/deployment/build/BuildConditionsWithResourceDisabledTest.java new file mode 100644 index 0000000000000..d1f2975bb9946 --- /dev/null +++ b/extensions/panache/hibernate-reactive-rest-data-panache/deployment/src/test/java/io/quarkus/hibernate/reactive/rest/data/panache/deployment/build/BuildConditionsWithResourceDisabledTest.java @@ -0,0 +1,25 @@ +package io.quarkus.hibernate.reactive.rest.data.panache.deployment.build; + +import static io.restassured.RestAssured.given; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; + +public class BuildConditionsWithResourceDisabledTest { + @RegisterExtension + static final QuarkusUnitTest TEST = new QuarkusUnitTest() + .overrideConfigKey("collections.endpoint", "disable") + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) + .addClasses(Collection.class, CollectionsResource.class)); + + @Test + void shouldResourceNotBeFound() { + given().accept("application/json") + .when().get("/collections") + .then().statusCode(404); + } +} diff --git a/extensions/panache/hibernate-reactive-rest-data-panache/deployment/src/test/java/io/quarkus/hibernate/reactive/rest/data/panache/deployment/build/BuildConditionsWithResourceEnabledTest.java b/extensions/panache/hibernate-reactive-rest-data-panache/deployment/src/test/java/io/quarkus/hibernate/reactive/rest/data/panache/deployment/build/BuildConditionsWithResourceEnabledTest.java new file mode 100644 index 0000000000000..c3b8e0a1efd74 --- /dev/null +++ b/extensions/panache/hibernate-reactive-rest-data-panache/deployment/src/test/java/io/quarkus/hibernate/reactive/rest/data/panache/deployment/build/BuildConditionsWithResourceEnabledTest.java @@ -0,0 +1,25 @@ +package io.quarkus.hibernate.reactive.rest.data.panache.deployment.build; + +import static io.restassured.RestAssured.given; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; + +public class BuildConditionsWithResourceEnabledTest { + @RegisterExtension + static final QuarkusUnitTest TEST = new QuarkusUnitTest() + .overrideConfigKey("collections.endpoint", "enable") + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) + .addClasses(Collection.class, CollectionsResource.class)); + + @Test + void shouldResourceBeFound() { + given().accept("application/json") + .when().get("/collections") + .then().statusCode(200); + } +} diff --git a/extensions/panache/hibernate-reactive-rest-data-panache/deployment/src/test/java/io/quarkus/hibernate/reactive/rest/data/panache/deployment/build/Collection.java b/extensions/panache/hibernate-reactive-rest-data-panache/deployment/src/test/java/io/quarkus/hibernate/reactive/rest/data/panache/deployment/build/Collection.java new file mode 100644 index 0000000000000..76cc0a6463392 --- /dev/null +++ b/extensions/panache/hibernate-reactive-rest-data-panache/deployment/src/test/java/io/quarkus/hibernate/reactive/rest/data/panache/deployment/build/Collection.java @@ -0,0 +1,15 @@ +package io.quarkus.hibernate.reactive.rest.data.panache.deployment.build; + +import jakarta.persistence.Entity; +import jakarta.persistence.Id; + +import io.quarkus.hibernate.reactive.panache.PanacheEntityBase; + +@Entity +public class Collection extends PanacheEntityBase { + + @Id + public String id; + + public String name; +} diff --git a/extensions/panache/hibernate-reactive-rest-data-panache/deployment/src/test/java/io/quarkus/hibernate/reactive/rest/data/panache/deployment/build/CollectionsResource.java b/extensions/panache/hibernate-reactive-rest-data-panache/deployment/src/test/java/io/quarkus/hibernate/reactive/rest/data/panache/deployment/build/CollectionsResource.java new file mode 100644 index 0000000000000..3ca5888704f93 --- /dev/null +++ b/extensions/panache/hibernate-reactive-rest-data-panache/deployment/src/test/java/io/quarkus/hibernate/reactive/rest/data/panache/deployment/build/CollectionsResource.java @@ -0,0 +1,8 @@ +package io.quarkus.hibernate.reactive.rest.data.panache.deployment.build; + +import io.quarkus.hibernate.reactive.rest.data.panache.PanacheEntityResource; +import io.quarkus.resteasy.reactive.server.EndpointDisabled; + +@EndpointDisabled(name = "collections.endpoint", stringValue = "disable") +public interface CollectionsResource extends PanacheEntityResource { +} diff --git a/extensions/panache/rest-data-panache/deployment/src/main/java/io/quarkus/rest/data/panache/deployment/RestDataProcessor.java b/extensions/panache/rest-data-panache/deployment/src/main/java/io/quarkus/rest/data/panache/deployment/RestDataProcessor.java index 69bac5fd7f7ea..1c2b5e6607aa0 100644 --- a/extensions/panache/rest-data-panache/deployment/src/main/java/io/quarkus/rest/data/panache/deployment/RestDataProcessor.java +++ b/extensions/panache/rest-data-panache/deployment/src/main/java/io/quarkus/rest/data/panache/deployment/RestDataProcessor.java @@ -2,7 +2,12 @@ import java.util.Collections; import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; +import org.jboss.jandex.AnnotationTarget; + +import io.quarkus.arc.deployment.BuildTimeConditionBuildItem; import io.quarkus.arc.deployment.GeneratedBeanBuildItem; import io.quarkus.arc.deployment.GeneratedBeanGizmoAdaptor; import io.quarkus.deployment.Capabilities; @@ -50,8 +55,11 @@ void supportingBuildItems(Capabilities capabilities, } @BuildStep - void implementResources(CombinedIndexBuildItem index, List resourceBuildItems, - List resourcePropertiesBuildItems, Capabilities capabilities, + void implementResources(CombinedIndexBuildItem index, + List resourceBuildItems, + List resourcePropertiesBuildItems, + List buildTimeConditions, + Capabilities capabilities, BuildProducer resteasyClassicImplementationsProducer, BuildProducer resteasyReactiveImplementationsProducer) { @@ -63,27 +71,30 @@ void implementResources(CombinedIndexBuildItem index, List excludedClasses = getExcludedClasses(buildTimeConditions); ClassOutput classOutput = isResteasyClassic ? new GeneratedBeanGizmoAdaptor(resteasyClassicImplementationsProducer) : new GeneratedJaxRsResourceGizmoAdaptor(resteasyReactiveImplementationsProducer); JaxRsResourceImplementor jaxRsResourceImplementor = new JaxRsResourceImplementor(capabilities); ResourcePropertiesProvider resourcePropertiesProvider = new ResourcePropertiesProvider(index.getIndex()); for (RestDataResourceBuildItem resourceBuildItem : resourceBuildItems) { - ResourceMetadata resourceMetadata = resourceBuildItem.getResourceMetadata(); - ResourceProperties resourceProperties = getResourceProperties(resourcePropertiesProvider, - resourceMetadata, resourcePropertiesBuildItems); - if (resourceProperties.isHal()) { - if (isResteasyClassic && !hasAnyJsonCapabilityForResteasyClassic(capabilities)) { - throw new IllegalStateException("Cannot generate HAL endpoints without " - + "either 'quarkus-resteasy-jsonb' or 'quarkus-resteasy-jackson'"); - } else if (!isResteasyClassic && !hasAnyJsonCapabilityForResteasyReactive(capabilities)) { - throw new IllegalStateException("Cannot generate HAL endpoints without " - + "either 'quarkus-resteasy-reactive-jsonb' or 'quarkus-resteasy-reactive-jackson'"); - } + if (!excludedClasses.contains(resourceBuildItem.getResourceMetadata().getResourceInterface())) { + ResourceMetadata resourceMetadata = resourceBuildItem.getResourceMetadata(); + ResourceProperties resourceProperties = getResourceProperties(resourcePropertiesProvider, + resourceMetadata, resourcePropertiesBuildItems); + if (resourceProperties.isHal()) { + if (isResteasyClassic && !hasAnyJsonCapabilityForResteasyClassic(capabilities)) { + throw new IllegalStateException("Cannot generate HAL endpoints without " + + "either 'quarkus-resteasy-jsonb' or 'quarkus-resteasy-jackson'"); + } else if (!isResteasyClassic && !hasAnyJsonCapabilityForResteasyReactive(capabilities)) { + throw new IllegalStateException("Cannot generate HAL endpoints without " + + "either 'quarkus-resteasy-reactive-jsonb' or 'quarkus-resteasy-reactive-jackson'"); + } - } - if (resourceProperties.isExposed()) { - jaxRsResourceImplementor.implement(classOutput, resourceMetadata, resourceProperties, capabilities); + } + if (resourceProperties.isExposed()) { + jaxRsResourceImplementor.implement(classOutput, resourceMetadata, resourceProperties, capabilities); + } } } } @@ -108,4 +119,13 @@ private boolean hasAnyJsonCapabilityForResteasyReactive(Capabilities capabilitie return capabilities.isPresent(Capability.RESTEASY_REACTIVE_JSON_JSONB) || capabilities.isPresent(Capability.RESTEASY_REACTIVE_JSON_JACKSON); } + + private static Set getExcludedClasses(List buildTimeConditions) { + return buildTimeConditions.stream() + .filter(item -> !item.isEnabled()) + .map(BuildTimeConditionBuildItem::getTarget) + .filter(target -> target.kind() == AnnotationTarget.Kind.CLASS) + .map(target -> target.asClass().toString()) + .collect(Collectors.toSet()); + } } diff --git a/extensions/panache/rest-data-panache/deployment/src/main/java/io/quarkus/rest/data/panache/deployment/properties/ResourcePropertiesProvider.java b/extensions/panache/rest-data-panache/deployment/src/main/java/io/quarkus/rest/data/panache/deployment/properties/ResourcePropertiesProvider.java index fa79af6f29d4a..ed2ea73b5c74d 100644 --- a/extensions/panache/rest-data-panache/deployment/src/main/java/io/quarkus/rest/data/panache/deployment/properties/ResourcePropertiesProvider.java +++ b/extensions/panache/rest-data-panache/deployment/src/main/java/io/quarkus/rest/data/panache/deployment/properties/ResourcePropertiesProvider.java @@ -25,7 +25,9 @@ public class ResourcePropertiesProvider { private static final DotName METHOD_PROPERTIES_ANNOTATION = DotName.createSimple( io.quarkus.rest.data.panache.MethodProperties.class.getName()); - private static final List ANNOTATIONS_TO_COPY = List.of(RolesAllowed.class.getPackageName()); + private static final List ANNOTATIONS_TO_COPY = List.of(RolesAllowed.class.getPackageName(), + // To also support `@EndpointDisabled` if used + "io.quarkus.resteasy.reactive.server"); private final IndexView index;