diff --git a/documentation/src/main/asciidoc/introduction/Advanced.adoc b/documentation/src/main/asciidoc/introduction/Advanced.adoc index c8138e504011..d100415f2757 100644 --- a/documentation/src/main/asciidoc/introduction/Advanced.adoc +++ b/documentation/src/main/asciidoc/introduction/Advanced.adoc @@ -320,6 +320,7 @@ To make use of multi-tenancy, we'll usually need to set at least one of these co | `hibernate.multi_tenant_connection_provider` | Specifies the `MultiTenantConnectionProvider` |=== +Do not configure those properties if you would like the configured `BeanContainer` provide the implementation. A longer discussion of multi-tenancy may be found in the {multitenacy-doc}[User Guide]. [[custom-sql]] diff --git a/documentation/src/main/asciidoc/userguide/chapters/multitenancy/MultiTenancy.adoc b/documentation/src/main/asciidoc/userguide/chapters/multitenancy/MultiTenancy.adoc index 283142ea1ebe..ffe8b4e14817 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/multitenancy/MultiTenancy.adoc +++ b/documentation/src/main/asciidoc/userguide/chapters/multitenancy/MultiTenancy.adoc @@ -118,6 +118,7 @@ The `MultiTenantConnectionProvider` to use can be specified in a number of ways: * Use the `hibernate.multi_tenant_connection_provider` setting. It could name a `MultiTenantConnectionProvider` instance, a `MultiTenantConnectionProvider` implementation class reference or a `MultiTenantConnectionProvider` implementation class name. +* Provided by the configured `BeanContainer`. * Passed directly to the `org.hibernate.boot.registry.StandardServiceRegistryBuilder`. * If none of the above options match, but the settings do specify a `hibernate.connection.datasource` value, Hibernate will assume it should use the specific `DataSourceBasedMultiTenantConnectionProviderImpl` implementation which works on a number of pretty reasonable assumptions when running inside of an app server and using one `javax.sql.DataSource` per tenant. @@ -161,7 +162,7 @@ include::{example-dir-multitenancy}/AbstractMultiTenancyTest.java[tags=multitena `org.hibernate.context.spi.CurrentTenantIdentifierResolver` is a contract for Hibernate to be able to resolve what the application considers the current tenant identifier. The implementation to use can be either passed directly to `Configuration` via its `setCurrentTenantIdentifierResolver` method, -or be specified via the `hibernate.tenant_identifier_resolver` setting. +or be specified via the `hibernate.tenant_identifier_resolver` setting, or be provided by the configured `BeanContainer`. There are two situations where `CurrentTenantIdentifierResolver` is used: diff --git a/hibernate-core/src/main/java/org/hibernate/boot/internal/SessionFactoryOptionsBuilder.java b/hibernate-core/src/main/java/org/hibernate/boot/internal/SessionFactoryOptionsBuilder.java index cefc1b58a6cf..77d609a4d7f6 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/internal/SessionFactoryOptionsBuilder.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/internal/SessionFactoryOptionsBuilder.java @@ -63,6 +63,10 @@ import org.hibernate.query.sqm.mutation.spi.SqmMultiTableInsertStrategy; import org.hibernate.query.sqm.mutation.spi.SqmMultiTableMutationStrategy; import org.hibernate.query.sqm.sql.SqmTranslatorFactory; +import org.hibernate.resource.beans.container.spi.BeanContainer; +import org.hibernate.resource.beans.internal.Helper; +import org.hibernate.resource.beans.spi.BeanInstanceProducer; +import org.hibernate.resource.beans.spi.ManagedBeanRegistry; import org.hibernate.resource.jdbc.spi.PhysicalConnectionHandlingMode; import org.hibernate.resource.jdbc.spi.StatementInspector; import org.hibernate.resource.transaction.spi.TransactionCoordinatorBuilder; @@ -283,6 +287,7 @@ public class SessionFactoryOptionsBuilder implements SessionFactoryOptions { private final int queryStatisticsMaxSize; + @SuppressWarnings( "unchecked" ) public SessionFactoryOptionsBuilder(StandardServiceRegistry serviceRegistry, BootstrapContext context) { this.serviceRegistry = serviceRegistry; this.jpaBootstrap = context.isJpaBootstrap(); @@ -372,6 +377,38 @@ public SessionFactoryOptionsBuilder(StandardServiceRegistry serviceRegistry, Boo CurrentTenantIdentifierResolver.class, configurationSettings.get( MULTI_TENANT_IDENTIFIER_RESOLVER ) ); + if ( this.currentTenantIdentifierResolver == null ) { + final BeanContainer beanContainer = Helper.allowExtensionsInCdi( serviceRegistry ) ? serviceRegistry.requireService( ManagedBeanRegistry.class ).getBeanContainer() : null; + if (beanContainer != null) { + this.currentTenantIdentifierResolver = beanContainer.getBean( + CurrentTenantIdentifierResolver.class, + new BeanContainer.LifecycleOptions() { + @Override + public boolean canUseCachedReferences() { + return true; + } + + @Override + public boolean useJpaCompliantCreation() { + return false; + } + }, + new BeanInstanceProducer() { + + @Override + public B produceBeanInstance(Class beanType) { + return null; + } + + @Override + public B produceBeanInstance(String name, Class beanType) { + return null; + } + + } + ).getBeanInstance(); + } + } this.delayBatchFetchLoaderCreations = configurationService.getSetting( DELAY_ENTITY_LOADER_CREATIONS, BOOLEAN, true ); this.defaultBatchFetchSize = getInt( DEFAULT_BATCH_FETCH_SIZE, configurationSettings, -1 ); diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/internal/ConnectionProviderInitiator.java b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/internal/ConnectionProviderInitiator.java index 055a91461cd4..c2599a5b8914 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/internal/ConnectionProviderInitiator.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/internal/ConnectionProviderInitiator.java @@ -4,6 +4,7 @@ */ package org.hibernate.engine.jdbc.connections.internal; +import java.lang.reflect.InvocationTargetException; import java.sql.Connection; import java.util.Collection; import java.util.HashSet; @@ -19,6 +20,11 @@ import org.hibernate.internal.CoreLogging; import org.hibernate.internal.CoreMessageLogger; import org.hibernate.internal.util.StringHelper; +import org.hibernate.resource.beans.container.spi.BeanContainer; +import org.hibernate.resource.beans.internal.FallbackBeanInstanceProducer; +import org.hibernate.resource.beans.internal.Helper; +import org.hibernate.resource.beans.spi.BeanInstanceProducer; +import org.hibernate.resource.beans.spi.ManagedBeanRegistry; import org.hibernate.service.spi.ServiceRegistryImplementor; import static java.sql.Connection.TRANSACTION_NONE; @@ -102,6 +108,7 @@ public ConnectionProvider initiateService( return null; } + final BeanContainer beanContainer = Helper.allowExtensionsInCdi( registry ) ? registry.requireService( ManagedBeanRegistry.class ).getBeanContainer() : null; final StrategySelector strategySelector = registry.requireService( StrategySelector.class ); final Object explicitSetting = configurationValues.get( CONNECTION_PROVIDER ); if ( explicitSetting != null ) { @@ -111,25 +118,25 @@ public ConnectionProvider initiateService( } else if ( explicitSetting instanceof Class providerClass ) { LOG.instantiatingExplicitConnectionProvider( providerClass.getName() ); - return instantiateExplicitConnectionProvider( providerClass ); + return instantiateExplicitConnectionProvider( providerClass, beanContainer ); } else { final String providerName = nullIfEmpty( explicitSetting.toString() ); if ( providerName != null ) { - return instantiateNamedConnectionProvider(providerName, strategySelector); + return instantiateNamedConnectionProvider(providerName, strategySelector, beanContainer); } } } - return instantiateConnectionProvider( configurationValues, strategySelector ); + return instantiateConnectionProvider( configurationValues, strategySelector, beanContainer ); } - private ConnectionProvider instantiateNamedConnectionProvider(String providerName, StrategySelector strategySelector) { + private ConnectionProvider instantiateNamedConnectionProvider(String providerName, StrategySelector strategySelector, BeanContainer beanContainer) { LOG.instantiatingExplicitConnectionProvider( providerName ); final Class providerClass = strategySelector.selectStrategyImplementor( ConnectionProvider.class, providerName ); try { - return instantiateExplicitConnectionProvider( providerClass ); + return instantiateExplicitConnectionProvider( providerClass, beanContainer ); } catch (Exception e) { throw new HibernateException( @@ -140,7 +147,7 @@ private ConnectionProvider instantiateNamedConnectionProvider(String providerNam } private ConnectionProvider instantiateConnectionProvider( - Map configurationValues, StrategySelector strategySelector) { + Map configurationValues, StrategySelector strategySelector, BeanContainer beanContainer) { if ( configurationValues.containsKey( DATASOURCE ) ) { return new DatasourceConnectionProviderImpl(); } @@ -149,9 +156,9 @@ private ConnectionProvider instantiateConnectionProvider( getSingleRegisteredProvider( strategySelector ); if ( singleRegisteredProvider != null ) { try { - return singleRegisteredProvider.newInstance(); + return singleRegisteredProvider.getConstructor().newInstance(); } - catch (IllegalAccessException | InstantiationException e) { + catch (IllegalAccessException | NoSuchMethodException | InvocationTargetException | InstantiationException e) { throw new HibernateException( "Could not instantiate singular-registered ConnectionProvider", e ); } } @@ -177,11 +184,47 @@ else if ( configurationValues.containsKey( URL ) ) { return new DriverManagerConnectionProviderImpl(); } else { - LOG.noAppropriateConnectionProvider(); - return new UserSuppliedConnectionProviderImpl(); + if (beanContainer != null) { + return beanContainer.getBean( + ConnectionProvider.class, + new BeanContainer.LifecycleOptions() { + @Override + public boolean canUseCachedReferences() { + return true; + } + + @Override + public boolean useJpaCompliantCreation() { + return true; + } + }, + new BeanInstanceProducer() { + + @Override + public B produceBeanInstance(Class beanType) { + return (B) noAppropriateConnectionProvider(); + } + + @Override + public B produceBeanInstance(String name, Class beanType) { + return (B) noAppropriateConnectionProvider(); + } + + } + ).getBeanInstance(); + } + else { + return noAppropriateConnectionProvider(); + } + } } + private ConnectionProvider noAppropriateConnectionProvider() { + LOG.noAppropriateConnectionProvider(); + return new UserSuppliedConnectionProviderImpl(); + } + private Class getSingleRegisteredProvider(StrategySelector strategySelector) { final Collection> implementors = strategySelector.getRegisteredStrategyImplementors( ConnectionProvider.class ); @@ -190,9 +233,28 @@ private Class getSingleRegisteredProvider(Strategy : null; } - private ConnectionProvider instantiateExplicitConnectionProvider(Class providerClass) { + private ConnectionProvider instantiateExplicitConnectionProvider(Class providerClass, BeanContainer beanContainer) { try { - return (ConnectionProvider) providerClass.newInstance(); + if ( beanContainer != null ) { + return (ConnectionProvider) beanContainer.getBean( + providerClass, + new BeanContainer.LifecycleOptions() { + @Override + public boolean canUseCachedReferences() { + return true; + } + + @Override + public boolean useJpaCompliantCreation() { + return true; + } + }, + FallbackBeanInstanceProducer.INSTANCE + ).getBeanInstance(); + } + else { + return (ConnectionProvider) providerClass.getConstructor().newInstance(); + } } catch (Exception e) { throw new HibernateException( "Could not instantiate connection provider [" + providerClass.getName() + "]", e ); @@ -201,7 +263,7 @@ private ConnectionProvider instantiateExplicitConnectionProvider(Class provid private static ConnectionProvider instantiateProvider(StrategySelector selector, String strategy) { try { - return selector.selectStrategyImplementor( ConnectionProvider.class, strategy ).newInstance(); + return selector.selectStrategyImplementor( ConnectionProvider.class, strategy ).getConstructor().newInstance(); } catch ( Exception e ) { LOG.providerClassNotFound(strategy); diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/internal/MultiTenantConnectionProviderInitiator.java b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/internal/MultiTenantConnectionProviderInitiator.java index b1f5d80d1cd2..57179a5e8d82 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/internal/MultiTenantConnectionProviderInitiator.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/internal/MultiTenantConnectionProviderInitiator.java @@ -12,6 +12,10 @@ import org.hibernate.cfg.AvailableSettings; import org.hibernate.engine.jdbc.connections.spi.DataSourceBasedMultiTenantConnectionProviderImpl; import org.hibernate.engine.jdbc.connections.spi.MultiTenantConnectionProvider; +import org.hibernate.resource.beans.container.spi.BeanContainer; +import org.hibernate.resource.beans.internal.Helper; +import org.hibernate.resource.beans.spi.BeanInstanceProducer; +import org.hibernate.resource.beans.spi.ManagedBeanRegistry; import org.hibernate.service.spi.ServiceException; import org.hibernate.service.spi.ServiceRegistryImplementor; @@ -39,7 +43,36 @@ public Class> getServiceInitiated() { @Override public MultiTenantConnectionProvider initiateService(Map configurationValues, ServiceRegistryImplementor registry) { if ( !configurationValues.containsKey( AvailableSettings.MULTI_TENANT_CONNECTION_PROVIDER ) ) { - // nothing to do, but given the separate hierarchies have to handle this here. + final BeanContainer beanContainer = Helper.allowExtensionsInCdi( registry ) ? registry.requireService( ManagedBeanRegistry.class ).getBeanContainer() : null; + if (beanContainer != null) { + return beanContainer.getBean( + MultiTenantConnectionProvider.class, + new BeanContainer.LifecycleOptions() { + @Override + public boolean canUseCachedReferences() { + return true; + } + + @Override + public boolean useJpaCompliantCreation() { + return true; + } + }, + new BeanInstanceProducer() { + + @Override + public B produceBeanInstance(Class beanType) { + return null; + } + + @Override + public B produceBeanInstance(String name, Class beanType) { + return null; + } + + } + ).getBeanInstance(); + } return null; } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/connections/ConnectionProviderFromBeanContainerTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/connections/ConnectionProviderFromBeanContainerTest.java new file mode 100644 index 000000000000..808a1f623c28 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/connections/ConnectionProviderFromBeanContainerTest.java @@ -0,0 +1,104 @@ +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.test.connections; + +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.dialect.H2Dialect; +import org.hibernate.engine.jdbc.connections.spi.ConnectionProvider; +import org.hibernate.resource.beans.container.spi.BeanContainer; +import org.hibernate.resource.beans.container.spi.ContainedBean; +import org.hibernate.resource.beans.spi.BeanInstanceProducer; +import org.hibernate.service.ServiceRegistry; +import org.hibernate.testing.junit4.BaseUnitTestCase; +import org.hibernate.testing.orm.junit.RequiresDialect; +import org.hibernate.testing.util.ServiceRegistryUtil; +import org.junit.Test; + +import java.sql.Connection; +import java.sql.SQLException; +import java.util.HashMap; +import java.util.Map; + +import static org.hibernate.cfg.JdbcSettings.CONNECTION_PROVIDER; +import static org.junit.Assert.assertSame; + +/** + * @author Yanming Zhou + */ +@RequiresDialect(H2Dialect.class) +public class ConnectionProviderFromBeanContainerTest extends BaseUnitTestCase { + + private final ConnectionProvider dummyConnectionProvider = new DummyConnectionProvider(); + + private Map createSettings() { + Map settings = new HashMap<>(); + settings.put( AvailableSettings.ALLOW_EXTENSIONS_IN_CDI, "true" ); + settings.put( AvailableSettings.BEAN_CONTAINER, new BeanContainer() { + @Override + @SuppressWarnings("unchecked") + public ContainedBean getBean( + Class beanType, + LifecycleOptions lifecycleOptions, + BeanInstanceProducer fallbackProducer) { + return () -> (B) ( beanType == DummyConnectionProvider.class ? + dummyConnectionProvider : fallbackProducer.produceBeanInstance( beanType ) ); + } + + @Override + public ContainedBean getBean( + String name, + Class beanType, + LifecycleOptions lifecycleOptions, + BeanInstanceProducer fallbackProducer) { + return () -> (B) fallbackProducer.produceBeanInstance( beanType ); + } + + @Override + public void stop() { + + } + } ); + return settings; + } + + @Test + public void testProviderFromBeanContainerInUse() { + Map settings = createSettings(); + settings.putIfAbsent( CONNECTION_PROVIDER, DummyConnectionProvider.class.getName() ); + try ( ServiceRegistry serviceRegistry = ServiceRegistryUtil.serviceRegistryBuilder() + .applySettings( settings ).build() ) { + ConnectionProvider providerInUse = serviceRegistry.getService( ConnectionProvider.class ); + assertSame( dummyConnectionProvider, providerInUse ); + } + } + + public static class DummyConnectionProvider implements ConnectionProvider { + + @Override + public boolean isUnwrappableAs(Class unwrapType) { + return false; + } + + @Override + public T unwrap(Class unwrapType) { + return null; + } + + @Override + public Connection getConnection() throws SQLException { + return null; + } + + @Override + public void closeConnection(Connection connection) throws SQLException { + + } + + @Override + public boolean supportsAggressiveRelease() { + return false; + } + }; +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/idgen/userdefined/SimpleBeanContainer.java b/hibernate-core/src/test/java/org/hibernate/orm/test/idgen/userdefined/SimpleBeanContainer.java index 26c6ea746b66..bc7f895e6635 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/idgen/userdefined/SimpleBeanContainer.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/idgen/userdefined/SimpleBeanContainer.java @@ -11,7 +11,7 @@ import org.hibernate.resource.beans.spi.BeanInstanceProducer; /** - * + * @author Yanming Zhou */ @SuppressWarnings("unchecked") public class SimpleBeanContainer implements BeanContainer { @@ -23,10 +23,8 @@ public ContainedBean getBean( Class beanType, LifecycleOptions lifecycleOptions, BeanInstanceProducer fallbackProducer) { - if ( beanType == SimpleGenerator.class ) { - return () -> (B) new SimpleGenerator( new AtomicLong( INITIAL_VALUE ) ); - } - return null; + return () -> (B) ( beanType == SimpleGenerator.class ? + new SimpleGenerator( new AtomicLong( INITIAL_VALUE ) ) : fallbackProducer.produceBeanInstance( beanType ) ); } @Override diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/idgen/userdefined/UserDefinedGeneratorsTests.java b/hibernate-core/src/test/java/org/hibernate/orm/test/idgen/userdefined/UserDefinedGeneratorsTests.java index 31dbc2fcaef1..910c0c43452e 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/idgen/userdefined/UserDefinedGeneratorsTests.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/idgen/userdefined/UserDefinedGeneratorsTests.java @@ -24,6 +24,7 @@ import org.hibernate.resource.beans.container.spi.BeanContainer.LifecycleOptions; import org.hibernate.resource.beans.container.spi.ContainedBean; import org.hibernate.resource.beans.internal.FallbackBeanInstanceProducer; +import org.hibernate.resource.beans.spi.BeanInstanceProducer; import org.hibernate.testing.orm.junit.JiraKey; import org.hibernate.testing.orm.junit.BaseUnitTest; @@ -39,7 +40,7 @@ import static org.hamcrest.CoreMatchers.instanceOf; import static org.hamcrest.CoreMatchers.is; -import static org.junit.Assert.assertThat; +import static org.hamcrest.MatcherAssert.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.same; import static org.mockito.BDDMockito.given; @@ -59,10 +60,16 @@ public void testCreateGeneratorsByBeanContainer() { final BeanContainer beanContainer = Mockito.mock( BeanContainer.class ); given(beanContainer.getBean( any(), any(), any() ) ).willAnswer( invocation -> { + Class beanType = (Class) invocation.getArguments()[0]; LifecycleOptions options = (LifecycleOptions) invocation.getArguments()[1]; - assertThat( options.canUseCachedReferences(), is( false ) ); - assertThat( options.useJpaCompliantCreation(), is( true ) ); - return (ContainedBean) TestIdentifierGenerator::new; + if (beanType == TestIdentifierGenerator.class) { + assertThat( options.canUseCachedReferences(), is( false ) ); + assertThat( options.useJpaCompliantCreation(), is( true ) ); + return (ContainedBean) TestIdentifierGenerator::new; + } + else { + return (ContainedBean) () -> ( ( BeanInstanceProducer ) invocation.getArguments()[2] ).produceBeanInstance( beanType ); + } } ); final StandardServiceRegistryBuilder ssrb = ServiceRegistryUtil.serviceRegistryBuilder(); diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/multitenancy/AbstractMultiTenancyTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/multitenancy/AbstractMultiTenancyTest.java index 11fcadb38243..d06783936a5c 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/multitenancy/AbstractMultiTenancyTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/multitenancy/AbstractMultiTenancyTest.java @@ -55,9 +55,9 @@ public abstract class AbstractMultiTenancyTest extends BaseUnitTestCase { protected static final String FRONT_END_TENANT = "front_end"; protected static final String BACK_END_TENANT = "back_end"; - private Map connectionProviderMap = new HashMap<>(); + protected Map connectionProviderMap = new HashMap<>(); - private SessionFactory sessionFactory; + protected SessionFactory sessionFactory; public AbstractMultiTenancyTest() { init(); @@ -67,13 +67,15 @@ public AbstractMultiTenancyTest() { private void init() { registerConnectionProvider(FRONT_END_TENANT); registerConnectionProvider(BACK_END_TENANT); + sessionFactory = sessionFactory(createSettings()); + } + protected Map createSettings() { Map settings = new HashMap<>(); settings.put(AvailableSettings.MULTI_TENANT_CONNECTION_PROVIDER, - new ConfigurableMultiTenantConnectionProvider(connectionProviderMap)); - - sessionFactory = sessionFactory(settings); + new ConfigurableMultiTenantConnectionProvider(connectionProviderMap)); + return settings; } //end::multitenacy-hibernate-MultiTenantConnectionProvider-example[] diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/multitenancy/beancontainer/AbstractTenantResolverBeanContainerTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/multitenancy/beancontainer/AbstractTenantResolverBeanContainerTest.java new file mode 100644 index 000000000000..bdbf5de2d93d --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/multitenancy/beancontainer/AbstractTenantResolverBeanContainerTest.java @@ -0,0 +1,34 @@ +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.test.multitenancy.beancontainer; + +import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.JiraKey; +import org.hibernate.testing.orm.junit.SessionFactory; +import org.hibernate.testing.orm.junit.SessionFactoryScope; +import org.junit.jupiter.api.Test; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; + +/** + * @author Yanming Zhou + */ +@JiraKey("HHH-15422") +@SessionFactory +@DomainModel(annotatedClasses = TestEntity.class) +abstract class AbstractTenantResolverBeanContainerTest { + + @Test + void tentantIdShouldBeFilled(SessionFactoryScope scope) { + scope.inTransaction( s -> { + TestEntity entity = new TestEntity(); + s.persist( entity ); + s.flush(); + assertThat( entity.getTenant(), is( TestCurrentTenantIdentifierResolver.FIXED_TENANT ) ); + } ); + } + +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/multitenancy/beancontainer/MultiTenantConnectionProviderFromBeanContainerTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/multitenancy/beancontainer/MultiTenantConnectionProviderFromBeanContainerTest.java new file mode 100644 index 000000000000..5788ea40a5c2 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/multitenancy/beancontainer/MultiTenantConnectionProviderFromBeanContainerTest.java @@ -0,0 +1,80 @@ +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.test.multitenancy.beancontainer; + +import java.util.HashMap; +import java.util.Map; + +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.dialect.H2Dialect; +import org.hibernate.engine.jdbc.connections.spi.MultiTenantConnectionProvider; +import org.hibernate.internal.SessionFactoryImpl; +import org.hibernate.orm.test.multitenancy.AbstractMultiTenancyTest; +import org.hibernate.orm.test.multitenancy.ConfigurableMultiTenantConnectionProvider; +import org.hibernate.resource.beans.container.spi.BeanContainer; +import org.hibernate.resource.beans.container.spi.ContainedBean; +import org.hibernate.resource.beans.spi.BeanInstanceProducer; + +import org.hibernate.testing.orm.junit.RequiresDialect; +import org.junit.Test; + +import static org.junit.Assert.assertSame; + +/** + * @author Yanming Zhou + */ +@RequiresDialect(H2Dialect.class) +public class MultiTenantConnectionProviderFromBeanContainerTest extends AbstractMultiTenancyTest { + + private ConfigurableMultiTenantConnectionProvider providerFromBeanContainer; + + @Override + protected Map createSettings() { + Map settings = new HashMap<>(); + + providerFromBeanContainer = new ConfigurableMultiTenantConnectionProvider( connectionProviderMap); + settings.put( AvailableSettings.ALLOW_EXTENSIONS_IN_CDI, "true" ); + settings.put( AvailableSettings.BEAN_CONTAINER, new BeanContainer() { + @Override + @SuppressWarnings("unchecked") + public ContainedBean getBean( + Class beanType, + LifecycleOptions lifecycleOptions, + BeanInstanceProducer fallbackProducer) { + return () -> (B) ( beanType == MultiTenantConnectionProvider.class ? providerFromBeanContainer : fallbackProducer.produceBeanInstance( beanType ) ); + } + + @Override + public ContainedBean getBean( + String name, + Class beanType, + LifecycleOptions lifecycleOptions, + BeanInstanceProducer fallbackProducer) { + return () -> (B) fallbackProducer.produceBeanInstance( beanType ); + } + + @Override + public void stop() { + + } + } ); + return settings; + } + + @Override + protected String tenantUrl(String originalUrl, String tenantIdentifier) { + return originalUrl.replace("db1", tenantIdentifier); + } + + @Test + public void testProviderInUse() { + MultiTenantConnectionProvider providerInUse = ((SessionFactoryImpl) sessionFactory).getServiceRegistry().getService( MultiTenantConnectionProvider.class ); + assertSame( providerInUse, expectedProviderInUse()); + } + + protected MultiTenantConnectionProvider expectedProviderInUse() { + return providerFromBeanContainer; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/multitenancy/beancontainer/MultiTenantConnectionProviderFromSettingsOverBeanContainerTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/multitenancy/beancontainer/MultiTenantConnectionProviderFromSettingsOverBeanContainerTest.java new file mode 100644 index 000000000000..beeb428ca4a6 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/multitenancy/beancontainer/MultiTenantConnectionProviderFromSettingsOverBeanContainerTest.java @@ -0,0 +1,36 @@ +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.test.multitenancy.beancontainer; + +import java.util.Map; + +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.dialect.H2Dialect; +import org.hibernate.engine.jdbc.connections.spi.MultiTenantConnectionProvider; +import org.hibernate.orm.test.multitenancy.ConfigurableMultiTenantConnectionProvider; + +import org.hibernate.testing.orm.junit.RequiresDialect; + +/** + * @author Yanming Zhou + */ +@RequiresDialect(H2Dialect.class) +public class MultiTenantConnectionProviderFromSettingsOverBeanContainerTest extends MultiTenantConnectionProviderFromBeanContainerTest { + + private ConfigurableMultiTenantConnectionProvider providerFromSettings; + + @Override + protected Map createSettings() { + Map settings = super.createSettings(); + providerFromSettings = new ConfigurableMultiTenantConnectionProvider(connectionProviderMap); + settings.put(AvailableSettings.MULTI_TENANT_CONNECTION_PROVIDER, providerFromSettings); + return settings; + } + + @Override + protected MultiTenantConnectionProvider expectedProviderInUse() { + return providerFromSettings; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/multitenancy/beancontainer/TenantResolverFromBeanContainerTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/multitenancy/beancontainer/TenantResolverFromBeanContainerTest.java new file mode 100644 index 000000000000..9e7af80424e5 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/multitenancy/beancontainer/TenantResolverFromBeanContainerTest.java @@ -0,0 +1,36 @@ +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.test.multitenancy.beancontainer; + +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.context.spi.CurrentTenantIdentifierResolver; + +import org.hibernate.testing.orm.junit.ServiceRegistry; +import org.hibernate.testing.orm.junit.SessionFactoryScope; +import org.hibernate.testing.orm.junit.Setting; + +import org.junit.jupiter.api.Test; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; + +/** + * @author Yanming Zhou + */ +@ServiceRegistry( + settings = { + @Setting(name = AvailableSettings.ALLOW_EXTENSIONS_IN_CDI, value = "true"), + @Setting(name = AvailableSettings.BEAN_CONTAINER, value = "org.hibernate.orm.test.multitenancy.beancontainer.TestBeanContainer") + } +) +public class TenantResolverFromBeanContainerTest extends AbstractTenantResolverBeanContainerTest { + + @Test + void tenantResolverFromBeanContainerShouldBeUsed(SessionFactoryScope scope) { + CurrentTenantIdentifierResolver tenantResolver = scope.getSessionFactory().getCurrentTenantIdentifierResolver(); + assertThat(tenantResolver, is(TestCurrentTenantIdentifierResolver.INSTANCE_FOR_BEAN_CONTAINER)); + } + +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/multitenancy/beancontainer/TenantResolverFromSettingsOverBeanContainerTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/multitenancy/beancontainer/TenantResolverFromSettingsOverBeanContainerTest.java new file mode 100644 index 000000000000..5220cdbd812c --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/multitenancy/beancontainer/TenantResolverFromSettingsOverBeanContainerTest.java @@ -0,0 +1,39 @@ +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.test.multitenancy.beancontainer; + +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.context.spi.CurrentTenantIdentifierResolver; + +import org.hibernate.testing.orm.junit.ServiceRegistry; +import org.hibernate.testing.orm.junit.SessionFactoryScope; +import org.hibernate.testing.orm.junit.Setting; +import org.junit.jupiter.api.Test; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.instanceOf; +import static org.hamcrest.CoreMatchers.not; +import static org.hamcrest.MatcherAssert.assertThat; + +/** + * @author Yanming Zhou + */ +@ServiceRegistry( + settings = { + @Setting(name = AvailableSettings.ALLOW_EXTENSIONS_IN_CDI, value = "true"), + @Setting(name = AvailableSettings.MULTI_TENANT_IDENTIFIER_RESOLVER, value = "org.hibernate.orm.test.multitenancy.beancontainer.TestCurrentTenantIdentifierResolver"), + @Setting(name = AvailableSettings.BEAN_CONTAINER, value = "org.hibernate.orm.test.multitenancy.beancontainer.TestBeanContainer") + } +) +public class TenantResolverFromSettingsOverBeanContainerTest extends AbstractTenantResolverBeanContainerTest { + + @Test + void tenantResolverFromSettingsShouldBeUsed(SessionFactoryScope scope) { + CurrentTenantIdentifierResolver tenantResolver = scope.getSessionFactory().getCurrentTenantIdentifierResolver(); + assertThat(tenantResolver, instanceOf(TestCurrentTenantIdentifierResolver.class)); + assertThat(tenantResolver, is(not(TestCurrentTenantIdentifierResolver.INSTANCE_FOR_BEAN_CONTAINER))); + } + +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/multitenancy/beancontainer/TestBeanContainer.java b/hibernate-core/src/test/java/org/hibernate/orm/test/multitenancy/beancontainer/TestBeanContainer.java new file mode 100644 index 000000000000..788c95727f04 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/multitenancy/beancontainer/TestBeanContainer.java @@ -0,0 +1,40 @@ +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.test.multitenancy.beancontainer; + +import org.hibernate.context.spi.CurrentTenantIdentifierResolver; +import org.hibernate.resource.beans.container.spi.BeanContainer; +import org.hibernate.resource.beans.container.spi.ContainedBean; +import org.hibernate.resource.beans.spi.BeanInstanceProducer; + +/** + * @author Yanming Zhou + */ +@SuppressWarnings("unchecked") +public class TestBeanContainer implements BeanContainer { + + @Override + public ContainedBean getBean( + Class beanType, + LifecycleOptions lifecycleOptions, + BeanInstanceProducer fallbackProducer) { + return () -> (B) ( beanType == CurrentTenantIdentifierResolver.class ? + TestCurrentTenantIdentifierResolver.INSTANCE_FOR_BEAN_CONTAINER : fallbackProducer.produceBeanInstance( beanType ) ); + } + + @Override + public ContainedBean getBean( + String name, + Class beanType, + LifecycleOptions lifecycleOptions, + BeanInstanceProducer fallbackProducer) { + return null; + } + + @Override + public void stop() { + + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/multitenancy/beancontainer/TestCurrentTenantIdentifierResolver.java b/hibernate-core/src/test/java/org/hibernate/orm/test/multitenancy/beancontainer/TestCurrentTenantIdentifierResolver.java new file mode 100644 index 000000000000..9bae3218d417 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/multitenancy/beancontainer/TestCurrentTenantIdentifierResolver.java @@ -0,0 +1,27 @@ +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.test.multitenancy.beancontainer; + +import org.hibernate.context.spi.CurrentTenantIdentifierResolver; + +/** + * @author Yanming Zhou + */ +public class TestCurrentTenantIdentifierResolver implements CurrentTenantIdentifierResolver { + + public static final String FIXED_TENANT = "FIXED"; + + public static final CurrentTenantIdentifierResolver INSTANCE_FOR_BEAN_CONTAINER = new TestCurrentTenantIdentifierResolver(); + + @Override + public boolean validateExistingCurrentSessions() { + return false; + } + + @Override + public String resolveCurrentTenantIdentifier() { + return FIXED_TENANT; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/multitenancy/beancontainer/TestEntity.java b/hibernate-core/src/test/java/org/hibernate/orm/test/multitenancy/beancontainer/TestEntity.java new file mode 100644 index 000000000000..188daec3c3b1 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/multitenancy/beancontainer/TestEntity.java @@ -0,0 +1,48 @@ +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.test.multitenancy.beancontainer; + +import org.hibernate.annotations.TenantId; + +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; + +/** + * @author Yanming Zhou + */ +@Entity +public class TestEntity { + + @Id + @GeneratedValue + public Long id; + + private String name; + + @TenantId + private String tenant; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getTenant() { + return tenant; + } + + public void setTenant(String tenant) { + this.tenant = tenant; + } + + public Long getId() { + return id; + } + +}