From bf369061c540bab56ec6f295eb911aef94a524d8 Mon Sep 17 00:00:00 2001 From: Jose Date: Wed, 2 Nov 2022 16:22:33 +0100 Subject: [PATCH 01/22] Propagate the javax.annotation.security annotations in REST Data With these changes, the REST Data with Panache extension will propagate the Security annotations within the package `javax.annotation.security` that are defined on your resource interfaces: ```java import javax.annotation.security.DenyAll; import javax.annotation.security.RolesAllowed; @DenyAll @ResourceProperties public interface PeopleResource extends PanacheEntityResource { @RolesAllowed("superuser") boolean delete(Long id); } ``` Additionally, if you are only interested in specifying the roles that are allowed to use the resources, the `@ResourceProperties` and `@MethodProperties` annotations have the field `rolesAllowed` to list the security roles permitted to access the resource or operation. Fix https://github.com/quarkusio/quarkus/issues/28995 (cherry picked from commit e1ae1d8eb47fb37184ad58119184a344df898849) --- docs/src/main/asciidoc/rest-data-panache.adoc | 20 ++++++ docs/src/main/asciidoc/security.adoc | 5 ++ .../deployment/openapi/AbstractEntity.java | 17 +++++ .../deployment/openapi/AbstractItem.java | 25 +++++++ .../deployment/openapi/Collection.java | 45 +++++++++++++ .../openapi/CollectionsRepository.java | 9 +++ .../openapi/CollectionsResource.java | 16 +++++ .../deployment/openapi/EmptyListItem.java | 8 +++ .../openapi/EmptyListItemsRepository.java | 9 +++ .../openapi/EmptyListItemsResource.java | 8 +++ .../data/panache/deployment/openapi/Item.java | 8 +++ .../deployment/openapi/ItemsRepository.java | 9 +++ .../deployment/openapi/ItemsResource.java | 8 +++ .../openapi/OpenApiIntegrationTest.java | 13 +--- .../repository/CollectionsResource.java | 5 +- .../deployment/JaxRsResourceImplementor.java | 6 ++ .../methods/AddMethodImplementor.java | 1 + .../methods/CountMethodImplementor.java | 1 + .../methods/DeleteMethodImplementor.java | 1 + .../methods/GetMethodImplementor.java | 1 + .../methods/ListMethodImplementor.java | 2 + .../methods/StandardMethodImplementor.java | 11 ++++ .../methods/UpdateMethodImplementor.java | 1 + .../methods/hal/ListHalMethodImplementor.java | 2 + .../properties/MethodProperties.java | 14 +++- .../properties/ResourceProperties.java | 22 ++++++- .../ResourcePropertiesProvider.java | 61 +++++++++++++---- .../ResourcePropertiesProvider.java | 65 ++++++++++++++++--- 28 files changed, 355 insertions(+), 38 deletions(-) create mode 100644 extensions/panache/hibernate-orm-rest-data-panache/deployment/src/test/java/io/quarkus/hibernate/orm/rest/data/panache/deployment/openapi/AbstractEntity.java create mode 100644 extensions/panache/hibernate-orm-rest-data-panache/deployment/src/test/java/io/quarkus/hibernate/orm/rest/data/panache/deployment/openapi/AbstractItem.java create mode 100644 extensions/panache/hibernate-orm-rest-data-panache/deployment/src/test/java/io/quarkus/hibernate/orm/rest/data/panache/deployment/openapi/Collection.java create mode 100644 extensions/panache/hibernate-orm-rest-data-panache/deployment/src/test/java/io/quarkus/hibernate/orm/rest/data/panache/deployment/openapi/CollectionsRepository.java create mode 100644 extensions/panache/hibernate-orm-rest-data-panache/deployment/src/test/java/io/quarkus/hibernate/orm/rest/data/panache/deployment/openapi/CollectionsResource.java create mode 100644 extensions/panache/hibernate-orm-rest-data-panache/deployment/src/test/java/io/quarkus/hibernate/orm/rest/data/panache/deployment/openapi/EmptyListItem.java create mode 100644 extensions/panache/hibernate-orm-rest-data-panache/deployment/src/test/java/io/quarkus/hibernate/orm/rest/data/panache/deployment/openapi/EmptyListItemsRepository.java create mode 100644 extensions/panache/hibernate-orm-rest-data-panache/deployment/src/test/java/io/quarkus/hibernate/orm/rest/data/panache/deployment/openapi/EmptyListItemsResource.java create mode 100644 extensions/panache/hibernate-orm-rest-data-panache/deployment/src/test/java/io/quarkus/hibernate/orm/rest/data/panache/deployment/openapi/Item.java create mode 100644 extensions/panache/hibernate-orm-rest-data-panache/deployment/src/test/java/io/quarkus/hibernate/orm/rest/data/panache/deployment/openapi/ItemsRepository.java create mode 100644 extensions/panache/hibernate-orm-rest-data-panache/deployment/src/test/java/io/quarkus/hibernate/orm/rest/data/panache/deployment/openapi/ItemsResource.java diff --git a/docs/src/main/asciidoc/rest-data-panache.adoc b/docs/src/main/asciidoc/rest-data-panache.adoc index 3d66a770412c1..6dc0b32d028d3 100644 --- a/docs/src/main/asciidoc/rest-data-panache.adoc +++ b/docs/src/main/asciidoc/rest-data-panache.adoc @@ -322,6 +322,26 @@ Default is `false`. * `path` - operation path (this is appended to the resource base path). Default is an empty string. * `rolesAllowed` - List of the security roles permitted to access this operation. It needs a Quarkus security extension to be present, otherwise it will be ignored. Default is empty. +== Securing endpoints + +REST Data with Panache will use the Security annotations within the package `javax.annotation.security` that are defined on your resource interfaces: + +[source,java] +---- + +import javax.annotation.security.DenyAll; +import javax.annotation.security.RolesAllowed; + +@DenyAll +@ResourceProperties +public interface PeopleResource extends PanacheEntityResource { + @RolesAllowed("superuser") + boolean delete(Long id); +} +---- + +Additionally, if you are only interested in specifying the roles that are allowed to use the resources, the `@ResourceProperties` and `@MethodProperties` annotations have the field `rolesAllowed` to list the security roles permitted to access the resource or operation. + == Query parameters REST Data with Panache supports the following query parameters with the generated resources. diff --git a/docs/src/main/asciidoc/security.adoc b/docs/src/main/asciidoc/security.adoc index 6e6815d721c03..358f0b0c7cf72 100644 --- a/docs/src/main/asciidoc/security.adoc +++ b/docs/src/main/asciidoc/security.adoc @@ -318,6 +318,11 @@ For more information, see the link:{vault-guide}[Quarkus and HashiCorp Vault] do If your Quarkus Security architecture includes RESTEasy Reactive and Jackson, Quarkus can limit the fields that are included in JSON serialization based on the configured security. For more information, see xref:resteasy-reactive.adoc#secure-serialization[Writing REST services with RESTEasy Reactive]. +== Secure auto-generated resources by REST Data with Panache + +If you're using the REST Data with Panache extension to auto-generate your resources, you can still use the Security annotations within the package `javax.annotation.security`. +For more information, see xref:rest-data-panache.adoc#securing-endpoints[Securing auto-generated resources]. + == National Vulnerability Database Most of the Quarkus tags are registered in the US link:https://nvd.nist.gov[National Vulnerability Database] (NVD) in Common Platform Enumeration (CPE) name format. diff --git a/extensions/panache/hibernate-orm-rest-data-panache/deployment/src/test/java/io/quarkus/hibernate/orm/rest/data/panache/deployment/openapi/AbstractEntity.java b/extensions/panache/hibernate-orm-rest-data-panache/deployment/src/test/java/io/quarkus/hibernate/orm/rest/data/panache/deployment/openapi/AbstractEntity.java new file mode 100644 index 0000000000000..12164b9b77b98 --- /dev/null +++ b/extensions/panache/hibernate-orm-rest-data-panache/deployment/src/test/java/io/quarkus/hibernate/orm/rest/data/panache/deployment/openapi/AbstractEntity.java @@ -0,0 +1,17 @@ +package io.quarkus.hibernate.orm.rest.data.panache.deployment.openapi; + +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.MappedSuperclass; + +@MappedSuperclass +public abstract class AbstractEntity { + + @Id + @GeneratedValue + private IdType id; + + public IdType getId() { + return id; + } +} diff --git a/extensions/panache/hibernate-orm-rest-data-panache/deployment/src/test/java/io/quarkus/hibernate/orm/rest/data/panache/deployment/openapi/AbstractItem.java b/extensions/panache/hibernate-orm-rest-data-panache/deployment/src/test/java/io/quarkus/hibernate/orm/rest/data/panache/deployment/openapi/AbstractItem.java new file mode 100644 index 0000000000000..9cc7828fa24d4 --- /dev/null +++ b/extensions/panache/hibernate-orm-rest-data-panache/deployment/src/test/java/io/quarkus/hibernate/orm/rest/data/panache/deployment/openapi/AbstractItem.java @@ -0,0 +1,25 @@ +package io.quarkus.hibernate.orm.rest.data.panache.deployment.openapi; + +import javax.persistence.ManyToOne; +import javax.persistence.MappedSuperclass; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonProperty.Access; + +@MappedSuperclass +public abstract class AbstractItem extends AbstractEntity { + + private String name; + + @ManyToOne(optional = false) + @JsonProperty(access = Access.WRITE_ONLY) + private Collection collection; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } +} diff --git a/extensions/panache/hibernate-orm-rest-data-panache/deployment/src/test/java/io/quarkus/hibernate/orm/rest/data/panache/deployment/openapi/Collection.java b/extensions/panache/hibernate-orm-rest-data-panache/deployment/src/test/java/io/quarkus/hibernate/orm/rest/data/panache/deployment/openapi/Collection.java new file mode 100644 index 0000000000000..0722edf007579 --- /dev/null +++ b/extensions/panache/hibernate-orm-rest-data-panache/deployment/src/test/java/io/quarkus/hibernate/orm/rest/data/panache/deployment/openapi/Collection.java @@ -0,0 +1,45 @@ +package io.quarkus.hibernate.orm.rest.data.panache.deployment.openapi; + +import java.util.LinkedList; +import java.util.List; + +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.Id; +import javax.persistence.OneToMany; + +@Entity +public class Collection { + + @Id + private String id; + + private String name; + + @OneToMany(fetch = FetchType.EAGER, mappedBy = "collection") + private List items = new LinkedList<>(); + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public List getItems() { + return items; + } + + public void setItems(List items) { + this.items = items; + } +} diff --git a/extensions/panache/hibernate-orm-rest-data-panache/deployment/src/test/java/io/quarkus/hibernate/orm/rest/data/panache/deployment/openapi/CollectionsRepository.java b/extensions/panache/hibernate-orm-rest-data-panache/deployment/src/test/java/io/quarkus/hibernate/orm/rest/data/panache/deployment/openapi/CollectionsRepository.java new file mode 100644 index 0000000000000..2423c9b6ce78c --- /dev/null +++ b/extensions/panache/hibernate-orm-rest-data-panache/deployment/src/test/java/io/quarkus/hibernate/orm/rest/data/panache/deployment/openapi/CollectionsRepository.java @@ -0,0 +1,9 @@ +package io.quarkus.hibernate.orm.rest.data.panache.deployment.openapi; + +import javax.enterprise.context.ApplicationScoped; + +import io.quarkus.hibernate.orm.panache.PanacheRepositoryBase; + +@ApplicationScoped +public class CollectionsRepository implements PanacheRepositoryBase { +} diff --git a/extensions/panache/hibernate-orm-rest-data-panache/deployment/src/test/java/io/quarkus/hibernate/orm/rest/data/panache/deployment/openapi/CollectionsResource.java b/extensions/panache/hibernate-orm-rest-data-panache/deployment/src/test/java/io/quarkus/hibernate/orm/rest/data/panache/deployment/openapi/CollectionsResource.java new file mode 100644 index 0000000000000..323e85f727b4b --- /dev/null +++ b/extensions/panache/hibernate-orm-rest-data-panache/deployment/src/test/java/io/quarkus/hibernate/orm/rest/data/panache/deployment/openapi/CollectionsResource.java @@ -0,0 +1,16 @@ +package io.quarkus.hibernate.orm.rest.data.panache.deployment.openapi; + +import javax.annotation.security.RolesAllowed; + +import io.quarkus.hibernate.orm.rest.data.panache.PanacheRepositoryResource; +import io.quarkus.rest.data.panache.MethodProperties; +import io.quarkus.rest.data.panache.ResourceProperties; + +@ResourceProperties(hal = true, paged = false, halCollectionName = "item-collections", rolesAllowed = "user") +public interface CollectionsResource extends PanacheRepositoryResource { + @RolesAllowed("superuser") + Collection update(String id, Collection entity); + + @MethodProperties(rolesAllowed = "admin") + boolean delete(String name); +} diff --git a/extensions/panache/hibernate-orm-rest-data-panache/deployment/src/test/java/io/quarkus/hibernate/orm/rest/data/panache/deployment/openapi/EmptyListItem.java b/extensions/panache/hibernate-orm-rest-data-panache/deployment/src/test/java/io/quarkus/hibernate/orm/rest/data/panache/deployment/openapi/EmptyListItem.java new file mode 100644 index 0000000000000..b233ea1166bec --- /dev/null +++ b/extensions/panache/hibernate-orm-rest-data-panache/deployment/src/test/java/io/quarkus/hibernate/orm/rest/data/panache/deployment/openapi/EmptyListItem.java @@ -0,0 +1,8 @@ +package io.quarkus.hibernate.orm.rest.data.panache.deployment.openapi; + +import javax.persistence.Entity; + +@Entity +public class EmptyListItem extends AbstractItem { + +} diff --git a/extensions/panache/hibernate-orm-rest-data-panache/deployment/src/test/java/io/quarkus/hibernate/orm/rest/data/panache/deployment/openapi/EmptyListItemsRepository.java b/extensions/panache/hibernate-orm-rest-data-panache/deployment/src/test/java/io/quarkus/hibernate/orm/rest/data/panache/deployment/openapi/EmptyListItemsRepository.java new file mode 100644 index 0000000000000..134faddb349e7 --- /dev/null +++ b/extensions/panache/hibernate-orm-rest-data-panache/deployment/src/test/java/io/quarkus/hibernate/orm/rest/data/panache/deployment/openapi/EmptyListItemsRepository.java @@ -0,0 +1,9 @@ +package io.quarkus.hibernate.orm.rest.data.panache.deployment.openapi; + +import javax.enterprise.context.ApplicationScoped; + +import io.quarkus.hibernate.orm.panache.PanacheRepository; + +@ApplicationScoped +public class EmptyListItemsRepository implements PanacheRepository { +} diff --git a/extensions/panache/hibernate-orm-rest-data-panache/deployment/src/test/java/io/quarkus/hibernate/orm/rest/data/panache/deployment/openapi/EmptyListItemsResource.java b/extensions/panache/hibernate-orm-rest-data-panache/deployment/src/test/java/io/quarkus/hibernate/orm/rest/data/panache/deployment/openapi/EmptyListItemsResource.java new file mode 100644 index 0000000000000..26b41e8545e15 --- /dev/null +++ b/extensions/panache/hibernate-orm-rest-data-panache/deployment/src/test/java/io/quarkus/hibernate/orm/rest/data/panache/deployment/openapi/EmptyListItemsResource.java @@ -0,0 +1,8 @@ +package io.quarkus.hibernate.orm.rest.data.panache.deployment.openapi; + +import io.quarkus.hibernate.orm.rest.data.panache.PanacheRepositoryResource; +import io.quarkus.rest.data.panache.ResourceProperties; + +@ResourceProperties(hal = true) +public interface EmptyListItemsResource extends PanacheRepositoryResource { +} diff --git a/extensions/panache/hibernate-orm-rest-data-panache/deployment/src/test/java/io/quarkus/hibernate/orm/rest/data/panache/deployment/openapi/Item.java b/extensions/panache/hibernate-orm-rest-data-panache/deployment/src/test/java/io/quarkus/hibernate/orm/rest/data/panache/deployment/openapi/Item.java new file mode 100644 index 0000000000000..e448245ee8509 --- /dev/null +++ b/extensions/panache/hibernate-orm-rest-data-panache/deployment/src/test/java/io/quarkus/hibernate/orm/rest/data/panache/deployment/openapi/Item.java @@ -0,0 +1,8 @@ +package io.quarkus.hibernate.orm.rest.data.panache.deployment.openapi; + +import javax.persistence.Entity; + +@Entity +public class Item extends AbstractItem { + +} diff --git a/extensions/panache/hibernate-orm-rest-data-panache/deployment/src/test/java/io/quarkus/hibernate/orm/rest/data/panache/deployment/openapi/ItemsRepository.java b/extensions/panache/hibernate-orm-rest-data-panache/deployment/src/test/java/io/quarkus/hibernate/orm/rest/data/panache/deployment/openapi/ItemsRepository.java new file mode 100644 index 0000000000000..6367d59d99d30 --- /dev/null +++ b/extensions/panache/hibernate-orm-rest-data-panache/deployment/src/test/java/io/quarkus/hibernate/orm/rest/data/panache/deployment/openapi/ItemsRepository.java @@ -0,0 +1,9 @@ +package io.quarkus.hibernate.orm.rest.data.panache.deployment.openapi; + +import javax.enterprise.context.ApplicationScoped; + +import io.quarkus.hibernate.orm.panache.PanacheRepository; + +@ApplicationScoped +public class ItemsRepository implements PanacheRepository { +} diff --git a/extensions/panache/hibernate-orm-rest-data-panache/deployment/src/test/java/io/quarkus/hibernate/orm/rest/data/panache/deployment/openapi/ItemsResource.java b/extensions/panache/hibernate-orm-rest-data-panache/deployment/src/test/java/io/quarkus/hibernate/orm/rest/data/panache/deployment/openapi/ItemsResource.java new file mode 100644 index 0000000000000..9d6325e5719e8 --- /dev/null +++ b/extensions/panache/hibernate-orm-rest-data-panache/deployment/src/test/java/io/quarkus/hibernate/orm/rest/data/panache/deployment/openapi/ItemsResource.java @@ -0,0 +1,8 @@ +package io.quarkus.hibernate.orm.rest.data.panache.deployment.openapi; + +import io.quarkus.hibernate.orm.rest.data.panache.PanacheRepositoryResource; +import io.quarkus.rest.data.panache.ResourceProperties; + +@ResourceProperties(hal = true) +public interface ItemsResource extends PanacheRepositoryResource { +} diff --git a/extensions/panache/hibernate-orm-rest-data-panache/deployment/src/test/java/io/quarkus/hibernate/orm/rest/data/panache/deployment/openapi/OpenApiIntegrationTest.java b/extensions/panache/hibernate-orm-rest-data-panache/deployment/src/test/java/io/quarkus/hibernate/orm/rest/data/panache/deployment/openapi/OpenApiIntegrationTest.java index 8c1e6cabd5800..aca92b5b95199 100644 --- a/extensions/panache/hibernate-orm-rest-data-panache/deployment/src/test/java/io/quarkus/hibernate/orm/rest/data/panache/deployment/openapi/OpenApiIntegrationTest.java +++ b/extensions/panache/hibernate-orm-rest-data-panache/deployment/src/test/java/io/quarkus/hibernate/orm/rest/data/panache/deployment/openapi/OpenApiIntegrationTest.java @@ -9,17 +9,6 @@ import org.junit.jupiter.api.extension.RegisterExtension; import io.quarkus.builder.Version; -import io.quarkus.hibernate.orm.rest.data.panache.deployment.repository.AbstractEntity; -import io.quarkus.hibernate.orm.rest.data.panache.deployment.repository.AbstractItem; -import io.quarkus.hibernate.orm.rest.data.panache.deployment.repository.Collection; -import io.quarkus.hibernate.orm.rest.data.panache.deployment.repository.CollectionsRepository; -import io.quarkus.hibernate.orm.rest.data.panache.deployment.repository.CollectionsResource; -import io.quarkus.hibernate.orm.rest.data.panache.deployment.repository.EmptyListItem; -import io.quarkus.hibernate.orm.rest.data.panache.deployment.repository.EmptyListItemsRepository; -import io.quarkus.hibernate.orm.rest.data.panache.deployment.repository.EmptyListItemsResource; -import io.quarkus.hibernate.orm.rest.data.panache.deployment.repository.Item; -import io.quarkus.hibernate.orm.rest.data.panache.deployment.repository.ItemsRepository; -import io.quarkus.hibernate.orm.rest.data.panache.deployment.repository.ItemsResource; import io.quarkus.maven.dependency.Dependency; import io.quarkus.test.QuarkusProdModeTest; import io.restassured.RestAssured; @@ -73,7 +62,7 @@ public void testOpenApiForGeneratedResources() { is(COLLECTIONS_SCHEMA_REF)) .body("paths.'/collections/{id}'.put.responses.'201'.content.'application/json'.schema.$ref", is(COLLECTIONS_SCHEMA_REF)) - .body("paths.'/collections/{id}'.put.security[0].SecurityScheme", Matchers.hasItem("user")) + .body("paths.'/collections/{id}'.put.security[0].SecurityScheme", Matchers.hasItem("superuser")) .body("paths.'/collections/{id}'", Matchers.hasKey("delete")) .body("paths.'/collections/{id}'.delete.responses", Matchers.hasKey("204")) .body("paths.'/collections/{id}'.delete.security[0].SecurityScheme", Matchers.hasItem("admin")) diff --git a/extensions/panache/hibernate-orm-rest-data-panache/deployment/src/test/java/io/quarkus/hibernate/orm/rest/data/panache/deployment/repository/CollectionsResource.java b/extensions/panache/hibernate-orm-rest-data-panache/deployment/src/test/java/io/quarkus/hibernate/orm/rest/data/panache/deployment/repository/CollectionsResource.java index 55379c59b2f56..428e6a5be54fb 100644 --- a/extensions/panache/hibernate-orm-rest-data-panache/deployment/src/test/java/io/quarkus/hibernate/orm/rest/data/panache/deployment/repository/CollectionsResource.java +++ b/extensions/panache/hibernate-orm-rest-data-panache/deployment/src/test/java/io/quarkus/hibernate/orm/rest/data/panache/deployment/repository/CollectionsResource.java @@ -1,11 +1,8 @@ package io.quarkus.hibernate.orm.rest.data.panache.deployment.repository; import io.quarkus.hibernate.orm.rest.data.panache.PanacheRepositoryResource; -import io.quarkus.rest.data.panache.MethodProperties; import io.quarkus.rest.data.panache.ResourceProperties; -@ResourceProperties(hal = true, paged = false, halCollectionName = "item-collections", rolesAllowed = "user") +@ResourceProperties(hal = true, paged = false, halCollectionName = "item-collections") public interface CollectionsResource extends PanacheRepositoryResource { - @MethodProperties(rolesAllowed = "admin") - boolean delete(String name); } diff --git a/extensions/panache/rest-data-panache/deployment/src/main/java/io/quarkus/rest/data/panache/deployment/JaxRsResourceImplementor.java b/extensions/panache/rest-data-panache/deployment/src/main/java/io/quarkus/rest/data/panache/deployment/JaxRsResourceImplementor.java index 8a52d03eae3f0..aa6d680123d33 100644 --- a/extensions/panache/rest-data-panache/deployment/src/main/java/io/quarkus/rest/data/panache/deployment/JaxRsResourceImplementor.java +++ b/extensions/panache/rest-data-panache/deployment/src/main/java/io/quarkus/rest/data/panache/deployment/JaxRsResourceImplementor.java @@ -8,6 +8,7 @@ import javax.ws.rs.Path; import org.apache.commons.lang3.StringUtils; +import org.jboss.jandex.AnnotationInstance; import org.jboss.logging.Logger; import io.quarkus.deployment.Capabilities; @@ -88,6 +89,11 @@ private void implementClassAnnotations(ClassCreator classCreator, ResourceMetada String className = StringUtils.substringAfterLast(resourceMetadata.getResourceInterface(), "."); classCreator.addAnnotation(OPENAPI_TAG_ANNOTATION).add("name", className); } + if (resourceProperties.getClassAnnotations() != null) { + for (AnnotationInstance classAnnotation : resourceProperties.getClassAnnotations()) { + classCreator.addAnnotation(classAnnotation); + } + } } private FieldDescriptor implementResourceField(ClassCreator classCreator, ResourceMetadata resourceMetadata) { diff --git a/extensions/panache/rest-data-panache/deployment/src/main/java/io/quarkus/rest/data/panache/deployment/methods/AddMethodImplementor.java b/extensions/panache/rest-data-panache/deployment/src/main/java/io/quarkus/rest/data/panache/deployment/methods/AddMethodImplementor.java index ab1d6a59245fd..99a750d24f751 100644 --- a/extensions/panache/rest-data-panache/deployment/src/main/java/io/quarkus/rest/data/panache/deployment/methods/AddMethodImplementor.java +++ b/extensions/panache/rest-data-panache/deployment/src/main/java/io/quarkus/rest/data/panache/deployment/methods/AddMethodImplementor.java @@ -106,6 +106,7 @@ protected void implementInternal(ClassCreator classCreator, ResourceMetadata res // Add method annotations addPathAnnotation(methodCreator, resourceProperties.getPath(RESOURCE_METHOD_NAME)); + addMethodAnnotations(methodCreator, resourceProperties.getMethodAnnotations(RESOURCE_METHOD_NAME)); addPostAnnotation(methodCreator); addConsumesAnnotation(methodCreator, APPLICATION_JSON); addProducesJsonAnnotation(methodCreator, resourceProperties); diff --git a/extensions/panache/rest-data-panache/deployment/src/main/java/io/quarkus/rest/data/panache/deployment/methods/CountMethodImplementor.java b/extensions/panache/rest-data-panache/deployment/src/main/java/io/quarkus/rest/data/panache/deployment/methods/CountMethodImplementor.java index de41bdd36fd91..efc45733a161a 100644 --- a/extensions/panache/rest-data-panache/deployment/src/main/java/io/quarkus/rest/data/panache/deployment/methods/CountMethodImplementor.java +++ b/extensions/panache/rest-data-panache/deployment/src/main/java/io/quarkus/rest/data/panache/deployment/methods/CountMethodImplementor.java @@ -80,6 +80,7 @@ protected void implementInternal(ClassCreator classCreator, ResourceMetadata res addGetAnnotation(methodCreator); addProducesAnnotation(methodCreator, APPLICATION_JSON); addPathAnnotation(methodCreator, appendToPath(resourceProperties.getPath(RESOURCE_METHOD_NAME), RESOURCE_METHOD_NAME)); + addMethodAnnotations(methodCreator, resourceProperties.getMethodAnnotations(RESOURCE_METHOD_NAME)); addOpenApiResponseAnnotation(methodCreator, Response.Status.OK, Long.class, false); addSecurityAnnotations(methodCreator, resourceProperties); if (!isResteasyClassic()) { diff --git a/extensions/panache/rest-data-panache/deployment/src/main/java/io/quarkus/rest/data/panache/deployment/methods/DeleteMethodImplementor.java b/extensions/panache/rest-data-panache/deployment/src/main/java/io/quarkus/rest/data/panache/deployment/methods/DeleteMethodImplementor.java index 7bd2f25f85f48..37f1165300512 100644 --- a/extensions/panache/rest-data-panache/deployment/src/main/java/io/quarkus/rest/data/panache/deployment/methods/DeleteMethodImplementor.java +++ b/extensions/panache/rest-data-panache/deployment/src/main/java/io/quarkus/rest/data/panache/deployment/methods/DeleteMethodImplementor.java @@ -90,6 +90,7 @@ protected void implementInternal(ClassCreator classCreator, ResourceMetadata res addDeleteAnnotation(methodCreator); addPathParamAnnotation(methodCreator.getParameterAnnotations(0), "id"); addLinksAnnotation(methodCreator, resourceMetadata.getEntityType(), REL); + addMethodAnnotations(methodCreator, resourceProperties.getMethodAnnotations(RESOURCE_METHOD_NAME)); addOpenApiResponseAnnotation(methodCreator, Response.Status.NO_CONTENT); addSecurityAnnotations(methodCreator, resourceProperties); diff --git a/extensions/panache/rest-data-panache/deployment/src/main/java/io/quarkus/rest/data/panache/deployment/methods/GetMethodImplementor.java b/extensions/panache/rest-data-panache/deployment/src/main/java/io/quarkus/rest/data/panache/deployment/methods/GetMethodImplementor.java index ad1ebc72adeb1..0b3b4039f2d1b 100644 --- a/extensions/panache/rest-data-panache/deployment/src/main/java/io/quarkus/rest/data/panache/deployment/methods/GetMethodImplementor.java +++ b/extensions/panache/rest-data-panache/deployment/src/main/java/io/quarkus/rest/data/panache/deployment/methods/GetMethodImplementor.java @@ -91,6 +91,7 @@ protected void implementInternal(ClassCreator classCreator, ResourceMetadata res addPathAnnotation(methodCreator, appendToPath(resourceProperties.getPath(RESOURCE_METHOD_NAME), "{id}")); addGetAnnotation(methodCreator); addProducesJsonAnnotation(methodCreator, resourceProperties); + addMethodAnnotations(methodCreator, resourceProperties.getMethodAnnotations(RESOURCE_METHOD_NAME)); addOpenApiResponseAnnotation(methodCreator, Response.Status.OK, resourceMetadata.getEntityType()); addSecurityAnnotations(methodCreator, resourceProperties); diff --git a/extensions/panache/rest-data-panache/deployment/src/main/java/io/quarkus/rest/data/panache/deployment/methods/ListMethodImplementor.java b/extensions/panache/rest-data-panache/deployment/src/main/java/io/quarkus/rest/data/panache/deployment/methods/ListMethodImplementor.java index 5180ffa6afc31..03edb26b98cb0 100644 --- a/extensions/panache/rest-data-panache/deployment/src/main/java/io/quarkus/rest/data/panache/deployment/methods/ListMethodImplementor.java +++ b/extensions/panache/rest-data-panache/deployment/src/main/java/io/quarkus/rest/data/panache/deployment/methods/ListMethodImplementor.java @@ -143,6 +143,7 @@ private void implementPaged(ClassCreator classCreator, ResourceMetadata resource addPathAnnotation(methodCreator, resourceProperties.getPath(RESOURCE_METHOD_NAME)); addProducesAnnotation(methodCreator, APPLICATION_JSON); addLinksAnnotation(methodCreator, resourceMetadata.getEntityType(), REL); + addMethodAnnotations(methodCreator, resourceProperties.getMethodAnnotations(RESOURCE_METHOD_NAME)); addOpenApiResponseAnnotation(methodCreator, Response.Status.OK, resourceMetadata.getEntityType(), true); addSecurityAnnotations(methodCreator, resourceProperties); addSortQueryParamValidatorAnnotation(methodCreator); @@ -210,6 +211,7 @@ private void implementNotPaged(ClassCreator classCreator, ResourceMetadata resou addPathAnnotation(methodCreator, resourceProperties.getPath(RESOURCE_METHOD_NAME)); addProducesAnnotation(methodCreator, APPLICATION_JSON); addLinksAnnotation(methodCreator, resourceMetadata.getEntityType(), REL); + addMethodAnnotations(methodCreator, resourceProperties.getMethodAnnotations(RESOURCE_METHOD_NAME)); addOpenApiResponseAnnotation(methodCreator, Response.Status.OK, resourceMetadata.getEntityType(), true); addSecurityAnnotations(methodCreator, resourceProperties); addQueryParamAnnotation(methodCreator.getParameterAnnotations(0), "sort"); diff --git a/extensions/panache/rest-data-panache/deployment/src/main/java/io/quarkus/rest/data/panache/deployment/methods/StandardMethodImplementor.java b/extensions/panache/rest-data-panache/deployment/src/main/java/io/quarkus/rest/data/panache/deployment/methods/StandardMethodImplementor.java index aa74c43a1a369..1af632b140993 100644 --- a/extensions/panache/rest-data-panache/deployment/src/main/java/io/quarkus/rest/data/panache/deployment/methods/StandardMethodImplementor.java +++ b/extensions/panache/rest-data-panache/deployment/src/main/java/io/quarkus/rest/data/panache/deployment/methods/StandardMethodImplementor.java @@ -1,5 +1,7 @@ package io.quarkus.rest.data.panache.deployment.methods; +import java.util.Collection; + import javax.ws.rs.Consumes; import javax.ws.rs.DELETE; import javax.ws.rs.DefaultValue; @@ -13,6 +15,7 @@ import javax.ws.rs.core.Context; import javax.ws.rs.core.Response; +import org.jboss.jandex.AnnotationInstance; import org.jboss.logging.Logger; import io.quarkus.deployment.Capabilities; @@ -154,6 +157,14 @@ protected void addSortQueryParamValidatorAnnotation(AnnotatedElement element) { element.addAnnotation(SortQueryParamValidator.class); } + protected void addMethodAnnotations(AnnotatedElement element, Collection methodAnnotations) { + if (methodAnnotations != null) { + for (AnnotationInstance methodAnnotation : methodAnnotations) { + element.addAnnotation(methodAnnotation); + } + } + } + protected void addSecurityAnnotations(AnnotatedElement element, ResourceProperties resourceProperties) { String[] rolesAllowed = resourceProperties.getRolesAllowed(getResourceMethodName()); if (rolesAllowed.length > 0 && hasSecurityCapability()) { diff --git a/extensions/panache/rest-data-panache/deployment/src/main/java/io/quarkus/rest/data/panache/deployment/methods/UpdateMethodImplementor.java b/extensions/panache/rest-data-panache/deployment/src/main/java/io/quarkus/rest/data/panache/deployment/methods/UpdateMethodImplementor.java index e74b77a908a79..e05fa639072df 100644 --- a/extensions/panache/rest-data-panache/deployment/src/main/java/io/quarkus/rest/data/panache/deployment/methods/UpdateMethodImplementor.java +++ b/extensions/panache/rest-data-panache/deployment/src/main/java/io/quarkus/rest/data/panache/deployment/methods/UpdateMethodImplementor.java @@ -143,6 +143,7 @@ protected void implementInternal(ClassCreator classCreator, ResourceMetadata res addConsumesAnnotation(methodCreator, APPLICATION_JSON); addProducesJsonAnnotation(methodCreator, resourceProperties); addLinksAnnotation(methodCreator, resourceMetadata.getEntityType(), REL); + addMethodAnnotations(methodCreator, resourceProperties.getMethodAnnotations(RESOURCE_UPDATE_METHOD_NAME)); addOpenApiResponseAnnotation(methodCreator, Response.Status.CREATED, resourceMetadata.getEntityType()); addSecurityAnnotations(methodCreator, resourceProperties); // Add parameter annotations diff --git a/extensions/panache/rest-data-panache/deployment/src/main/java/io/quarkus/rest/data/panache/deployment/methods/hal/ListHalMethodImplementor.java b/extensions/panache/rest-data-panache/deployment/src/main/java/io/quarkus/rest/data/panache/deployment/methods/hal/ListHalMethodImplementor.java index 09da8fbfe7e73..df974df30777d 100644 --- a/extensions/panache/rest-data-panache/deployment/src/main/java/io/quarkus/rest/data/panache/deployment/methods/hal/ListHalMethodImplementor.java +++ b/extensions/panache/rest-data-panache/deployment/src/main/java/io/quarkus/rest/data/panache/deployment/methods/hal/ListHalMethodImplementor.java @@ -134,6 +134,7 @@ private void implementPaged(ClassCreator classCreator, ResourceMetadata resource addPathAnnotation(methodCreator, resourceProperties.getPath(RESOURCE_METHOD_NAME)); addGetAnnotation(methodCreator); addProducesAnnotation(methodCreator, APPLICATION_HAL_JSON); + addMethodAnnotations(methodCreator, resourceProperties.getMethodAnnotations(RESOURCE_METHOD_NAME)); addSecurityAnnotations(methodCreator, resourceProperties); addSortQueryParamValidatorAnnotation(methodCreator); addQueryParamAnnotation(methodCreator.getParameterAnnotations(0), "sort"); @@ -209,6 +210,7 @@ private void implementNotPaged(ClassCreator classCreator, ResourceMetadata resou addPathAnnotation(methodCreator, resourceProperties.getPath(RESOURCE_METHOD_NAME)); addGetAnnotation(methodCreator); addProducesAnnotation(methodCreator, APPLICATION_HAL_JSON); + addMethodAnnotations(methodCreator, resourceProperties.getMethodAnnotations(RESOURCE_METHOD_NAME)); addSecurityAnnotations(methodCreator, resourceProperties); addQueryParamAnnotation(methodCreator.getParameterAnnotations(0), "sort"); diff --git a/extensions/panache/rest-data-panache/deployment/src/main/java/io/quarkus/rest/data/panache/deployment/properties/MethodProperties.java b/extensions/panache/rest-data-panache/deployment/src/main/java/io/quarkus/rest/data/panache/deployment/properties/MethodProperties.java index e9f6b48e05928..827ef91a36ef4 100644 --- a/extensions/panache/rest-data-panache/deployment/src/main/java/io/quarkus/rest/data/panache/deployment/properties/MethodProperties.java +++ b/extensions/panache/rest-data-panache/deployment/src/main/java/io/quarkus/rest/data/panache/deployment/properties/MethodProperties.java @@ -1,5 +1,9 @@ package io.quarkus.rest.data.panache.deployment.properties; +import java.util.Collection; + +import org.jboss.jandex.AnnotationInstance; + public class MethodProperties { private final boolean exposed; @@ -8,10 +12,14 @@ public class MethodProperties { private final String[] rolesAllowed; - public MethodProperties(boolean exposed, String path, String[] rolesAllowed) { + private final Collection methodAnnotations; + + public MethodProperties(boolean exposed, String path, String[] rolesAllowed, + Collection methodAnnotations) { this.exposed = exposed; this.path = path; this.rolesAllowed = rolesAllowed; + this.methodAnnotations = methodAnnotations; } public boolean isExposed() { @@ -25,4 +33,8 @@ public String getPath() { public String[] getRolesAllowed() { return rolesAllowed; } + + public Collection getMethodAnnotations() { + return methodAnnotations; + } } diff --git a/extensions/panache/rest-data-panache/deployment/src/main/java/io/quarkus/rest/data/panache/deployment/properties/ResourceProperties.java b/extensions/panache/rest-data-panache/deployment/src/main/java/io/quarkus/rest/data/panache/deployment/properties/ResourceProperties.java index 9208b8ef049c0..20002de2a496e 100644 --- a/extensions/panache/rest-data-panache/deployment/src/main/java/io/quarkus/rest/data/panache/deployment/properties/ResourceProperties.java +++ b/extensions/panache/rest-data-panache/deployment/src/main/java/io/quarkus/rest/data/panache/deployment/properties/ResourceProperties.java @@ -1,7 +1,11 @@ package io.quarkus.rest.data.panache.deployment.properties; +import java.util.Collection; +import java.util.Collections; import java.util.Map; +import org.jboss.jandex.AnnotationInstance; + public class ResourceProperties { private final boolean exposed; @@ -16,16 +20,20 @@ public class ResourceProperties { private final String[] rolesAllowed; + private final Collection classAnnotations; + private final Map methodProperties; public ResourceProperties(boolean exposed, String path, boolean paged, boolean hal, String halCollectionName, - String[] rolesAllowed, Map methodProperties) { + String[] rolesAllowed, Collection classAnnotations, + Map methodProperties) { this.exposed = exposed; this.path = path; this.paged = paged; this.hal = hal; this.halCollectionName = halCollectionName; this.rolesAllowed = rolesAllowed; + this.classAnnotations = classAnnotations; this.methodProperties = methodProperties; } @@ -79,4 +87,16 @@ public String[] getRolesAllowed(String methodName) { return rolesAllowed; } + + public Collection getClassAnnotations() { + return classAnnotations; + } + + public Collection getMethodAnnotations(String methodName) { + if (methodProperties.containsKey(methodName)) { + return methodProperties.get(methodName).getMethodAnnotations(); + } + + return Collections.emptyList(); + } } 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 c2c3e72f025b2..feb4b4976bdb4 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 @@ -1,7 +1,13 @@ package io.quarkus.rest.data.panache.deployment.properties; +import java.util.Collection; import java.util.HashMap; +import java.util.HashSet; +import java.util.List; import java.util.Map; +import java.util.Set; + +import javax.annotation.security.RolesAllowed; import org.jboss.jandex.AnnotationInstance; import org.jboss.jandex.ClassInfo; @@ -19,6 +25,8 @@ 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 final IndexView index; public ResourcePropertiesProvider(IndexView index) { @@ -32,9 +40,6 @@ public ResourcePropertiesProvider(IndexView index) { public ResourceProperties getForInterface(String resourceInterface) { DotName resourceInterfaceName = DotName.createSimple(resourceInterface); AnnotationInstance annotation = findResourcePropertiesAnnotation(resourceInterfaceName); - Map methodProperties = new HashMap<>(); - collectMethodProperties(resourceInterfaceName, methodProperties); - return new ResourceProperties( isExposed(annotation), getPath(annotation, resourceInterface), @@ -42,7 +47,28 @@ public ResourceProperties getForInterface(String resourceInterface) { isHal(annotation), getHalCollectionName(annotation, resourceInterface), getRolesAllowed(annotation), - methodProperties); + collectAnnotationsToCopy(resourceInterfaceName), + collectMethodProperties(resourceInterfaceName)); + } + + private Collection collectAnnotationsToCopy(DotName className) { + Set annotations = new HashSet<>(); + ClassInfo classInfo = index.getClassByName(className); + if (classInfo == null) { + return annotations; + } + + for (AnnotationInstance annotation : classInfo.classAnnotations()) { + if (ANNOTATIONS_TO_COPY.stream().anyMatch(annotation.name().toString()::startsWith)) { + annotations.add(annotation); + } + } + + if (classInfo.superName() != null) { + annotations.addAll(collectAnnotationsToCopy(classInfo.superName())); + } + + return annotations; } private AnnotationInstance findResourcePropertiesAnnotation(DotName className) { @@ -59,23 +85,36 @@ private AnnotationInstance findResourcePropertiesAnnotation(DotName className) { return null; } - private void collectMethodProperties(DotName className, Map properties) { + private Map collectMethodProperties(DotName className) { + Map methodProperties = new HashMap<>(); ClassInfo classInfo = index.getClassByName(className); if (classInfo == null) { - return; + return methodProperties; } + for (MethodInfo method : classInfo.methods()) { - if (!properties.containsKey(method.name()) && method.hasAnnotation(METHOD_PROPERTIES_ANNOTATION)) { - properties.put(method.name(), getMethodProperties(method.annotation(METHOD_PROPERTIES_ANNOTATION))); + AnnotationInstance annotation = method.annotation(METHOD_PROPERTIES_ANNOTATION); + Set annotationsToCopy = new HashSet<>(); + for (AnnotationInstance ann : method.annotations()) { + if (ANNOTATIONS_TO_COPY.stream().anyMatch(ann.name().toString()::startsWith)) { + annotationsToCopy.add(ann); + } + } + + if (!methodProperties.containsKey(method.name()) + && (annotation != null || !annotationsToCopy.isEmpty())) { + methodProperties.put(method.name(), getMethodProperties(annotation, annotationsToCopy)); } } if (classInfo.superName() != null) { - collectMethodProperties(classInfo.superName(), properties); + methodProperties.putAll(collectMethodProperties(classInfo.superName())); } + + return methodProperties; } - private MethodProperties getMethodProperties(AnnotationInstance annotation) { - return new MethodProperties(isExposed(annotation), getPath(annotation), getRolesAllowed(annotation)); + private MethodProperties getMethodProperties(AnnotationInstance annotation, Set annotationsToCopy) { + return new MethodProperties(isExposed(annotation), getPath(annotation), getRolesAllowed(annotation), annotationsToCopy); } private boolean isHal(AnnotationInstance annotation) { diff --git a/extensions/spring-data-rest/deployment/src/main/java/io/quarkus/spring/data/rest/deployment/ResourcePropertiesProvider.java b/extensions/spring-data-rest/deployment/src/main/java/io/quarkus/spring/data/rest/deployment/ResourcePropertiesProvider.java index 4613e4e22150e..85ce58d1a4d2f 100644 --- a/extensions/spring-data-rest/deployment/src/main/java/io/quarkus/spring/data/rest/deployment/ResourcePropertiesProvider.java +++ b/extensions/spring-data-rest/deployment/src/main/java/io/quarkus/spring/data/rest/deployment/ResourcePropertiesProvider.java @@ -1,9 +1,15 @@ package io.quarkus.spring.data.rest.deployment; +import java.util.Collection; import java.util.HashMap; +import java.util.HashSet; +import java.util.List; import java.util.Map; +import java.util.Set; import java.util.function.Predicate; +import javax.annotation.security.RolesAllowed; + import org.jboss.jandex.AnnotationInstance; import org.jboss.jandex.ClassInfo; import org.jboss.jandex.DotName; @@ -23,6 +29,8 @@ public abstract class ResourcePropertiesProvider { private static final DotName REPOSITORY_REST_RESOURCE_ANNOTATION = DotName .createSimple(RepositoryRestResource.class.getName()); + private static final List ANNOTATIONS_TO_COPY = List.of(RolesAllowed.class.getPackageName()); + private final IndexView index; private final boolean paged; @@ -41,22 +49,50 @@ public ResourceProperties getResourceProperties(String interfaceName) { String halCollectionName = getHalCollectionName(annotation, ResourceName.fromClass(interfaceName)); return new ResourceProperties(isExposed(annotation), resourcePath, paged, true, halCollectionName, - new String[0], getMethodProperties(repositoryInterfaceName)); + new String[0], collectAnnotationsToCopy(repositoryInterfaceName), getMethodProperties(repositoryInterfaceName)); } private Map getMethodProperties(DotName interfaceName) { Map methodPropertiesMap = new HashMap<>(); for (Map.Entry> method : getMethodPredicates().entrySet()) { - AnnotationInstance annotation = findMethodAnnotation(interfaceName, method.getValue()); - if (annotation != null) { - methodPropertiesMap.putIfAbsent(method.getKey(), getMethodProperties(annotation)); + MethodWithAnnotation methodWithAnnotation = findMethodAnnotation(interfaceName, method.getValue()); + if (methodWithAnnotation != null) { + Set annotationsToCopy = new HashSet<>(); + for (AnnotationInstance ann : methodWithAnnotation.method.annotations()) { + if (ANNOTATIONS_TO_COPY.stream().anyMatch(ann.name().toString()::startsWith)) { + annotationsToCopy.add(ann); + } + } + + methodPropertiesMap.putIfAbsent(method.getKey(), + getMethodProperties(methodWithAnnotation.annotation, annotationsToCopy)); } } return methodPropertiesMap; } - private MethodProperties getMethodProperties(AnnotationInstance annotation) { - return new MethodProperties(isExposed(annotation), getPath(annotation, ""), new String[0]); + private MethodProperties getMethodProperties(AnnotationInstance annotation, Set annotationsToCopy) { + return new MethodProperties(isExposed(annotation), getPath(annotation, ""), new String[0], annotationsToCopy); + } + + private Collection collectAnnotationsToCopy(DotName className) { + Set annotations = new HashSet<>(); + ClassInfo classInfo = index.getClassByName(className); + if (classInfo == null) { + return annotations; + } + + for (AnnotationInstance annotation : classInfo.classAnnotations()) { + if (ANNOTATIONS_TO_COPY.stream().anyMatch(annotation.name().toString()::startsWith)) { + annotations.add(annotation); + } + } + + if (classInfo.superName() != null) { + annotations.addAll(collectAnnotationsToCopy(classInfo.superName())); + } + + return annotations; } private AnnotationInstance findClassAnnotation(DotName interfaceName) { @@ -76,7 +112,7 @@ private AnnotationInstance findClassAnnotation(DotName interfaceName) { return null; } - private AnnotationInstance findMethodAnnotation(DotName interfaceName, Predicate methodPredicate) { + private MethodWithAnnotation findMethodAnnotation(DotName interfaceName, Predicate methodPredicate) { ClassInfo classInfo = index.getClassByName(interfaceName); if (classInfo == null) { return null; @@ -84,9 +120,15 @@ private AnnotationInstance findMethodAnnotation(DotName interfaceName, Predicate for (MethodInfo method : classInfo.methods()) { if (methodPredicate.test(method)) { if (method.hasAnnotation(REPOSITORY_REST_RESOURCE_ANNOTATION)) { - return method.annotation(REPOSITORY_REST_RESOURCE_ANNOTATION); + MethodWithAnnotation found = new MethodWithAnnotation(); + found.method = method; + found.annotation = method.annotation(REPOSITORY_REST_RESOURCE_ANNOTATION); + return found; } else if (method.hasAnnotation(REST_RESOURCE_ANNOTATION)) { - return method.annotation(REST_RESOURCE_ANNOTATION); + MethodWithAnnotation found = new MethodWithAnnotation(); + found.method = method; + found.annotation = method.annotation(REST_RESOURCE_ANNOTATION); + return found; } } } @@ -115,4 +157,9 @@ private String getHalCollectionName(AnnotationInstance annotation, String defaul } return defaultValue; } + + class MethodWithAnnotation { + MethodInfo method; + AnnotationInstance annotation; + } } From c2615490995028738e9b126be8201f27441ceb4a Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Wed, 2 Nov 2022 17:23:00 +0200 Subject: [PATCH 02/22] Make the combination of @JsonView and @SecureField work (cherry picked from commit 58c15652827711abc2bdeda643d5006f2f911250) --- .../deployment/test/MultipartTest.java | 2 +- .../jackson/deployment/test/Person.java | 13 +++++++++ .../deployment/test/SimpleJsonResource.java | 24 ++++++++++++++++ .../deployment/test/SimpleJsonTest.java | 28 +++++++++++++++++++ ...eaturedServerJacksonMessageBodyWriter.java | 4 +++ 5 files changed, 70 insertions(+), 1 deletion(-) diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/MultipartTest.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/MultipartTest.java index 3daab7970555c..ee6084c247bd3 100644 --- a/extensions/resteasy-reactive/quarkus-resteasy-reactive-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/MultipartTest.java +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/MultipartTest.java @@ -25,7 +25,7 @@ public class MultipartTest { @Override public JavaArchive get() { return ShrinkWrap.create(JavaArchive.class) - .addClasses(FormData.class, Person.class, MultipartResource.class) + .addClasses(FormData.class, Person.class, Views.class, MultipartResource.class) .addAsResource(new StringAsset("quarkus.http.body.delete-uploaded-files-on-end=true\n"), "application.properties"); } diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/Person.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/Person.java index 3d1034f5e5638..2bf5dde005981 100644 --- a/extensions/resteasy-reactive/quarkus-resteasy-reactive-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/Person.java +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/Person.java @@ -2,6 +2,8 @@ import javax.validation.constraints.NotBlank; +import com.fasterxml.jackson.annotation.JsonView; + import io.quarkus.resteasy.reactive.jackson.SecureField; public class Person { @@ -12,6 +14,9 @@ public class Person { @SecureField(rolesAllowed = "admin") private String last; + @JsonView(Views.Private.class) + public int id = 0; + public String getFirst() { return first; } @@ -27,4 +32,12 @@ public String getLast() { public void setLast(String last) { this.last = last; } + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } } diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/SimpleJsonResource.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/SimpleJsonResource.java index 4dc9fae2c87a7..579d8f1428c0b 100644 --- a/extensions/resteasy-reactive/quarkus-resteasy-reactive-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/SimpleJsonResource.java +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/SimpleJsonResource.java @@ -69,6 +69,30 @@ public Person getSecurePerson() { return getPerson(); } + @JsonView(Views.Public.class) + @EnableSecureSerialization + @GET + @Path("secure-person-with-public-view") + public Person getSecurePersonWithPublicView() { + return getPerson(); + } + + @JsonView(Views.Public.class) + @EnableSecureSerialization + @GET + @Path("uni-secure-person-with-public-view") + public Uni getUniSecurePersonWithPublicView() { + return Uni.createFrom().item(getPerson()); + } + + @JsonView(Views.Private.class) + @EnableSecureSerialization + @GET + @Path("secure-person-with-private-view") + public Person getSecurePersonWithPrivateView() { + return getPerson(); + } + @EnableSecureSerialization @GET @Path("secure-uni-person") diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/SimpleJsonTest.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/SimpleJsonTest.java index 1442ba73da81a..ba64b502ffc03 100644 --- a/extensions/resteasy-reactive/quarkus-resteasy-reactive-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/SimpleJsonTest.java +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/SimpleJsonTest.java @@ -313,6 +313,21 @@ public void testSecurePerson() { doTestSecurePerson("/simple", "/secure-person"); } + @Test + public void testSecurePersonWithPrivateView() { + doTestSecurePerson("/simple", "/secure-person-with-private-view"); + } + + @Test + public void testSecurePersonWithPublicView() { + doTestSecurePersonWithPublicView("/simple", "/secure-person-with-public-view"); + } + + @Test + public void testUniSecurePersonWithPublicView() { + doTestSecurePersonWithPublicView("/simple", "/uni-secure-person-with-public-view"); + } + @Test public void testSecurePersonFromAbstract() { doTestSecurePerson("/other", "/abstract-with-security"); @@ -336,6 +351,19 @@ private void doTestSecurePerson(String basePath, final String path) { .header("transfer-encoding", nullValue()) .header("content-length", notNullValue()) .body(containsString("Bob")) + .body(containsString("0")) + .body(not(containsString("Builder"))); + } + + private void doTestSecurePersonWithPublicView(String basePath, final String path) { + RestAssured.get(basePath + path) + .then() + .statusCode(200) + .contentType("application/json") + .header("transfer-encoding", nullValue()) + .header("content-length", notNullValue()) + .body(containsString("Bob")) + .body(not(containsString("0"))) .body(not(containsString("Builder"))); } diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive-jackson/runtime/src/main/java/io/quarkus/resteasy/reactive/jackson/runtime/serialisers/FullyFeaturedServerJacksonMessageBodyWriter.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive-jackson/runtime/src/main/java/io/quarkus/resteasy/reactive/jackson/runtime/serialisers/FullyFeaturedServerJacksonMessageBodyWriter.java index 1612b48721e80..67c59f418b20f 100644 --- a/extensions/resteasy-reactive/quarkus-resteasy-reactive-jackson/runtime/src/main/java/io/quarkus/resteasy/reactive/jackson/runtime/serialisers/FullyFeaturedServerJacksonMessageBodyWriter.java +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive-jackson/runtime/src/main/java/io/quarkus/resteasy/reactive/jackson/runtime/serialisers/FullyFeaturedServerJacksonMessageBodyWriter.java @@ -63,6 +63,10 @@ public void writeResponse(Object o, Type genericType, ServerRequestContext conte if (customSerializationValue != null) { ObjectWriter objectWriter = perMethodWriter.computeIfAbsent(methodId, new MethodObjectWriterFunction(customSerializationValue, genericType, effectiveMapper)); + Class jsonViewValue = ResteasyReactiveServerJacksonRecorder.jsonViewForMethod(methodId); + if (jsonViewValue != null) { + objectWriter = objectWriter.withView(jsonViewValue); + } objectWriter.writeValue(stream, o); return; } From 82529cf8da3dcf63d6a0b9e0834c298c3a334b94 Mon Sep 17 00:00:00 2001 From: George Gastaldi Date: Mon, 31 Oct 2022 23:38:11 -0300 Subject: [PATCH 03/22] Quarkiverse: Support release from feature branches This updates the release workflow to support releasing from feature branches - Bump actions/checkout and actions/setup-java to v3 (cherry picked from commit 8db677c73d82dd27cb584ab17039616a368b7a3e) --- .../code/quarkiverse/java/.github/workflows/build.yml | 4 ++-- .../java/.github/workflows/quarkus-snapshot.tpl.qute.yaml | 6 +++--- .../code/quarkiverse/java/.github/workflows/release.yml | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/independent-projects/tools/base-codestarts/src/main/resources/codestarts/quarkus-extension/code/quarkiverse/java/.github/workflows/build.yml b/independent-projects/tools/base-codestarts/src/main/resources/codestarts/quarkus-extension/code/quarkiverse/java/.github/workflows/build.yml index c7aa10d5c32f7..03a05295ef8ad 100644 --- a/independent-projects/tools/base-codestarts/src/main/resources/codestarts/quarkus-extension/code/quarkiverse/java/.github/workflows/build.yml +++ b/independent-projects/tools/base-codestarts/src/main/resources/codestarts/quarkus-extension/code/quarkiverse/java/.github/workflows/build.yml @@ -36,9 +36,9 @@ jobs: run: git config --global core.autocrlf false if: startsWith(matrix.os, 'windows') - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Set up JDK 11 - uses: actions/setup-java@v2 + uses: actions/setup-java@v3 with: distribution: temurin java-version: 11 diff --git a/independent-projects/tools/base-codestarts/src/main/resources/codestarts/quarkus-extension/code/quarkiverse/java/.github/workflows/quarkus-snapshot.tpl.qute.yaml b/independent-projects/tools/base-codestarts/src/main/resources/codestarts/quarkus-extension/code/quarkiverse/java/.github/workflows/quarkus-snapshot.tpl.qute.yaml index b0f2f4a4c48d1..3329ea6101196 100644 --- a/independent-projects/tools/base-codestarts/src/main/resources/codestarts/quarkus-extension/code/quarkiverse/java/.github/workflows/quarkus-snapshot.tpl.qute.yaml +++ b/independent-projects/tools/base-codestarts/src/main/resources/codestarts/quarkus-extension/code/quarkiverse/java/.github/workflows/quarkus-snapshot.tpl.qute.yaml @@ -30,18 +30,18 @@ jobs: run: sudo add-apt-repository ppa:rmescandon/yq && sudo apt update && sudo apt install yq -y - name: Set up Java - uses: actions/setup-java@v2 + uses: actions/setup-java@v3 with: distribution: temurin java-version: ${{ env.JAVA_VERSION }} - name: Checkout repo - uses: actions/checkout@v2 + uses: actions/checkout@v3 with: path: current-repo - name: Checkout Ecosystem - uses: actions/checkout@v2 + uses: actions/checkout@v3 with: repository: ${{ env.ECOSYSTEM_CI_REPO }} path: ecosystem-ci diff --git a/independent-projects/tools/base-codestarts/src/main/resources/codestarts/quarkus-extension/code/quarkiverse/java/.github/workflows/release.yml b/independent-projects/tools/base-codestarts/src/main/resources/codestarts/quarkus-extension/code/quarkiverse/java/.github/workflows/release.yml index 00c011edbd3b6..9bd97b523e449 100644 --- a/independent-projects/tools/base-codestarts/src/main/resources/codestarts/quarkus-extension/code/quarkiverse/java/.github/workflows/release.yml +++ b/independent-projects/tools/base-codestarts/src/main/resources/codestarts/quarkus-extension/code/quarkiverse/java/.github/workflows/release.yml @@ -69,8 +69,8 @@ jobs: # Move the tag after inclusion of documentation adjustments git tag -f ${{steps.metadata.outputs.current-version}} fi - # Go back to main - git checkout main + # Go back to base branch + git checkout ${{github.base_ref}} - name: Push changes to ${{github.base_ref}} uses: ad-m/github-push-action@v0.6.0 From 9ad56e59e2fd1c2e842cfe3c817c05d4e1b72a3a Mon Sep 17 00:00:00 2001 From: Clement Escoffier Date: Wed, 26 Oct 2022 16:27:58 +0200 Subject: [PATCH 04/22] Update GraalVM 22.3.0 images (cherry picked from commit 258ea39038c5601f26a4377b3c246009ae457c51) --- .github/workflows/native-cron-build.yml.disabled | 4 ++-- bom/application/pom.xml | 2 +- build-parent/pom.xml | 1 - .../src/main/java/io/quarkus/deployment/pkg/NativeConfig.java | 4 ++-- .../main/java/io/quarkus/deployment/pkg/steps/GraalVM.java | 2 +- docs/pom.xml | 4 ++-- docs/src/main/asciidoc/building-native-image.adoc | 4 ++-- independent-projects/bootstrap/pom.xml | 2 +- 8 files changed, 11 insertions(+), 12 deletions(-) diff --git a/.github/workflows/native-cron-build.yml.disabled b/.github/workflows/native-cron-build.yml.disabled index 958f21e6aefb3..0440990e0f744 100644 --- a/.github/workflows/native-cron-build.yml.disabled +++ b/.github/workflows/native-cron-build.yml.disabled @@ -20,7 +20,7 @@ jobs: run: sudo systemctl stop mysql - name: Pull docker image - run: docker pull quay.io/quarkus/ubi-quarkus-graalvmce-builder-image:22.2-java${{ matrix.java }} + run: docker pull quay.io/quarkus/ubi-quarkus-graalvmce-builder-image:22.3-java${{ matrix.java }} - name: Set up JDK ${{ matrix.java }} uses: actions/setup-java@v2 @@ -55,7 +55,7 @@ jobs: run: ./mvnw -B install -DskipTests -DskipITs -Dformat.skip - name: Run integration tests in native - run: ./mvnw -B --settings .github/mvn-settings.xml verify -f integration-tests/pom.xml --fail-at-end -Dno-format -Dtest-containers -Dstart-containers -Dnative -Dquarkus.native.container-build=true -Dquarkus.native.builder-image=quay.io/quarkus/ubi-quarkus-graalvmce-builder-image:22.2-java${{ matrix.java }} -pl '!io.quarkus:quarkus-integration-test-google-cloud-functions-http,!io.quarkus:quarkus-integration-test-google-cloud-functions,!io.quarkus:quarkus-integration-test-funqy-google-cloud-functions' + run: ./mvnw -B --settings .github/mvn-settings.xml verify -f integration-tests/pom.xml --fail-at-end -Dno-format -Dtest-containers -Dstart-containers -Dnative -Dquarkus.native.container-build=true -Dquarkus.native.builder-image=quay.io/quarkus/ubi-quarkus-graalvmce-builder-image:22.3-java${{ matrix.java }} -pl '!io.quarkus:quarkus-integration-test-google-cloud-functions-http,!io.quarkus:quarkus-integration-test-google-cloud-functions,!io.quarkus:quarkus-integration-test-funqy-google-cloud-functions' - name: Report if: always() diff --git a/bom/application/pom.xml b/bom/application/pom.xml index c2b9e6c00e50d..730755f59ae0a 100644 --- a/bom/application/pom.xml +++ b/bom/application/pom.xml @@ -80,7 +80,7 @@ 3.0-alpha-2 3.3.0 2.1.0 - 22.2.0 + 22.3.0 ${graal-sdk.version} 1.4.0.Final 2.13.4.20221013 diff --git a/build-parent/pom.xml b/build-parent/pom.xml index de90d71b6ab25..644a34a6b30c3 100644 --- a/build-parent/pom.xml +++ b/build-parent/pom.xml @@ -37,7 +37,6 @@ 3.0.1 1.0.0 - 2.5.7 2.40.0 3.24.2 diff --git a/core/deployment/src/main/java/io/quarkus/deployment/pkg/NativeConfig.java b/core/deployment/src/main/java/io/quarkus/deployment/pkg/NativeConfig.java index 66fe61a84ffca..d07ef3279f12e 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/pkg/NativeConfig.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/pkg/NativeConfig.java @@ -16,7 +16,7 @@ @ConfigRoot(phase = ConfigPhase.BUILD_TIME) public class NativeConfig { - public static final String DEFAULT_GRAALVM_BUILDER_IMAGE = "quay.io/quarkus/ubi-quarkus-graalvmce-builder-image:22.2-java17"; + public static final String DEFAULT_GRAALVM_BUILDER_IMAGE = "quay.io/quarkus/ubi-quarkus-graalvmce-builder-image:22.3-java17"; public static final String DEFAULT_MANDREL_BUILDER_IMAGE = "quay.io/quarkus/ubi-quarkus-mandrel-builder-image:22.2-java17"; /** @@ -211,7 +211,7 @@ public boolean isContainerBuild() { /** * The docker image to use to do the image build. It can be one of `graalvm`, `mandrel`, or the full image path, e.g. - * {@code quay.io/quarkus/ubi-quarkus-mandrel-builder-image:22.2-java17}. + * {@code quay.io/quarkus/ubi-quarkus-mandrel-builder-image:22.3-java17}. */ @ConfigItem(defaultValue = "${platform.quarkus.native.builder-image}") public String builderImage; diff --git a/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/GraalVM.java b/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/GraalVM.java index ff9426f3a9c5b..c4a441876f141 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/GraalVM.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/GraalVM.java @@ -27,7 +27,7 @@ public static final class Version implements Comparable { public static final Version VERSION_22_2_0 = new Version("GraalVM 22.2.0", "22.2.0", Distribution.ORACLE); public static final Version MINIMUM = VERSION_22_2_0; - public static final Version CURRENT = VERSION_22_2_0; + public static final Version CURRENT = VERSION_22_3_0; public static final int UNDEFINED = -1; final String fullVersion; diff --git a/docs/pom.xml b/docs/pom.xml index be85311b46bcf..77423c26d443a 100644 --- a/docs/pom.xml +++ b/docs/pom.xml @@ -17,8 +17,8 @@ - 22.2 - 22.2 + 22.3 + 22.3 2.0.0 1.5.0-beta.8 diff --git a/docs/src/main/asciidoc/building-native-image.adoc b/docs/src/main/asciidoc/building-native-image.adoc index b1e2a9ef71425..d7d2c1fc0df20 100644 --- a/docs/src/main/asciidoc/building-native-image.adoc +++ b/docs/src/main/asciidoc/building-native-image.adoc @@ -653,10 +653,10 @@ Building fully statically linked binaries enables the usage of a https://hub.doc Sample multistage Dockerfile for building an image from `scratch`: -[source, dockerfile] +[source,dockerfile,subs=attributes+] ---- ## Stage 1 : build with maven builder image with native capabilities -FROM quay.io/quarkus/ubi-quarkus-graalvmce-builder-image:22.2-java11 AS build +FROM quay.io/quarkus/ubi-quarkus-graalvmce-builder-image:{graalvm-flavor} AS build USER root RUN microdnf install make gcc COPY --chown=quarkus:quarkus mvnw /code/mvnw diff --git a/independent-projects/bootstrap/pom.xml b/independent-projects/bootstrap/pom.xml index a5515274a6908..f09b4e2fa3021 100644 --- a/independent-projects/bootstrap/pom.xml +++ b/independent-projects/bootstrap/pom.xml @@ -62,7 +62,7 @@ 1.0.11 1.1.0.Final 1.7.36 - 22.2.0 + 22.3.0 2.6.0 1.13.2 7.5.1 From 8ad3af59414a6e2620f50a4e503ecf75dbcd96f9 Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Thu, 27 Oct 2022 11:45:06 +0200 Subject: [PATCH 05/22] Use the GraalVM 22.3 AlwaysInline annotation This annotation has moved to a new package and we need to use the 22.3 version to make sure our code compiles. As for using 22.2, it still works but the annotation is ignored, which looks OK as the annotation is not used in places where it is strictly necessary. If people use the recommended GraalVM version, everything will be fine. If not, things will mostly work anyway. (cherry picked from commit 3d856512cb4b85b800a446266159ceeebed5a578) --- .../io/quarkus/runtime/graal/ConfigurationSubstitutions.java | 2 +- .../microsoft/sqlserver/jdbc/SQLServerJDBCSubstitutions.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/core/runtime/src/main/java/io/quarkus/runtime/graal/ConfigurationSubstitutions.java b/core/runtime/src/main/java/io/quarkus/runtime/graal/ConfigurationSubstitutions.java index b7d18c0bd394e..c65c8bbad718c 100644 --- a/core/runtime/src/main/java/io/quarkus/runtime/graal/ConfigurationSubstitutions.java +++ b/core/runtime/src/main/java/io/quarkus/runtime/graal/ConfigurationSubstitutions.java @@ -2,8 +2,8 @@ import org.eclipse.microprofile.config.Config; +import com.oracle.svm.core.AlwaysInline; import com.oracle.svm.core.annotate.Alias; -import com.oracle.svm.core.annotate.AlwaysInline; import com.oracle.svm.core.annotate.Substitute; import com.oracle.svm.core.annotate.TargetClass; diff --git a/extensions/jdbc/jdbc-mssql/runtime/src/main/java/io/quarkus/jdbc/mssql/runtime/graal/com/microsoft/sqlserver/jdbc/SQLServerJDBCSubstitutions.java b/extensions/jdbc/jdbc-mssql/runtime/src/main/java/io/quarkus/jdbc/mssql/runtime/graal/com/microsoft/sqlserver/jdbc/SQLServerJDBCSubstitutions.java index b5da254262cee..7d1eda785d190 100644 --- a/extensions/jdbc/jdbc-mssql/runtime/src/main/java/io/quarkus/jdbc/mssql/runtime/graal/com/microsoft/sqlserver/jdbc/SQLServerJDBCSubstitutions.java +++ b/extensions/jdbc/jdbc-mssql/runtime/src/main/java/io/quarkus/jdbc/mssql/runtime/graal/com/microsoft/sqlserver/jdbc/SQLServerJDBCSubstitutions.java @@ -8,7 +8,7 @@ import com.microsoft.sqlserver.jdbc.SQLServerException; import com.microsoft.sqlserver.jdbc.SQLServerStatement; -import com.oracle.svm.core.annotate.AlwaysInline; +import com.oracle.svm.core.AlwaysInline; import com.oracle.svm.core.annotate.Delete; import com.oracle.svm.core.annotate.Substitute; import com.oracle.svm.core.annotate.TargetClass; From 2a51e3e2c6500b76c888d3b017a27307cfe2ac3a Mon Sep 17 00:00:00 2001 From: Clement Escoffier Date: Wed, 2 Nov 2022 19:57:03 +0100 Subject: [PATCH 06/22] Update to Mandrel 22.3 images (cherry picked from commit f2bf43dc131a5fcd45f5aefff704cc717eff371d) --- .../src/main/java/io/quarkus/deployment/pkg/NativeConfig.java | 2 +- docs/src/main/asciidoc/doc-reference.adoc | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/core/deployment/src/main/java/io/quarkus/deployment/pkg/NativeConfig.java b/core/deployment/src/main/java/io/quarkus/deployment/pkg/NativeConfig.java index d07ef3279f12e..507f88db286d7 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/pkg/NativeConfig.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/pkg/NativeConfig.java @@ -17,7 +17,7 @@ public class NativeConfig { public static final String DEFAULT_GRAALVM_BUILDER_IMAGE = "quay.io/quarkus/ubi-quarkus-graalvmce-builder-image:22.3-java17"; - public static final String DEFAULT_MANDREL_BUILDER_IMAGE = "quay.io/quarkus/ubi-quarkus-mandrel-builder-image:22.2-java17"; + public static final String DEFAULT_MANDREL_BUILDER_IMAGE = "quay.io/quarkus/ubi-quarkus-mandrel-builder-image:22.3-java17"; /** * Comma-separated, additional arguments to pass to the build process. diff --git a/docs/src/main/asciidoc/doc-reference.adoc b/docs/src/main/asciidoc/doc-reference.adoc index 4bee5f0ee4b98..ab16b97efef45 100644 --- a/docs/src/main/asciidoc/doc-reference.adoc +++ b/docs/src/main/asciidoc/doc-reference.adoc @@ -324,6 +324,6 @@ The complete list of externalized variables for use is given in the following ta |\{quickstarts-tree-url}|{quickstarts-tree-url}| Quickstarts URL to main source tree root; used for referencing directories. |\{graalvm-version}|{graalvm-version}| Recommended GraalVM version to use. -|\{graalvm-flavor}|{graalvm-flavor}| The builder image tag of GraalVM to use e.g. `22.2-java17`. Use a `java17` version. +|\{graalvm-flavor}|{graalvm-flavor}| The builder image tag of GraalVM to use e.g. `22.3-java17`. Use a `java17` version. |=== From 9aafc6f1921e44a4d6a1538566d042a6d378e522 Mon Sep 17 00:00:00 2001 From: George Gastaldi Date: Thu, 3 Nov 2022 11:45:13 -0300 Subject: [PATCH 07/22] Removed unused method call to `PathsUtil#findMainSourcesRoot` in `JibProcessor` (cherry picked from commit 99ba1b2f728dbe997f30fcade4e53eb39dd1bbac) --- .../quarkus/container/image/jib/deployment/JibProcessor.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/extensions/container-image/container-image-jib/deployment/src/main/java/io/quarkus/container/image/jib/deployment/JibProcessor.java b/extensions/container-image/container-image-jib/deployment/src/main/java/io/quarkus/container/image/jib/deployment/JibProcessor.java index 194144ed1ac86..2ae069cdf3905 100644 --- a/extensions/container-image/container-image-jib/deployment/src/main/java/io/quarkus/container/image/jib/deployment/JibProcessor.java +++ b/extensions/container-image/container-image-jib/deployment/src/main/java/io/quarkus/container/image/jib/deployment/JibProcessor.java @@ -61,7 +61,6 @@ import io.quarkus.container.spi.ContainerImageInfoBuildItem; import io.quarkus.container.spi.ContainerImageLabelBuildItem; import io.quarkus.container.spi.ContainerImagePushRequestBuildItem; -import io.quarkus.container.util.PathsUtil; import io.quarkus.deployment.IsNormal; import io.quarkus.deployment.annotations.BuildProducer; import io.quarkus.deployment.annotations.BuildStep; @@ -756,7 +755,6 @@ private Map getEnvironmentVariables(JibConfig jibConfig) { */ private void handleExtraFiles(OutputTargetBuildItem outputTarget, JibContainerBuilder jibContainerBuilder) { Path outputDirectory = outputTarget.getOutputDirectory(); - PathsUtil.findMainSourcesRoot(outputTarget.getOutputDirectory()); Map.Entry mainSourcesRoot = findMainSourcesRoot(outputDirectory); if (mainSourcesRoot == null) { // this should never happen return; @@ -810,4 +808,4 @@ public boolean test(Path path) { return path.getFileName().toString().endsWith(".class"); } } -} \ No newline at end of file +} From 9859ecc9fac7caf881e49a930dc11513c2e94d54 Mon Sep 17 00:00:00 2001 From: Clement Escoffier Date: Thu, 3 Nov 2022 14:06:49 +0100 Subject: [PATCH 08/22] When propagating the duplicated context, drop the request scope. Fix #29017 (cherry picked from commit e8a5f01cfc335b9df515cc36f48966229d2c3b34) --- .../vertx/core/runtime/VertxCoreRecorder.java | 22 ++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/extensions/vertx/runtime/src/main/java/io/quarkus/vertx/core/runtime/VertxCoreRecorder.java b/extensions/vertx/runtime/src/main/java/io/quarkus/vertx/core/runtime/VertxCoreRecorder.java index 05d88e50dbd35..f2347ec69aa02 100644 --- a/extensions/vertx/runtime/src/main/java/io/quarkus/vertx/core/runtime/VertxCoreRecorder.java +++ b/extensions/vertx/runtime/src/main/java/io/quarkus/vertx/core/runtime/VertxCoreRecorder.java @@ -17,6 +17,7 @@ import java.util.Set; import java.util.UUID; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentMap; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.ThreadFactory; @@ -42,6 +43,7 @@ import io.quarkus.vertx.core.runtime.config.ClusterConfiguration; import io.quarkus.vertx.core.runtime.config.EventBusConfiguration; import io.quarkus.vertx.core.runtime.config.VertxConfiguration; +import io.quarkus.vertx.core.runtime.context.VertxContextSafetyToggle; import io.quarkus.vertx.mdc.provider.LateBoundMDCProvider; import io.vertx.core.AsyncResult; import io.vertx.core.Context; @@ -558,7 +560,25 @@ public void runWith(Runnable task, Object context) { ContextInternal currentContext = (ContextInternal) Vertx.currentContext(); if (context != null && context != currentContext) { // Only do context handling if it's non-null - final ContextInternal vertxContext = (ContextInternal) context; + ContextInternal vertxContext = (ContextInternal) context; + // The request scope must not be propagated + Object requestScope = null; + ConcurrentMap local = vertxContext.localContextData(); + for (Object k : local.keySet()) { + if (k.getClass().getName() + .equals("io.quarkus.vertx.runtime.VertxCurrentContextFactory$VertxCurrentContext")) { + requestScope = k; + break; + } + } + if (requestScope != null) { + // Duplicate the context, copy the data, remove the request scope + vertxContext = vertxContext.duplicate(); + vertxContext.localContextData().putAll(local); + vertxContext.localContextData().remove(requestScope); + VertxContextSafetyToggle.setContextSafe(vertxContext, true); + } + vertxContext.beginDispatch(); try { task.run(); From 6a4f0eb90242e803bf37c4812f69b9d47081c566 Mon Sep 17 00:00:00 2001 From: Martin Kouba Date: Thu, 3 Nov 2022 17:29:06 +0100 Subject: [PATCH 09/22] VertxCurrentContextFactory - use a string constant as a key (cherry picked from commit 4293f3797365134f84280834167399d37e4c6e5a) --- .../vertx/core/runtime/VertxCoreRecorder.java | 18 +++++------------- .../runtime/VertxCurrentContextFactory.java | 8 +++++--- 2 files changed, 10 insertions(+), 16 deletions(-) diff --git a/extensions/vertx/runtime/src/main/java/io/quarkus/vertx/core/runtime/VertxCoreRecorder.java b/extensions/vertx/runtime/src/main/java/io/quarkus/vertx/core/runtime/VertxCoreRecorder.java index f2347ec69aa02..5ac0b5769c9d4 100644 --- a/extensions/vertx/runtime/src/main/java/io/quarkus/vertx/core/runtime/VertxCoreRecorder.java +++ b/extensions/vertx/runtime/src/main/java/io/quarkus/vertx/core/runtime/VertxCoreRecorder.java @@ -45,6 +45,7 @@ import io.quarkus.vertx.core.runtime.config.VertxConfiguration; import io.quarkus.vertx.core.runtime.context.VertxContextSafetyToggle; import io.quarkus.vertx.mdc.provider.LateBoundMDCProvider; +import io.quarkus.vertx.runtime.VertxCurrentContextFactory; import io.vertx.core.AsyncResult; import io.vertx.core.Context; import io.vertx.core.Handler; @@ -561,24 +562,15 @@ public void runWith(Runnable task, Object context) { if (context != null && context != currentContext) { // Only do context handling if it's non-null ContextInternal vertxContext = (ContextInternal) context; - // The request scope must not be propagated - Object requestScope = null; + // The CDI request context must not be propagated ConcurrentMap local = vertxContext.localContextData(); - for (Object k : local.keySet()) { - if (k.getClass().getName() - .equals("io.quarkus.vertx.runtime.VertxCurrentContextFactory$VertxCurrentContext")) { - requestScope = k; - break; - } - } - if (requestScope != null) { - // Duplicate the context, copy the data, remove the request scope + if (local.containsKey(VertxCurrentContextFactory.LOCAL_KEY)) { + // Duplicate the context, copy the data, remove the request context vertxContext = vertxContext.duplicate(); vertxContext.localContextData().putAll(local); - vertxContext.localContextData().remove(requestScope); + vertxContext.localContextData().remove(VertxCurrentContextFactory.LOCAL_KEY); VertxContextSafetyToggle.setContextSafe(vertxContext, true); } - vertxContext.beginDispatch(); try { task.run(); diff --git a/extensions/vertx/runtime/src/main/java/io/quarkus/vertx/runtime/VertxCurrentContextFactory.java b/extensions/vertx/runtime/src/main/java/io/quarkus/vertx/runtime/VertxCurrentContextFactory.java index a2806ebc62899..88db266deac81 100644 --- a/extensions/vertx/runtime/src/main/java/io/quarkus/vertx/runtime/VertxCurrentContextFactory.java +++ b/extensions/vertx/runtime/src/main/java/io/quarkus/vertx/runtime/VertxCurrentContextFactory.java @@ -14,6 +14,8 @@ public class VertxCurrentContextFactory implements CurrentContextFactory { + public static final String LOCAL_KEY = "io.quarkus.vertx.cdi-current-context"; + @Override public CurrentContext create(Class scope) { return new VertxCurrentContext<>(); @@ -27,7 +29,7 @@ private static final class VertxCurrentContext implement public T get() { Context context = Vertx.currentContext(); if (context != null && VertxContext.isDuplicatedContext(context)) { - return context.getLocal(this); + return context.getLocal(LOCAL_KEY); } return fallback.get(); } @@ -37,7 +39,7 @@ public void set(T state) { Context context = Vertx.currentContext(); if (context != null && VertxContext.isDuplicatedContext(context)) { VertxContextSafetyToggle.setContextSafe(context, true); - context.putLocal(this, state); + context.putLocal(LOCAL_KEY, state); } else { fallback.set(state); } @@ -48,7 +50,7 @@ public void remove() { Context context = Vertx.currentContext(); if (context != null && VertxContext.isDuplicatedContext(context)) { // NOOP - the DC should not be shared. - // context.removeLocal(this); + // context.removeLocal(LOCAL_KEY); } else { fallback.remove(); } From 0e77edf406aac4909387f54fb17f8517fca4f76a Mon Sep 17 00:00:00 2001 From: Sergey Beryozkin Date: Thu, 3 Nov 2022 13:18:24 +0000 Subject: [PATCH 10/22] Prevent possible NPE in OIDC BackChannelLogoutHandler (cherry picked from commit 7ed539c5e308b9e7997bfae93bb7d8c63c953906) --- .../io/quarkus/oidc/runtime/BackChannelLogoutHandler.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/BackChannelLogoutHandler.java b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/BackChannelLogoutHandler.java index 3d3561e63b111..3d8a35d026b8b 100644 --- a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/BackChannelLogoutHandler.java +++ b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/BackChannelLogoutHandler.java @@ -56,9 +56,12 @@ public void handle(RoutingContext context) { LOG.debugf("Back channel logout request for the tenant %s received", oidcTenantConfig.getTenantId().get()); final TenantConfigContext tenantContext = getTenantConfigContext(context); if (tenantContext == null) { - LOG.debugf( + LOG.errorf( "Tenant configuration for the tenant %s is not available or does not match the backchannel logout path", oidcTenantConfig.getTenantId().get()); + context.response().setStatusCode(400); + context.response().end(); + return; } if (OidcUtils.isFormUrlEncodedRequest(context)) { From 0ea6508e5f10321c5b1921eb5f7d152b895bb167 Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Thu, 3 Nov 2022 14:00:38 +0200 Subject: [PATCH 11/22] Add Kotlin capability (cherry picked from commit 422a492c32ab1046f7a872ff4434ebac2923e06e) --- .../src/main/java/io/quarkus/deployment/Capability.java | 2 ++ extensions/kotlin/runtime/pom.xml | 3 +++ 2 files changed, 5 insertions(+) diff --git a/core/deployment/src/main/java/io/quarkus/deployment/Capability.java b/core/deployment/src/main/java/io/quarkus/deployment/Capability.java index 29d12c53be6e1..b9e629786daae 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/Capability.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/Capability.java @@ -33,6 +33,8 @@ public interface Capability { String JACKSON = QUARKUS_PREFIX + "jackson"; + String KOTLIN = QUARKUS_PREFIX + "kotlin"; + String JSONB = QUARKUS_PREFIX + "jsonb"; String HAL = QUARKUS_PREFIX + "hal"; diff --git a/extensions/kotlin/runtime/pom.xml b/extensions/kotlin/runtime/pom.xml index 3382cef722b5c..b83193e94f16e 100644 --- a/extensions/kotlin/runtime/pom.xml +++ b/extensions/kotlin/runtime/pom.xml @@ -39,6 +39,9 @@ --> org.jetbrains.kotlin:kotlin-compiler + + io.quarkus.kotlin + From 05cf1268d101ec219b0561def25611c7a8cee44f Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Thu, 3 Nov 2022 14:10:25 +0200 Subject: [PATCH 12/22] Automatically register nested Kotlin classes for reflection Users have come to expect that @RegisterForReflection fixes a lot of native image issues and use it a lot. For Kotlin classes however, registering a class is often not enough - the companion classes are need to be registered. These companion classes are nested classes, so we automatically set the property to true for Kotlin classes Closes: #28167 (cherry picked from commit 62748638fd3d2c0424eb71ef2707cdb5028ffcc3) --- .../steps/RegisterForReflectionBuildStep.java | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/core/deployment/src/main/java/io/quarkus/deployment/steps/RegisterForReflectionBuildStep.java b/core/deployment/src/main/java/io/quarkus/deployment/steps/RegisterForReflectionBuildStep.java index f6a2801c55207..f77e336d84d42 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/steps/RegisterForReflectionBuildStep.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/steps/RegisterForReflectionBuildStep.java @@ -18,6 +18,8 @@ import org.jboss.jandex.Type.Kind; import org.jboss.logging.Logger; +import io.quarkus.deployment.Capabilities; +import io.quarkus.deployment.Capability; import io.quarkus.deployment.annotations.BuildProducer; import io.quarkus.deployment.annotations.BuildStep; import io.quarkus.deployment.builditem.CombinedIndexBuildItem; @@ -32,8 +34,10 @@ public class RegisterForReflectionBuildStep { private static final Logger log = Logger.getLogger(RegisterForReflectionBuildStep.class); + private static final DotName KOTLIN_METADATA_ANNOTATION = DotName.createSimple("kotlin.Metadata"); + @BuildStep - public void build(CombinedIndexBuildItem combinedIndexBuildItem, + public void build(CombinedIndexBuildItem combinedIndexBuildItem, Capabilities capabilities, BuildProducer reflectiveClass, BuildProducer reflectiveClassHierarchy, BuildProducer lambdaCapturingTypeProducer) { @@ -63,6 +67,12 @@ public void build(CombinedIndexBuildItem combinedIndexBuildItem, ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); if (targetsValue == null && classNamesValue == null) { ClassInfo classInfo = i.target().asClass(); + if (capabilities.isPresent(Capability.KOTLIN) && ignoreNested) { + // for Kotlin classes, we need to register the nested classes as well because companion classes are very often necessary at runtime + if (isKotlinClass(classInfo)) { + ignoreNested = false; + } + } registerClass(classLoader, classInfo.name().toString(), methods, fields, ignoreNested, serialization, reflectiveClass, reflectiveClassHierarchy, processedReflectiveHierarchies, registerFullHierarchyVaue, builder); @@ -88,6 +98,10 @@ public void build(CombinedIndexBuildItem combinedIndexBuildItem, } } + private static boolean isKotlinClass(ClassInfo classInfo) { + return classInfo.hasDeclaredAnnotation(KOTLIN_METADATA_ANNOTATION); + } + /** * BFS Recursive Method to register a class and it's inner classes for Reflection. * @@ -213,4 +227,4 @@ private static Type getMethodReturnType(IndexView indexView, DotName initialName private static boolean getBooleanValue(AnnotationInstance i, String name) { return i.value(name) == null || i.value(name).asBoolean(); } -} \ No newline at end of file +} From 3d6a308f3f3104223ce62d21f1ed42a31f4223c0 Mon Sep 17 00:00:00 2001 From: Marco Bungart Date: Fri, 4 Nov 2022 08:54:52 +0000 Subject: [PATCH 13/22] removed unused import (cherry picked from commit ceb8abb8cf36a9f2de2e7ee691f0bfdec07c5445) --- docs/src/main/asciidoc/grpc-getting-started.adoc | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/src/main/asciidoc/grpc-getting-started.adoc b/docs/src/main/asciidoc/grpc-getting-started.adoc index 839e43675eef6..508e69a7dd32b 100644 --- a/docs/src/main/asciidoc/grpc-getting-started.adoc +++ b/docs/src/main/asciidoc/grpc-getting-started.adoc @@ -194,7 +194,6 @@ Create the `src/main/java/org/acme/HelloService.java` file with the following co ---- package org.acme; -import io.grpc.stub.StreamObserver; import io.quarkus.example.Greeter; import io.quarkus.example.HelloReply; import io.quarkus.example.HelloRequest; From 4cf83fa62f249914431ecee8f62f27a2692e48e8 Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Fri, 4 Nov 2022 08:34:25 +0200 Subject: [PATCH 14/22] Add parentFirst configuration for byteman Relates to: #29043 (cherry picked from commit 4929e1aa1482450f9006ea2476a192f07ca4b7f7) --- core/runtime/pom.xml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/core/runtime/pom.xml b/core/runtime/pom.xml index 384fa521a59f9..730328212c385 100644 --- a/core/runtime/pom.xml +++ b/core/runtime/pom.xml @@ -214,6 +214,9 @@ au.com.dius.pact.core:matcher au.com.dius.pact.consumer:junit5 au.com.dius.pact:consumer + + + org.jboss.byteman:byteman org.graalvm.sdk:graal-sdk @@ -231,6 +234,8 @@ io.smallrye.common:smallrye-common-io io.github.crac:org-crac + + org.jboss.byteman:byteman io.smallrye:smallrye-config From b09a8894e9a393c9ab16548fc383baf86938c030 Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Fri, 4 Nov 2022 08:39:53 +0200 Subject: [PATCH 15/22] Update the classloading reference guide to mention fast-jar vs legacy-jar Closes: #29043 (cherry picked from commit f1876adb859397f249ab585b2fb8e1a829a9d79f) --- .../main/asciidoc/class-loading-reference.adoc | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/docs/src/main/asciidoc/class-loading-reference.adoc b/docs/src/main/asciidoc/class-loading-reference.adoc index 9bbe01b02a6d9..196acf69e63ab 100644 --- a/docs/src/main/asciidoc/class-loading-reference.adoc +++ b/docs/src/main/asciidoc/class-loading-reference.adoc @@ -12,13 +12,19 @@ This document explains the Quarkus class loading architecture. It is intended fo authors and advanced users who want to understand exactly how Quarkus works. The Quarkus class loading architecture is slightly different depending on the mode that -the application is run in. When running a production application everything is loaded -in the system ClassLoader, so it is a completely flat class path. This also applies to -native image mode which does not really support multiple ClassLoaders, and is based on -a normal production Quarkus application. +the application is run in. + +When running a production application using the `fast-jar` package type +(which is the default), almost all dependencies are loaded via the `io.quarkus.bootstrap.runner.RunnerClassLoader` +which indexes class at build time, while a small set of dependencies is loaded from the system ClassLoader. + +When running a production application using the `legacy-jar` package type everything is loaded +in the system ClassLoader, so it is a completely flat classpath. + +The flat classpath strategy is also used for GraalVM native images, since GraalVM does not really support multiple ClassLoaders. For all other use cases (e.g. tests, dev mode, and building the application) Quarkus -uses the class loading architecture outlined here. +uses the class loading architecture outlined in following section. == Bootstrapping Quarkus From 084b90fafcbecc79e5f79fe0a98badf5e554a04b Mon Sep 17 00:00:00 2001 From: Alexey Loubyansky Date: Fri, 4 Nov 2022 09:17:04 +0100 Subject: [PATCH 16/22] Add RR-common-types to the BOM (cherry picked from commit 012b6352d55bdc7873d69fa7c3ae6c9320421001) --- bom/application/pom.xml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/bom/application/pom.xml b/bom/application/pom.xml index 730755f59ae0a..80b4cf72671b6 100644 --- a/bom/application/pom.xml +++ b/bom/application/pom.xml @@ -4586,6 +4586,11 @@ resteasy-reactive-common-processor ${project.version} + + io.quarkus.resteasy.reactive + resteasy-reactive-common-types + ${project.version} + io.quarkus.resteasy.reactive resteasy-reactive-client From 0acffd048a690ef53fecdbae91dda77da8dea0ef Mon Sep 17 00:00:00 2001 From: Katia Aresti Date: Thu, 3 Nov 2022 17:26:01 +0100 Subject: [PATCH 17/22] Improve the Infinispan Client documentation with gradle (cherry picked from commit 406e295418b17416fa7221603e0a8cb49dc95fb1) --- docs/src/main/asciidoc/infinispan-client.adoc | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/src/main/asciidoc/infinispan-client.adoc b/docs/src/main/asciidoc/infinispan-client.adoc index 587e6acec0884..64713953d81a3 100644 --- a/docs/src/main/asciidoc/infinispan-client.adoc +++ b/docs/src/main/asciidoc/infinispan-client.adoc @@ -43,8 +43,10 @@ This command adds the following dependency to your build file: [source,gradle,role="secondary asciidoc-tabs-target-sync-gradle"] .build.gradle ---- -implementation("io.quarkus:quarkus-infinispan-client") +implementation 'io.quarkus:quarkus-infinispan-client' +annotationProcessor 'org.infinispan.protostream:protostream-processor:4.5.0.Final' <1> ---- +<1> Mandatory in the gradle build to enable the generation of the files in the annotation based serialization == Configuring the Infinispan client From 1adb3c912fb2d7ee822f568a2e5b822b2fe4ae37 Mon Sep 17 00:00:00 2001 From: Sanne Grinovero Date: Fri, 4 Nov 2022 11:12:18 +0000 Subject: [PATCH 18/22] H2 embedded: disable support for compiling javascript/groovy triggers (cherry picked from commit add0d3976a7e6a43439cee9c18fce205997a069c) --- .../h2/runtime/graalvm/DeleteCompilers.java | 32 ++++++++++++ .../graalvm/DisableSourceCompiler.java | 50 +++++++++++++++++++ .../graalvm/NoCompiledTriggersSupport.java | 18 +++++++ 3 files changed, 100 insertions(+) create mode 100644 extensions/jdbc/jdbc-h2/runtime/src/main/java/io/quarkus/jdbc/h2/runtime/graalvm/DeleteCompilers.java create mode 100644 extensions/jdbc/jdbc-h2/runtime/src/main/java/io/quarkus/jdbc/h2/runtime/graalvm/DisableSourceCompiler.java create mode 100644 extensions/jdbc/jdbc-h2/runtime/src/main/java/io/quarkus/jdbc/h2/runtime/graalvm/NoCompiledTriggersSupport.java diff --git a/extensions/jdbc/jdbc-h2/runtime/src/main/java/io/quarkus/jdbc/h2/runtime/graalvm/DeleteCompilers.java b/extensions/jdbc/jdbc-h2/runtime/src/main/java/io/quarkus/jdbc/h2/runtime/graalvm/DeleteCompilers.java new file mode 100644 index 0000000000000..b277132b2cf79 --- /dev/null +++ b/extensions/jdbc/jdbc-h2/runtime/src/main/java/io/quarkus/jdbc/h2/runtime/graalvm/DeleteCompilers.java @@ -0,0 +1,32 @@ +package io.quarkus.jdbc.h2.runtime.graalvm; + +import com.oracle.svm.core.annotate.Delete; +import com.oracle.svm.core.annotate.TargetClass; + +@Delete +@TargetClass(className = "org.h2.util.SourceCompiler$GroovyCompiler") +final class DeleteGroovyCompiler { + +} + +@Delete +@TargetClass(className = "org.h2.util.SourceCompiler$StringJavaFileObject") +final class DeleteStringJavaFileObject { + +} + +@Delete +@TargetClass(className = "org.h2.util.SourceCompiler$JavaClassObject") +final class DeleteJavaClassObject { + +} + +@Delete +@TargetClass(className = "org.h2.util.SourceCompiler$ClassFileManager") +final class DeleteClassFileManager { + +} + +public final class DeleteCompilers { + +} diff --git a/extensions/jdbc/jdbc-h2/runtime/src/main/java/io/quarkus/jdbc/h2/runtime/graalvm/DisableSourceCompiler.java b/extensions/jdbc/jdbc-h2/runtime/src/main/java/io/quarkus/jdbc/h2/runtime/graalvm/DisableSourceCompiler.java new file mode 100644 index 0000000000000..361d8be32b1a3 --- /dev/null +++ b/extensions/jdbc/jdbc-h2/runtime/src/main/java/io/quarkus/jdbc/h2/runtime/graalvm/DisableSourceCompiler.java @@ -0,0 +1,50 @@ +package io.quarkus.jdbc.h2.runtime.graalvm; + +import java.lang.reflect.Method; + +import javax.script.CompiledScript; +import javax.script.ScriptException; + +import com.oracle.svm.core.annotate.Substitute; +import com.oracle.svm.core.annotate.TargetClass; + +@Substitute +@TargetClass(className = "org.h2.util.SourceCompiler") +public final class DisableSourceCompiler { + + private static final String ERR = "It's not possible to compile H2 triggers when embedding the engine in GraalVM native images"; + + //Delete it all + + @Substitute + public static boolean isJavaxScriptSource(String source) { + throw new UnsupportedOperationException(ERR); + } + + @Substitute + public CompiledScript getCompiledScript(String packageAndClassName) throws ScriptException { + throw new UnsupportedOperationException(ERR); + } + + @Substitute + public Class getClass(String packageAndClassName) + throws ClassNotFoundException { + throw new UnsupportedOperationException(ERR); + } + + @Substitute + public void setSource(String className, String source) { + //no-op + } + + @Substitute + public void setJavaSystemCompiler(boolean enabled) { + //no-op + } + + @Substitute + public Method getMethod(String className) { + throw new UnsupportedOperationException(ERR); + } + +} diff --git a/extensions/jdbc/jdbc-h2/runtime/src/main/java/io/quarkus/jdbc/h2/runtime/graalvm/NoCompiledTriggersSupport.java b/extensions/jdbc/jdbc-h2/runtime/src/main/java/io/quarkus/jdbc/h2/runtime/graalvm/NoCompiledTriggersSupport.java new file mode 100644 index 0000000000000..4ee183a255247 --- /dev/null +++ b/extensions/jdbc/jdbc-h2/runtime/src/main/java/io/quarkus/jdbc/h2/runtime/graalvm/NoCompiledTriggersSupport.java @@ -0,0 +1,18 @@ +package io.quarkus.jdbc.h2.runtime.graalvm; + +import org.h2.engine.Database; +import org.h2.util.SourceCompiler; + +import com.oracle.svm.core.annotate.Substitute; +import com.oracle.svm.core.annotate.TargetClass; + +@TargetClass(Database.class) +public final class NoCompiledTriggersSupport { + + @Substitute + public SourceCompiler getCompiler() { + throw new UnsupportedOperationException( + "It's not possible to compile H2 triggers when embedding the engine in GraalVM native images"); + } + +} From 9bc1162c360a4879f223d966487c637614f39c79 Mon Sep 17 00:00:00 2001 From: Alexey Loubyansky Date: Fri, 4 Nov 2022 14:03:09 +0100 Subject: [PATCH 19/22] Don't attach an uber-jar without a classifier (cherry picked from commit c4a37c3bbc469b2b7c24d01e42648040760aeb54) --- .../maven/src/main/java/io/quarkus/maven/BuildMojo.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/devtools/maven/src/main/java/io/quarkus/maven/BuildMojo.java b/devtools/maven/src/main/java/io/quarkus/maven/BuildMojo.java index c055a57a478c4..6745692b559dd 100644 --- a/devtools/maven/src/main/java/io/quarkus/maven/BuildMojo.java +++ b/devtools/maven/src/main/java/io/quarkus/maven/BuildMojo.java @@ -153,8 +153,12 @@ && isNativeProfileEnabled(mavenProject())) { } } if (uberJarWithSuffix) { - projectHelper.attachArtifact(mavenProject(), result.getJar().getPath().toFile(), - result.getJar().getClassifier()); + if (result.getJar().getClassifier().isEmpty()) { + original.setFile(result.getJar().getPath().toFile()); + } else { + projectHelper.attachArtifact(mavenProject(), result.getJar().getPath().toFile(), + result.getJar().getClassifier()); + } } } } finally { From c286b1863d90c978c2027d8dc9ade213ebbaa7b5 Mon Sep 17 00:00:00 2001 From: Sanne Grinovero Date: Thu, 27 Oct 2022 15:05:54 +0100 Subject: [PATCH 20/22] Upgrade to ByteBuddy 1.12.18 (cherry picked from commit 05fcd907f3e78331f09ee103b54d3a592dba8f47) --- bom/application/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bom/application/pom.xml b/bom/application/pom.xml index 80b4cf72671b6..46a8154d522de 100644 --- a/bom/application/pom.xml +++ b/bom/application/pom.xml @@ -89,7 +89,7 @@ 1.15 1.5.1 5.6.12.Final - 1.12.9 + 1.12.18 1.1.9.Final 6.2.5.Final 6.1.7.Final From 0a315567341c7cda301007bf2d52f4bac60b643d Mon Sep 17 00:00:00 2001 From: Sanne Grinovero Date: Thu, 3 Nov 2022 16:10:51 +0000 Subject: [PATCH 21/22] Upgrade Hibernate ORM to 5.6.14.Final (cherry picked from commit ba0443bb9123cc4fbb635327eb1c4275f15a6204) --- bom/application/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bom/application/pom.xml b/bom/application/pom.xml index 46a8154d522de..ed6a0fa071b15 100644 --- a/bom/application/pom.xml +++ b/bom/application/pom.xml @@ -88,7 +88,7 @@ 3.12.0 1.15 1.5.1 - 5.6.12.Final + 5.6.14.Final 1.12.18 1.1.9.Final 6.2.5.Final From f6b797e3038195c0634d5fe835e324eaf6876c81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yoann=20Rodi=C3=A8re?= Date: Thu, 27 Oct 2022 14:47:04 +0200 Subject: [PATCH 22/22] Tests for HHH-15634 Lazy basic property does not get updated on change (cherry picked from commit ea5960bebea1081adc2b0dff914fa9d1377ed8bf) --- .../lazyloading/AbstractLazyBasicTest.java | 200 ++++++++++++++++++ .../LazyBasicDefaultGroupTest.java | 118 +++++++++++ .../LazyBasicMultiNonDefaultGroupTest.java | 121 +++++++++++ .../LazyBasicNonDefaultGroupTest.java | 121 +++++++++++ 4 files changed, 560 insertions(+) create mode 100644 extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/lazyloading/AbstractLazyBasicTest.java create mode 100644 extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/lazyloading/LazyBasicDefaultGroupTest.java create mode 100644 extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/lazyloading/LazyBasicMultiNonDefaultGroupTest.java create mode 100644 extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/lazyloading/LazyBasicNonDefaultGroupTest.java diff --git a/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/lazyloading/AbstractLazyBasicTest.java b/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/lazyloading/AbstractLazyBasicTest.java new file mode 100644 index 0000000000000..d19a4dd62178a --- /dev/null +++ b/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/lazyloading/AbstractLazyBasicTest.java @@ -0,0 +1,200 @@ +package io.quarkus.hibernate.orm.lazyloading; + +import static io.quarkus.hibernate.orm.TransactionTestUtils.inTransaction; + +import javax.inject.Inject; +import javax.persistence.EntityManager; +import javax.transaction.UserTransaction; + +import org.junit.jupiter.api.Test; + +public abstract class AbstractLazyBasicTest { + + @Inject + EntityManager em; + + @Inject + UserTransaction transaction; + + private final AccessDelegate delegate; + private Long entityId; + + public AbstractLazyBasicTest(AccessDelegate delegate) { + this.delegate = delegate; + } + + @Test + public void update_all_nullToNonNull() { + initNull(); + inTransaction(transaction, () -> { + delegate.updateAllProperties(em, entityId, "updated1", "updated2", "updated3"); + }); + inTransaction(transaction, () -> { + delegate.testLazyLoadingAndPersistedValues(em, entityId, "updated1", "updated2", "updated3"); + }); + } + + @Test + public void update_allLazy_nullToNonNull() { + initNull(); + inTransaction(transaction, () -> { + delegate.updateAllLazyProperties(em, entityId, "updated1", "updated2"); + }); + inTransaction(transaction, () -> { + delegate.testLazyLoadingAndPersistedValues(em, entityId, null, "updated1", "updated2"); + }); + } + + @Test + public void update_oneEager_nullToNonNull() { + initNull(); + inTransaction(transaction, () -> { + delegate.updateOneEagerProperty(em, entityId, "updated1"); + }); + inTransaction(transaction, () -> { + delegate.testLazyLoadingAndPersistedValues(em, entityId, "updated1", null, null); + }); + } + + @Test + public void update_oneLazy_nullToNonNull() { + initNull(); + inTransaction(transaction, () -> { + delegate.updateOneLazyProperty(em, entityId, "updated2"); + }); + inTransaction(transaction, () -> { + delegate.testLazyLoadingAndPersistedValues(em, entityId, null, "updated2", null); + }); + } + + @Test + public void update_all_nonNullToNonNull() { + initNonNull(); + inTransaction(transaction, () -> { + delegate.updateAllProperties(em, entityId, "updated1", "updated2", "updated3"); + }); + inTransaction(transaction, () -> { + delegate.testLazyLoadingAndPersistedValues(em, entityId, "updated1", "updated2", "updated3"); + }); + } + + @Test + public void update_allLazy_nonNullToNonNull() { + initNonNull(); + inTransaction(transaction, () -> { + delegate.updateAllLazyProperties(em, entityId, "updated1", "updated2"); + }); + inTransaction(transaction, () -> { + delegate.testLazyLoadingAndPersistedValues(em, entityId, "initial1", "updated1", "updated2"); + }); + } + + @Test + public void update_oneEager_nonNullToNonNull() { + initNonNull(); + inTransaction(transaction, () -> { + delegate.updateOneEagerProperty(em, entityId, "updated1"); + }); + inTransaction(transaction, () -> { + delegate.testLazyLoadingAndPersistedValues(em, entityId, "updated1", "initial2", "initial3"); + }); + } + + @Test + public void update_oneLazy_nonNullToNonNull() { + initNonNull(); + inTransaction(transaction, () -> { + delegate.updateOneLazyProperty(em, entityId, "updated2"); + }); + inTransaction(transaction, () -> { + delegate.testLazyLoadingAndPersistedValues(em, entityId, "initial1", "updated2", "initial3"); + }); + } + + @Test + public void update_all_nonNullToNull() { + initNonNull(); + inTransaction(transaction, () -> { + delegate.updateAllProperties(em, entityId, null, null, null); + }); + inTransaction(transaction, () -> { + delegate.testLazyLoadingAndPersistedValues(em, entityId, null, null, null); + }); + } + + @Test + public void update_allLazy_nonNullToNull() { + initNonNull(); + inTransaction(transaction, () -> { + delegate.updateAllLazyProperties(em, entityId, null, null); + }); + inTransaction(transaction, () -> { + delegate.testLazyLoadingAndPersistedValues(em, entityId, "initial1", null, null); + }); + } + + @Test + public void update_oneEager_nonNullToNull() { + initNonNull(); + inTransaction(transaction, () -> { + delegate.updateOneEagerProperty(em, entityId, null); + }); + inTransaction(transaction, () -> { + delegate.testLazyLoadingAndPersistedValues(em, entityId, null, "initial2", "initial3"); + }); + } + + @Test + public void update_oneLazy_nonNullToNull() { + initNonNull(); + inTransaction(transaction, () -> { + delegate.updateOneLazyProperty(em, entityId, null); + }); + inTransaction(transaction, () -> { + delegate.testLazyLoadingAndPersistedValues(em, entityId, "initial1", null, "initial3"); + }); + } + + private void initNull() { + inTransaction(transaction, () -> { + entityId = delegate.create(em, null, null, null); + }); + inTransaction(transaction, () -> { + delegate.testLazyLoadingAndPersistedValues(em, entityId, null, null, null); + }); + } + + private void initNonNull() { + inTransaction(transaction, () -> { + entityId = delegate.create(em, "initial1", "initial2", "initial3"); + }); + inTransaction(transaction, () -> { + delegate.testLazyLoadingAndPersistedValues(em, entityId, "initial1", "initial2", "initial3"); + }); + } + + /** + * An interface for delegate classes, + * classes whose bytecode is transformed by Quarkus to replace public field access with getter/setter access. + *

