Skip to content

Commit

Permalink
Merge pull request #11772 from gsmet/multi-pus-multitenancy
Browse files Browse the repository at this point in the history
Support multitenancy with multiple persistence units
  • Loading branch information
gsmet authored Sep 2, 2020
2 parents cb32eef + aabda57 commit 2466510
Show file tree
Hide file tree
Showing 13 changed files with 221 additions and 154 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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<String> 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<String> multitenantSchemaDatasource;

public boolean isAnyPropertySet() {
return defaultPersistenceUnit.isAnyPropertySet() ||
!persistenceUnits.isEmpty() ||
log.isAnyPropertySet() ||
statistics.isPresent() ||
metricsEnabled ||
multitenant.isPresent() ||
multitenantSchemaDatasource.isPresent();
metricsEnabled;
}

@ConfigGroup
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -149,16 +149,40 @@ 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<String> 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<String> multitenantSchemaDatasource;

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 ||
multitenant.isPresent() ||
multitenantSchemaDatasource.isPresent();
}

@ConfigGroup
Expand Down Expand Up @@ -260,7 +284,7 @@ public static class HibernateOrmConfigPersistenceUnitDatabase {
* <p>
* Used for DDL generation and also for the SQL import scripts.
*/
@ConfigItem(defaultValue = "UTF-8")
@ConfigItem(defaultValue = DEFAULT_CHARSET)
public Charset charset;

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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()) {
Expand Down Expand Up @@ -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()]))
Expand Down Expand Up @@ -464,9 +468,6 @@ public void build(HibernateOrmRecorder recorder, HibernateOrmConfig hibernateOrm
return;
}

MultiTenancyStrategy multiTenancyStrategy = MultiTenancyStrategy
.valueOf(hibernateOrmConfig.multitenant.orElse(MultiTenancyStrategy.NONE.name()));

Set<String> persistenceUnitNames = new HashSet<>();

Map<String, Set<String>> entityPersistenceUnitMapping = new HashMap<>();
Expand All @@ -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());
}

Expand All @@ -504,6 +504,39 @@ public void startPersistenceUnits(HibernateOrmRecorder recorder, BeanContainerBu
recorder.startAllPersistenceUnits(beanContainer.getValue());
}

@BuildStep
@Record(RUNTIME_INIT)
public void multitenancy(HibernateOrmRecorder recorder,
List<PersistenceUnitDescriptorBuildItem> persistenceUnitDescriptors,
BuildProducer<SyntheticBeanBuildItem> 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<LogCategoryBuildItem> categories) {
Expand Down Expand Up @@ -809,7 +842,8 @@ private static void producePersistenceUnitDescriptorFromConfig(

persistenceUnitDescriptors.produce(
new PersistenceUnitDescriptorBuildItem(descriptor, dataSource,
getMultiTenancyStrategy(hibernateOrmConfig),
getMultiTenancyStrategy(persistenceUnitConfig.multitenant),
persistenceUnitConfig.multitenantSchemaDatasource.orElse(null),
false));
}

Expand Down Expand Up @@ -1061,12 +1095,13 @@ public static QuarkusScanner buildQuarkusScanner(JpaEntitiesBuildItem domainObje
return scanner;
}

private static MultiTenancyStrategy getMultiTenancyStrategy(HibernateOrmConfig hibernateOrmConfig) {
private static MultiTenancyStrategy getMultiTenancyStrategy(Optional<String> 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;
Expand Down
Original file line number Diff line number Diff line change
@@ -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;

/**
Expand All @@ -18,38 +18,45 @@
public final class PersistenceUnitDescriptorBuildItem extends MultiBuildItem {

private final ParsedPersistenceXmlDescriptor descriptor;
private final Optional<String> 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;
}

Expand All @@ -65,10 +72,18 @@ public String getPersistenceUnitName() {
return descriptor.getName();
}

public Optional<String> 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);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -261,7 +260,7 @@ private void verifyProperties(Map properties) {
}
}

private void injectDataSource(String persistenceUnitName, Optional<String> dataSource,
private void injectDataSource(String persistenceUnitName, String dataSource,
RuntimeSettings.Builder runtimeSettingsBuilder) {
// first convert

Expand All @@ -274,18 +273,15 @@ private void injectDataSource(String persistenceUnitName, Optional<String> dataS
}

InstanceHandle<DataSource> 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());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;

/**
Expand Down Expand Up @@ -64,6 +66,18 @@ public JPAConfigSupport get() {
};
}

public Supplier<DataSourceTenantConnectionResolver> dataSourceTenantConnectionResolver(String persistenceUnitName,
String dataSourceName,
MultiTenancyStrategy multiTenancyStrategy, String multiTenancySchemaDataSourceName) {
return new Supplier<DataSourceTenantConnectionResolver>() {
@Override
public DataSourceTenantConnectionResolver get() {
return new DataSourceTenantConnectionResolver(persistenceUnitName, dataSourceName, multiTenancyStrategy,
multiTenancySchemaDataSourceName);
}
};
}

public void startAllPersistenceUnits(BeanContainer beanContainer) {
beanContainer.instance(JPAConfig.class).startAll();
}
Expand Down
Loading

0 comments on commit 2466510

Please sign in to comment.