HHH-6336 - Add TenantIdentifierResolver

This commit is contained in:
Steve Ebersole 2012-01-31 12:39:13 -06:00
parent 230cff7d00
commit 89911003e3
11 changed files with 290 additions and 39 deletions

View File

@ -547,5 +547,7 @@ public interface AvailableSettings {
*/
public static final String CUSTOM_ENTITY_DIRTINESS_STRATEGY = "hibernate.entity_dirtiness_strategy";
public static final String TENANT_IDENTIFIER_RESOLVER = "hibernate.tenant_identifier_resolver";
public static final String FORCE_DISCRIMINATOR_IN_SELECTS_BY_DEFAULT = "hibernate.discriminator.force_in_select";
}

View File

@ -79,6 +79,7 @@ import org.hibernate.annotations.common.reflection.ReflectionManager;
import org.hibernate.annotations.common.reflection.XClass;
import org.hibernate.annotations.common.reflection.java.JavaReflectionManager;
import org.hibernate.cfg.annotations.reflection.JPAMetadataProvider;
import org.hibernate.context.spi.CurrentTenantIdentifierResolver;
import org.hibernate.dialect.Dialect;
import org.hibernate.dialect.MySQLDialect;
import org.hibernate.dialect.function.SQLFunction;
@ -254,6 +255,7 @@ public class Configuration implements Serializable {
private boolean isValidatorNotPresentLogged;
private Map<XClass, Map<String, PropertyData>> propertiesAnnotatedWithMapsId;
private Map<XClass, Map<String, PropertyData>> propertiesAnnotatedWithIdAndToOne;
private CurrentTenantIdentifierResolver currentTenantIdentifierResolver;
private boolean specjProprietarySyntaxEnabled;
@ -2436,6 +2438,14 @@ public class Configuration implements Serializable {
this.sessionFactoryObserver = sessionFactoryObserver;
}
public CurrentTenantIdentifierResolver getCurrentTenantIdentifierResolver() {
return currentTenantIdentifierResolver;
}
public void setCurrentTenantIdentifierResolver(CurrentTenantIdentifierResolver currentTenantIdentifierResolver) {
this.currentTenantIdentifierResolver = currentTenantIdentifierResolver;
}
// Mappings impl ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

View File

@ -0,0 +1,43 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* Copyright (c) 2012, 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.context;
import org.hibernate.HibernateException;
/**
* Indicates that tenant identifiers did not match in cases where
* {@link org.hibernate.context.spi.CurrentTenantIdentifierResolver#validateExistingCurrentSessions()} returns
* {@code true} and there is a mismatch found.
*
* @author Steve Ebersole
*/
public class TenantIdentifierMismatchException extends HibernateException{
public TenantIdentifierMismatchException(String message) {
super( message );
}
public TenantIdentifierMismatchException(String message, Throwable root) {
super( message, root );
}
}

View File

@ -34,6 +34,7 @@ import org.jboss.logging.Logger;
import org.hibernate.ConnectionReleaseMode;
import org.hibernate.HibernateException;
import org.hibernate.Session;
import org.hibernate.context.spi.AbstractCurrentSessionContext;
import org.hibernate.context.spi.CurrentSessionContext;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.engine.transaction.internal.jta.JtaStatusHelper;
@ -63,19 +64,18 @@ import org.hibernate.service.jta.platform.spi.JtaPlatform;
*
* @author Steve Ebersole
*/
public class JTASessionContext implements CurrentSessionContext {
public class JTASessionContext extends AbstractCurrentSessionContext {
private static final CoreMessageLogger LOG = Logger.getMessageLogger(CoreMessageLogger.class, JTASessionContext.class.getName());
protected final SessionFactoryImplementor factory;
private transient Map currentSessionMap = new Hashtable();
public JTASessionContext(SessionFactoryImplementor factory) {
this.factory = factory;
super( factory );
}
@Override
public Session currentSession() throws HibernateException {
final JtaPlatform jtaPlatform = factory.getServiceRegistry().getService( JtaPlatform.class );
final JtaPlatform jtaPlatform = factory().getServiceRegistry().getService( JtaPlatform.class );
final TransactionManager transactionManager = jtaPlatform.retrieveTransactionManager();
if ( transactionManager == null ) {
throw new HibernateException( "No TransactionManagerLookup specified" );
@ -123,6 +123,9 @@ public class JTASessionContext implements CurrentSessionContext {
currentSessionMap.put( txnIdentifier, currentSession );
}
else {
validateExistingSession( currentSession );
}
return currentSession;
}
@ -147,7 +150,7 @@ public class JTASessionContext implements CurrentSessionContext {
* @return the built or (re)obtained session.
*/
protected Session buildOrObtainSession() {
return factory.withOptions()
return baseSessionBuilder()
.autoClose( isAutoCloseEnabled() )
.connectionReleaseMode( getConnectionReleaseMode() )
.flushBeforeCompletion( isAutoFlushEnabled() )

View File

@ -29,7 +29,7 @@ import java.util.Map;
import org.hibernate.HibernateException;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.context.spi.CurrentSessionContext;
import org.hibernate.context.spi.AbstractCurrentSessionContext;
import org.hibernate.engine.spi.SessionFactoryImplementor;
/**
@ -56,21 +56,23 @@ import org.hibernate.engine.spi.SessionFactoryImplementor;
*
* @author Steve Ebersole
*/
public class ManagedSessionContext implements CurrentSessionContext {
public class ManagedSessionContext extends AbstractCurrentSessionContext {
private static final ThreadLocal<Map<SessionFactory,Session>> context = new ThreadLocal<Map<SessionFactory,Session>>();
private final SessionFactoryImplementor factory;
public ManagedSessionContext(SessionFactoryImplementor factory) {
this.factory = factory;
super( factory );
}
@Override
public Session currentSession() {
Session current = existingSession( factory );
Session current = existingSession( factory() );
if ( current == null ) {
throw new HibernateException( "No session currently bound to execution context" );
}
else {
validateExistingSession( current );
}
return current;
}

View File

@ -41,6 +41,7 @@ import org.hibernate.ConnectionReleaseMode;
import org.hibernate.HibernateException;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.context.spi.AbstractCurrentSessionContext;
import org.hibernate.context.spi.CurrentSessionContext;
import org.hibernate.engine.jdbc.LobCreationContext;
import org.hibernate.engine.spi.SessionFactoryImplementor;
@ -75,7 +76,7 @@ import org.hibernate.internal.CoreMessageLogger;
*
* @author Steve Ebersole
*/
public class ThreadLocalSessionContext implements CurrentSessionContext {
public class ThreadLocalSessionContext extends AbstractCurrentSessionContext {
private static final CoreMessageLogger LOG = Logger.getMessageLogger(CoreMessageLogger.class,
ThreadLocalSessionContext.class.getName());
@ -95,18 +96,14 @@ public class ThreadLocalSessionContext implements CurrentSessionContext {
*/
private static final ThreadLocal<Map> context = new ThreadLocal<Map>();
protected final SessionFactoryImplementor factory;
public ThreadLocalSessionContext(SessionFactoryImplementor factory) {
this.factory = factory;
super( factory );
}
/**
* {@inheritDoc}
*/
@Override
public final Session currentSession() throws HibernateException {
Session current = existingSession( factory );
if (current == null) {
Session current = existingSession( factory() );
if ( current == null ) {
current = buildOrObtainSession();
// register a cleanup sync
current.getTransaction().registerSynchronization( buildCleanupSynch() );
@ -115,7 +112,10 @@ public class ThreadLocalSessionContext implements CurrentSessionContext {
current = wrap( current );
}
// then bind it
doBind( current, factory );
doBind( current, factory() );
}
else {
validateExistingSession( current );
}
return current;
}
@ -134,7 +134,7 @@ public class ThreadLocalSessionContext implements CurrentSessionContext {
* @return Value for property 'factory'.
*/
protected SessionFactoryImplementor getFactory() {
return factory;
return factory();
}
/**
@ -146,7 +146,7 @@ public class ThreadLocalSessionContext implements CurrentSessionContext {
* @return the built or (re)obtained session.
*/
protected Session buildOrObtainSession() {
return factory.withOptions()
return baseSessionBuilder()
.autoClose( isAutoCloseEnabled() )
.connectionReleaseMode( getConnectionReleaseMode() )
.flushBeforeCompletion( isAutoFlushEnabled() )
@ -154,7 +154,7 @@ public class ThreadLocalSessionContext implements CurrentSessionContext {
}
protected CleanupSynch buildCleanupSynch() {
return new CleanupSynch( factory );
return new CleanupSynch( factory() );
}
/**
@ -181,7 +181,7 @@ public class ThreadLocalSessionContext implements CurrentSessionContext {
* @return The connection release mode for any built sessions.
*/
protected ConnectionReleaseMode getConnectionReleaseMode() {
return factory.getSettings().getConnectionReleaseMode();
return factory().getSettings().getConnectionReleaseMode();
}
protected Session wrap(Session session) {
@ -240,7 +240,9 @@ public class ThreadLocalSessionContext implements CurrentSessionContext {
private static Session existingSession(SessionFactory factory) {
Map sessionMap = sessionMap();
if ( sessionMap == null ) return null;
if ( sessionMap == null ) {
return null;
}
return (Session) sessionMap.get( factory );
}
@ -372,8 +374,8 @@ public class ThreadLocalSessionContext implements CurrentSessionContext {
// serialized, to be completely correct, we need to make sure
// that unbinding of that session occurs.
oos.defaultWriteObject();
if ( existingSession( factory ) == wrappedSession ) {
unbind( factory );
if ( existingSession( factory() ) == wrappedSession ) {
unbind( factory() );
}
}
@ -383,7 +385,7 @@ public class ThreadLocalSessionContext implements CurrentSessionContext {
// the ThreadLocalSessionContext session map.
ois.defaultReadObject();
realSession.getTransaction().registerSynchronization( buildCleanupSynch() );
doBind( wrappedSession, factory );
doBind( wrappedSession, factory() );
}
}
}

View File

@ -0,0 +1,73 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* Copyright (c) 2012, 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.context.spi;
import org.hibernate.Session;
import org.hibernate.SessionBuilder;
import org.hibernate.context.TenantIdentifierMismatchException;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.internal.util.compare.EqualsHelper;
/**
* Base support for {@link CurrentSessionContext} implementors.
*
* @author Steve Ebersole
*/
public abstract class AbstractCurrentSessionContext implements CurrentSessionContext {
private final SessionFactoryImplementor factory;
protected AbstractCurrentSessionContext(SessionFactoryImplementor factory) {
this.factory = factory;
}
public SessionFactoryImplementor factory() {
return factory;
}
protected SessionBuilder baseSessionBuilder() {
final SessionBuilder builder = factory.withOptions();
final CurrentTenantIdentifierResolver resolver = factory.getCurrentTenantIdentifierResolver();
if ( resolver != null ) {
builder.tenantIdentifier( resolver.resolveCurrentTenantIdentifier() );
}
return builder;
}
protected void validateExistingSession(Session existingSession) {
final CurrentTenantIdentifierResolver resolver = factory.getCurrentTenantIdentifierResolver();
if ( resolver != null && resolver.validateExistingCurrentSessions() ) {
final String current = resolver.resolveCurrentTenantIdentifier();
if ( ! EqualsHelper.equals( existingSession.getTenantIdentifier(), current ) ) {
throw new TenantIdentifierMismatchException(
String.format(
"Reported current tenant identifier [%s] did not match tenant identifier from " +
"existing session [%s]",
current,
existingSession.getTenantIdentifier()
)
);
}
}
}
}

View File

@ -0,0 +1,51 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* Copyright (c) 2012, 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.context.spi;
/**
* A callback registered with the {@link org.hibernate.SessionFactory} that is responsible for resolving the
* current tenant identifier for use with {@link CurrentSessionContext} and
* {@link org.hibernate.SessionFactory#getCurrentSession()}
*
* @author Steve Ebersole
*/
public interface CurrentTenantIdentifierResolver {
/**
* Resolve the current tenant identifier.
*
* @return The current tenant identifier
*/
public String resolveCurrentTenantIdentifier();
/**
* Should we validate that the tenant identifier on "current sessions" that already exist when
* {@link CurrentSessionContext#currentSession()} is called matches the value returned here from
* {@link #resolveCurrentTenantIdentifier()}?
*
* @return {@code true} indicates that the extra validation will be performed; {@code false} indicates it will not.
*
* @see org.hibernate.context.TenantIdentifierMismatchException
*/
public boolean validateExistingCurrentSessions();
}

View File

@ -38,6 +38,7 @@ import org.hibernate.cache.spi.QueryCache;
import org.hibernate.cache.spi.Region;
import org.hibernate.cache.spi.UpdateTimestampsCache;
import org.hibernate.cfg.Settings;
import org.hibernate.context.spi.CurrentTenantIdentifierResolver;
import org.hibernate.dialect.Dialect;
import org.hibernate.dialect.function.SQLFunctionRegistry;
import org.hibernate.engine.ResultSetMappingDefinition;
@ -241,4 +242,6 @@ public interface SessionFactoryImplementor extends Mapping, SessionFactory {
public void addObserver(SessionFactoryObserver observer);
public CustomEntityDirtinessStrategy getCustomEntityDirtinessStrategy();
public CurrentTenantIdentifierResolver getCurrentTenantIdentifierResolver();
}

View File

@ -82,6 +82,7 @@ import org.hibernate.context.internal.JTASessionContext;
import org.hibernate.context.internal.ManagedSessionContext;
import org.hibernate.context.internal.ThreadLocalSessionContext;
import org.hibernate.context.spi.CurrentSessionContext;
import org.hibernate.context.spi.CurrentTenantIdentifierResolver;
import org.hibernate.dialect.Dialect;
import org.hibernate.dialect.function.SQLFunction;
import org.hibernate.dialect.function.SQLFunctionRegistry;
@ -210,6 +211,7 @@ public final class SessionFactoryImpl
private final transient TransactionEnvironment transactionEnvironment;
private final transient SessionFactoryOptions sessionFactoryOptions;
private final transient CustomEntityDirtinessStrategy customEntityDirtinessStrategy;
private final transient CurrentTenantIdentifierResolver currentTenantIdentifierResolver;
@SuppressWarnings( {"unchecked", "ThrowableResultOfMethodCallIgnored"})
public SessionFactoryImpl(
@ -536,6 +538,10 @@ public final class SessionFactoryImpl
}
this.customEntityDirtinessStrategy = determineCustomEntityDirtinessStrategy( properties );
this.currentTenantIdentifierResolver = determineCurrentTenantIdentifierResolver(
cfg.getCurrentTenantIdentifierResolver(),
properties
);
this.transactionEnvironment = new TransactionEnvironmentImpl( this );
this.observer.sessionFactoryCreated( this );
}
@ -564,14 +570,16 @@ public final class SessionFactoryImpl
customEntityDirtinessStrategyClass = null;
}
}
try {
return customEntityDirtinessStrategyClass.newInstance();
}
catch (Exception e) {
LOG.debugf(
"Unable to instantiate CustomEntityDirtinessStrategy class %s",
customEntityDirtinessStrategyClass.getName()
);
if ( customEntityDirtinessStrategyClass != null ) {
try {
return customEntityDirtinessStrategyClass.newInstance();
}
catch (Exception e) {
LOG.debugf(
"Unable to instantiate CustomEntityDirtinessStrategy class %s",
customEntityDirtinessStrategyClass.getName()
);
}
}
}
@ -602,6 +610,53 @@ public final class SessionFactoryImpl
};
}
@SuppressWarnings( {"unchecked"})
private CurrentTenantIdentifierResolver determineCurrentTenantIdentifierResolver(
CurrentTenantIdentifierResolver explicitResolver,
Properties properties) {
if ( explicitResolver != null ) {
return explicitResolver;
}
final Object value = properties.get( AvailableSettings.TENANT_IDENTIFIER_RESOLVER );
if ( value == null ) {
return null;
}
if ( CurrentTenantIdentifierResolver.class.isInstance( value ) ) {
return CurrentTenantIdentifierResolver.class.cast( value );
}
Class<CurrentTenantIdentifierResolver> implClass;
if ( Class.class.isInstance( value ) ) {
implClass = Class.class.cast( customEntityDirtinessStrategy );
}
else {
try {
implClass = serviceRegistry.getService( ClassLoaderService.class ).classForName( value.toString() );
}
catch (Exception e) {
LOG.debugf(
"Unable to locate CurrentTenantIdentifierResolver implementation class %s",
value.toString()
);
return null;
}
}
try {
return implClass.newInstance();
}
catch (Exception e) {
LOG.debugf(
"Unable to instantiate CurrentTenantIdentifierResolver class %s",
implClass.getName()
);
}
return null;
}
@SuppressWarnings( {"ThrowableResultOfMethodCallIgnored"})
public SessionFactoryImpl(
MetadataImplementor metadata,
@ -921,6 +976,7 @@ public final class SessionFactoryImpl
}
this.customEntityDirtinessStrategy = determineCustomEntityDirtinessStrategy( properties );
this.currentTenantIdentifierResolver = determineCurrentTenantIdentifierResolver( null, properties );
this.transactionEnvironment = new TransactionEnvironmentImpl( this );
this.observer.sessionFactoryCreated( this );
}
@ -1791,10 +1847,16 @@ public final class SessionFactoryImpl
}
}
@Override
public CustomEntityDirtinessStrategy getCustomEntityDirtinessStrategy() {
return customEntityDirtinessStrategy;
}
@Override
public CurrentTenantIdentifierResolver getCurrentTenantIdentifierResolver() {
return currentTenantIdentifierResolver;
}
// Serialization handling ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

View File

@ -120,12 +120,12 @@ public class ThreadLocalCurrentSessionTest extends ConnectionManagementTestCase
}
public static boolean isSessionBound(Session session) {
return sessionMap() != null && sessionMap().containsKey( me.factory )
&& sessionMap().get( me.factory ) == session;
return sessionMap() != null && sessionMap().containsKey( me.factory() )
&& sessionMap().get( me.factory() ) == session;
}
public static boolean hasBind() {
return sessionMap() != null && sessionMap().containsKey( me.factory );
return sessionMap() != null && sessionMap().containsKey( me.factory() );
}
}
}