Open Source Repository

Home /hibernate/hibernate-3.2.4.ga | Repository Home



org/hibernate/context/ThreadLocalSessionContext.java
package org.hibernate.context;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;
import javax.transaction.Synchronization;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hibernate.ConnectionReleaseMode;
import org.hibernate.HibernateException;
import org.hibernate.SessionFactory;
import org.hibernate.classic.Session;
import org.hibernate.engine.SessionFactoryImplementor;

/**
 * A {@link CurrentSessionContext} impl which scopes the notion of current
 * session by the current thread of execution.  Unlike the JTA counterpart,
 * threads do not give us a nice hook to perform any type of cleanup making
 * it questionable for this impl to actually generate Session instances.  In
 * the interest of usability, it was decided to have this default impl
 * actually generate a session upon first request and then clean it up
 * after the {@link org.hibernate.Transaction} associated with that session
 * is committed/rolled-back.  In order for ensuring that happens, the sessions
 * generated here are unusable until after {@link Session#beginTransaction()}
 * has been called. If <tt>close()</tt> is called on a session managed by
 * this class, it will be automatically unbound.
 <p/>
 * Additionally, the static {@link #bind} and {@link #unbind} methods are
 * provided to allow application code to explicitly control opening and
 * closing of these sessions.  This, with some from of interception,
 * is the preferred approach.  It also allows easy framework integration
 * and one possible approach for implementing long-sessions.
 <p/>
 * The {@link #buildOrObtainSession}{@link #isAutoCloseEnabled},
 {@link #isAutoFlushEnabled}{@link #getConnectionReleaseMode}, and
 {@link #buildCleanupSynch} methods are all provided to allow easy
 * subclassing (for long- running session scenarios, for example).
 *
 @author <a href="mailto:[email protected]">Steve Ebersole </a>
 */
public class ThreadLocalSessionContext implements CurrentSessionContext {

