/*
* 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 org.hibernate.HibernateException;
import org.hibernate.MultiTenancyStrategy;
import org.hibernate.Session;
import org.hibernate.cfg.Configuration;
import org.hibernate.cfg.Environment;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.mapping.RootClass;
import org.hibernate.service.ServiceRegistryBuilder;
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.testing.cache.CachingRegionFactory;
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.BaseUnitTestCase;
/**
* @author Steve Ebersole
*/
public class SchemaBasedMultiTenancyTest extends BaseUnitTestCase {
private DriverManagerConnectionProviderImpl acmeProvider;
private DriverManagerConnectionProviderImpl jbossProvider;
private ServiceRegistryImplementor serviceRegistry;
private SessionFactoryImplementor 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.setProperty( Environment.CACHE_REGION_FACTORY, CachingRegionFactory.class.getName() );
cfg.setProperty( Environment.GENERATE_STATISTICS, "true" );
cfg.addAnnotatedClass( Customer.class );
cfg.buildMappings();
RootClass meta = (RootClass) cfg.getClassMapping( Customer.class.getName() );
meta.setCacheConcurrencyStrategy( "read-write" );
// 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 = (ServiceRegistryImplementor) new ServiceRegistryBuilder()
.applySettings( cfg.getProperties() )
.addService( MultiTenantConnectionProvider.class, multiTenantConnectionProvider )
.buildServiceRegistry();
sessionFactory = (SessionFactoryImplementor) 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();
}
}
@Test
public void testBasicExpectedBehavior() {
Session session = sessionFactory.withOptions().tenantIdentifier( "jboss" ).openSession();
session.beginTransaction();
Customer steve = new Customer( 1L, "steve" );
session.save( steve );
session.getTransaction().commit();
session.close();
session = sessionFactory.withOptions().tenantIdentifier( "acme" ).openSession();
try {
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 = sessionFactory.withOptions().tenantIdentifier( "jboss" ).openSession();
session.beginTransaction();
session.delete( steve );
session.getTransaction().commit();
session.close();
}
@Test
public void testSameIdentifiers() {
// create a customer 'steve' in jboss
Session session = sessionFactory.withOptions().tenantIdentifier( "jboss" ).openSession();
session.beginTransaction();
Customer steve = new Customer( 1L, "steve" );
session.save( steve );
session.getTransaction().commit();
session.close();
// now, create a customer 'john' in acme
session = sessionFactory.withOptions().tenantIdentifier( "acme" ).openSession();
session.beginTransaction();
Customer john = new Customer( 1L, "john" );
session.save( john );
session.getTransaction().commit();
session.close();
sessionFactory.getStatisticsImplementor().clear();
// make sure we get the correct people back, from cache
// first, jboss
{
session = sessionFactory.withOptions().tenantIdentifier( "jboss" ).openSession();
session.beginTransaction();
Customer customer = (Customer) session.load( Customer.class, 1L );
Assert.assertEquals( "steve", customer.getName() );
// also, make sure this came from second level
Assert.assertEquals( 1, sessionFactory.getStatisticsImplementor().getSecondLevelCacheHitCount() );
session.getTransaction().commit();
session.close();
}
sessionFactory.getStatisticsImplementor().clear();
// then, acme
{
session = sessionFactory.withOptions().tenantIdentifier( "acme" ).openSession();
session.beginTransaction();
Customer customer = (Customer) session.load( Customer.class, 1L );
Assert.assertEquals( "john", customer.getName() );
// also, make sure this came from second level
Assert.assertEquals( 1, sessionFactory.getStatisticsImplementor().getSecondLevelCacheHitCount() );
session.getTransaction().commit();
session.close();
}
// make sure the same works from datastore too
sessionFactory.getStatisticsImplementor().clear();
sessionFactory.getCache().evictEntityRegions();
// first jboss
{
session = sessionFactory.withOptions().tenantIdentifier( "jboss" ).openSession();
session.beginTransaction();
Customer customer = (Customer) session.load( Customer.class, 1L );
Assert.assertEquals( "steve", customer.getName() );
// also, make sure this came from second level
Assert.assertEquals( 0, sessionFactory.getStatisticsImplementor().getSecondLevelCacheHitCount() );
session.getTransaction().commit();
session.close();
}
sessionFactory.getStatisticsImplementor().clear();
// then, acme
{
session = sessionFactory.withOptions().tenantIdentifier( "acme" ).openSession();
session.beginTransaction();
Customer customer = (Customer) session.load( Customer.class, 1L );
Assert.assertEquals( "john", customer.getName() );
// also, make sure this came from second level
Assert.assertEquals( 0, sessionFactory.getStatisticsImplementor().getSecondLevelCacheHitCount() );
session.getTransaction().commit();
session.close();
}
session = sessionFactory.withOptions().tenantIdentifier( "jboss" ).openSession();
session.beginTransaction();
session.delete( steve );
session.getTransaction().commit();
session.close();
session = sessionFactory.withOptions().tenantIdentifier( "acme" ).openSession();
session.beginTransaction();
session.delete( john );
session.getTransaction().commit();
session.close();
}
}