Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Hibernate Reactive Panache: open session on demand for repositories #34671

Merged
merged 1 commit into from
Jul 31, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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,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<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> 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<DotName> 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<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());
}
}

Expand All @@ -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;
Expand All @@ -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();
}
}));
}
Expand Down Expand Up @@ -221,15 +217,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"));
}
}