+ * (Test bytecode was not transformed by Quarkus when using QuarkusUnitTest last time I checked). + */ + interface AccessDelegate { + + long create(EntityManager entityManager, String eagerProperty1, String lazyProperty1, String lazyProperty2); + + void updateAllProperties(EntityManager entityManager, long entityId, String eagerProperty1, String lazyProperty1, + String lazyProperty2); + + void updateAllLazyProperties(EntityManager entityManager, long entityId, String lazyProperty1, String lazyProperty2); + + void updateOneEagerProperty(EntityManager entityManager, long entityId, String eagerProperty1); + + void updateOneLazyProperty(EntityManager entityManager, long entityId, String lazyProperty1); + + void testLazyLoadingAndPersistedValues(EntityManager entityManager, long entityId, + String expectedEagerProperty1, + String expectedLazyProperty1, + String expectedLazyProperty2); + } +} diff --git a/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/lazyloading/LazyBasicDefaultGroupTest.java b/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/lazyloading/LazyBasicDefaultGroupTest.java new file mode 100644 index 0000000000000..9de6fb25239b1 --- /dev/null +++ b/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/lazyloading/LazyBasicDefaultGroupTest.java @@ -0,0 +1,118 @@ +package io.quarkus.hibernate.orm.lazyloading; + +import static org.assertj.core.api.Assertions.assertThat; + +import javax.persistence.Basic; +import javax.persistence.Entity; +import javax.persistence.EntityManager; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; + +import org.hibernate.Hibernate; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.hibernate.orm.TransactionTestUtils; +import io.quarkus.test.QuarkusUnitTest; + +public class LazyBasicDefaultGroupTest extends AbstractLazyBasicTest { + + @RegisterExtension + static QuarkusUnitTest runner = new QuarkusUnitTest() + .withApplicationRoot((jar) -> jar + .addClass(TransactionTestUtils.class) + .addClass(MyEntity.class) + .addClass(AccessDelegate.class) + .addClass(AccessDelegateImpl.class)) + .withConfigurationResource("application.properties"); + + public LazyBasicDefaultGroupTest() { + super(new AccessDelegateImpl()); + } + + @Entity(name = "MyEntity") + public static class MyEntity { + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + public Long id; + + @Basic + public String eagerProperty1; + + @Basic(fetch = FetchType.LAZY) + public String lazyProperty1; + + @Basic(fetch = FetchType.LAZY) + public String lazyProperty2; + } + + private static class AccessDelegateImpl implements AccessDelegate { + + @Override + public long create(EntityManager entityManager, String eagerProperty1, String lazyProperty1, String lazyProperty2) { + MyEntity myEntity = new MyEntity(); + myEntity.eagerProperty1 = eagerProperty1; + myEntity.lazyProperty1 = lazyProperty1; + myEntity.lazyProperty2 = lazyProperty2; + entityManager.persist(myEntity); + return myEntity.id; + } + + @Override + public void updateAllProperties(EntityManager entityManager, long entityId, String eagerProperty1, String lazyProperty1, + String lazyProperty2) { + MyEntity entity = entityManager.find(MyEntity.class, entityId); + entity.eagerProperty1 = eagerProperty1; + entity.lazyProperty1 = lazyProperty1; + entity.lazyProperty2 = lazyProperty2; + } + + @Override + public void updateAllLazyProperties(EntityManager entityManager, long entityId, String lazyProperty1, + String lazyProperty2) { + MyEntity entity = entityManager.find(MyEntity.class, entityId); + entity.lazyProperty1 = lazyProperty1; + entity.lazyProperty2 = lazyProperty2; + } + + @Override + public void updateOneEagerProperty(EntityManager entityManager, long entityId, String eagerProperty1) { + MyEntity entity = entityManager.find(MyEntity.class, entityId); + entity.eagerProperty1 = eagerProperty1; + } + + @Override + public void updateOneLazyProperty(EntityManager entityManager, long entityId, String lazyProperty1) { + MyEntity entity = entityManager.find(MyEntity.class, entityId); + entity.lazyProperty1 = lazyProperty1; + } + + @Override + public void testLazyLoadingAndPersistedValues(EntityManager entityManager, long entityId, + String expectedEagerProperty1, + String expectedLazyProperty1, + String expectedLazyProperty2) { + MyEntity entity = entityManager.find(MyEntity.class, entityId); + assertThat(entity).isNotNull(); + assertThat(Hibernate.isPropertyInitialized(entity, "eagerProperty1")).isTrue(); + assertThat(Hibernate.isPropertyInitialized(entity, "lazyProperty1")).isFalse(); + assertThat(Hibernate.isPropertyInitialized(entity, "lazyProperty2")).isFalse(); + + assertThat(entity.eagerProperty1).isEqualTo(expectedEagerProperty1); + assertThat(Hibernate.isPropertyInitialized(entity, "eagerProperty1")).isTrue(); + assertThat(Hibernate.isPropertyInitialized(entity, "lazyProperty1")).isFalse(); + assertThat(Hibernate.isPropertyInitialized(entity, "lazyProperty2")).isFalse(); + + assertThat(entity.lazyProperty1).isEqualTo(expectedLazyProperty1); + assertThat(Hibernate.isPropertyInitialized(entity, "eagerProperty1")).isTrue(); + assertThat(Hibernate.isPropertyInitialized(entity, "lazyProperty1")).isTrue(); + assertThat(Hibernate.isPropertyInitialized(entity, "lazyProperty2")).isTrue(); + + assertThat(entity.lazyProperty2).isEqualTo(expectedLazyProperty2); + assertThat(Hibernate.isPropertyInitialized(entity, "eagerProperty1")).isTrue(); + assertThat(Hibernate.isPropertyInitialized(entity, "lazyProperty1")).isTrue(); + assertThat(Hibernate.isPropertyInitialized(entity, "lazyProperty2")).isTrue(); + } + } +} diff --git a/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/lazyloading/LazyBasicMultiNonDefaultGroupTest.java b/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/lazyloading/LazyBasicMultiNonDefaultGroupTest.java new file mode 100644 index 0000000000000..b55f00ab4e8f1 --- /dev/null +++ b/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/lazyloading/LazyBasicMultiNonDefaultGroupTest.java @@ -0,0 +1,121 @@ +package io.quarkus.hibernate.orm.lazyloading; + +import static org.assertj.core.api.Assertions.assertThat; + +import javax.persistence.Basic; +import javax.persistence.Entity; +import javax.persistence.EntityManager; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; + +import org.hibernate.Hibernate; +import org.hibernate.annotations.LazyGroup; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.hibernate.orm.TransactionTestUtils; +import io.quarkus.test.QuarkusUnitTest; + +public class LazyBasicMultiNonDefaultGroupTest extends AbstractLazyBasicTest { + + @RegisterExtension + static QuarkusUnitTest runner = new QuarkusUnitTest() + .withApplicationRoot((jar) -> jar + .addClass(TransactionTestUtils.class) + .addClass(MyEntity.class) + .addClass(AccessDelegate.class) + .addClass(AccessDelegateImpl.class)) + .withConfigurationResource("application.properties"); + + public LazyBasicMultiNonDefaultGroupTest() { + super(new AccessDelegateImpl()); + } + + @Entity(name = "MyEntity") + public static class MyEntity { + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + public Long id; + + @Basic + public String eagerProperty1; + + @Basic(fetch = FetchType.LAZY) + @LazyGroup("myGroup1") + public String lazyProperty1; + + @Basic(fetch = FetchType.LAZY) + @LazyGroup("myGroup2") + public String lazyProperty2; + } + + private static class AccessDelegateImpl implements AccessDelegate { + + @Override + public long create(EntityManager entityManager, String eagerProperty1, String lazyProperty1, String lazyProperty2) { + MyEntity myEntity = new MyEntity(); + myEntity.eagerProperty1 = eagerProperty1; + myEntity.lazyProperty1 = lazyProperty1; + myEntity.lazyProperty2 = lazyProperty2; + entityManager.persist(myEntity); + return myEntity.id; + } + + @Override + public void updateAllProperties(EntityManager entityManager, long entityId, String eagerProperty1, String lazyProperty1, + String lazyProperty2) { + MyEntity entity = entityManager.find(MyEntity.class, entityId); + entity.eagerProperty1 = eagerProperty1; + entity.lazyProperty1 = lazyProperty1; + entity.lazyProperty2 = lazyProperty2; + } + + @Override + public void updateAllLazyProperties(EntityManager entityManager, long entityId, String lazyProperty1, + String lazyProperty2) { + MyEntity entity = entityManager.find(MyEntity.class, entityId); + entity.lazyProperty1 = lazyProperty1; + entity.lazyProperty2 = lazyProperty2; + } + + @Override + public void updateOneEagerProperty(EntityManager entityManager, long entityId, String eagerProperty1) { + MyEntity entity = entityManager.find(MyEntity.class, entityId); + entity.eagerProperty1 = eagerProperty1; + } + + @Override + public void updateOneLazyProperty(EntityManager entityManager, long entityId, String lazyProperty1) { + MyEntity entity = entityManager.find(MyEntity.class, entityId); + entity.lazyProperty1 = lazyProperty1; + } + + @Override + public void testLazyLoadingAndPersistedValues(EntityManager entityManager, long entityId, + String expectedEagerProperty1, + String expectedLazyProperty1, + String expectedLazyProperty2) { + MyEntity entity = entityManager.find(MyEntity.class, entityId); + assertThat(entity).isNotNull(); + assertThat(Hibernate.isPropertyInitialized(entity, "eagerProperty1")).isTrue(); + assertThat(Hibernate.isPropertyInitialized(entity, "lazyProperty1")).isFalse(); + assertThat(Hibernate.isPropertyInitialized(entity, "lazyProperty2")).isFalse(); + + assertThat(entity.eagerProperty1).isEqualTo(expectedEagerProperty1); + assertThat(Hibernate.isPropertyInitialized(entity, "eagerProperty1")).isTrue(); + assertThat(Hibernate.isPropertyInitialized(entity, "lazyProperty1")).isFalse(); + assertThat(Hibernate.isPropertyInitialized(entity, "lazyProperty2")).isFalse(); + + assertThat(entity.lazyProperty1).isEqualTo(expectedLazyProperty1); + assertThat(Hibernate.isPropertyInitialized(entity, "eagerProperty1")).isTrue(); + assertThat(Hibernate.isPropertyInitialized(entity, "lazyProperty1")).isTrue(); + assertThat(Hibernate.isPropertyInitialized(entity, "lazyProperty2")).isFalse(); + + assertThat(entity.lazyProperty2).isEqualTo(expectedLazyProperty2); + assertThat(Hibernate.isPropertyInitialized(entity, "eagerProperty1")).isTrue(); + assertThat(Hibernate.isPropertyInitialized(entity, "lazyProperty1")).isTrue(); + assertThat(Hibernate.isPropertyInitialized(entity, "lazyProperty2")).isTrue(); + } + } +} diff --git a/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/lazyloading/LazyBasicNonDefaultGroupTest.java b/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/lazyloading/LazyBasicNonDefaultGroupTest.java new file mode 100644 index 0000000000000..066369407e320 --- /dev/null +++ b/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/lazyloading/LazyBasicNonDefaultGroupTest.java @@ -0,0 +1,121 @@ +package io.quarkus.hibernate.orm.lazyloading; + +import static org.assertj.core.api.Assertions.assertThat; + +import javax.persistence.Basic; +import javax.persistence.Entity; +import javax.persistence.EntityManager; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; + +import org.hibernate.Hibernate; +import org.hibernate.annotations.LazyGroup; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.hibernate.orm.TransactionTestUtils; +import io.quarkus.test.QuarkusUnitTest; + +public class LazyBasicNonDefaultGroupTest extends AbstractLazyBasicTest { + + @RegisterExtension + static QuarkusUnitTest runner = new QuarkusUnitTest() + .withApplicationRoot((jar) -> jar + .addClass(TransactionTestUtils.class) + .addClass(MyEntity.class) + .addClass(AccessDelegate.class) + .addClass(AccessDelegateImpl.class)) + .withConfigurationResource("application.properties"); + + public LazyBasicNonDefaultGroupTest() { + super(new AccessDelegateImpl()); + } + + @Entity(name = "MyEntity") + public static class MyEntity { + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + public Long id; + + @Basic + public String eagerProperty1; + + @Basic(fetch = FetchType.LAZY) + @LazyGroup("myGroup") + public String lazyProperty1; + + @Basic(fetch = FetchType.LAZY) + @LazyGroup("myGroup") + public String lazyProperty2; + } + + private static class AccessDelegateImpl implements AccessDelegate { + + @Override + public long create(EntityManager entityManager, String eagerProperty1, String lazyProperty1, String lazyProperty2) { + MyEntity myEntity = new MyEntity(); + myEntity.eagerProperty1 = eagerProperty1; + myEntity.lazyProperty1 = lazyProperty1; + myEntity.lazyProperty2 = lazyProperty2; + entityManager.persist(myEntity); + return myEntity.id; + } + + @Override + public void updateAllProperties(EntityManager entityManager, long entityId, String eagerProperty1, String lazyProperty1, + String lazyProperty2) { + MyEntity entity = entityManager.find(MyEntity.class, entityId); + entity.eagerProperty1 = eagerProperty1; + entity.lazyProperty1 = lazyProperty1; + entity.lazyProperty2 = lazyProperty2; + } + + @Override + public void updateAllLazyProperties(EntityManager entityManager, long entityId, String lazyProperty1, + String lazyProperty2) { + MyEntity entity = entityManager.find(MyEntity.class, entityId); + entity.lazyProperty1 = lazyProperty1; + entity.lazyProperty2 = lazyProperty2; + } + + @Override + public void updateOneEagerProperty(EntityManager entityManager, long entityId, String eagerProperty1) { + MyEntity entity = entityManager.find(MyEntity.class, entityId); + entity.eagerProperty1 = eagerProperty1; + } + + @Override + public void updateOneLazyProperty(EntityManager entityManager, long entityId, String lazyProperty1) { + MyEntity entity = entityManager.find(MyEntity.class, entityId); + entity.lazyProperty1 = lazyProperty1; + } + + @Override + public void testLazyLoadingAndPersistedValues(EntityManager entityManager, long entityId, + String expectedEagerProperty1, + String expectedLazyProperty1, + String expectedLazyProperty2) { + MyEntity entity = entityManager.find(MyEntity.class, entityId); + assertThat(entity).isNotNull(); + assertThat(Hibernate.isPropertyInitialized(entity, "eagerProperty1")).isTrue(); + assertThat(Hibernate.isPropertyInitialized(entity, "lazyProperty1")).isFalse(); + assertThat(Hibernate.isPropertyInitialized(entity, "lazyProperty2")).isFalse(); + + assertThat(entity.eagerProperty1).isEqualTo(expectedEagerProperty1); + assertThat(Hibernate.isPropertyInitialized(entity, "eagerProperty1")).isTrue(); + assertThat(Hibernate.isPropertyInitialized(entity, "lazyProperty1")).isFalse(); + assertThat(Hibernate.isPropertyInitialized(entity, "lazyProperty2")).isFalse(); + + assertThat(entity.lazyProperty1).isEqualTo(expectedLazyProperty1); + assertThat(Hibernate.isPropertyInitialized(entity, "eagerProperty1")).isTrue(); + assertThat(Hibernate.isPropertyInitialized(entity, "lazyProperty1")).isTrue(); + assertThat(Hibernate.isPropertyInitialized(entity, "lazyProperty2")).isTrue(); + + assertThat(entity.lazyProperty2).isEqualTo(expectedLazyProperty2); + assertThat(Hibernate.isPropertyInitialized(entity, "eagerProperty1")).isTrue(); + assertThat(Hibernate.isPropertyInitialized(entity, "lazyProperty1")).isTrue(); + assertThat(Hibernate.isPropertyInitialized(entity, "lazyProperty2")).isTrue(); + } + } +}