From 0af95bbf8fb7e4df4c9e13b04f324a7a07ebd0bc Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Tue, 1 Sep 2020 09:18:52 +0200 Subject: [PATCH 1/2] Add elements missing in Hibernate ORM config isAnyPropertySet() methods --- .../deployment/HibernateOrmConfigPersistenceUnit.java | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/HibernateOrmConfigPersistenceUnit.java b/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/HibernateOrmConfigPersistenceUnit.java index 2a942d2a7c0fa..10698c40ccd2d 100644 --- a/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/HibernateOrmConfigPersistenceUnit.java +++ b/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/HibernateOrmConfigPersistenceUnit.java @@ -150,15 +150,20 @@ public class HibernateOrmConfigPersistenceUnit { public boolean secondLevelCachingEnabled; public boolean isAnyPropertySet() { - return dialect.isAnyPropertySet() || + return datasource.isPresent() || + packages.isPresent() || + dialect.isAnyPropertySet() || sqlLoadScript.isPresent() || batchFetchSize > 0 || maxFetchDepth.isPresent() || + physicalNamingStrategy.isPresent() || + implicitNamingStrategy.isPresent() || query.isAnyPropertySet() || database.isAnyPropertySet() || jdbc.isAnyPropertySet() || log.isAnyPropertySet() || - !cache.isEmpty(); + !cache.isEmpty() || + !secondLevelCachingEnabled; } @ConfigGroup @@ -260,7 +265,7 @@ public static class HibernateOrmConfigPersistenceUnitDatabase { *

* Used for DDL generation and also for the SQL import scripts. */ - @ConfigItem(defaultValue = "UTF-8") + @ConfigItem(defaultValue = DEFAULT_CHARSET) public Charset charset; /** From aabda577e2b50efc77228192829bf3b93fa3af41 Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Tue, 1 Sep 2020 12:22:25 +0200 Subject: [PATCH 2/2] Support multitenancy with multiple persistence units --- .../orm/deployment/HibernateOrmConfig.java | 24 +----- .../HibernateOrmConfigPersistenceUnit.java | 21 ++++- .../orm/deployment/HibernateOrmProcessor.java | 61 ++++++++++--- .../PersistenceUnitDescriptorBuildItem.java | 33 +++++-- .../FastBootHibernatePersistenceProvider.java | 12 +-- .../orm/runtime/HibernateOrmRecorder.java | 14 +++ .../hibernate/orm/runtime/JPAConfig.java | 24 ------ .../orm/runtime/JPAConfigSupport.java | 11 +-- .../runtime/boot/FastBootMetadataBuilder.java | 6 +- .../QuarkusPersistenceUnitDefinition.java | 15 ++-- .../orm/runtime/recording/RecordedState.java | 7 +- .../DataSourceTenantConnectionResolver.java | 85 +++++++++++-------- ...ibernateMultiTenantConnectionProvider.java | 53 +++++++++--- 13 files changed, 214 insertions(+), 152 deletions(-) diff --git a/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/HibernateOrmConfig.java b/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/HibernateOrmConfig.java index 3cd5ab51b6625..43974dd7916ba 100644 --- a/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/HibernateOrmConfig.java +++ b/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/HibernateOrmConfig.java @@ -46,34 +46,12 @@ public class HibernateOrmConfig { @ConfigItem(name = "metrics.enabled") public boolean metricsEnabled; - // TODO MULTI-PUS: the multi-tenant configuration will need some profound changes but we can do that later - - /** - * Defines the method for multi-tenancy (DATABASE, NONE, SCHEMA). The complete list of allowed values is available in the - * https://docs.jboss.org/hibernate/stable/orm/javadocs/org/hibernate/MultiTenancyStrategy.html[Hibernate ORM JavaDoc]. - * The type DISCRIMINATOR is currently not supported. The default value is NONE (no multi-tenancy). - * - * @asciidoclet - */ - @ConfigItem - public Optional multitenant; - - /** - * Defines the name of the data source to use in case of SCHEMA approach. The default data source will be used if not set. - * - * @asciidoclet - */ - @ConfigItem - public Optional multitenantSchemaDatasource; - public boolean isAnyPropertySet() { return defaultPersistenceUnit.isAnyPropertySet() || !persistenceUnits.isEmpty() || log.isAnyPropertySet() || statistics.isPresent() || - metricsEnabled || - multitenant.isPresent() || - multitenantSchemaDatasource.isPresent(); + metricsEnabled; } @ConfigGroup diff --git a/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/HibernateOrmConfigPersistenceUnit.java b/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/HibernateOrmConfigPersistenceUnit.java index 10698c40ccd2d..c51ee6048d2bd 100644 --- a/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/HibernateOrmConfigPersistenceUnit.java +++ b/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/HibernateOrmConfigPersistenceUnit.java @@ -149,6 +149,23 @@ public class HibernateOrmConfigPersistenceUnit { @ConfigItem(defaultValue = "true") public boolean secondLevelCachingEnabled; + /** + * Defines the method for multi-tenancy (DATABASE, NONE, SCHEMA). The complete list of allowed values is available in the + * https://docs.jboss.org/hibernate/stable/orm/javadocs/org/hibernate/MultiTenancyStrategy.html[Hibernate ORM JavaDoc]. + * The type DISCRIMINATOR is currently not supported. The default value is NONE (no multi-tenancy). + * + * @asciidoclet + */ + @ConfigItem + public Optional multitenant; + + /** + * Defines the name of the datasource to use in case of SCHEMA approach. The datasource of the persistence unit will be used + * if not set. + */ + @ConfigItem + public Optional multitenantSchemaDatasource; + public boolean isAnyPropertySet() { return datasource.isPresent() || packages.isPresent() || @@ -163,7 +180,9 @@ public boolean isAnyPropertySet() { jdbc.isAnyPropertySet() || log.isAnyPropertySet() || !cache.isEmpty() || - !secondLevelCachingEnabled; + !secondLevelCachingEnabled || + multitenant.isPresent() || + multitenantSchemaDatasource.isPresent(); } @ConfigGroup 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 b103e633fa084..fdf4c7cfd2b66 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 @@ -26,6 +26,8 @@ import java.util.TreeSet; import java.util.stream.Collectors; +import javax.enterprise.context.ApplicationScoped; +import javax.enterprise.inject.Default; import javax.inject.Singleton; import javax.persistence.Entity; import javax.persistence.MappedSuperclass; @@ -67,6 +69,7 @@ import io.quarkus.arc.deployment.BeanContainerBuildItem; import io.quarkus.arc.deployment.BeanContainerListenerBuildItem; import io.quarkus.arc.deployment.SyntheticBeanBuildItem; +import io.quarkus.arc.deployment.SyntheticBeanBuildItem.ExtendedBeanConfigurator; import io.quarkus.arc.deployment.staticmethods.InterceptedStaticMethodsTransformersRegisteredBuildItem; import io.quarkus.arc.processor.DotNames; import io.quarkus.datasource.common.runtime.DataSourceUtil; @@ -113,6 +116,7 @@ import io.quarkus.hibernate.orm.runtime.dialect.QuarkusPostgreSQL10Dialect; import io.quarkus.hibernate.orm.runtime.proxies.PreGeneratedProxies; import io.quarkus.hibernate.orm.runtime.tenant.DataSourceTenantConnectionResolver; +import io.quarkus.hibernate.orm.runtime.tenant.TenantConnectionResolver; import io.quarkus.runtime.LaunchMode; import io.quarkus.runtime.configuration.ConfigurationException; import net.bytebuddy.description.type.TypeDescription; @@ -271,7 +275,10 @@ public void configurationDescriptorBuilding( for (PersistenceXmlDescriptorBuildItem persistenceXmlDescriptorBuildItem : persistenceXmlDescriptors) { persistenceUnitDescriptors .produce(new PersistenceUnitDescriptorBuildItem(persistenceXmlDescriptorBuildItem.getDescriptor(), - getMultiTenancyStrategy(hibernateOrmConfig), false)); + getMultiTenancyStrategy(Optional.ofNullable(persistenceXmlDescriptorBuildItem.getDescriptor() + .getProperties().getProperty(AvailableSettings.MULTI_TENANT))), + null, + false)); } if (impliedPU.shouldGenerateImpliedBlockingPersistenceUnit()) { @@ -431,9 +438,6 @@ void registerBeans(HibernateOrmConfig hibernateOrmConfig, unremovableClasses.add(TransactionEntityManagers.class); } unremovableClasses.add(RequestScopedEntityManagerHolder.class); - if (getMultiTenancyStrategy(hibernateOrmConfig) != MultiTenancyStrategy.NONE) { - unremovableClasses.add(DataSourceTenantConnectionResolver.class); - } additionalBeans.produce(AdditionalBeanBuildItem.builder().setUnremovable() .addBeanClasses(unremovableClasses.toArray(new Class[unremovableClasses.size()])) @@ -464,9 +468,6 @@ public void build(HibernateOrmRecorder recorder, HibernateOrmConfig hibernateOrm return; } - MultiTenancyStrategy multiTenancyStrategy = MultiTenancyStrategy - .valueOf(hibernateOrmConfig.multitenant.orElse(MultiTenancyStrategy.NONE.name())); - Set persistenceUnitNames = new HashSet<>(); Map> entityPersistenceUnitMapping = new HashMap<>(); @@ -485,8 +486,7 @@ public void build(HibernateOrmRecorder recorder, HibernateOrmConfig hibernateOrm .scope(Singleton.class) .unremovable() .supplier(recorder.jpaConfigSupportSupplier( - new JPAConfigSupport(persistenceUnitNames, entityPersistenceUnitMapping, multiTenancyStrategy, - hibernateOrmConfig.multitenantSchemaDatasource.orElse(null)))) + new JPAConfigSupport(persistenceUnitNames, entityPersistenceUnitMapping))) .done()); } @@ -504,6 +504,39 @@ public void startPersistenceUnits(HibernateOrmRecorder recorder, BeanContainerBu recorder.startAllPersistenceUnits(beanContainer.getValue()); } + @BuildStep + @Record(RUNTIME_INIT) + public void multitenancy(HibernateOrmRecorder recorder, + List persistenceUnitDescriptors, + BuildProducer syntheticBeans) { + for (PersistenceUnitDescriptorBuildItem persistenceUnitDescriptor : persistenceUnitDescriptors) { + if (persistenceUnitDescriptor.getMultiTenancyStrategy() == MultiTenancyStrategy.NONE) { + continue; + } + + ExtendedBeanConfigurator configurator = SyntheticBeanBuildItem.configure(DataSourceTenantConnectionResolver.class) + .scope(ApplicationScoped.class) + .types(TenantConnectionResolver.class) + .setRuntimeInit() + .defaultBean() + .unremovable() + .supplier(recorder.dataSourceTenantConnectionResolver(persistenceUnitDescriptor.getPersistenceUnitName(), + persistenceUnitDescriptor.getDataSource(), persistenceUnitDescriptor.getMultiTenancyStrategy(), + persistenceUnitDescriptor.getMultiTenancySchemaDataSource())); + + if (PersistenceUnitUtil.isDefaultPersistenceUnit(persistenceUnitDescriptor.getPersistenceUnitName())) { + configurator.addQualifier(Default.class); + } else { + configurator.addQualifier().annotation(DotNames.NAMED) + .addValue("value", persistenceUnitDescriptor.getPersistenceUnitName()).done(); + configurator.addQualifier().annotation(PersistenceUnit.class) + .addValue("value", persistenceUnitDescriptor.getPersistenceUnitName()).done(); + } + + syntheticBeans.produce(configurator.done()); + } + } + @BuildStep public void produceLoggingCategories(HibernateOrmConfig hibernateOrmConfig, BuildProducer categories) { @@ -809,7 +842,8 @@ private static void producePersistenceUnitDescriptorFromConfig( persistenceUnitDescriptors.produce( new PersistenceUnitDescriptorBuildItem(descriptor, dataSource, - getMultiTenancyStrategy(hibernateOrmConfig), + getMultiTenancyStrategy(persistenceUnitConfig.multitenant), + persistenceUnitConfig.multitenantSchemaDatasource.orElse(null), false)); } @@ -1061,12 +1095,13 @@ public static QuarkusScanner buildQuarkusScanner(JpaEntitiesBuildItem domainObje return scanner; } - private static MultiTenancyStrategy getMultiTenancyStrategy(HibernateOrmConfig hibernateOrmConfig) { + private static MultiTenancyStrategy getMultiTenancyStrategy(Optional multitenancyStrategy) { final MultiTenancyStrategy multiTenancyStrategy = MultiTenancyStrategy - .valueOf(hibernateOrmConfig.multitenant.orElse(MultiTenancyStrategy.NONE.name())); + .valueOf(multitenancyStrategy.orElse(MultiTenancyStrategy.NONE.name()) + .toUpperCase(Locale.ROOT)); if (multiTenancyStrategy == MultiTenancyStrategy.DISCRIMINATOR) { // See https://hibernate.atlassian.net/browse/HHH-6054 - throw new ConfigurationError("The Hibernate ORM multi tenancy strategy " + throw new ConfigurationError("The Hibernate ORM multitenancy strategy " + MultiTenancyStrategy.DISCRIMINATOR + " is currently not supported"); } return multiTenancyStrategy; diff --git a/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/PersistenceUnitDescriptorBuildItem.java b/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/PersistenceUnitDescriptorBuildItem.java index 6556d1c711175..fe256eb185d59 100644 --- a/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/PersistenceUnitDescriptorBuildItem.java +++ b/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/PersistenceUnitDescriptorBuildItem.java @@ -1,12 +1,12 @@ package io.quarkus.hibernate.orm.deployment; import java.util.Collection; -import java.util.Optional; import org.hibernate.MultiTenancyStrategy; import org.hibernate.jpa.boot.internal.ParsedPersistenceXmlDescriptor; import io.quarkus.builder.item.MultiBuildItem; +import io.quarkus.datasource.common.runtime.DataSourceUtil; import io.quarkus.hibernate.orm.runtime.boot.QuarkusPersistenceUnitDefinition; /** @@ -18,38 +18,45 @@ public final class PersistenceUnitDescriptorBuildItem extends MultiBuildItem { private final ParsedPersistenceXmlDescriptor descriptor; - private final Optional dataSource; + private final String dataSource; private final MultiTenancyStrategy multiTenancyStrategy; + private final String multiTenancySchemaDataSource; private final boolean isReactive; public PersistenceUnitDescriptorBuildItem(ParsedPersistenceXmlDescriptor descriptor, boolean isReactive) { this.descriptor = descriptor; - this.dataSource = Optional.empty(); + this.dataSource = DataSourceUtil.DEFAULT_DATASOURCE_NAME; this.multiTenancyStrategy = MultiTenancyStrategy.NONE; + this.multiTenancySchemaDataSource = null; this.isReactive = isReactive; } public PersistenceUnitDescriptorBuildItem(ParsedPersistenceXmlDescriptor descriptor, - MultiTenancyStrategy multiTenancyStrategy, boolean isReactive) { + MultiTenancyStrategy multiTenancyStrategy, + String multiTenancySchemaDataSource, + boolean isReactive) { this.descriptor = descriptor; - this.dataSource = Optional.empty(); + this.dataSource = DataSourceUtil.DEFAULT_DATASOURCE_NAME; this.multiTenancyStrategy = multiTenancyStrategy; + this.multiTenancySchemaDataSource = multiTenancySchemaDataSource; this.isReactive = isReactive; } public PersistenceUnitDescriptorBuildItem(ParsedPersistenceXmlDescriptor descriptor, String dataSource, boolean isReactive) { this.descriptor = descriptor; - this.dataSource = Optional.of(dataSource); + this.dataSource = dataSource; this.multiTenancyStrategy = MultiTenancyStrategy.NONE; + this.multiTenancySchemaDataSource = null; this.isReactive = isReactive; } public PersistenceUnitDescriptorBuildItem(ParsedPersistenceXmlDescriptor descriptor, String dataSource, - MultiTenancyStrategy multiTenancyStrategy, boolean isReactive) { + MultiTenancyStrategy multiTenancyStrategy, String multiTenancySchemaDataSource, boolean isReactive) { this.descriptor = descriptor; - this.dataSource = Optional.of(dataSource); + this.dataSource = dataSource; this.multiTenancyStrategy = multiTenancyStrategy; + this.multiTenancySchemaDataSource = multiTenancySchemaDataSource; this.isReactive = isReactive; } @@ -65,10 +72,18 @@ public String getPersistenceUnitName() { return descriptor.getName(); } - public Optional getDataSource() { + public String getDataSource() { return dataSource; } + public MultiTenancyStrategy getMultiTenancyStrategy() { + return multiTenancyStrategy; + } + + public String getMultiTenancySchemaDataSource() { + return multiTenancySchemaDataSource; + } + public QuarkusPersistenceUnitDefinition asOutputPersistenceUnitDefinition() { return new QuarkusPersistenceUnitDefinition(descriptor, dataSource, multiTenancyStrategy, isReactive); } diff --git a/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/FastBootHibernatePersistenceProvider.java b/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/FastBootHibernatePersistenceProvider.java index b5d52fa2f296e..e250a1fe5052c 100644 --- a/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/FastBootHibernatePersistenceProvider.java +++ b/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/FastBootHibernatePersistenceProvider.java @@ -3,7 +3,6 @@ import java.util.Collections; import java.util.List; import java.util.Map; -import java.util.Optional; import javax.persistence.EntityManagerFactory; import javax.persistence.PersistenceException; @@ -261,7 +260,7 @@ private void verifyProperties(Map properties) { } } - private void injectDataSource(String persistenceUnitName, Optional dataSource, + private void injectDataSource(String persistenceUnitName, String dataSource, RuntimeSettings.Builder runtimeSettingsBuilder) { // first convert @@ -274,18 +273,15 @@ private void injectDataSource(String persistenceUnitName, Optional dataS } InstanceHandle dataSourceHandle; - if (!dataSource.isPresent()) { - // we are in the case of a XML-defined PU with no datasource defined, we use the default datasource - dataSourceHandle = Arc.container().instance(DataSource.class); - } else if (DataSourceUtil.isDefault(dataSource.get())) { + if (DataSourceUtil.isDefault(dataSource)) { dataSourceHandle = Arc.container().instance(DataSource.class); } else { - dataSourceHandle = Arc.container().instance(DataSource.class, new DataSourceLiteral(dataSource.get())); + dataSourceHandle = Arc.container().instance(DataSource.class, new DataSourceLiteral(dataSource)); } if (!dataSourceHandle.isAvailable()) { throw new IllegalStateException( - "No datasource " + dataSource.get() + " has been defined for persistence unit " + persistenceUnitName); + "No datasource " + dataSource + " has been defined for persistence unit " + persistenceUnitName); } runtimeSettingsBuilder.put(AvailableSettings.DATASOURCE, dataSourceHandle.get()); diff --git a/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/HibernateOrmRecorder.java b/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/HibernateOrmRecorder.java index 1fd34633059ea..7f5b48724b2ce 100644 --- a/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/HibernateOrmRecorder.java +++ b/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/HibernateOrmRecorder.java @@ -9,6 +9,7 @@ import javax.persistence.EntityManager; import javax.persistence.EntityManagerFactory; +import org.hibernate.MultiTenancyStrategy; import org.hibernate.boot.archive.scan.spi.Scanner; import org.hibernate.integrator.spi.Integrator; import org.jboss.logging.Logger; @@ -19,6 +20,7 @@ import io.quarkus.hibernate.orm.runtime.boot.QuarkusPersistenceUnitDefinition; import io.quarkus.hibernate.orm.runtime.entitymanager.ForwardingEntityManager; import io.quarkus.hibernate.orm.runtime.proxies.PreGeneratedProxies; +import io.quarkus.hibernate.orm.runtime.tenant.DataSourceTenantConnectionResolver; import io.quarkus.runtime.annotations.Recorder; /** @@ -64,6 +66,18 @@ public JPAConfigSupport get() { }; } + public Supplier dataSourceTenantConnectionResolver(String persistenceUnitName, + String dataSourceName, + MultiTenancyStrategy multiTenancyStrategy, String multiTenancySchemaDataSourceName) { + return new Supplier() { + @Override + public DataSourceTenantConnectionResolver get() { + return new DataSourceTenantConnectionResolver(persistenceUnitName, dataSourceName, multiTenancyStrategy, + multiTenancySchemaDataSourceName); + } + }; + } + public void startAllPersistenceUnits(BeanContainer beanContainer) { beanContainer.instance(JPAConfig.class).startAll(); } diff --git a/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/JPAConfig.java b/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/JPAConfig.java index 44399a095ed94..a47169ef6777d 100644 --- a/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/JPAConfig.java +++ b/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/JPAConfig.java @@ -14,7 +14,6 @@ import javax.persistence.EntityManagerFactory; import javax.persistence.Persistence; -import org.hibernate.MultiTenancyStrategy; import org.jboss.logging.Logger; @Singleton @@ -24,16 +23,11 @@ public class JPAConfig { private final Map> entityPersistenceUnitMapping; - private final MultiTenancyStrategy multiTenancyStrategy; - private final String multiTenancySchemaDataSource; - private final Map persistenceUnits; @Inject public JPAConfig(JPAConfigSupport jpaConfigSupport) { this.entityPersistenceUnitMapping = Collections.unmodifiableMap(jpaConfigSupport.entityPersistenceUnitMapping); - this.multiTenancyStrategy = jpaConfigSupport.multiTenancyStrategy; - this.multiTenancySchemaDataSource = jpaConfigSupport.multiTenancySchemaDataSource; Map persistenceUnitsBuilder = new HashMap<>(); for (String persistenceUnitName : jpaConfigSupport.persistenceUnitNames) { @@ -75,24 +69,6 @@ public Set getPersistenceUnits() { return persistenceUnits.keySet(); } - /** - * Returns the selected multitenancy strategy. - * - * @return Strategy to use. - */ - public MultiTenancyStrategy getMultiTenancyStrategy() { - return multiTenancyStrategy; - } - - /** - * Determines which data source should be used in case of {@link MultiTenancyStrategy#SCHEMA} approach. - * - * @return Data source name or {@link null} in case the default data source should be used. - */ - public String getMultiTenancySchemaDataSource() { - return multiTenancySchemaDataSource; - } - /** * Returns the set of persistence units an entity is attached to. */ diff --git a/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/JPAConfigSupport.java b/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/JPAConfigSupport.java index eab2bd649c321..b0a04a21a1f46 100644 --- a/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/JPAConfigSupport.java +++ b/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/JPAConfigSupport.java @@ -3,26 +3,17 @@ import java.util.Map; import java.util.Set; -import org.hibernate.MultiTenancyStrategy; - public class JPAConfigSupport { public Set persistenceUnitNames; public Map> entityPersistenceUnitMapping; - public MultiTenancyStrategy multiTenancyStrategy; - public String multiTenancySchemaDataSource; - public JPAConfigSupport() { } public JPAConfigSupport(Set persistenceUnitNames, - Map> entityPersistenceUnitMapping, - MultiTenancyStrategy multiTenancyStrategy, - String multiTenancySchemaDataSource) { + Map> entityPersistenceUnitMapping) { this.persistenceUnitNames = persistenceUnitNames; this.entityPersistenceUnitMapping = entityPersistenceUnitMapping; - this.multiTenancyStrategy = multiTenancyStrategy; - this.multiTenancySchemaDataSource = multiTenancySchemaDataSource; } } diff --git a/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/boot/FastBootMetadataBuilder.java b/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/boot/FastBootMetadataBuilder.java index 7fa590f49f6ec..22749575b2cd6 100644 --- a/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/boot/FastBootMetadataBuilder.java +++ b/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/boot/FastBootMetadataBuilder.java @@ -25,7 +25,6 @@ import java.util.LinkedHashSet; import java.util.List; import java.util.Map; -import java.util.Optional; import java.util.StringTokenizer; import java.util.concurrent.ConcurrentHashMap; @@ -97,7 +96,7 @@ public class FastBootMetadataBuilder { private final Collection> additionalIntegrators; private final Collection providedServices; private final PreGeneratedProxies preGeneratedProxies; - private final Optional dataSource; + private final String dataSource; private final MultiTenancyStrategy multiTenancyStrategy; private final boolean isReactive; @@ -176,7 +175,8 @@ public FastBootMetadataBuilder(final QuarkusPersistenceUnitDefinition puDefiniti final MultiTenancyStrategy strategy = puDefinition.getMultitenancyStrategy(); if (strategy != null && strategy != MultiTenancyStrategy.NONE) { - ssrBuilder.addService(MultiTenantConnectionProvider.class, new HibernateMultiTenantConnectionProvider()); + ssrBuilder.addService(MultiTenantConnectionProvider.class, + new HibernateMultiTenantConnectionProvider(puDefinition.getName())); } this.multiTenancyStrategy = strategy; diff --git a/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/boot/QuarkusPersistenceUnitDefinition.java b/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/boot/QuarkusPersistenceUnitDefinition.java index 7dfa7af9c1341..a13f63910774e 100644 --- a/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/boot/QuarkusPersistenceUnitDefinition.java +++ b/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/boot/QuarkusPersistenceUnitDefinition.java @@ -2,7 +2,6 @@ import java.util.List; import java.util.Objects; -import java.util.Optional; import java.util.Properties; import javax.persistence.SharedCacheMode; @@ -21,11 +20,11 @@ public final class QuarkusPersistenceUnitDefinition { private final LightPersistenceXmlDescriptor actualHibernateDescriptor; - private final Optional dataSource; + private final String dataSource; private final MultiTenancyStrategy multitenancyStrategy; private final boolean isReactive; - public QuarkusPersistenceUnitDefinition(PersistenceUnitDescriptor persistenceUnitDescriptor, Optional dataSource, + public QuarkusPersistenceUnitDefinition(PersistenceUnitDescriptor persistenceUnitDescriptor, String dataSource, MultiTenancyStrategy multitenancyStrategy, boolean isReactive) { Objects.requireNonNull(persistenceUnitDescriptor); Objects.requireNonNull(multitenancyStrategy); @@ -39,7 +38,7 @@ public QuarkusPersistenceUnitDefinition(PersistenceUnitDescriptor persistenceUni * For bytecode deserialization */ private QuarkusPersistenceUnitDefinition(LightPersistenceXmlDescriptor persistenceUnitDescriptor, - Optional dataSource, + String dataSource, MultiTenancyStrategy multitenancyStrategy, boolean isReactive) { Objects.requireNonNull(persistenceUnitDescriptor); @@ -59,7 +58,7 @@ public String getName() { return actualHibernateDescriptor.getName(); } - public Optional getDataSource() { + public String getDataSource() { return dataSource; } @@ -78,7 +77,7 @@ public boolean isReactive() { */ public static class Serialized { - private Optional dataSource; + private String dataSource; private MultiTenancyStrategy multitenancyStrategy; private boolean isReactive; private String puName; @@ -92,11 +91,11 @@ public static class Serialized { //All standard getters and setters generated by IDE: - public Optional getDataSource() { + public String getDataSource() { return dataSource; } - public void setDataSource(Optional dataSource) { + public void setDataSource(String dataSource) { this.dataSource = dataSource; } diff --git a/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/recording/RecordedState.java b/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/recording/RecordedState.java index d06474a30776f..0ad28b7059a96 100644 --- a/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/recording/RecordedState.java +++ b/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/recording/RecordedState.java @@ -1,7 +1,6 @@ package io.quarkus.hibernate.orm.runtime.recording; import java.util.Collection; -import java.util.Optional; import org.hibernate.MultiTenancyStrategy; import org.hibernate.dialect.Dialect; @@ -15,7 +14,7 @@ public final class RecordedState { private final Dialect dialect; - private final Optional dataSource; + private final String dataSource; private final PrevalidatedQuarkusMetadata metadata; private final BuildTimeSettings settings; private final Collection integrators; @@ -28,7 +27,7 @@ public final class RecordedState { public RecordedState(Dialect dialect, PrevalidatedQuarkusMetadata metadata, BuildTimeSettings settings, Collection integrators, Collection providedServices, IntegrationSettings integrationSettings, - ProxyDefinitions classDefinitions, Optional dataSource, MultiTenancyStrategy strategy, boolean isReactive) { + ProxyDefinitions classDefinitions, String dataSource, MultiTenancyStrategy strategy, boolean isReactive) { this.dialect = dialect; this.metadata = metadata; this.settings = settings; @@ -69,7 +68,7 @@ public ProxyDefinitions getProxyClassDefinitions() { return proxyClassDefinitions; } - public Optional getDataSource() { + public String getDataSource() { return dataSource; } diff --git a/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/tenant/DataSourceTenantConnectionResolver.java b/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/tenant/DataSourceTenantConnectionResolver.java index 357615c0bbe05..4ccf32662fed7 100644 --- a/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/tenant/DataSourceTenantConnectionResolver.java +++ b/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/tenant/DataSourceTenantConnectionResolver.java @@ -3,8 +3,7 @@ import java.sql.Connection; import java.sql.SQLException; -import javax.enterprise.context.ApplicationScoped; -import javax.inject.Inject; +import javax.enterprise.inject.Default; import org.hibernate.MultiTenancyStrategy; import org.hibernate.engine.jdbc.connections.spi.ConnectionProvider; @@ -14,8 +13,7 @@ import io.agroal.api.configuration.AgroalDataSourceConfiguration; import io.quarkus.agroal.DataSource; import io.quarkus.arc.Arc; -import io.quarkus.arc.DefaultBean; -import io.quarkus.hibernate.orm.runtime.JPAConfig; +import io.quarkus.datasource.common.runtime.DataSourceUtil; import io.quarkus.hibernate.orm.runtime.customized.QuarkusConnectionProvider; /** @@ -25,73 +23,90 @@ * @author Michael Schnell * */ -@DefaultBean -@ApplicationScoped public class DataSourceTenantConnectionResolver implements TenantConnectionResolver { private static final Logger LOG = Logger.getLogger(DataSourceTenantConnectionResolver.class); - @Inject - JPAConfig jpaConfig; + private String persistenceUnitName; + + private String dataSourceName; + + private MultiTenancyStrategy multiTenancyStrategy; + + private String multiTenancySchemaDataSourceName; + + public DataSourceTenantConnectionResolver() { + } + + public DataSourceTenantConnectionResolver(String persistenceUnitName, String dataSourceName, + MultiTenancyStrategy multiTenancyStrategy, String multiTenancySchemaDataSourceName) { + this.persistenceUnitName = persistenceUnitName; + this.dataSourceName = dataSourceName; + this.multiTenancyStrategy = multiTenancyStrategy; + this.multiTenancySchemaDataSourceName = multiTenancySchemaDataSourceName; + } @Override public ConnectionProvider resolve(String tenantId) { + LOG.debugv("resolve((persistenceUnitName={0}, tenantIdentifier={1})", persistenceUnitName, tenantId); + LOG.debugv("multitenancy strategy: {0}", multiTenancyStrategy); - LOG.debugv("resolve({0})", tenantId); - - final MultiTenancyStrategy strategy = jpaConfig.getMultiTenancyStrategy(); - LOG.debugv("multitenancy strategy: {0}", strategy); - AgroalDataSource dataSource = tenantDataSource(jpaConfig, tenantId, strategy); + AgroalDataSource dataSource = tenantDataSource(dataSourceName, tenantId, multiTenancyStrategy, + multiTenancySchemaDataSourceName); if (dataSource == null) { - throw new IllegalStateException("No instance of datasource found for tenant: " + tenantId); + throw new IllegalStateException( + String.format("No instance of datasource found for persistence unit '%1$s' and tenant '%2$s'", + persistenceUnitName, tenantId)); } - if (strategy == MultiTenancyStrategy.SCHEMA) { - return new TenantConnectionProvider(tenantId, dataSource); + if (multiTenancyStrategy == MultiTenancyStrategy.SCHEMA) { + return new SchemaTenantConnectionProvider(tenantId, dataSource); } return new QuarkusConnectionProvider(dataSource); } /** * Create a new data source from the given configuration. - * + * * @param config Configuration to use. - * + * * @return New data source instance. */ private static AgroalDataSource createFrom(AgroalDataSourceConfiguration config) { try { return AgroalDataSource.from(config); } catch (SQLException ex) { - throw new IllegalStateException("Failed to create a new data source based on the default config", ex); + throw new IllegalStateException("Failed to create a new data source based on the existing datasource configuration", + ex); } } - /** - * Returns either the default data source or the tenant specific one. - * - * @param tenantId Tenant identifier. The value is required (non-{@literal null}) in case of - * {@link MultiTenancyStrategy#DATABASE}. - * @param strategy Current multitenancy strategy Required value that cannot be {@literal null}. - * - * @return Data source. - */ - private static AgroalDataSource tenantDataSource(JPAConfig jpaConfig, String tenantId, MultiTenancyStrategy strategy) { + private static AgroalDataSource tenantDataSource(String dataSourceName, String tenantId, MultiTenancyStrategy strategy, + String multiTenancySchemaDataSourceName) { if (strategy != MultiTenancyStrategy.SCHEMA) { return Arc.container().instance(AgroalDataSource.class, new DataSource.DataSourceLiteral(tenantId)).get(); } - String dataSourceName = jpaConfig.getMultiTenancySchemaDataSource(); - if (dataSourceName == null) { - AgroalDataSource dataSource = Arc.container().instance(AgroalDataSource.class).get(); + + if (multiTenancySchemaDataSourceName == null) { + AgroalDataSource dataSource = getDataSource(dataSourceName); return createFrom(dataSource.getConfiguration()); } - return Arc.container().instance(AgroalDataSource.class, new DataSource.DataSourceLiteral(dataSourceName)).get(); + + return getDataSource(multiTenancySchemaDataSourceName); + } + + private static AgroalDataSource getDataSource(String dataSourceName) { + if (DataSourceUtil.isDefault(dataSourceName)) { + return Arc.container().instance(AgroalDataSource.class, Default.Literal.INSTANCE).get(); + } else { + return Arc.container().instance(AgroalDataSource.class, new DataSource.DataSourceLiteral(dataSourceName)).get(); + } } - private static class TenantConnectionProvider extends QuarkusConnectionProvider { + private static class SchemaTenantConnectionProvider extends QuarkusConnectionProvider { private final String tenantId; - public TenantConnectionProvider(String tenantId, AgroalDataSource dataSource) { + public SchemaTenantConnectionProvider(String tenantId, AgroalDataSource dataSource) { super(dataSource); this.tenantId = tenantId; } diff --git a/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/tenant/HibernateMultiTenantConnectionProvider.java b/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/tenant/HibernateMultiTenantConnectionProvider.java index 5c56ea09f3caf..ea22a78637761 100644 --- a/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/tenant/HibernateMultiTenantConnectionProvider.java +++ b/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/tenant/HibernateMultiTenantConnectionProvider.java @@ -3,16 +3,20 @@ import java.util.Map; import java.util.concurrent.ConcurrentHashMap; +import javax.enterprise.inject.Default; + import org.hibernate.engine.jdbc.connections.spi.AbstractMultiTenantConnectionProvider; import org.hibernate.engine.jdbc.connections.spi.ConnectionProvider; import org.jboss.logging.Logger; import io.quarkus.arc.Arc; import io.quarkus.arc.InstanceHandle; +import io.quarkus.hibernate.orm.PersistenceUnit.PersistenceUnitLiteral; +import io.quarkus.hibernate.orm.runtime.PersistenceUnitUtil; /** * Maps from the Quarkus {@link TenantConnectionResolver} to the {@link HibernateMultiTenantConnectionProvider} model. - * + * * @author Michael Schnell * */ @@ -20,11 +24,16 @@ public final class HibernateMultiTenantConnectionProvider extends AbstractMultiT private static final Logger LOG = Logger.getLogger(HibernateMultiTenantConnectionProvider.class); + private final String persistenceUnitName; private final Map providerMap = new ConcurrentHashMap<>(); + public HibernateMultiTenantConnectionProvider(String persistenceUnitName) { + this.persistenceUnitName = persistenceUnitName; + } + @Override protected ConnectionProvider getAnyConnectionProvider() { - String tenantId = tenantResolver().getDefaultTenantId(); + String tenantId = tenantResolver(persistenceUnitName).getDefaultTenantId(); if (tenantId == null) { throw new IllegalStateException("Method 'TenantResolver.getDefaultTenantId()' returned a null value. " + "This violates the contract of the interface!"); @@ -34,11 +43,12 @@ protected ConnectionProvider getAnyConnectionProvider() { @Override protected ConnectionProvider selectConnectionProvider(final String tenantIdentifier) { - LOG.debugv("selectConnectionProvider({0})", tenantIdentifier); + LOG.debugv("selectConnectionProvider(persistenceUnitName={0}, tenantIdentifier={1})", persistenceUnitName, + tenantIdentifier); ConnectionProvider provider = providerMap.get(tenantIdentifier); if (provider == null) { - final ConnectionProvider connectionProvider = resolveConnectionProvider(tenantIdentifier); + final ConnectionProvider connectionProvider = resolveConnectionProvider(persistenceUnitName, tenantIdentifier); providerMap.put(tenantIdentifier, connectionProvider); return connectionProvider; } @@ -46,13 +56,21 @@ protected ConnectionProvider selectConnectionProvider(final String tenantIdentif } - private static ConnectionProvider resolveConnectionProvider(String tenantIdentifier) { - LOG.debugv("resolveConnectionProvider({0})", tenantIdentifier); - InstanceHandle instance = Arc.container().instance(TenantConnectionResolver.class); + private static ConnectionProvider resolveConnectionProvider(String persistenceUnitName, String tenantIdentifier) { + LOG.debugv("resolveConnectionProvider(persistenceUnitName={0}, tenantIdentifier={1})", persistenceUnitName, + tenantIdentifier); + InstanceHandle instance; + if (PersistenceUnitUtil.isDefaultPersistenceUnit(persistenceUnitName)) { + instance = Arc.container().instance(TenantConnectionResolver.class, Default.Literal.INSTANCE); + } else { + instance = Arc.container().instance(TenantConnectionResolver.class, + new PersistenceUnitLiteral(persistenceUnitName)); + } if (!instance.isAvailable()) { throw new IllegalStateException( - "No instance of " + TenantConnectionResolver.class.getSimpleName() + " was found. " - + "You need to create an implementation for this interface to allow resolving the current tenant connection."); + String.format("No instance of %1$s was found for persistence unit %2$s. " + + "You need to create an implementation for this interface to allow resolving the current tenant connection.", + TenantConnectionResolver.class.getSimpleName(), persistenceUnitName)); } TenantConnectionResolver resolver = instance.get(); ConnectionProvider cp = resolver.resolve(tenantIdentifier); @@ -65,14 +83,21 @@ private static ConnectionProvider resolveConnectionProvider(String tenantIdentif /** * Retrieves the tenant resolver or fails if it is not available. - * + * * @return Current tenant resolver. */ - private static TenantResolver tenantResolver() { - InstanceHandle resolverInstance = Arc.container().instance(TenantResolver.class); + private static TenantResolver tenantResolver(String persistenceUnitName) { + InstanceHandle resolverInstance; + if (PersistenceUnitUtil.isDefaultPersistenceUnit(persistenceUnitName)) { + resolverInstance = Arc.container().instance(TenantResolver.class, Default.Literal.INSTANCE); + } else { + resolverInstance = Arc.container().instance(TenantResolver.class, + new PersistenceUnitLiteral(persistenceUnitName)); + } if (!resolverInstance.isAvailable()) { - throw new IllegalStateException("No instance of " + TenantResolver.class.getName() + " was found. " - + "You need to create an implementation for this interface to allow resolving the current tenant identifier."); + throw new IllegalStateException(String.format("No instance of %1$s was found for persistence unit %2$s. " + + "You need to create an implementation for this interface to allow resolving the current tenant identifier.", + TenantResolver.class.getSimpleName(), persistenceUnitName)); } return resolverInstance.get(); }