From cf765fcec280d808b8c0acc071e4f1a70d72e9c9 Mon Sep 17 00:00:00 2001 From: Martin Kouba Date: Mon, 26 Feb 2024 12:41:54 +0100 Subject: [PATCH 1/2] Hibernate ORM: register some beans only if Hibernate Reactive is missing --- .../deployment/HibernateOrmCdiProcessor.java | 48 ++++++++++++++++++- .../orm/deployment/HibernateOrmProcessor.java | 39 +-------------- 2 files changed, 47 insertions(+), 40 deletions(-) diff --git a/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/HibernateOrmCdiProcessor.java b/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/HibernateOrmCdiProcessor.java index fc207f65444c3..7a17d32e38408 100644 --- a/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/HibernateOrmCdiProcessor.java +++ b/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/HibernateOrmCdiProcessor.java @@ -1,5 +1,6 @@ package io.quarkus.hibernate.orm.deployment; +import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Locale; @@ -11,6 +12,8 @@ import jakarta.enterprise.inject.Default; import jakarta.enterprise.inject.Instance; import jakarta.inject.Singleton; +import jakarta.persistence.AttributeConverter; +import jakarta.transaction.TransactionManager; import org.hibernate.Session; import org.hibernate.SessionFactory; @@ -31,6 +34,7 @@ import io.quarkus.arc.deployment.BeanDefiningAnnotationBuildItem; import io.quarkus.arc.deployment.SyntheticBeanBuildItem; import io.quarkus.arc.deployment.SyntheticBeanBuildItem.ExtendedBeanConfigurator; +import io.quarkus.arc.deployment.UnremovableBeanBuildItem; import io.quarkus.arc.deployment.ValidationPhaseBuildItem; import io.quarkus.arc.processor.AnnotationsTransformer; import io.quarkus.arc.processor.DotNames; @@ -42,12 +46,16 @@ import io.quarkus.deployment.annotations.BuildSteps; import io.quarkus.deployment.annotations.ExecutionTime; import io.quarkus.deployment.annotations.Record; +import io.quarkus.deployment.builditem.CombinedIndexBuildItem; import io.quarkus.hibernate.orm.PersistenceUnit; import io.quarkus.hibernate.orm.runtime.HibernateOrmRecorder; import io.quarkus.hibernate.orm.runtime.HibernateOrmRuntimeConfig; import io.quarkus.hibernate.orm.runtime.JPAConfig; import io.quarkus.hibernate.orm.runtime.PersistenceUnitUtil; +import io.quarkus.hibernate.orm.runtime.RequestScopedSessionHolder; +import io.quarkus.hibernate.orm.runtime.RequestScopedStatelessSessionHolder; import io.quarkus.hibernate.orm.runtime.TransactionSessions; +import io.quarkus.hibernate.orm.runtime.cdi.QuarkusArcBeanContainer; @BuildSteps(onlyIf = HibernateOrmEnabled.class) public class HibernateOrmCdiProcessor { @@ -182,7 +190,8 @@ void generateDataSourceBeans(HibernateOrmRecorder recorder, .addInjectionPoint(ClassType.create(DotName.createSimple(JPAConfig.class))) .done()); - if (capabilities.isPresent(Capability.TRANSACTIONS)) { + if (capabilities.isPresent(Capability.TRANSACTIONS) + && capabilities.isMissing(Capability.HIBERNATE_REACTIVE)) { // Do register a Session/EntityManager bean only if JTA is available // Note that the Hibernate Reactive extension excludes JTA intentionally syntheticBeanBuildItemBuildProducer @@ -222,7 +231,8 @@ void generateDataSourceBeans(HibernateOrmRecorder recorder, .addInjectionPoint(ClassType.create(DotName.createSimple(JPAConfig.class))) .done()); - if (capabilities.isPresent(Capability.TRANSACTIONS)) { + if (capabilities.isPresent(Capability.TRANSACTIONS) + && capabilities.isMissing(Capability.HIBERNATE_REACTIVE)) { // Do register a Session/EntityManager bean only if JTA is available // Note that the Hibernate Reactive extension excludes JTA intentionally syntheticBeanBuildItemBuildProducer @@ -245,6 +255,40 @@ void generateDataSourceBeans(HibernateOrmRecorder recorder, } } + @BuildStep + void registerBeans(HibernateOrmConfig hibernateOrmConfig, + BuildProducer additionalBeans, + BuildProducer unremovableBeans, + Capabilities capabilities, + CombinedIndexBuildItem combinedIndex, + List descriptors, + JpaModelBuildItem jpaModel) { + if (!HibernateOrmProcessor.hasEntities(jpaModel)) { + return; + } + + List> unremovableClasses = new ArrayList<>(); + unremovableClasses.add(QuarkusArcBeanContainer.class); + + if (capabilities.isMissing(Capability.HIBERNATE_REACTIVE)) { + // The following beans only make sense for Hibernate ORM, not for Hibernate Reactive + + if (capabilities.isPresent(Capability.TRANSACTIONS)) { + unremovableClasses.add(TransactionManager.class); + unremovableClasses.add(TransactionSessions.class); + } + unremovableClasses.add(RequestScopedSessionHolder.class); + unremovableClasses.add(RequestScopedStatelessSessionHolder.class); + } + additionalBeans.produce(AdditionalBeanBuildItem.builder().setUnremovable() + .addBeanClasses(unremovableClasses.toArray(new Class[unremovableClasses.size()])) + .build()); + + // Some user-injectable beans are retrieved programmatically and shouldn't be removed + unremovableBeans.produce(UnremovableBeanBuildItem.beanTypes(AttributeConverter.class)); + unremovableBeans.produce(UnremovableBeanBuildItem.beanTypes(jpaModel.getPotentialCdiBeanClassNames())); + } + @BuildStep void registerAnnotations(BuildProducer additionalBeans, BuildProducer beanDefiningAnnotations) { diff --git a/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/HibernateOrmProcessor.java b/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/HibernateOrmProcessor.java index 0d96b8375dc2a..c93d00b8ce5a7 100644 --- a/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/HibernateOrmProcessor.java +++ b/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/HibernateOrmProcessor.java @@ -36,11 +36,9 @@ import jakarta.enterprise.context.ApplicationScoped; import jakarta.enterprise.inject.Default; -import jakarta.persistence.AttributeConverter; import jakarta.persistence.SharedCacheMode; import jakarta.persistence.ValidationMode; import jakarta.persistence.spi.PersistenceUnitTransactionType; -import jakarta.transaction.TransactionManager; import jakarta.xml.bind.JAXBElement; import org.hibernate.boot.archive.scan.spi.ClassDescriptor; @@ -66,7 +64,6 @@ import io.quarkus.agroal.spi.JdbcDataSourceBuildItem; import io.quarkus.agroal.spi.JdbcDataSourceSchemaReadyBuildItem; -import io.quarkus.arc.deployment.AdditionalBeanBuildItem; import io.quarkus.arc.deployment.BeanContainerBuildItem; import io.quarkus.arc.deployment.BeanContainerListenerBuildItem; import io.quarkus.arc.deployment.RecorderBeanInitializedBuildItem; @@ -120,15 +117,11 @@ import io.quarkus.hibernate.orm.runtime.HibernateOrmRecorder; import io.quarkus.hibernate.orm.runtime.HibernateOrmRuntimeConfig; import io.quarkus.hibernate.orm.runtime.PersistenceUnitUtil; -import io.quarkus.hibernate.orm.runtime.RequestScopedSessionHolder; -import io.quarkus.hibernate.orm.runtime.RequestScopedStatelessSessionHolder; -import io.quarkus.hibernate.orm.runtime.TransactionSessions; import io.quarkus.hibernate.orm.runtime.boot.QuarkusPersistenceUnitDefinition; import io.quarkus.hibernate.orm.runtime.boot.scan.QuarkusScanner; import io.quarkus.hibernate.orm.runtime.boot.xml.JAXBElementSubstitution; import io.quarkus.hibernate.orm.runtime.boot.xml.QNameSubstitution; import io.quarkus.hibernate.orm.runtime.boot.xml.RecordableXmlMapping; -import io.quarkus.hibernate.orm.runtime.cdi.QuarkusArcBeanContainer; import io.quarkus.hibernate.orm.runtime.config.DialectVersions; import io.quarkus.hibernate.orm.runtime.dev.HibernateOrmDevIntegrator; import io.quarkus.hibernate.orm.runtime.integration.HibernateOrmIntegrationStaticDescriptor; @@ -592,36 +585,6 @@ void handleNativeImageImportSql(BuildProducer reso } } - @BuildStep - void registerBeans(HibernateOrmConfig hibernateOrmConfig, - BuildProducer additionalBeans, - BuildProducer unremovableBeans, - Capabilities capabilities, - CombinedIndexBuildItem combinedIndex, - List descriptors, - JpaModelBuildItem jpaModel) { - if (!hasEntities(jpaModel)) { - return; - } - - List> unremovableClasses = new ArrayList<>(); - if (capabilities.isPresent(Capability.TRANSACTIONS)) { - unremovableClasses.add(TransactionManager.class); - unremovableClasses.add(TransactionSessions.class); - } - unremovableClasses.add(RequestScopedSessionHolder.class); - unremovableClasses.add(RequestScopedStatelessSessionHolder.class); - unremovableClasses.add(QuarkusArcBeanContainer.class); - - additionalBeans.produce(AdditionalBeanBuildItem.builder().setUnremovable() - .addBeanClasses(unremovableClasses.toArray(new Class[unremovableClasses.size()])) - .build()); - - // Some user-injectable beans are retrieved programmatically and shouldn't be removed - unremovableBeans.produce(UnremovableBeanBuildItem.beanTypes(AttributeConverter.class)); - unremovableBeans.produce(UnremovableBeanBuildItem.beanTypes(jpaModel.getPotentialCdiBeanClassNames())); - } - @Consume(InterceptedStaticMethodsTransformersRegisteredBuildItem.class) @BuildStep @SuppressWarnings("deprecation") @@ -801,7 +764,7 @@ private static List getSqlLoadScript(Optional> sqlLoadScrip } } - private boolean hasEntities(JpaModelBuildItem jpaModel) { + static boolean hasEntities(JpaModelBuildItem jpaModel) { return !jpaModel.getEntityClassNames().isEmpty(); } From 70a11af47d64eb0bec6a05cb1bd029c0eac961a9 Mon Sep 17 00:00:00 2001 From: Martin Kouba Date: Mon, 26 Feb 2024 12:57:52 +0100 Subject: [PATCH 2/2] Hibernate Reactive: fail the build if necessary - fail the build if the default datasource is not configured and Dev Services are disabled - fail the build unless exactly one persistent unit is configured --- .../HibernateReactiveProcessor.java | 19 +++++++++++++++---- ...ithExplicitUnconfiguredDatasourceTest.java | 9 ++++++++- ...ithImplicitUnconfiguredDatasourceTest.java | 9 ++++++++- 3 files changed, 31 insertions(+), 6 deletions(-) diff --git a/extensions/hibernate-reactive/deployment/src/main/java/io/quarkus/hibernate/reactive/deployment/HibernateReactiveProcessor.java b/extensions/hibernate-reactive/deployment/src/main/java/io/quarkus/hibernate/reactive/deployment/HibernateReactiveProcessor.java index 7a4abf1fe751e..37303a0f579a1 100644 --- a/extensions/hibernate-reactive/deployment/src/main/java/io/quarkus/hibernate/reactive/deployment/HibernateReactiveProcessor.java +++ b/extensions/hibernate-reactive/deployment/src/main/java/io/quarkus/hibernate/reactive/deployment/HibernateReactiveProcessor.java @@ -93,13 +93,19 @@ public final class HibernateReactiveProcessor { void registerBeans(BuildProducer additionalBeans, CombinedIndexBuildItem combinedIndex, List descriptors, JpaModelBuildItem jpaModel) { + if (!hasEntities(jpaModel)) { + LOG.warnf("Skip registration of the %s bean - no JPA entities were found", + ReactiveSessionFactoryProducer.class.getSimpleName()); + return; + } if (descriptors.size() == 1) { // Only register the bean if their EMF dependency is also available, so use the same guard as the ORM extension additionalBeans.produce(new AdditionalBeanBuildItem(ReactiveSessionFactoryProducer.class)); } else { - LOG.warnf( - "Skipping registration of %s bean because exactly one persistence unit is required for their registration", - ReactiveSessionFactoryProducer.class.getSimpleName()); + throw new ConfigurationException( + "The Hibernate Reactive extension requires exactly one persistence unit configured: " + descriptors + .stream() + .map(PersistenceUnitDescriptorBuildItem::getPersistenceUnitName).collect(Collectors.toList())); } } @@ -172,7 +178,12 @@ public void buildReactivePersistenceUnit( .orElse(!dataSourcesBuildTimeConfig.hasNamedDataSources()), curateOutcomeBuildItem); - if (dbKindOptional.isPresent()) { + if (dbKindOptional.isEmpty()) { + throw new ConfigurationException( + "The default datasource must be configured for Hibernate Reactive. Refer to https://quarkus.io/guides/datasource for guidance.", + Set.of("quarkus.datasource.db-kind", "quarkus.datasource.username", + "quarkus.datasource.password")); + } else { HibernateOrmConfigPersistenceUnit persistenceUnitConfig = hibernateOrmConfig.defaultPersistenceUnit(); ParsedPersistenceXmlDescriptor reactivePU = generateReactivePersistenceUnit( hibernateOrmConfig, index, persistenceUnitConfig, jpaModel, diff --git a/extensions/hibernate-reactive/deployment/src/test/java/io/quarkus/hibernate/reactive/config/datasource/EntitiesInDefaultPUWithExplicitUnconfiguredDatasourceTest.java b/extensions/hibernate-reactive/deployment/src/test/java/io/quarkus/hibernate/reactive/config/datasource/EntitiesInDefaultPUWithExplicitUnconfiguredDatasourceTest.java index c4c11e2307d01..a70236bce167b 100644 --- a/extensions/hibernate-reactive/deployment/src/test/java/io/quarkus/hibernate/reactive/config/datasource/EntitiesInDefaultPUWithExplicitUnconfiguredDatasourceTest.java +++ b/extensions/hibernate-reactive/deployment/src/test/java/io/quarkus/hibernate/reactive/config/datasource/EntitiesInDefaultPUWithExplicitUnconfiguredDatasourceTest.java @@ -1,9 +1,12 @@ package io.quarkus.hibernate.reactive.config.datasource; +import static org.assertj.core.api.Assertions.assertThat; + import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; import io.quarkus.hibernate.reactive.config.MyEntity; +import io.quarkus.runtime.configuration.ConfigurationException; import io.quarkus.test.QuarkusUnitTest; public class EntitiesInDefaultPUWithExplicitUnconfiguredDatasourceTest { @@ -13,7 +16,11 @@ public class EntitiesInDefaultPUWithExplicitUnconfiguredDatasourceTest { .withApplicationRoot((jar) -> jar .addClass(MyEntity.class)) .overrideConfigKey("quarkus.hibernate-orm.datasource", "ds-1") - .overrideConfigKey("quarkus.hibernate-orm.database.generation", "drop-and-create"); + .overrideConfigKey("quarkus.hibernate-orm.database.generation", "drop-and-create") + .assertException(t -> assertThat(t) + .isInstanceOf(ConfigurationException.class) + .hasMessageContaining( + "The default datasource must be configured for Hibernate Reactive. Refer to https://quarkus.io/guides/datasource for guidance.")); @Test public void testInvalidConfiguration() { diff --git a/extensions/hibernate-reactive/deployment/src/test/java/io/quarkus/hibernate/reactive/config/datasource/EntitiesInDefaultPUWithImplicitUnconfiguredDatasourceTest.java b/extensions/hibernate-reactive/deployment/src/test/java/io/quarkus/hibernate/reactive/config/datasource/EntitiesInDefaultPUWithImplicitUnconfiguredDatasourceTest.java index 74f0f25029c80..495a8b8dc4fc6 100644 --- a/extensions/hibernate-reactive/deployment/src/test/java/io/quarkus/hibernate/reactive/config/datasource/EntitiesInDefaultPUWithImplicitUnconfiguredDatasourceTest.java +++ b/extensions/hibernate-reactive/deployment/src/test/java/io/quarkus/hibernate/reactive/config/datasource/EntitiesInDefaultPUWithImplicitUnconfiguredDatasourceTest.java @@ -1,9 +1,12 @@ package io.quarkus.hibernate.reactive.config.datasource; +import static org.assertj.core.api.Assertions.assertThat; + import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; import io.quarkus.hibernate.reactive.config.MyEntity; +import io.quarkus.runtime.configuration.ConfigurationException; import io.quarkus.test.QuarkusUnitTest; public class EntitiesInDefaultPUWithImplicitUnconfiguredDatasourceTest { @@ -13,7 +16,11 @@ public class EntitiesInDefaultPUWithImplicitUnconfiguredDatasourceTest { .withApplicationRoot((jar) -> jar .addClass(MyEntity.class)) // The datasource won't be truly "unconfigured" if dev services are enabled - .overrideConfigKey("quarkus.devservices.enabled", "false"); + .overrideConfigKey("quarkus.devservices.enabled", "false") + .assertException(t -> assertThat(t) + .isInstanceOf(ConfigurationException.class) + .hasMessageContaining( + "The default datasource must be configured for Hibernate Reactive. Refer to https://quarkus.io/guides/datasource for guidance.")); @Test public void testInvalidConfiguration() {