Skip to content

Commit

Permalink
Hibernate Reactive Panache: open session on demand for repositories
Browse files Browse the repository at this point in the history
- 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
  • Loading branch information
mkouba committed Jul 31, 2023
1 parent b530940 commit 6ca6a72
Show file tree
Hide file tree
Showing 5 changed files with 113 additions and 38 deletions.
Original file line number Diff line number Diff line change
@@ -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");

}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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<GeneratedBeanBuildItem> generatedBeanBuildItemBuildProducer,
BuildProducer<AdditionalBeanBuildItem> additionalBeans) {
Expand Down Expand Up @@ -109,11 +87,12 @@ void registerInterceptors(BuildProducer<AdditionalBeanBuildItem> additionalBeans
@BuildStep
void validateInterceptedMethods(ValidationPhaseBuildItem validationPhase,
BuildProducer<ValidationErrorBuildItem> errors) {
List<DotName> bindings = List.of(REACTIVE_TRANSACTIONAL, WITH_SESSION, WITH_SESSION_ON_DEMAND, WITH_TRANSACTION);
List<DotName> 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<MethodInfo, Set<AnnotationInstance>> 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;
}
Expand All @@ -132,27 +111,46 @@ 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<DotName> bindings = List.of(REACTIVE_TRANSACTIONAL, WITH_SESSION, WITH_SESSION_ON_DEMAND, WITH_TRANSACTION);
List<DotName> 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<DotName> 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<DotName> entityUsers = new HashSet<>();
Set<DotName> repos = new HashSet<>();
for (ClassInfo subclass : index.getIndex().getAllKnownImplementors(DotNames.PANACHE_REPOSITORY_BASE)) {
if (!subclass.name().equals(DotNames.PANACHE_REPOSITORY)) {
repos.add(subclass.name());
}
}
for (ClassInfo implementor : index.getIndex().getAllKnownImplementors(DotNames.PANACHE_KOTLIN_REPOSITORY_BASE)) {
if (!implementor.name().equals(DotNames.PANACHE_KOTLIN_REPOSITORY)) {
repos.add(implementor.name());
}
}
Set<DotName> 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());
}
}

LOG.infof("entityReposUsers: %s\n\nentities: %s\n\nrepos: %s", entityReposUsers, entities, repos);

annotationsTransformer.produce(new AnnotationsTransformerBuildItem(new AnnotationsTransformer() {
@Override
public boolean appliesTo(Kind kind) {
Expand All @@ -166,8 +164,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;
Expand All @@ -179,7 +177,7 @@ public void transform(TransformationContext context) {
// - is declared in a class that uses a panache entity
// - 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();
}
}));
}
Expand Down Expand Up @@ -221,15 +219,15 @@ private void lookupNamedQueries(CombinedIndexBuildItem index, DotName name, Map<
return;
}

List<AnnotationInstance> namedQueryInstances = classInfo.annotationsMap().get(DOTNAME_NAMED_QUERY);
List<AnnotationInstance> namedQueryInstances = classInfo.annotationsMap().get(DotNames.DOTNAME_NAMED_QUERY);
if (namedQueryInstances != null) {
for (AnnotationInstance namedQueryInstance : namedQueryInstances) {
namedQueries.put(namedQueryInstance.value("name").asString(),
namedQueryInstance.value("query").asString());
}
}

List<AnnotationInstance> namedQueriesInstances = classInfo.annotationsMap().get(DOTNAME_NAMED_QUERIES);
List<AnnotationInstance> namedQueriesInstances = classInfo.annotationsMap().get(DotNames.DOTNAME_NAMED_QUERIES);
if (namedQueriesInstances != null) {
for (AnnotationInstance namedQueriesInstance : namedQueriesInstances) {
AnnotationValue value = namedQueriesInstance.value();
Expand Down
Original file line number Diff line number Diff line change
@@ -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<Beer> {

}
Original file line number Diff line number Diff line change
@@ -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<String> testBeers() {
return beerRepository.count().map(v -> "OK");
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -298,4 +298,9 @@ Uni<Long> 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"));
}
}

0 comments on commit 6ca6a72

Please sign in to comment.