diff --git a/hibernate-core/src/main/java/org/hibernate/MultiTenancyStrategy.java b/hibernate-core/src/main/java/org/hibernate/MultiTenancyStrategy.java new file mode 100644 index 0000000000..999b584057 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/MultiTenancyStrategy.java @@ -0,0 +1,81 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2011, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate; + +import java.util.Map; + +import org.hibernate.cfg.Environment; + +/** + * Describes the methods for multi-tenancy understood by Hibernate. + * + * @author Steve Ebersole + */ +public enum MultiTenancyStrategy { + /** + * Multi-tenancy implemented by use of discriminator columns. + */ + DISCRIMINATOR, + /** + * Multi-tenancy implemented as separate schemas. + */ + SCHEMA, + /** + * Multi-tenancy implemented as separate databases. + */ + DATABASE, + /** + * No multi-tenancy + */ + NONE; + + public static MultiTenancyStrategy determineMultiTenancyStrategy(Map properties) { + final Object strategy = properties.get( Environment.MULTI_TENANT ); + if ( strategy == null ) { + return MultiTenancyStrategy.NONE; + } + + if ( MultiTenancyStrategy.class.isInstance( strategy ) ) { + return (MultiTenancyStrategy) strategy; + } + + final String strategyName = strategy.toString(); + if ( MultiTenancyStrategy.DISCRIMINATOR.name().equals( strategyName ) ) { + return MultiTenancyStrategy.DISCRIMINATOR; + } + else if ( MultiTenancyStrategy.SCHEMA.name().equals( strategyName ) ) { + return MultiTenancyStrategy.SCHEMA; + } + else if ( MultiTenancyStrategy.DATABASE.name().equals( strategyName ) ) { + return MultiTenancyStrategy.DATABASE; + } + else if ( MultiTenancyStrategy.NONE.name().equals( strategyName ) ) { + return MultiTenancyStrategy.NONE; + } + else { + // todo log? + return MultiTenancyStrategy.NONE; + } + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/SharedSessionContract.java b/hibernate-core/src/main/java/org/hibernate/SharedSessionContract.java index e25ecb1d33..e318f3a219 100644 --- a/hibernate-core/src/main/java/org/hibernate/SharedSessionContract.java +++ b/hibernate-core/src/main/java/org/hibernate/SharedSessionContract.java @@ -31,6 +31,24 @@ import java.io.Serializable; * @author Steve Ebersole */ public interface SharedSessionContract extends Serializable { + /** + * Obtain the tenant identifier associated with this session. + * + * @return The tenant identifier associated with this session, or {@code null} + */ + public String getTenantIdentifier(); + + /** + * Should be set only once for the session. Would rather this be supplied to opening the session, as + * being discussed for HHH-2860 + * + * @param identifier The tenant identifier. + * + * @deprecated HHH-2860 + */ + @Deprecated + public void setTenantIdentifier(String identifier); + /** * Begin a unit of work and return the associated {@link Transaction} object. If a new underlying transaction is * required, begin the transaction. Otherwise continue the new work in the context of the existing underlying diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/Environment.java b/hibernate-core/src/main/java/org/hibernate/cfg/Environment.java index cc3c8d36bc..a1b03f455b 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/Environment.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/Environment.java @@ -551,6 +551,11 @@ public final class Environment { */ public static final String NON_CONTEXTUAL_LOB_CREATION = "hibernate.jdbc.lob.non_contextual_creation"; + /** + * Strategy for multi-tenancy. + * @see org.hibernate.MultiTenancyStrategy + */ + public static final String MULTI_TENANT = "hibernate.multiTenancy"; private static final BytecodeProvider BYTECODE_PROVIDER_INSTANCE; private static final boolean ENABLE_BINARY_STREAMS; diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/Settings.java b/hibernate-core/src/main/java/org/hibernate/cfg/Settings.java index 5b9c416c25..3628b4d4ae 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/Settings.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/Settings.java @@ -26,6 +26,7 @@ package org.hibernate.cfg; import java.util.Map; import org.hibernate.ConnectionReleaseMode; import org.hibernate.EntityMode; +import org.hibernate.MultiTenancyStrategy; import org.hibernate.cache.QueryCacheFactory; import org.hibernate.cache.RegionFactory; import org.hibernate.hql.QueryTranslatorFactory; @@ -81,6 +82,7 @@ public final class Settings { // private ComponentTuplizerFactory componentTuplizerFactory; todo : HHH-3517 and HHH-1907 // private BytecodeProvider bytecodeProvider; private String importFiles; + private MultiTenancyStrategy multiTenancyStrategy; private JtaPlatform jtaPlatform; @@ -438,4 +440,12 @@ public final class Settings { void setJtaPlatform(JtaPlatform jtaPlatform) { this.jtaPlatform = jtaPlatform; } + + public MultiTenancyStrategy getMultiTenancyStrategy() { + return multiTenancyStrategy; + } + + void setMultiTenancyStrategy(MultiTenancyStrategy multiTenancyStrategy) { + this.multiTenancyStrategy = multiTenancyStrategy; + } } diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/SettingsFactory.java b/hibernate-core/src/main/java/org/hibernate/cfg/SettingsFactory.java index fe355cc039..efcfec4ccf 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/SettingsFactory.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/SettingsFactory.java @@ -30,6 +30,7 @@ import org.hibernate.ConnectionReleaseMode; import org.hibernate.EntityMode; import org.hibernate.HibernateException; import org.hibernate.HibernateLogger; +import org.hibernate.MultiTenancyStrategy; import org.hibernate.cache.QueryCacheFactory; import org.hibernate.cache.RegionFactory; import org.hibernate.cache.impl.NoCachingRegionFactory; @@ -241,6 +242,9 @@ public class SettingsFactory implements Serializable { LOG.checkNullability(enabledDisabled(checkNullability)); settings.setCheckNullability(checkNullability); + MultiTenancyStrategy multiTenancyStrategy = MultiTenancyStrategy.determineMultiTenancyStrategy( properties ); + LOG.debug( "multi-tenancy strategy : " + multiTenancyStrategy ); + settings.setMultiTenancyStrategy( multiTenancyStrategy ); // String provider = properties.getProperty( Environment.BYTECODE_PROVIDER ); // log.info( "Bytecode provider name : " + provider ); @@ -292,7 +296,7 @@ public class SettingsFactory implements Serializable { if ( regionFactoryClassName == null ) { regionFactoryClassName = DEF_CACHE_REG_FACTORY; } - LOG.cacheRegionFactory(regionFactoryClassName); + LOG.cacheRegionFactory( regionFactoryClassName ); try { try { return (RegionFactory) ReflectHelper.classForName( regionFactoryClassName ) @@ -314,7 +318,7 @@ public class SettingsFactory implements Serializable { String className = ConfigurationHelper.getString( Environment.QUERY_TRANSLATOR, properties, "org.hibernate.hql.ast.ASTQueryTranslatorFactory" ); - LOG.queryTranslator(className); + LOG.queryTranslator( className ); try { return (QueryTranslatorFactory) ReflectHelper.classForName(className).newInstance(); } diff --git a/hibernate-core/src/main/java/org/hibernate/engine/SessionFactoryImplementor.java b/hibernate-core/src/main/java/org/hibernate/engine/SessionFactoryImplementor.java index d06b800ec4..fa751170e9 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/SessionFactoryImplementor.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/SessionFactoryImplementor.java @@ -32,6 +32,7 @@ import org.hibernate.ConnectionReleaseMode; import org.hibernate.HibernateException; import org.hibernate.Interceptor; import org.hibernate.MappingException; +import org.hibernate.MultiTenancyStrategy; import org.hibernate.Session; import org.hibernate.SessionFactory; import org.hibernate.SessionFactoryObserver; @@ -52,6 +53,7 @@ import org.hibernate.persister.entity.EntityPersister; import org.hibernate.proxy.EntityNotFoundDelegate; import org.hibernate.service.ServiceRegistry; import org.hibernate.service.jdbc.connections.spi.ConnectionProvider; +import org.hibernate.service.spi.ServiceRegistryImplementor; import org.hibernate.stat.spi.StatisticsImplementor; import org.hibernate.type.Type; import org.hibernate.type.TypeResolver; @@ -255,7 +257,7 @@ public interface SessionFactoryImplementor extends Mapping, SessionFactory { */ public FetchProfile getFetchProfile(String name); - public ServiceRegistry getServiceRegistry(); + public ServiceRegistryImplementor getServiceRegistry(); public void addObserver(SessionFactoryObserver observer); } diff --git a/hibernate-core/src/main/java/org/hibernate/engine/SessionImplementor.java b/hibernate-core/src/main/java/org/hibernate/engine/SessionImplementor.java index abcf983b75..19758ee96d 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/SessionImplementor.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/SessionImplementor.java @@ -1,10 +1,10 @@ /* * Hibernate, Relational Persistence for Idiomatic Java * - * Copyright (c) 2008, Red Hat Middleware LLC or third-party contributors as + * Copyright (c) 2008-2011, Red Hat Inc. or third-party contributors as * indicated by the @author tags or express copyright attribution * statements applied by the authors. All third-party contributions are - * distributed under license by Red Hat Middleware LLC. + * distributed under license by Red Hat Inc. * * This copyrighted material is made available to anyone wishing to use, modify, * copy, or redistribute it subject to the terms and conditions of the GNU @@ -20,7 +20,6 @@ * Free Software Foundation, Inc. * 51 Franklin Street, Fifth Floor * Boston, MA 02110-1301 USA - * */ package org.hibernate.engine; @@ -39,6 +38,7 @@ import org.hibernate.ScrollMode; import org.hibernate.ScrollableResults; import org.hibernate.collection.PersistentCollection; import org.hibernate.engine.jdbc.LobCreationContext; +import org.hibernate.engine.jdbc.spi.JdbcConnectionAccess; import org.hibernate.engine.query.sql.NativeSQLQuerySpecification; import org.hibernate.engine.transaction.spi.TransactionCoordinator; import org.hibernate.event.EventListeners; @@ -47,16 +47,21 @@ import org.hibernate.loader.custom.CustomQuery; import org.hibernate.persister.entity.EntityPersister; import org.hibernate.type.Type; - /** - * Defines the internal contract between the Session and other parts of - * Hibernate such as implementors of Type or EntityPersister. + * Defines the internal contract between {@link org.hibernate.Session} / {@link org.hibernate.StatelessSession} and + * other parts of Hibernate such as {@link Type}, {@link EntityPersister} and + * {@link org.hibernate.persister.collection.CollectionPersister} implementors * - * @see org.hibernate.Session the interface to the application - * @see org.hibernate.impl.SessionImpl the actual implementation * @author Gavin King + * @author Steve Ebersole */ public interface SessionImplementor extends Serializable, LobCreationContext { + /** + * Provides access to JDBC connections + * + * @return The contract for accessing JDBC connections. + */ + public JdbcConnectionAccess getJdbcConnectionAccess(); /** * Retrieves the interceptor currently in use by this event source. diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/internal/JdbcCoordinatorImpl.java b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/internal/JdbcCoordinatorImpl.java index cc5125ac17..347ccb5875 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/internal/JdbcCoordinatorImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/internal/JdbcCoordinatorImpl.java @@ -71,7 +71,8 @@ public class JdbcCoordinatorImpl implements JdbcCoordinator { this.logicalConnection = new LogicalConnectionImpl( userSuppliedConnection, transactionCoordinator.getTransactionContext().getConnectionReleaseMode(), - transactionCoordinator.getTransactionContext().getTransactionEnvironment().getJdbcServices() + transactionCoordinator.getTransactionContext().getTransactionEnvironment().getJdbcServices(), + transactionCoordinator.getTransactionContext().getJdbcConnectionAccess() ); } diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/internal/JdbcServicesImpl.java b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/internal/JdbcServicesImpl.java index 183293c6f9..400f575011 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/internal/JdbcServicesImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/internal/JdbcServicesImpl.java @@ -22,21 +22,28 @@ * Boston, MA 02110-1301 USA */ package org.hibernate.engine.jdbc.internal; + import java.lang.reflect.InvocationTargetException; import java.sql.Connection; import java.sql.DatabaseMetaData; import java.sql.ResultSet; import java.sql.SQLException; +import java.util.Arrays; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.Map; import java.util.Set; + +import org.jboss.logging.Logger; + import org.hibernate.HibernateLogger; +import org.hibernate.MultiTenancyStrategy; import org.hibernate.cfg.Environment; import org.hibernate.dialect.Dialect; import org.hibernate.engine.jdbc.LobCreationContext; import org.hibernate.engine.jdbc.LobCreator; import org.hibernate.engine.jdbc.spi.ExtractedDatabaseMetaData; +import org.hibernate.engine.jdbc.spi.JdbcConnectionAccess; import org.hibernate.engine.jdbc.spi.JdbcServices; import org.hibernate.engine.jdbc.spi.ResultSetWrapper; import org.hibernate.engine.jdbc.spi.SchemaNameResolver; @@ -45,41 +52,39 @@ import org.hibernate.engine.jdbc.spi.SqlStatementLogger; import org.hibernate.internal.util.ReflectHelper; import org.hibernate.internal.util.config.ConfigurationHelper; import org.hibernate.service.jdbc.connections.spi.ConnectionProvider; +import org.hibernate.service.jdbc.connections.spi.MultiTenantConnectionProvider; import org.hibernate.service.jdbc.dialect.spi.DialectFactory; import org.hibernate.service.spi.Configurable; -import org.hibernate.service.spi.InjectService; -import org.jboss.logging.Logger; +import org.hibernate.service.spi.ServiceRegistryAwareService; +import org.hibernate.service.spi.ServiceRegistryImplementor; /** * Standard implementation of the {@link JdbcServices} contract * * @author Steve Ebersole */ -public class JdbcServicesImpl implements JdbcServices, Configurable { - +public class JdbcServicesImpl implements JdbcServices, ServiceRegistryAwareService, Configurable { private static final HibernateLogger LOG = Logger.getMessageLogger(HibernateLogger.class, JdbcServicesImpl.class.getName()); - private ConnectionProvider connectionProvider; - - @InjectService - public void setConnectionProvider(ConnectionProvider connectionProvider) { - this.connectionProvider = connectionProvider; - } - - private DialectFactory dialectFactory; - - @InjectService - public void setDialectFactory(DialectFactory dialectFactory) { - this.dialectFactory = dialectFactory; - } + private ServiceRegistryImplementor serviceRegistry; private Dialect dialect; + private ConnectionProvider connectionProvider; private SqlStatementLogger sqlStatementLogger; private SqlExceptionHelper sqlExceptionHelper; private ExtractedDatabaseMetaData extractedMetaDataSupport; private LobCreatorBuilder lobCreatorBuilder; + @Override + public void injectServices(ServiceRegistryImplementor serviceRegistry) { + this.serviceRegistry = serviceRegistry; + } + + @Override public void configure(Map configValues) { + final JdbcConnectionAccess jdbcConnectionAccess = buildJdbcConnectionAccess( configValues ); + final DialectFactory dialectFactory = serviceRegistry.getService( DialectFactory.class ); + Dialect dialect = null; LobCreatorBuilder lobCreatorBuilder = null; @@ -105,9 +110,9 @@ public class JdbcServicesImpl implements JdbcServices, Configurable { boolean useJdbcMetadata = ConfigurationHelper.getBoolean( "hibernate.temp.use_jdbc_metadata_defaults", configValues, true ); if ( useJdbcMetadata ) { try { - Connection conn = connectionProvider.getConnection(); + Connection connection = jdbcConnectionAccess.obtainConnection(); try { - DatabaseMetaData meta = conn.getMetaData(); + DatabaseMetaData meta = connection.getMetaData(); LOG.database(meta.getDatabaseProductName(), meta.getDatabaseProductVersion(), meta.getDatabaseMajorVersion(), @@ -128,25 +133,25 @@ public class JdbcServicesImpl implements JdbcServices, Configurable { lobLocatorUpdateCopy = meta.locatorsUpdateCopy(); typeInfoSet.addAll( TypeInfoExtracter.extractTypeInfo( meta ) ); - dialect = dialectFactory.buildDialect( configValues, conn ); + dialect = dialectFactory.buildDialect( configValues, connection ); - catalogName = conn.getCatalog(); + catalogName = connection.getCatalog(); SchemaNameResolver schemaNameResolver = determineExplicitSchemaNameResolver( configValues ); if ( schemaNameResolver == null ) { // todo : add dialect method // schemaNameResolver = dialect.getSchemaNameResolver(); } if ( schemaNameResolver != null ) { - schemaName = schemaNameResolver.resolveSchemaName( conn ); + schemaName = schemaNameResolver.resolveSchemaName( connection ); } - lobCreatorBuilder = new LobCreatorBuilder( configValues, conn ); + lobCreatorBuilder = new LobCreatorBuilder( configValues, connection ); } catch ( SQLException sqle ) { LOG.unableToObtainConnectionMetadata(sqle.getMessage()); } finally { - if ( conn != null ) { - connectionProvider.closeConnection( conn ); + if ( connection != null ) { + jdbcConnectionAccess.releaseConnection( connection ); } } } @@ -190,6 +195,57 @@ public class JdbcServicesImpl implements JdbcServices, Configurable { ); } + private JdbcConnectionAccess buildJdbcConnectionAccess(Map configValues) { + final MultiTenancyStrategy multiTenancyStrategy = MultiTenancyStrategy.determineMultiTenancyStrategy( configValues ); + + if ( MultiTenancyStrategy.NONE == multiTenancyStrategy ) { + connectionProvider = serviceRegistry.getService( ConnectionProvider.class ); + return new ConnectionProviderJdbcConnectionAccess( connectionProvider ); + } + else { + connectionProvider = null; + final MultiTenantConnectionProvider multiTenantConnectionProvider = serviceRegistry.getService( MultiTenantConnectionProvider.class ); + return new MultiTenantConnectionProviderJdbcConnectionAccess( multiTenantConnectionProvider ); + } + } + + private static class ConnectionProviderJdbcConnectionAccess implements JdbcConnectionAccess { + private final ConnectionProvider connectionProvider; + + public ConnectionProviderJdbcConnectionAccess(ConnectionProvider connectionProvider) { + this.connectionProvider = connectionProvider; + } + + @Override + public Connection obtainConnection() throws SQLException { + return connectionProvider.getConnection(); + } + + @Override + public void releaseConnection(Connection connection) throws SQLException { + connection.close(); + } + } + + private static class MultiTenantConnectionProviderJdbcConnectionAccess implements JdbcConnectionAccess { + private final MultiTenantConnectionProvider connectionProvider; + + public MultiTenantConnectionProviderJdbcConnectionAccess(MultiTenantConnectionProvider connectionProvider) { + this.connectionProvider = connectionProvider; + } + + @Override + public Connection obtainConnection() throws SQLException { + return connectionProvider.getAnyConnection(); + } + + @Override + public void releaseConnection(Connection connection) throws SQLException { + connection.close(); + } + } + + // todo : add to Environment public static final String SCHEMA_NAME_RESOLVER = "hibernate.schema_name_resolver"; @@ -220,9 +276,7 @@ public class JdbcServicesImpl implements JdbcServices, Configurable { private Set parseKeywords(String extraKeywordsString) { Set keywordSet = new HashSet(); - for ( String keyword : extraKeywordsString.split( "," ) ) { - keywordSet.add( keyword ); - } + keywordSet.addAll( Arrays.asList( extraKeywordsString.split( "," ) ) ); return keywordSet; } @@ -278,81 +332,93 @@ public class JdbcServicesImpl implements JdbcServices, Configurable { this.typeInfoSet = typeInfoSet; } + @Override public boolean supportsScrollableResults() { return supportsScrollableResults; } + @Override public boolean supportsGetGeneratedKeys() { return supportsGetGeneratedKeys; } + @Override public boolean supportsBatchUpdates() { return supportsBatchUpdates; } + @Override public boolean supportsDataDefinitionInTransaction() { return supportsDataDefinitionInTransaction; } + @Override public boolean doesDataDefinitionCauseTransactionCommit() { return doesDataDefinitionCauseTransactionCommit; } + @Override public Set getExtraKeywords() { return extraKeywords; } + @Override public SQLStateType getSqlStateType() { return sqlStateType; } + @Override public boolean doesLobLocatorUpdateCopy() { return lobLocatorUpdateCopy; } + @Override public String getConnectionSchemaName() { return connectionSchemaName; } + @Override public String getConnectionCatalogName() { return connectionCatalogName; } + @Override public LinkedHashSet getTypeInfoSet() { return typeInfoSet; } } + @Override public ConnectionProvider getConnectionProvider() { return connectionProvider; } + @Override public SqlStatementLogger getSqlStatementLogger() { return sqlStatementLogger; } + @Override public SqlExceptionHelper getSqlExceptionHelper() { return sqlExceptionHelper; } + @Override public Dialect getDialect() { return dialect; } + @Override public ExtractedDatabaseMetaData getExtractedMetaDataSupport() { return extractedMetaDataSupport; } - /** - * {@inheritDoc} - */ + @Override public LobCreator getLobCreator(LobCreationContext lobCreationContext) { return lobCreatorBuilder.buildLobCreator( lobCreationContext ); } - /** - * {@inheritDoc} - */ + @Override public ResultSetWrapper getResultSetWrapper() { return ResultSetWrapperImpl.INSTANCE; } diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/internal/LogicalConnectionImpl.java b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/internal/LogicalConnectionImpl.java index c6ed725dcf..8220ef8d09 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/internal/LogicalConnectionImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/internal/LogicalConnectionImpl.java @@ -37,6 +37,7 @@ import org.hibernate.HibernateLogger; import org.hibernate.JDBCException; import org.hibernate.engine.jdbc.internal.proxy.ProxyBuilder; import org.hibernate.engine.jdbc.spi.ConnectionObserver; +import org.hibernate.engine.jdbc.spi.JdbcConnectionAccess; import org.hibernate.engine.jdbc.spi.JdbcResourceRegistry; import org.hibernate.engine.jdbc.spi.JdbcServices; import org.hibernate.engine.jdbc.spi.LogicalConnectionImplementor; @@ -61,6 +62,7 @@ public class LogicalConnectionImpl implements LogicalConnectionImplementor { private final transient ConnectionReleaseMode connectionReleaseMode; private final transient JdbcServices jdbcServices; + private final transient JdbcConnectionAccess jdbcConnectionAccess; private final transient JdbcResourceRegistry jdbcResourceRegistry; private final transient List observers; @@ -73,10 +75,12 @@ public class LogicalConnectionImpl implements LogicalConnectionImplementor { public LogicalConnectionImpl( Connection userSuppliedConnection, ConnectionReleaseMode connectionReleaseMode, - JdbcServices jdbcServices) { + JdbcServices jdbcServices, + JdbcConnectionAccess jdbcConnectionAccess) { this( connectionReleaseMode, jdbcServices, + jdbcConnectionAccess, (userSuppliedConnection != null), false, new ArrayList() @@ -87,6 +91,7 @@ public class LogicalConnectionImpl implements LogicalConnectionImplementor { private LogicalConnectionImpl( ConnectionReleaseMode connectionReleaseMode, JdbcServices jdbcServices, + JdbcConnectionAccess jdbcConnectionAccess, boolean isUserSuppliedConnection, boolean isClosed, List observers) { @@ -94,6 +99,7 @@ public class LogicalConnectionImpl implements LogicalConnectionImplementor { jdbcServices, isUserSuppliedConnection, connectionReleaseMode ); this.jdbcServices = jdbcServices; + this.jdbcConnectionAccess = jdbcConnectionAccess; this.jdbcResourceRegistry = new JdbcResourceRegistryImpl( getJdbcServices().getSqlExceptionHelper() ); this.observers = observers; @@ -286,7 +292,7 @@ public class LogicalConnectionImpl implements LogicalConnectionImplementor { private void obtainConnection() throws JDBCException { LOG.debugf("Obtaining JDBC connection"); try { - physicalConnection = getJdbcServices().getConnectionProvider().getConnection(); + physicalConnection = jdbcConnectionAccess.obtainConnection(); for ( ConnectionObserver observer : observers ) { observer.physicalConnectionObtained( physicalConnection ); } @@ -304,13 +310,19 @@ public class LogicalConnectionImpl implements LogicalConnectionImplementor { */ private void releaseConnection() throws JDBCException { LOG.debugf("Releasing JDBC connection"); - if ( physicalConnection == null ) return; - try { - if (!physicalConnection.isClosed()) getJdbcServices().getSqlExceptionHelper().logAndClearWarnings(physicalConnection); - if (!isUserSuppliedConnection) getJdbcServices().getConnectionProvider().closeConnection(physicalConnection); + if ( physicalConnection == null ) { + return; } - catch (SQLException sqle) { - throw getJdbcServices().getSqlExceptionHelper().convert( sqle, "Could not close connection" ); + try { + if ( ! physicalConnection.isClosed() ) { + getJdbcServices().getSqlExceptionHelper().logAndClearWarnings( physicalConnection ); + } + if ( ! isUserSuppliedConnection ) { + jdbcConnectionAccess.releaseConnection( physicalConnection ); + } + } + catch (SQLException e) { + throw getJdbcServices().getSqlExceptionHelper().convert( e, "Could not close connection" ); } finally { physicalConnection = null; @@ -424,6 +436,7 @@ public class LogicalConnectionImpl implements LogicalConnectionImplementor { return new LogicalConnectionImpl( transactionContext.getConnectionReleaseMode(), transactionContext.getTransactionEnvironment().getJdbcServices(), + transactionContext.getJdbcConnectionAccess(), isUserSuppliedConnection, isClosed, observers diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/spi/ConnectionManager.java b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/spi/ConnectionManager.java deleted file mode 100644 index b76fcf467c..0000000000 --- a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/spi/ConnectionManager.java +++ /dev/null @@ -1,210 +0,0 @@ -/* - * Hibernate, Relational Persistence for Idiomatic Java - * - * Copyright (c) 2010, Red Hat Inc. or third-party contributors as - * indicated by the @author tags or express copyright attribution - * statements applied by the authors. All third-party contributions are - * distributed under license by Red Hat Inc. - * - * This copyrighted material is made available to anyone wishing to use, modify, - * copy, or redistribute it subject to the terms and conditions of the GNU - * Lesser General Public License, as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY - * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License - * for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this distribution; if not, write to: - * Free Software Foundation, Inc. - * 51 Franklin Street, Fifth Floor - * Boston, MA 02110-1301 USA - */ -package org.hibernate.engine.jdbc.spi; -import java.io.Serializable; -import java.sql.CallableStatement; -import java.sql.Connection; -import java.sql.PreparedStatement; -import org.hibernate.HibernateException; -import org.hibernate.ScrollMode; -import org.hibernate.jdbc.Expectation; - -/** - * Encapsulates JDBC Connection management SPI. - *

- * The lifecycle is intended to span a logical series of interactions with the - * database. Internally, this means the the lifecycle of the Session. - * - * @author Gail Badner - */ -public interface ConnectionManager extends Serializable { - - /** - * Retrieves the connection currently managed by this ConnectionManager. - *

- * Note, that we may need to obtain a connection to return here if a - * connection has either not yet been obtained (non-UserSuppliedConnectionProvider) - * or has previously been aggressively released (if supported in this environment). - * - * @return The current Connection. - * - * @throws HibernateException Indicates a connection is currently not - * available (we are currently manually disconnected). - */ - Connection getConnection(); - - // TODO: should this be removd from the SPI? - boolean hasBorrowedConnection(); - - // TODO: should this be removd from the SPI? - void releaseBorrowedConnection(); - - /** - * Is this ConnectionManager instance "logically" connected. Meaning - * do we either have a cached connection available or do we have the - * ability to obtain a connection on demand. - * - * @return True if logically connected; false otherwise. - */ - boolean isCurrentlyConnected(); - - /** - * To be called after execution of each JDBC statement. Used to - * conditionally release the JDBC connection aggressively if - * the configured release mode indicates. - */ - void afterStatement(); - - /** - * Sets the transaction timeout. - * @param seconds - number of seconds until the the transaction times out. - */ - void setTransactionTimeout(int seconds); - - /** - * To be called after Session completion. Used to release the JDBC - * connection. - * - * @return The connection mantained here at time of close. Null if - * there was no connection cached internally. - */ - Connection close(); - - /** - * Manually disconnect the underlying JDBC Connection. The assumption here - * is that the manager will be reconnected at a later point in time. - * - * @return The connection mantained here at time of disconnect. Null if - * there was no connection cached internally. - */ - Connection manualDisconnect(); - - /** - * Manually reconnect the underlying JDBC Connection. Should be called at - * some point after manualDisconnect(). - *

- * This form is used for ConnectionProvider-supplied connections. - */ - void manualReconnect(); - - /** - * Manually reconnect the underlying JDBC Connection. Should be called at - * some point after manualDisconnect(). - *

- * This form is used for user-supplied connections. - */ - void manualReconnect(Connection suppliedConnection); - - /** - * Callback to let us know that a flush is beginning. We use this fact - * to temporarily circumvent aggressive connection releasing until after - * the flush cycle is complete {@link #flushEnding()} - */ - void flushBeginning(); - - /** - * Callback to let us know that a flush is ending. We use this fact to - * stop circumventing aggressive releasing connections. - */ - void flushEnding(); - - /** - * Get a non-batchable prepared statement to use for inserting / deleting / updating, - * using JDBC3 getGeneratedKeys ({@link java.sql.Connection#prepareStatement(String, int)}). - *

- * If not explicitly closed via {@link java.sql.PreparedStatement#close()}, it will be - * released when the session is closed or disconnected. - */ - public PreparedStatement prepareStatement(String sql, int autoGeneratedKeys); - - /** - * Get a non-batchable prepared statement to use for inserting / deleting / updating. - * using JDBC3 getGeneratedKeys ({@link java.sql.Connection#prepareStatement(String, String[])}). - *

- * If not explicitly closed via {@link java.sql.PreparedStatement#close()}, it will be - * released when the session is closed or disconnected. - */ - public PreparedStatement prepareStatement(String sql, String[] columnNames); - - /** - * Get a non-batchable prepared statement to use for selecting. Does not - * result in execution of the current batch. - *

- * If not explicitly closed via {@link java.sql.PreparedStatement#close()}, - * it will be released when the session is closed or disconnected. - */ - public PreparedStatement prepareSelectStatement(String sql); - - /** - * Get a non-batchable prepared statement to use for inserting / deleting / updating. - *

- * If not explicitly closed via {@link java.sql.PreparedStatement#close()}, it will be - * released when the session is closed or disconnected. - */ - public PreparedStatement prepareStatement(String sql, boolean isCallable); - - /** - * Get a non-batchable callable statement to use for inserting / deleting / updating. - *

- * If not explicitly closed via {@link java.sql.PreparedStatement#close()}, it will be - * released when the session is closed or disconnected. - */ - public CallableStatement prepareCallableStatement(String sql); - - /** - * Get a batchable prepared statement to use for inserting / deleting / updating - * (might be called many times before a single call to executeBatch()). - * After setting parameters, call addToBatch - do not execute the - * statement explicitly. - * @see org.hibernate.engine.jdbc.batch.spi.Batch#addToBatch - *

- * If not explicitly closed via {@link java.sql.PreparedStatement#close()}, it will be - * released when the session is closed or disconnected. - */ - public PreparedStatement prepareBatchStatement(Object key, String sql, boolean isCallable); - - /** - * Get a prepared statement for use in loading / querying. Does not - * result in execution of the current batch. - *

- * If not explicitly closed via {@link java.sql.PreparedStatement#close()}, - * it will be released when the session is closed or disconnected. - */ - public PreparedStatement prepareQueryStatement( - String sql, - boolean isScrollable, - ScrollMode scrollMode, - boolean isCallable); - - /** - * Cancel the current query statement - */ - public void cancelLastQuery(); - - public void abortBatch(); - - public void addToBatch(Object batchKey, String sql, Expectation expectation); - - public void executeBatch(); -} diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/spi/JdbcConnectionAccess.java b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/spi/JdbcConnectionAccess.java new file mode 100644 index 0000000000..03b8438610 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/spi/JdbcConnectionAccess.java @@ -0,0 +1,39 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2011, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.engine.jdbc.spi; + +import java.io.Serializable; +import java.sql.Connection; +import java.sql.SQLException; + +/** + * Provides centralized access to JDBC connections. Centralized to hide the complexity of accounting for contextual + * (multi-tenant) versus non-contextual access. + * + * @author Steve Ebersole + */ +public interface JdbcConnectionAccess extends Serializable { + public Connection obtainConnection() throws SQLException; + public void releaseConnection(Connection connection) throws SQLException; +} diff --git a/hibernate-core/src/main/java/org/hibernate/engine/transaction/spi/TransactionContext.java b/hibernate-core/src/main/java/org/hibernate/engine/transaction/spi/TransactionContext.java index 85be89fd6e..a172afd071 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/transaction/spi/TransactionContext.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/transaction/spi/TransactionContext.java @@ -24,6 +24,7 @@ package org.hibernate.engine.transaction.spi; import org.hibernate.ConnectionReleaseMode; +import org.hibernate.engine.jdbc.spi.JdbcConnectionAccess; import java.io.Serializable; @@ -110,4 +111,6 @@ public interface TransactionContext extends Serializable { public void beforeTransactionCompletion(TransactionImplementor hibernateTransaction); public void afterTransactionCompletion(TransactionImplementor hibernateTransaction, boolean successful); + + public JdbcConnectionAccess getJdbcConnectionAccess(); } diff --git a/hibernate-core/src/main/java/org/hibernate/impl/AbstractSessionImpl.java b/hibernate-core/src/main/java/org/hibernate/impl/AbstractSessionImpl.java index ad2ff42fbb..6375181110 100755 --- a/hibernate-core/src/main/java/org/hibernate/impl/AbstractSessionImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/impl/AbstractSessionImpl.java @@ -30,11 +30,13 @@ import java.sql.SQLException; import java.util.List; import org.hibernate.HibernateException; import org.hibernate.MappingException; +import org.hibernate.MultiTenancyStrategy; import org.hibernate.Query; import org.hibernate.SQLQuery; import org.hibernate.ScrollableResults; import org.hibernate.SessionException; import org.hibernate.SharedSessionContract; +import org.hibernate.engine.jdbc.spi.JdbcConnectionAccess; import org.hibernate.engine.NamedQueryDefinition; import org.hibernate.engine.NamedSQLQueryDefinition; import org.hibernate.engine.QueryParameters; @@ -48,6 +50,8 @@ import org.hibernate.engine.transaction.spi.TransactionContext; import org.hibernate.engine.transaction.spi.TransactionEnvironment; import org.hibernate.jdbc.WorkExecutor; import org.hibernate.jdbc.WorkExecutorVisitable; +import org.hibernate.service.jdbc.connections.spi.ConnectionProvider; +import org.hibernate.service.jdbc.connections.spi.MultiTenantConnectionProvider; /** * Functionality common to stateless and stateful sessions @@ -57,6 +61,7 @@ import org.hibernate.jdbc.WorkExecutorVisitable; public abstract class AbstractSessionImpl implements Serializable, SharedSessionContract, SessionImplementor, TransactionContext { protected transient SessionFactoryImpl factory; + private String tenantIdentifier; private boolean closed = false; protected AbstractSessionImpl(SessionFactoryImpl factory) { @@ -203,4 +208,77 @@ public abstract class AbstractSessionImpl implements Serializable, SharedSession return scrollCustomQuery( getNativeSQLQueryPlan( spec ).getCustomQuery(), queryParameters ); } + @Override + public String getTenantIdentifier() { + return tenantIdentifier; + } + + @Override + public void setTenantIdentifier(String identifier) { + if ( MultiTenancyStrategy.NONE == factory.getSettings().getMultiTenancyStrategy() ) { + throw new HibernateException( "SessionFactory was not configured for multi-tenancy" ); + } + this.tenantIdentifier = identifier; + } + + private transient JdbcConnectionAccess jdbcConnectionAccess; + + @Override + public JdbcConnectionAccess getJdbcConnectionAccess() { + if ( jdbcConnectionAccess == null ) { + if ( MultiTenancyStrategy.NONE == factory.getSettings().getMultiTenancyStrategy() ) { + jdbcConnectionAccess = new NonContextualJdbcConnectionAccess( + factory.getServiceRegistry().getService( ConnectionProvider.class ) + ); + } + else { + jdbcConnectionAccess = new ContextualJdbcConnectionAccess( + factory.getServiceRegistry().getService( MultiTenantConnectionProvider.class ) + ); + } + } + return jdbcConnectionAccess; + } + + private static class NonContextualJdbcConnectionAccess implements JdbcConnectionAccess, Serializable { + private final ConnectionProvider connectionProvider; + + private NonContextualJdbcConnectionAccess(ConnectionProvider connectionProvider) { + this.connectionProvider = connectionProvider; + } + + @Override + public Connection obtainConnection() throws SQLException { + return connectionProvider.getConnection(); + } + + @Override + public void releaseConnection(Connection connection) throws SQLException { + connectionProvider.closeConnection( connection ); + } + } + + private class ContextualJdbcConnectionAccess implements JdbcConnectionAccess, Serializable { + private final MultiTenantConnectionProvider connectionProvider; + + private ContextualJdbcConnectionAccess(MultiTenantConnectionProvider connectionProvider) { + this.connectionProvider = connectionProvider; + } + + @Override + public Connection obtainConnection() throws SQLException { + if ( tenantIdentifier == null ) { + throw new HibernateException( "Tenant identifier required!" ); + } + return connectionProvider.getConnection( tenantIdentifier ); + } + + @Override + public void releaseConnection(Connection connection) throws SQLException { + if ( tenantIdentifier == null ) { + throw new HibernateException( "Tenant identifier required!" ); + } + connectionProvider.releaseConnection( tenantIdentifier, connection ); + } + } } diff --git a/hibernate-core/src/main/java/org/hibernate/impl/SessionFactoryImpl.java b/hibernate-core/src/main/java/org/hibernate/impl/SessionFactoryImpl.java index 478b376a55..5a360bd3af 100644 --- a/hibernate-core/src/main/java/org/hibernate/impl/SessionFactoryImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/impl/SessionFactoryImpl.java @@ -53,6 +53,7 @@ import org.hibernate.HibernateException; import org.hibernate.HibernateLogger; import org.hibernate.Interceptor; import org.hibernate.MappingException; +import org.hibernate.MultiTenancyStrategy; import org.hibernate.ObjectNotFoundException; import org.hibernate.QueryException; import org.hibernate.Session; @@ -212,6 +213,7 @@ public final class SessionFactoryImpl this.properties = new Properties(); this.properties.putAll( cfg.getProperties() ); this.interceptor = cfg.getInterceptor(); + this.serviceRegistry = serviceRegistry.getService( SessionFactoryServiceRegistryFactory.class ).buildServiceRegistry( this, cfg @@ -1260,10 +1262,11 @@ public final class SessionFactoryImpl } @Override - public ServiceRegistry getServiceRegistry() { + public ServiceRegistryImplementor getServiceRegistry() { return serviceRegistry; } + @Override public EntityNotFoundDelegate getEntityNotFoundDelegate() { return entityNotFoundDelegate; } diff --git a/hibernate-core/src/main/java/org/hibernate/persister/internal/PersisterFactoryImpl.java b/hibernate-core/src/main/java/org/hibernate/persister/internal/PersisterFactoryImpl.java index 4f34548b2c..7bf9741bad 100644 --- a/hibernate-core/src/main/java/org/hibernate/persister/internal/PersisterFactoryImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/persister/internal/PersisterFactoryImpl.java @@ -38,6 +38,7 @@ import org.hibernate.persister.spi.PersisterClassResolver; import org.hibernate.persister.spi.PersisterFactory; import org.hibernate.service.ServiceRegistry; import org.hibernate.service.spi.ServiceRegistryAwareService; +import org.hibernate.service.spi.ServiceRegistryImplementor; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; @@ -75,10 +76,10 @@ public final class PersisterFactoryImpl implements PersisterFactory, ServiceRegi SessionFactoryImplementor.class }; - private ServiceRegistry serviceRegistry; + private ServiceRegistryImplementor serviceRegistry; @Override - public void injectServices(ServiceRegistry serviceRegistry) { + public void injectServices(ServiceRegistryImplementor serviceRegistry) { this.serviceRegistry = serviceRegistry; } diff --git a/hibernate-core/src/main/java/org/hibernate/service/StandardServiceInitiators.java b/hibernate-core/src/main/java/org/hibernate/service/StandardServiceInitiators.java index 247e5199ea..44b2f91f3b 100644 --- a/hibernate-core/src/main/java/org/hibernate/service/StandardServiceInitiators.java +++ b/hibernate-core/src/main/java/org/hibernate/service/StandardServiceInitiators.java @@ -31,6 +31,7 @@ import org.hibernate.persister.internal.PersisterFactoryInitiator; import org.hibernate.service.classloading.internal.ClassLoaderServiceInitiator; import org.hibernate.service.internal.SessionFactoryServiceRegistryFactoryInitiator; import org.hibernate.service.jdbc.connections.internal.ConnectionProviderInitiator; +import org.hibernate.service.jdbc.connections.internal.MultiTenantConnectionProviderInitiator; import org.hibernate.service.jdbc.dialect.internal.DialectFactoryInitiator; import org.hibernate.service.jdbc.dialect.internal.DialectResolverInitiator; import org.hibernate.service.jmx.internal.JmxServiceInitiator; @@ -58,6 +59,7 @@ public class StandardServiceInitiators { serviceInitiators.add( PersisterFactoryInitiator.INSTANCE ); serviceInitiators.add( ConnectionProviderInitiator.INSTANCE ); + serviceInitiators.add( MultiTenantConnectionProviderInitiator.INSTANCE ); serviceInitiators.add( DialectResolverInitiator.INSTANCE ); serviceInitiators.add( DialectFactoryInitiator.INSTANCE ); serviceInitiators.add( BatchBuilderInitiator.INSTANCE ); diff --git a/hibernate-core/src/main/java/org/hibernate/service/internal/BasicServiceRegistryImpl.java b/hibernate-core/src/main/java/org/hibernate/service/internal/BasicServiceRegistryImpl.java index bb02d014e0..f3b7b03f2e 100644 --- a/hibernate-core/src/main/java/org/hibernate/service/internal/BasicServiceRegistryImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/service/internal/BasicServiceRegistryImpl.java @@ -1,7 +1,7 @@ /* * Hibernate, Relational Persistence for Idiomatic Java * - * Copyright (c) 2010, Red Hat Inc. or third-party contributors as + * Copyright (c) 2011, Red Hat Inc. or third-party contributors as * indicated by the @author tags or express copyright attribution * statements applied by the authors. All third-party contributions are * distributed under license by Red Hat Inc. @@ -125,12 +125,12 @@ public class BasicServiceRegistryImpl extends AbstractServiceRegistryImpl implem protected void configureService(T service) { applyInjections( service ); - if ( Configurable.class.isInstance( service ) ) { - ( (Configurable) service ).configure( configurationValues ); - } - if ( ServiceRegistryAwareService.class.isInstance( service ) ) { ( (ServiceRegistryAwareService) service ).injectServices( this ); } + + if ( Configurable.class.isInstance( service ) ) { + ( (Configurable) service ).configure( configurationValues ); + } } } diff --git a/hibernate-core/src/main/java/org/hibernate/service/jdbc/connections/internal/ConnectionProviderInitiator.java b/hibernate-core/src/main/java/org/hibernate/service/jdbc/connections/internal/ConnectionProviderInitiator.java index a7cb84e174..dc4b21010b 100644 --- a/hibernate-core/src/main/java/org/hibernate/service/jdbc/connections/internal/ConnectionProviderInitiator.java +++ b/hibernate-core/src/main/java/org/hibernate/service/jdbc/connections/internal/ConnectionProviderInitiator.java @@ -36,6 +36,7 @@ import org.jboss.logging.Logger; import org.hibernate.HibernateException; import org.hibernate.HibernateLogger; +import org.hibernate.MultiTenancyStrategy; import org.hibernate.cfg.Environment; import org.hibernate.internal.util.beans.BeanInfoHelper; import org.hibernate.service.classloading.spi.ClassLoaderService; @@ -100,6 +101,10 @@ public class ConnectionProviderInitiator implements BasicServiceInitiator { + public static final MultiTenantConnectionProviderInitiator INSTANCE = new MultiTenantConnectionProviderInitiator(); + + @Override + public Class getServiceInitiated() { + return MultiTenantConnectionProvider.class; + } + + @Override + public MultiTenantConnectionProvider initiateService(Map configurationValues, ServiceRegistryImplementor registry) { + if ( MultiTenancyStrategy.determineMultiTenancyStrategy( configurationValues ) == MultiTenancyStrategy.NONE ) { + // nothing to do, but given the separate hierarchies have to handle this here. + } + + // for now... + return null; + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/service/jdbc/connections/spi/AbstractMultiTenantConnectionProvider.java b/hibernate-core/src/main/java/org/hibernate/service/jdbc/connections/spi/AbstractMultiTenantConnectionProvider.java new file mode 100644 index 0000000000..b2f8e614b9 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/service/jdbc/connections/spi/AbstractMultiTenantConnectionProvider.java @@ -0,0 +1,80 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2011, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.service.jdbc.connections.spi; + +import java.sql.Connection; +import java.sql.SQLException; + +import org.hibernate.service.UnknownUnwrapTypeException; + +/** + * @author Steve Ebersole + */ +public abstract class AbstractMultiTenantConnectionProvider implements MultiTenantConnectionProvider { + protected abstract ConnectionProvider getAnyConnectionProvider(); + protected abstract ConnectionProvider selectConnectionProvider(String tenantIdentifier); + + @Override + public Connection getAnyConnection() throws SQLException { + return getAnyConnectionProvider().getConnection(); + } + + @Override + public void releaseAnyConnection(Connection connection) throws SQLException { + getAnyConnectionProvider().closeConnection( connection ); + } + + @Override + public Connection getConnection(String tenantIdentifier) throws SQLException { + return selectConnectionProvider( tenantIdentifier ).getConnection(); + } + + @Override + public void releaseConnection(String tenantIdentifier, Connection connection) throws SQLException { + selectConnectionProvider( tenantIdentifier ).getConnection(); + } + + @Override + public boolean supportsAggressiveRelease() { + return getAnyConnectionProvider().supportsAggressiveRelease(); + } + + @Override + public boolean isUnwrappableAs(Class unwrapType) { + return ConnectionProvider.class.equals( unwrapType ) || + MultiTenantConnectionProvider.class.equals( unwrapType ) || + AbstractMultiTenantConnectionProvider.class.isAssignableFrom( unwrapType ); + } + + @Override + @SuppressWarnings( {"unchecked"}) + public T unwrap(Class unwrapType) { + if ( isUnwrappableAs( unwrapType ) ) { + return (T) this; + } + else { + throw new UnknownUnwrapTypeException( unwrapType ); + } + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/service/jdbc/connections/spi/MultiTenantConnectionProvider.java b/hibernate-core/src/main/java/org/hibernate/service/jdbc/connections/spi/MultiTenantConnectionProvider.java new file mode 100644 index 0000000000..29ce194997 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/service/jdbc/connections/spi/MultiTenantConnectionProvider.java @@ -0,0 +1,95 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2011, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.service.jdbc.connections.spi; + +import java.sql.Connection; +import java.sql.SQLException; + +import org.hibernate.service.Service; +import org.hibernate.service.spi.Wrapped; + +/** + * @author Steve Ebersole + */ +public interface MultiTenantConnectionProvider extends Service, Wrapped { + /** + * Allows access to the database metadata of the underlying database(s) in situations where we do not have a + * tenant id (like startup processing, for example). + * + * @return The database metadata. + * + * @throws SQLException Indicates a problem opening a connection + */ + public Connection getAnyConnection() throws SQLException; + + /** + * Release a connection obtained from {@link #getAnyConnection} + * + * @param connection The JDBC connection to release + * + * @throws SQLException Indicates a problem closing the connection + */ + public void releaseAnyConnection(Connection connection) throws SQLException; + + /** + * Obtains a connection for Hibernate use according to the underlying strategy of this provider. + * + * @param tenantIdentifier The identifier of the tenant for which to get a connection + * + * @return The obtained JDBC connection + * + * @throws SQLException Indicates a problem opening a connection + * @throws org.hibernate.HibernateException Indicates a problem otherwise obtaining a connection. + */ + public Connection getConnection(String tenantIdentifier) throws SQLException; + + /** + * Release a connection from Hibernate use. + * + * @param connection The JDBC connection to release + * @param tenantIdentifier The identifier of the tenant. + * + * @throws SQLException Indicates a problem closing the connection + * @throws org.hibernate.HibernateException Indicates a problem otherwise releasing a connection. + */ + public void releaseConnection(String tenantIdentifier, Connection connection) throws SQLException; + + /** + * Does this connection provider support aggressive release of JDBC + * connections and re-acquisition of those connections (if need be) later? + *

+ * This is used in conjunction with {@link org.hibernate.cfg.Environment#RELEASE_CONNECTIONS} + * to aggressively release JDBC connections. However, the configured ConnectionProvider + * must support re-acquisition of the same underlying connection for that semantic to work. + *

+ * Typically, this is only true in managed environments where a container + * tracks connections by transaction or thread. + * + * Note that JTA semantic depends on the fact that the underlying connection provider does + * support aggressive release. + * + * @return {@code true} if aggressive releasing is supported; {@code false} otherwise. + */ + public boolean supportsAggressiveRelease(); +} diff --git a/hibernate-core/src/main/java/org/hibernate/service/jta/platform/internal/AbstractJtaPlatform.java b/hibernate-core/src/main/java/org/hibernate/service/jta/platform/internal/AbstractJtaPlatform.java index b3200900ca..97e6e34d0a 100644 --- a/hibernate-core/src/main/java/org/hibernate/service/jta/platform/internal/AbstractJtaPlatform.java +++ b/hibernate-core/src/main/java/org/hibernate/service/jta/platform/internal/AbstractJtaPlatform.java @@ -29,6 +29,7 @@ import org.hibernate.service.jta.platform.spi.JtaPlatform; import org.hibernate.service.spi.Configurable; import org.hibernate.service.ServiceRegistry; import org.hibernate.service.spi.ServiceRegistryAwareService; +import org.hibernate.service.spi.ServiceRegistryImplementor; import javax.transaction.Synchronization; import javax.transaction.SystemException; @@ -44,10 +45,10 @@ public abstract class AbstractJtaPlatform implements JtaPlatform, Configurable, ServiceRegistryAwareService, TransactionManagerAccess { private boolean cacheTransactionManager; private boolean cacheUserTransaction; - private ServiceRegistry serviceRegistry; + private ServiceRegistryImplementor serviceRegistry; @Override - public void injectServices(ServiceRegistry serviceRegistry) { + public void injectServices(ServiceRegistryImplementor serviceRegistry) { this.serviceRegistry = serviceRegistry; } diff --git a/hibernate-core/src/main/java/org/hibernate/service/spi/ServiceRegistryAwareService.java b/hibernate-core/src/main/java/org/hibernate/service/spi/ServiceRegistryAwareService.java index 62941d4a61..633ee4d862 100644 --- a/hibernate-core/src/main/java/org/hibernate/service/spi/ServiceRegistryAwareService.java +++ b/hibernate-core/src/main/java/org/hibernate/service/spi/ServiceRegistryAwareService.java @@ -23,8 +23,6 @@ */ package org.hibernate.service.spi; -import org.hibernate.service.ServiceRegistry; - /** * Allows services to be injected with the {@link org.hibernate.service.ServiceRegistry} during configuration phase. * @@ -36,5 +34,5 @@ public interface ServiceRegistryAwareService { * * @param serviceRegistry The registry */ - public void injectServices(ServiceRegistry serviceRegistry); + public void injectServices(ServiceRegistryImplementor serviceRegistry); } diff --git a/hibernate-core/src/main/java/org/hibernate/tool/hbm2ddl/ConnectionHelper.java b/hibernate-core/src/main/java/org/hibernate/tool/hbm2ddl/ConnectionHelper.java index 141aefffb2..d1b1c6d5e8 100644 --- a/hibernate-core/src/main/java/org/hibernate/tool/hbm2ddl/ConnectionHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/tool/hbm2ddl/ConnectionHelper.java @@ -32,7 +32,7 @@ import java.sql.SQLException; * * @author Steve Ebersole */ -interface ConnectionHelper { +public interface ConnectionHelper { /** * Prepare the helper for use. * diff --git a/hibernate-core/src/main/java/org/hibernate/tool/hbm2ddl/SchemaExport.java b/hibernate-core/src/main/java/org/hibernate/tool/hbm2ddl/SchemaExport.java index eba32ea470..438ce25ea1 100644 --- a/hibernate-core/src/main/java/org/hibernate/tool/hbm2ddl/SchemaExport.java +++ b/hibernate-core/src/main/java/org/hibernate/tool/hbm2ddl/SchemaExport.java @@ -157,6 +157,17 @@ public class SchemaExport { ); } + public SchemaExport( + ConnectionHelper connectionHelper, + String[] dropSql, + String[] createSql) { + this.connectionHelper = connectionHelper; + this.dropSQL = dropSql; + this.createSQL = createSql; + this.importFiles = ""; + this.formatter = FormatStyle.DDL.getFormatter(); + } + /** * For generating a export script file, this is the file which will be written. * diff --git a/hibernate-core/src/test/java/org/hibernate/test/common/JdbcConnectionAccessImpl.java b/hibernate-core/src/test/java/org/hibernate/test/common/JdbcConnectionAccessImpl.java new file mode 100644 index 0000000000..d9042a7631 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/common/JdbcConnectionAccessImpl.java @@ -0,0 +1,61 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2011, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.test.common; + +import java.sql.Connection; +import java.sql.SQLException; + +import org.hibernate.engine.jdbc.spi.JdbcConnectionAccess; +import org.hibernate.engine.transaction.spi.TransactionEnvironment; +import org.hibernate.service.ServiceRegistry; +import org.hibernate.service.jdbc.connections.spi.ConnectionProvider; + +/** + * @author Steve Ebersole + */ +public class JdbcConnectionAccessImpl implements JdbcConnectionAccess { + private final ConnectionProvider connectionProvider; + + public JdbcConnectionAccessImpl(TransactionEnvironment transactionEnvironment) { + this( transactionEnvironment.getSessionFactory().getServiceRegistry() ); + } + + public JdbcConnectionAccessImpl(ConnectionProvider connectionProvider) { + this.connectionProvider = connectionProvider; + } + + public JdbcConnectionAccessImpl(ServiceRegistry serviceRegistry) { + this( serviceRegistry.getService( ConnectionProvider.class ) ); + } + + @Override + public Connection obtainConnection() throws SQLException { + return connectionProvider.getConnection(); + } + + @Override + public void releaseConnection(Connection connection) throws SQLException { + connectionProvider.closeConnection( connection ); + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/common/TransactionContextImpl.java b/hibernate-core/src/test/java/org/hibernate/test/common/TransactionContextImpl.java index 549294c789..16d1a95218 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/common/TransactionContextImpl.java +++ b/hibernate-core/src/test/java/org/hibernate/test/common/TransactionContextImpl.java @@ -24,18 +24,30 @@ package org.hibernate.test.common; import org.hibernate.ConnectionReleaseMode; +import org.hibernate.engine.jdbc.spi.JdbcConnectionAccess; import org.hibernate.engine.transaction.spi.TransactionContext; import org.hibernate.engine.transaction.spi.TransactionEnvironment; import org.hibernate.engine.transaction.spi.TransactionImplementor; +import org.hibernate.service.ServiceRegistry; /** * @author Steve Ebersole */ public class TransactionContextImpl implements TransactionContext { private final TransactionEnvironment transactionEnvironment; + private final JdbcConnectionAccess jdbcConnectionAccess; + + public TransactionContextImpl(TransactionEnvironment transactionEnvironment, JdbcConnectionAccess jdbcConnectionAccess) { + this.transactionEnvironment = transactionEnvironment; + this.jdbcConnectionAccess = jdbcConnectionAccess; + } + + public TransactionContextImpl(TransactionEnvironment transactionEnvironment, ServiceRegistry serviceRegistry) { + this( transactionEnvironment, new JdbcConnectionAccessImpl( serviceRegistry ) ); + } public TransactionContextImpl(TransactionEnvironment transactionEnvironment) { - this.transactionEnvironment = transactionEnvironment; + this( transactionEnvironment, new JdbcConnectionAccessImpl( transactionEnvironment.getJdbcServices().getConnectionProvider() ) ); } @Override @@ -48,6 +60,11 @@ public class TransactionContextImpl implements TransactionContext { return transactionEnvironment.getTransactionFactory().getDefaultReleaseMode(); } + @Override + public JdbcConnectionAccess getJdbcConnectionAccess() { + return jdbcConnectionAccess; + } + @Override public boolean shouldAutoJoinTransaction() { return true; diff --git a/hibernate-core/src/test/java/org/hibernate/test/jdbc/proxies/AggressiveReleaseTest.java b/hibernate-core/src/test/java/org/hibernate/test/jdbc/proxies/AggressiveReleaseTest.java index 91b0b2bad9..0546485242 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/jdbc/proxies/AggressiveReleaseTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/jdbc/proxies/AggressiveReleaseTest.java @@ -38,6 +38,7 @@ import org.junit.Test; import org.hibernate.testing.junit4.BaseUnitTestCase; import org.hibernate.test.common.BasicTestingJdbcServiceImpl; +import org.hibernate.test.common.JdbcConnectionAccessImpl; import org.hibernate.test.common.JournalingConnectionObserver; import static org.junit.Assert.assertEquals; @@ -112,7 +113,12 @@ public class AggressiveReleaseTest extends BaseUnitTestCase { @Test public void testBasicRelease() { - LogicalConnectionImpl logicalConnection = new LogicalConnectionImpl( null, ConnectionReleaseMode.AFTER_STATEMENT, services ); + LogicalConnectionImpl logicalConnection = new LogicalConnectionImpl( + null, + ConnectionReleaseMode.AFTER_STATEMENT, + services , + new JdbcConnectionAccessImpl( services.getConnectionProvider() ) + ); Connection proxiedConnection = ProxyBuilder.buildConnection( logicalConnection ); JournalingConnectionObserver observer = new JournalingConnectionObserver(); logicalConnection.addObserver( observer ); @@ -142,7 +148,12 @@ public class AggressiveReleaseTest extends BaseUnitTestCase { @Test public void testReleaseCircumventedByHeldResources() { - LogicalConnectionImpl logicalConnection = new LogicalConnectionImpl( null, ConnectionReleaseMode.AFTER_STATEMENT, services ); + LogicalConnectionImpl logicalConnection = new LogicalConnectionImpl( + null, + ConnectionReleaseMode.AFTER_STATEMENT, + services, + new JdbcConnectionAccessImpl( services.getConnectionProvider() ) + ); Connection proxiedConnection = ProxyBuilder.buildConnection( logicalConnection ); JournalingConnectionObserver observer = new JournalingConnectionObserver(); logicalConnection.addObserver( observer ); @@ -196,7 +207,12 @@ public class AggressiveReleaseTest extends BaseUnitTestCase { @Test public void testReleaseCircumventedManually() { - LogicalConnectionImpl logicalConnection = new LogicalConnectionImpl( null, ConnectionReleaseMode.AFTER_STATEMENT, services ); + LogicalConnectionImpl logicalConnection = new LogicalConnectionImpl( + null, + ConnectionReleaseMode.AFTER_STATEMENT, + services, + new JdbcConnectionAccessImpl( services.getConnectionProvider() ) + ); Connection proxiedConnection = ProxyBuilder.buildConnection( logicalConnection ); JournalingConnectionObserver observer = new JournalingConnectionObserver(); logicalConnection.addObserver( observer ); diff --git a/hibernate-core/src/test/java/org/hibernate/test/jdbc/proxies/BasicConnectionProxyTest.java b/hibernate-core/src/test/java/org/hibernate/test/jdbc/proxies/BasicConnectionProxyTest.java index b41e42c62b..b7edfc9fe5 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/jdbc/proxies/BasicConnectionProxyTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/jdbc/proxies/BasicConnectionProxyTest.java @@ -41,6 +41,7 @@ import org.hibernate.JDBCException; import org.hibernate.engine.jdbc.internal.LogicalConnectionImpl; import org.hibernate.engine.jdbc.internal.proxy.ProxyBuilder; import org.hibernate.test.common.BasicTestingJdbcServiceImpl; +import org.hibernate.test.common.JdbcConnectionAccessImpl; import org.hibernate.testing.junit4.BaseUnitTestCase; /** @@ -64,7 +65,8 @@ public class BasicConnectionProxyTest extends BaseUnitTestCase { LogicalConnectionImpl logicalConnection = new LogicalConnectionImpl( null, ConnectionReleaseMode.AFTER_TRANSACTION, - services + services, + new JdbcConnectionAccessImpl( services.getConnectionProvider() ) ); Connection proxiedConnection = ProxyBuilder.buildConnection( logicalConnection ); try { @@ -92,7 +94,8 @@ public class BasicConnectionProxyTest extends BaseUnitTestCase { LogicalConnectionImpl logicalConnection = new LogicalConnectionImpl( null, ConnectionReleaseMode.AFTER_TRANSACTION, - services + services, + new JdbcConnectionAccessImpl( services.getConnectionProvider() ) ); Connection proxiedConnection = ProxyBuilder.buildConnection( logicalConnection ); try { @@ -114,7 +117,8 @@ public class BasicConnectionProxyTest extends BaseUnitTestCase { LogicalConnectionImpl logicalConnection = new LogicalConnectionImpl( null, ConnectionReleaseMode.AFTER_TRANSACTION, - services + services, + new JdbcConnectionAccessImpl( services.getConnectionProvider() ) ); Connection proxiedConnection = ProxyBuilder.buildConnection( logicalConnection ); diff --git a/hibernate-core/src/test/java/org/hibernate/test/jdbc/proxies/BatchingTest.java b/hibernate-core/src/test/java/org/hibernate/test/jdbc/proxies/BatchingTest.java index 26cd2739d5..2dad3fb5f1 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/jdbc/proxies/BatchingTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/jdbc/proxies/BatchingTest.java @@ -91,7 +91,9 @@ public class BatchingTest extends BaseUnitTestCase implements BatchKey { @Test public void testNonBatchingUsage() throws Exception { - final TransactionContext transactionContext = new TransactionContextImpl( new TransactionEnvironmentImpl( serviceRegistry ) ); + final TransactionContext transactionContext = new TransactionContextImpl( + new TransactionEnvironmentImpl( serviceRegistry ) + ); TransactionCoordinatorImpl transactionCoordinator = new TransactionCoordinatorImpl( null, transactionContext ); JournalingTransactionObserver observer = new JournalingTransactionObserver(); diff --git a/hibernate-core/src/test/java/org/hibernate/test/multitenancy/schema/Customer.java b/hibernate-core/src/test/java/org/hibernate/test/multitenancy/schema/Customer.java new file mode 100644 index 0000000000..cc1e06425e --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/multitenancy/schema/Customer.java @@ -0,0 +1,62 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2011, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.test.multitenancy.schema; + +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; + +/** + * @author Steve Ebersole + */ +@Entity +public class Customer { + private Long id; + private String name; + + public Customer() { + } + + public Customer(String name) { + this.name = name; + } + + @Id + @GeneratedValue + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/multitenancy/schema/SchemaBasedMultiTenancyTest.java b/hibernate-core/src/test/java/org/hibernate/test/multitenancy/schema/SchemaBasedMultiTenancyTest.java new file mode 100644 index 0000000000..05b8e38da3 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/multitenancy/schema/SchemaBasedMultiTenancyTest.java @@ -0,0 +1,206 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2011, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.test.multitenancy.schema; + +import java.sql.Connection; +import java.sql.SQLException; +import java.util.Map; + +import org.hibernate.HibernateException; +import org.hibernate.MultiTenancyStrategy; +import org.hibernate.Session; +import org.hibernate.SessionFactory; +import org.hibernate.cfg.Configuration; +import org.hibernate.cfg.Environment; +import org.hibernate.service.ServiceRegistry; +import org.hibernate.service.internal.BasicServiceRegistryImpl; +import org.hibernate.service.jdbc.connections.internal.DriverManagerConnectionProviderImpl; +import org.hibernate.service.jdbc.connections.spi.AbstractMultiTenantConnectionProvider; +import org.hibernate.service.jdbc.connections.spi.ConnectionProvider; +import org.hibernate.service.jdbc.connections.spi.MultiTenantConnectionProvider; +import org.hibernate.service.spi.ServiceRegistryImplementor; +import org.hibernate.tool.hbm2ddl.ConnectionHelper; +import org.hibernate.tool.hbm2ddl.SchemaExport; + +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +import org.hibernate.testing.env.ConnectionProviderBuilder; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.hibernate.testing.junit4.BaseUnitTestCase; + +/** + * @author Steve Ebersole + */ +public class SchemaBasedMultiTenancyTest extends BaseUnitTestCase { + private DriverManagerConnectionProviderImpl acmeProvider; + private DriverManagerConnectionProviderImpl jbossProvider; + + private ServiceRegistryImplementor serviceRegistry; + + private SessionFactory sessionFactory; + + @Before + public void setUp() { + acmeProvider = ConnectionProviderBuilder.buildConnectionProvider( "acme" ); + jbossProvider = ConnectionProviderBuilder.buildConnectionProvider( "jboss" ); + AbstractMultiTenantConnectionProvider multiTenantConnectionProvider = new AbstractMultiTenantConnectionProvider() { + @Override + protected ConnectionProvider getAnyConnectionProvider() { + return acmeProvider; + } + + @Override + protected ConnectionProvider selectConnectionProvider(String tenantIdentifier) { + if ( "acme".equals( tenantIdentifier ) ) { + return acmeProvider; + } + else if ( "jboss".equals( tenantIdentifier ) ) { + return jbossProvider; + } + throw new HibernateException( "Unknown tenant identifier" ); + } + }; + + Configuration cfg = new Configuration(); + cfg.getProperties().put( Environment.MULTI_TENANT, MultiTenancyStrategy.DATABASE ); + cfg.addAnnotatedClass( Customer.class ); + + cfg.buildMappings(); + + // do the acme export + new SchemaExport( + new ConnectionHelper() { + private Connection connection; + @Override + public void prepare(boolean needsAutoCommit) throws SQLException { + connection = acmeProvider.getConnection(); + } + + @Override + public Connection getConnection() throws SQLException { + return connection; + } + + @Override + public void release() throws SQLException { + acmeProvider.closeConnection( connection ); + } + }, + cfg.generateDropSchemaScript( ConnectionProviderBuilder.getCorrespondingDialect() ), + cfg.generateSchemaCreationScript( ConnectionProviderBuilder.getCorrespondingDialect() ) + ).execute( // so stupid... + false, // do not script the export (write it to file) + true, // do run it against the database + false, // do not *just* perform the drop + false // do not *just* perform the create + ); + + // do the jboss export + new SchemaExport( + new ConnectionHelper() { + private Connection connection; + @Override + public void prepare(boolean needsAutoCommit) throws SQLException { + connection = jbossProvider.getConnection(); + } + + @Override + public Connection getConnection() throws SQLException { + return connection; + } + + @Override + public void release() throws SQLException { + jbossProvider.closeConnection( connection ); + } + }, + cfg.generateDropSchemaScript( ConnectionProviderBuilder.getCorrespondingDialect() ), + cfg.generateSchemaCreationScript( ConnectionProviderBuilder.getCorrespondingDialect() ) + ).execute( // so stupid... + false, // do not script the export (write it to file) + true, // do run it against the database + false, // do not *just* perform the drop + false // do not *just* perform the create + ); + + serviceRegistry = new BasicServiceRegistryImpl( cfg.getProperties() ); + serviceRegistry.registerService( MultiTenantConnectionProvider.class, multiTenantConnectionProvider ); + + sessionFactory = cfg.buildSessionFactory( serviceRegistry ); + } + + @After + public void tearDown() { + if ( sessionFactory != null ) { + sessionFactory.close(); + } + if ( serviceRegistry != null ) { + serviceRegistry.destroy(); + } + if ( jbossProvider != null ) { + jbossProvider.stop(); + } + if ( acmeProvider != null ) { + acmeProvider.stop(); + } + } + + private Session openSession() { + return sessionFactory.openSession(); + } + + @Test + public void testBasicExpectedBehavior() { + Session session = openSession(); + session.setTenantIdentifier( "jboss" ); + session.beginTransaction(); + Customer steve = new Customer( "steve" ); + session.save( steve ); + session.getTransaction().commit(); + session.close(); + + session = openSession(); + try { + session.setTenantIdentifier( "acme" ); + session.beginTransaction(); + Customer check = (Customer) session.get( Customer.class, steve.getId() ); + Assert.assertNull( "tenancy not properly isolated", check ); + } + finally { + session.getTransaction().commit(); + session.close(); + } + + session = openSession(); + session.setTenantIdentifier( "jboss" ); + session.beginTransaction(); + session.delete( steve ); + session.getTransaction().commit(); + session.close(); + } + +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/env/ConnectionProviderBuilder.java b/hibernate-testing/src/main/java/org/hibernate/testing/env/ConnectionProviderBuilder.java index 410653273c..443a13a346 100644 --- a/hibernate-testing/src/main/java/org/hibernate/testing/env/ConnectionProviderBuilder.java +++ b/hibernate-testing/src/main/java/org/hibernate/testing/env/ConnectionProviderBuilder.java @@ -29,7 +29,6 @@ import org.hibernate.cfg.Environment; import org.hibernate.dialect.Dialect; import org.hibernate.dialect.H2Dialect; import org.hibernate.service.jdbc.connections.internal.DriverManagerConnectionProviderImpl; -import org.hibernate.service.jdbc.connections.spi.ConnectionProvider; /** * Defines the JDBC connection information (currently H2) used by Hibernate for unit (not functional!) tests @@ -38,24 +37,36 @@ import org.hibernate.service.jdbc.connections.spi.ConnectionProvider; */ public class ConnectionProviderBuilder { public static final String DRIVER = "org.h2.Driver"; - public static final String URL = "jdbc:h2:mem:db1;DB_CLOSE_DELAY=-1;MVCC=TRUE"; + public static final String URL = "jdbc:h2:mem:%s;DB_CLOSE_DELAY=-1;MVCC=TRUE"; public static final String USER = "sa"; public static final String PASS = ""; - public static Properties getConnectionProviderProperties() { + public static Properties getConnectionProviderProperties(String dbName) { Properties props = new Properties( null ); props.put( Environment.DRIVER, DRIVER ); - props.put( Environment.URL, URL ); + props.put( Environment.URL, String.format( URL, dbName ) ); props.put( Environment.USER, USER ); + props.put( Environment.PASS, PASS ); return props; } - public static ConnectionProvider buildConnectionProvider() { + public static Properties getConnectionProviderProperties() { + return getConnectionProviderProperties( "db1" ); + } + + public static DriverManagerConnectionProviderImpl buildConnectionProvider() { return buildConnectionProvider( false ); } - public static ConnectionProvider buildConnectionProvider(final boolean allowAggressiveRelease) { - final Properties props = getConnectionProviderProperties(); + public static DriverManagerConnectionProviderImpl buildConnectionProvider(String dbName) { + return buildConnectionProvider( getConnectionProviderProperties( dbName ), false ); + } + + public static DriverManagerConnectionProviderImpl buildConnectionProvider(final boolean allowAggressiveRelease) { + return buildConnectionProvider( getConnectionProviderProperties( "db1" ), allowAggressiveRelease ); + } + + private static DriverManagerConnectionProviderImpl buildConnectionProvider(Properties props, final boolean allowAggressiveRelease) { DriverManagerConnectionProviderImpl connectionProvider = new DriverManagerConnectionProviderImpl() { public boolean supportsAggressiveRelease() { return allowAggressiveRelease;