  private static final Log log = LogFactory.getLogThreadLocalSessionContext.class );
  private static final Class[] SESS_PROXY_INTERFACES = new Class[] {
      org.hibernate.classic.Session.class,
          org.hibernate.engine.SessionImplementor.class,
          org.hibernate.jdbc.JDBCContext.Context.class,
          org.hibernate.event.EventSource.class
  };

  /**
   * A ThreadLocal maintaining current sessions for the given execution thread.
   * The actual ThreadLocal variable is a java.util.Map to account for
   * the possibility for multiple SessionFactorys being used during execution
   * of the given thread.
   */
  private static final ThreadLocal context = new ThreadLocal();

  protected final SessionFactoryImplementor factory;

  public ThreadLocalSessionContext(SessionFactoryImplementor factory) {
    this.factory = factory;
  }

  public final Session currentSession() throws HibernateException {
    Session current = existingSessionfactory );
    if (current == null) {
      current = buildOrObtainSession();
      // register a cleanup synch
      current.getTransaction().registerSynchronizationbuildCleanupSynch() );
      // wrap the session in the transaction-protection proxy
      if needsWrappingcurrent ) ) {
        current = wrapcurrent );
      }
      // then bind it
      doBindcurrent, factory );
    }
    return current;
  }

  private boolean needsWrapping(Session session) {
    // try to make sure we don't wrap and already wrapped session
    return session != null
           && ! Proxy.isProxyClasssession.getClass() )
           || Proxy.getInvocationHandlersession != null
           && ! Proxy.getInvocationHandlersession instanceof TransactionProtectionWrapper ) );
  }

  protected SessionFactoryImplementor getFactory() {
    return factory;
  }

  /**
   * Strictly provided for subclassing purposes; specifically to allow long-session
   * support.
   <p/>
   * This implementation always just opens a new session.
   *
   @return the built or (re)obtained session.
   */
  protected Session buildOrObtainSession() {
    return factory.openSession(
        null,
            isAutoFlushEnabled(),
            isAutoCloseEnabled(),
            getConnectionReleaseMode()
      );
  }

  protected CleanupSynch buildCleanupSynch() {
    return new CleanupSynchfactory );
  }

  /**
   * Mainly for subclass usage.  This impl always returns true.
   *
   @return Whether or not the the session should be closed by transaction completion.
   */
  protected boolean isAutoCloseEnabled() {
    return true;
  }

  /**
   * Mainly for subclass usage.  This impl always returns true.
   *
   @return Whether or not the the session should be flushed prior transaction completion.
   */
  protected boolean isAutoFlushEnabled() {
    return true;
  }

  /**
   * Mainly for subclass usage.  This impl always returns after_transaction.
   *
   @return The connection release mode for any built sessions.
   */
  protected ConnectionReleaseMode getConnectionReleaseMode() {
    return factory.getSettings().getConnectionReleaseMode();
  }

  protected Session wrap(Session session) {
    TransactionProtectionWrapper wrapper = new TransactionProtectionWrappersession );
    Session wrapped = Session Proxy.newProxyInstance(
        Session.class.getClassLoader(),
            SESS_PROXY_INTERFACES,
            wrapper
      );
    // yick!  need this for proper serialization/deserialization handling...
    wrapper.setWrappedwrapped );
    return wrapped;
  }

  /**
   * Associates the given session with the current thread of execution.
   *
   @param session The session to bind.
   */
  public static void bind(org.hibernate.Session session) {
    SessionFactory factory = session.getSessionFactory();
    cleanupAnyOrphanedSessionfactory );
    doBindsession, factory );
  }

  private static void cleanupAnyOrphanedSession(SessionFactory factory) {
    Session orphan = doUnbindfactory, false );
    if orphan != null ) {
      log.warn"Already session bound on call to bind(); make sure you clean up your sessions!" );
      try {
        if orphan.getTransaction() != null && orphan.getTransaction().isActive() ) {
          try {
            orphan.getTransaction().rollback();
          }
          catchThrowable t ) {
            log.debug"Unable to rollback transaction for orphaned session", t );
          }
        }
        orphan.close();
      }
      catchThrowable t ) {
        log.debug"Unable to close orphaned session", t );
      }
    }
  }

  /**
   * Unassociate a previously bound session from the current thread of execution.
   *
   @return The session which was unbound.
   */
  public static Session unbind(SessionFactory factory) {
    return doUnbindfactory, true );
  }

  private static Session existingSession(SessionFactory factory) {
    Map sessionMap = sessionMap();
    if sessionMap == null ) {
      return null;
    }
    else {
      return Session sessionMap.getfactory );
    }
  }

  protected static Map sessionMap() {
    return Map context.get();
  }

  private static void doBind(org.hibernate.Session session, SessionFactory factory) {
    Map sessionMap = sessionMap();
    if sessionMap == null ) {
      sessionMap = new HashMap();
      context.setsessionMap );
    }
    sessionMap.putfactory, session );
  }

  private static Session doUnbind(SessionFactory factory, boolean releaseMapIfEmpty) {
    Map sessionMap = sessionMap();
    Session session = null;
    if sessionMap != null ) {
      session = Session sessionMap.removefactory );
      if releaseMapIfEmpty && sessionMap.isEmpty() ) {
        context.setnull );
      }
    }
    return session;
  }

  /**
   * JTA transaction synch used for cleanup of the internal session map.
   */
  protected static class CleanupSynch implements Synchronization, Serializable {
    protected final SessionFactory factory;

    public CleanupSynch(SessionFactory factory) {
      this.factory = factory;
    }

    public void beforeCompletion() {
    }

    public void afterCompletion(int i) {
      unbindfactory );
    }
  }

  private class TransactionProtectionWrapper implements InvocationHandler, Serializable {
    private final Session realSession;
    private Session wrappedSession;

    public TransactionProtectionWrapper(Session realSession) {
      this.realSession = realSession;
    }

    public Object invoke(Object proxy, Method method, Object[] argsthrows Throwable {
      try {
        // If close() is called, guarantee unbind()
        if "close".equalsmethod.getName()) ) {
          unbindrealSession.getSessionFactory() );
        }
        else if "toString".equalsmethod.getName() )
               || "equals".equalsmethod.getName() )
               || "hashCode".equalsmethod.getName() )
                 || "getStatistics".equalsmethod.getName() )
               || "isOpen".equalsmethod.getName() ) ) {
          // allow these to go through the the real session no matter what
        }
        else if !realSession.isOpen() ) {
          // essentially, if the real session is closed allow any
          // method call to pass through since the real session
          // will complain by throwing an appropriate exception;
          // NOTE that allowing close() above has the same basic effect,
          //   but we capture that there simply to perform the unbind...
        }
        else if !realSession.getTransaction().isActive() ) {
          // limit the methods available if no transaction is active
          if "beginTransaction".equalsmethod.getName() )
               || "getTransaction".equalsmethod.getName() )
               || "isTransactionInProgress".equalsmethod.getName() )
               || "setFlushMode".equalsmethod.getName() )
               || "getSessionFactory".equalsmethod.getName() ) ) {
            log.trace"allowing method [" + method.getName() "] in non-transacted context" );
          }
          else if "reconnect".equalsmethod.getName() )
                    || "disconnect".equalsmethod.getName() ) ) {
            // allow these (deprecated) methods to pass through
          }
          else {
            throw new HibernateExceptionmethod.getName() " is not valid without active transaction" );
          }
        }
        log.trace"allowing proxied method [" + method.getName() "] to proceed to real session" );
        return method.invokerealSession, args );
      }
      catch InvocationTargetException e ) {
        if e.getTargetException() instanceof RuntimeException ) {
          throw RuntimeException e.getTargetException();
        }
        else {
          throw e;
        }
      }
    }

    public void setWrapped(Session wrapped) {
      this.wrappedSession = wrapped;
    }


    // serialization ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    private void writeObject(ObjectOutputStream oosthrows IOException {
      // if a ThreadLocalSessionContext-bound session happens to get
      // serialized, to be completely correct, we need to make sure
      // that unbinding of that session occurs.
      oos.defaultWriteObject();
      if existingSessionfactory == wrappedSession ) {
        unbindfactory );
      }
    }

    private void readObject(ObjectInputStream oisthrows IOException, ClassNotFoundException {
      // on the inverse, it makes sense that if a ThreadLocalSessionContext-
      // bound session then gets deserialized to go ahead and re-bind it to
      // the ThreadLocalSessionContext session map.
      ois.defaultReadObject();
      realSession.getTransaction().registerSynchronizationbuildCleanupSynch() );
      doBindwrappedSession, factory );
    }
  }
}