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;