From 8d6578a23b12ca597e980e01d73d0e74839bfbe8 Mon Sep 17 00:00:00 2001 From: Martin Kouba Date: Tue, 11 Jul 2023 10:37:10 +0200 Subject: [PATCH] Hibernate Reactive Panache: open session on demand for repositories - also open reactive session automatically on demand if a JAX-RS endpoint returns Uni and the resource class imports a Panache repository; currently only Panache entities are supported (cherry picked from commit cabb4de55958c2f058ceb62cafff14356f4767ee) --- .../panache/common/deployment/DotNames.java | 40 ++++++++++ .../PanacheJpaCommonResourceProcessor.java | 74 +++++++++---------- .../it/panache/reactive/BeerRepository.java | 10 +++ .../reactive/TestRepositoryEndpoint.java | 22 ++++++ .../reactive/PanacheFunctionalityTest.java | 5 ++ 5 files changed, 112 insertions(+), 39 deletions(-) create mode 100644 extensions/panache/hibernate-reactive-panache-common/deployment/src/main/java/io/quarkus/hibernate/reactive/panache/common/deployment/DotNames.java create mode 100644 integration-tests/hibernate-reactive-panache/src/main/java/io/quarkus/it/panache/reactive/BeerRepository.java create mode 100644 integration-tests/hibernate-reactive-panache/src/main/java/io/quarkus/it/panache/reactive/TestRepositoryEndpoint.java diff --git a/extensions/panache/hibernate-reactive-panache-common/deployment/src/main/java/io/quarkus/hibernate/reactive/panache/common/deployment/DotNames.java b/extensions/panache/hibernate-reactive-panache-common/deployment/src/main/java/io/quarkus/hibernate/reactive/panache/common/deployment/DotNames.java new file mode 100644 index 0000000000000..a0eeab02de1b4 --- /dev/null +++ b/extensions/panache/hibernate-reactive-panache-common/deployment/src/main/java/io/quarkus/hibernate/reactive/panache/common/deployment/DotNames.java @@ -0,0 +1,40 @@ +package io.quarkus.hibernate.reactive.panache.common.deployment; + +import jakarta.persistence.NamedQueries; +import jakarta.persistence.NamedQuery; + +import org.jboss.jandex.DotName; + +import io.quarkus.hibernate.reactive.panache.common.WithSession; +import io.quarkus.hibernate.reactive.panache.common.WithSessionOnDemand; +import io.quarkus.hibernate.reactive.panache.common.WithTransaction; +import io.quarkus.hibernate.reactive.panache.common.runtime.ReactiveTransactional; +import io.smallrye.mutiny.Uni; + +final class DotNames { + + static final DotName DOTNAME_NAMED_QUERY = DotName.createSimple(NamedQuery.class.getName()); + static final DotName DOTNAME_NAMED_QUERIES = DotName.createSimple(NamedQueries.class.getName()); + static final DotName REACTIVE_TRANSACTIONAL = DotName.createSimple(ReactiveTransactional.class.getName()); + static final DotName WITH_SESSION_ON_DEMAND = DotName.createSimple(WithSessionOnDemand.class.getName()); + static final DotName WITH_SESSION = DotName.createSimple(WithSession.class.getName()); + static final DotName WITH_TRANSACTION = DotName.createSimple(WithTransaction.class.getName()); + static final DotName UNI = DotName.createSimple(Uni.class.getName()); + + static final DotName PANACHE_ENTITY_BASE = DotName + .createSimple("io.quarkus.hibernate.reactive.panache.PanacheEntityBase"); + static final DotName PANACHE_ENTITY = DotName.createSimple("io.quarkus.hibernate.reactive.panache.PanacheEntity"); + static final DotName PANACHE_KOTLIN_ENTITY_BASE = DotName + .createSimple("io.quarkus.hibernate.reactive.panache.kotlin.PanacheEntityBase"); + static final DotName PANACHE_KOTLIN_ENTITY = DotName + .createSimple("io.quarkus.hibernate.reactive.panache.kotlin.PanacheEntity"); + + static final DotName PANACHE_REPOSITORY_BASE = DotName + .createSimple("io.quarkus.hibernate.reactive.panache.PanacheRepositoryBase"); + static final DotName PANACHE_REPOSITORY = DotName.createSimple("io.quarkus.hibernate.reactive.panache.PanacheRepository"); + static final DotName PANACHE_KOTLIN_REPOSITORY_BASE = DotName + .createSimple("io.quarkus.hibernate.reactive.panache.kotlin.PanacheRepositoryBase"); + static final DotName PANACHE_KOTLIN_REPOSITORY = DotName + .createSimple("io.quarkus.hibernate.reactive.panache.kotlin.PanacheRepository"); + +} diff --git a/extensions/panache/hibernate-reactive-panache-common/deployment/src/main/java/io/quarkus/hibernate/reactive/panache/common/deployment/PanacheJpaCommonResourceProcessor.java b/extensions/panache/hibernate-reactive-panache-common/deployment/src/main/java/io/quarkus/hibernate/reactive/panache/common/deployment/PanacheJpaCommonResourceProcessor.java index fca9ba39f6520..c98dea9249c47 100644 --- a/extensions/panache/hibernate-reactive-panache-common/deployment/src/main/java/io/quarkus/hibernate/reactive/panache/common/deployment/PanacheJpaCommonResourceProcessor.java +++ b/extensions/panache/hibernate-reactive-panache-common/deployment/src/main/java/io/quarkus/hibernate/reactive/panache/common/deployment/PanacheJpaCommonResourceProcessor.java @@ -11,8 +11,6 @@ import jakarta.annotation.Priority; import jakarta.interceptor.Interceptor; -import jakarta.persistence.NamedQueries; -import jakarta.persistence.NamedQuery; import org.jboss.jandex.AnnotationInstance; import org.jboss.jandex.AnnotationTarget.Kind; @@ -46,39 +44,19 @@ import io.quarkus.gizmo.ClassCreator; import io.quarkus.hibernate.orm.deployment.HibernateOrmEnabled; import io.quarkus.hibernate.orm.deployment.JpaModelBuildItem; -import io.quarkus.hibernate.reactive.panache.common.WithSession; -import io.quarkus.hibernate.reactive.panache.common.WithSessionOnDemand; -import io.quarkus.hibernate.reactive.panache.common.WithTransaction; import io.quarkus.hibernate.reactive.panache.common.runtime.PanacheHibernateRecorder; -import io.quarkus.hibernate.reactive.panache.common.runtime.ReactiveTransactional; import io.quarkus.hibernate.reactive.panache.common.runtime.ReactiveTransactionalInterceptor; import io.quarkus.hibernate.reactive.panache.common.runtime.TestReactiveTransactionalInterceptor; import io.quarkus.hibernate.reactive.panache.common.runtime.WithSessionInterceptor; import io.quarkus.hibernate.reactive.panache.common.runtime.WithSessionOnDemandInterceptor; -import io.smallrye.mutiny.Uni; @BuildSteps(onlyIf = HibernateOrmEnabled.class) public final class PanacheJpaCommonResourceProcessor { private static final Logger LOG = Logger.getLogger(PanacheJpaCommonResourceProcessor.class); - private static final DotName DOTNAME_NAMED_QUERY = DotName.createSimple(NamedQuery.class.getName()); - private static final DotName DOTNAME_NAMED_QUERIES = DotName.createSimple(NamedQueries.class.getName()); private static final String TEST_REACTIVE_TRANSACTION = "io.quarkus.test.TestReactiveTransaction"; - private static final DotName REACTIVE_TRANSACTIONAL = DotName.createSimple(ReactiveTransactional.class.getName()); - private static final DotName WITH_SESSION_ON_DEMAND = DotName.createSimple(WithSessionOnDemand.class.getName()); - private static final DotName WITH_SESSION = DotName.createSimple(WithSession.class.getName()); - private static final DotName WITH_TRANSACTION = DotName.createSimple(WithTransaction.class.getName()); - private static final DotName UNI = DotName.createSimple(Uni.class.getName()); - private static final DotName PANACHE_ENTITY_BASE = DotName - .createSimple("io.quarkus.hibernate.reactive.panache.PanacheEntityBase"); - private static final DotName PANACHE_ENTITY = DotName.createSimple("io.quarkus.hibernate.reactive.panache.PanacheEntity"); - private static final DotName PANACHE_KOTLIN_ENTITY_BASE = DotName - .createSimple("io.quarkus.hibernate.reactive.panache.kotlin.PanacheEntityBase"); - private static final DotName PANACHE_KOTLIN_ENTITY = DotName - .createSimple("io.quarkus.hibernate.reactive.panache.kotlin.PanacheEntity"); - @BuildStep(onlyIf = IsTest.class) void testTx(BuildProducer generatedBeanBuildItemBuildProducer, BuildProducer additionalBeans) { @@ -109,11 +87,12 @@ void registerInterceptors(BuildProducer additionalBeans @BuildStep void validateInterceptedMethods(ValidationPhaseBuildItem validationPhase, BuildProducer errors) { - List bindings = List.of(REACTIVE_TRANSACTIONAL, WITH_SESSION, WITH_SESSION_ON_DEMAND, WITH_TRANSACTION); + List bindings = List.of(DotNames.REACTIVE_TRANSACTIONAL, DotNames.WITH_SESSION, + DotNames.WITH_SESSION_ON_DEMAND, DotNames.WITH_TRANSACTION); for (BeanInfo bean : validationPhase.getContext().beans().withAroundInvokeInterceptor()) { for (Entry> e : bean.getInterceptedMethodsBindings().entrySet()) { DotName returnTypeName = e.getKey().returnType().name(); - if (returnTypeName.equals(UNI)) { + if (returnTypeName.equals(DotNames.UNI)) { // Method returns Uni - no need to iterate over the bindings continue; } @@ -132,24 +111,41 @@ void transformResourceMethods(CombinedIndexBuildItem index, Capabilities capabil DotName.createSimple("jakarta.ws.rs.DELETE"), DotName.createSimple("jakarta.ws.rs.OPTIONS"), DotName.createSimple("jakarta.ws.rs.PATCH"), DotName.createSimple("jakarta.ws.rs.POST"), DotName.createSimple("jakarta.ws.rs.PUT")); - List bindings = List.of(REACTIVE_TRANSACTIONAL, WITH_SESSION, WITH_SESSION_ON_DEMAND, WITH_TRANSACTION); + List bindings = List.of(DotNames.REACTIVE_TRANSACTIONAL, DotNames.WITH_SESSION, + DotNames.WITH_SESSION_ON_DEMAND, DotNames.WITH_TRANSACTION); - // Collect all panache entities + // Collect all Panache entities and repositories Set entities = new HashSet<>(); - for (ClassInfo subclass : index.getIndex().getAllKnownSubclasses(PANACHE_ENTITY_BASE)) { - if (!subclass.name().equals(PANACHE_ENTITY)) { + for (ClassInfo subclass : index.getIndex().getAllKnownSubclasses(DotNames.PANACHE_ENTITY_BASE)) { + if (!subclass.name().equals(DotNames.PANACHE_ENTITY)) { entities.add(subclass.name()); } } - for (ClassInfo subclass : index.getIndex().getAllKnownImplementors(PANACHE_KOTLIN_ENTITY_BASE)) { - if (!subclass.name().equals(PANACHE_KOTLIN_ENTITY)) { - entities.add(subclass.name()); + for (ClassInfo implementor : index.getIndex().getAllKnownImplementors(DotNames.PANACHE_KOTLIN_ENTITY_BASE)) { + if (!implementor.name().equals(DotNames.PANACHE_KOTLIN_ENTITY)) { + entities.add(implementor.name()); + } + } + Set repos = new HashSet<>(); + for (ClassInfo subclass : index.getIndex().getAllKnownImplementors(DotNames.PANACHE_REPOSITORY_BASE)) { + if (!subclass.name().equals(DotNames.PANACHE_REPOSITORY)) { + repos.add(subclass.name()); } } - Set entityUsers = new HashSet<>(); + for (ClassInfo implementor : index.getIndex().getAllKnownImplementors(DotNames.PANACHE_KOTLIN_REPOSITORY_BASE)) { + if (!implementor.name().equals(DotNames.PANACHE_KOTLIN_REPOSITORY)) { + repos.add(implementor.name()); + } + } + Set entityReposUsers = new HashSet<>(); for (DotName entity : entities) { for (ClassInfo user : index.getIndex().getKnownUsers(entity)) { - entityUsers.add(user.name()); + entityReposUsers.add(user.name()); + } + } + for (DotName repo : repos) { + for (ClassInfo user : index.getIndex().getKnownUsers(repo)) { + entityReposUsers.add(user.name()); } } @@ -166,8 +162,8 @@ public void transform(TransformationContext context) { if (method.isSynthetic() || Modifier.isStatic(method.flags()) || method.declaringClass().isInterface() - || !method.returnType().name().equals(UNI) - || !entityUsers.contains(method.declaringClass().name()) + || !method.returnType().name().equals(DotNames.UNI) + || !entityReposUsers.contains(method.declaringClass().name()) || !Annotations.containsAny(annotations, designators) || Annotations.containsAny(annotations, bindings)) { return; @@ -176,10 +172,10 @@ public void transform(TransformationContext context) { // - is not static // - is not synthetic // - returns Uni - // - is declared in a class that uses a panache entity + // - is declared in a class that uses a panache entity/repository // - is annotated with @GET, @POST, @PUT, @DELETE ,@PATCH ,@HEAD or @OPTIONS // - is not annotated with @ReactiveTransactional, @WithSession, @WithSessionOnDemand, or @WithTransaction - context.transform().add(WITH_SESSION_ON_DEMAND).done(); + context.transform().add(DotNames.WITH_SESSION_ON_DEMAND).done(); } })); } @@ -221,7 +217,7 @@ private void lookupNamedQueries(CombinedIndexBuildItem index, DotName name, Map< return; } - List namedQueryInstances = classInfo.annotationsMap().get(DOTNAME_NAMED_QUERY); + List namedQueryInstances = classInfo.annotationsMap().get(DotNames.DOTNAME_NAMED_QUERY); if (namedQueryInstances != null) { for (AnnotationInstance namedQueryInstance : namedQueryInstances) { namedQueries.put(namedQueryInstance.value("name").asString(), @@ -229,7 +225,7 @@ private void lookupNamedQueries(CombinedIndexBuildItem index, DotName name, Map< } } - List namedQueriesInstances = classInfo.annotationsMap().get(DOTNAME_NAMED_QUERIES); + List namedQueriesInstances = classInfo.annotationsMap().get(DotNames.DOTNAME_NAMED_QUERIES); if (namedQueriesInstances != null) { for (AnnotationInstance namedQueriesInstance : namedQueriesInstances) { AnnotationValue value = namedQueriesInstance.value(); diff --git a/integration-tests/hibernate-reactive-panache/src/main/java/io/quarkus/it/panache/reactive/BeerRepository.java b/integration-tests/hibernate-reactive-panache/src/main/java/io/quarkus/it/panache/reactive/BeerRepository.java new file mode 100644 index 0000000000000..6f6f27d1b3ac9 --- /dev/null +++ b/integration-tests/hibernate-reactive-panache/src/main/java/io/quarkus/it/panache/reactive/BeerRepository.java @@ -0,0 +1,10 @@ +package io.quarkus.it.panache.reactive; + +import jakarta.enterprise.context.ApplicationScoped; + +import io.quarkus.hibernate.reactive.panache.PanacheRepository; + +@ApplicationScoped +public class BeerRepository implements PanacheRepository { + +} diff --git a/integration-tests/hibernate-reactive-panache/src/main/java/io/quarkus/it/panache/reactive/TestRepositoryEndpoint.java b/integration-tests/hibernate-reactive-panache/src/main/java/io/quarkus/it/panache/reactive/TestRepositoryEndpoint.java new file mode 100644 index 0000000000000..0b1b9c77ed2b0 --- /dev/null +++ b/integration-tests/hibernate-reactive-panache/src/main/java/io/quarkus/it/panache/reactive/TestRepositoryEndpoint.java @@ -0,0 +1,22 @@ +package io.quarkus.it.panache.reactive; + +import jakarta.inject.Inject; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; + +import io.smallrye.mutiny.Uni; + +@Path("test-repo") +public class TestRepositoryEndpoint { + + @Inject + BeerRepository beerRepository; + + // @WithSessionOnDemand is added automatically + @GET + @Path("beers") + public Uni testBeers() { + return beerRepository.count().map(v -> "OK"); + } + +} diff --git a/integration-tests/hibernate-reactive-panache/src/test/java/io/quarkus/it/panache/reactive/PanacheFunctionalityTest.java b/integration-tests/hibernate-reactive-panache/src/test/java/io/quarkus/it/panache/reactive/PanacheFunctionalityTest.java index a3388fb0590be..c4b044aaf07da 100644 --- a/integration-tests/hibernate-reactive-panache/src/test/java/io/quarkus/it/panache/reactive/PanacheFunctionalityTest.java +++ b/integration-tests/hibernate-reactive-panache/src/test/java/io/quarkus/it/panache/reactive/PanacheFunctionalityTest.java @@ -298,4 +298,9 @@ Uni testReactiveTransactional3() { public void testPersistenceException(UniAsserter asserter) { asserter.assertFailedWith(() -> Panache.withTransaction(() -> new Person().delete()), PersistenceException.class); } + + @Test + public void testBeerRepository() { + RestAssured.when().get("/test-repo/beers").then().body(is("OK")); + } }