001: package org.hibernate.context;
002:
003: import org.hibernate.HibernateException;
004: import org.hibernate.ConnectionReleaseMode;
005: import org.hibernate.classic.Session;
006: import org.hibernate.engine.SessionFactoryImplementor;
007: import org.hibernate.util.JTAHelper;
008: import org.apache.commons.logging.Log;
009: import org.apache.commons.logging.LogFactory;
010:
011: import javax.transaction.Transaction;
012: import javax.transaction.TransactionManager;
013: import javax.transaction.Synchronization;
014: import java.util.Map;
015: import java.util.Hashtable;
016:
017: /**
018: * An implementation of {@link CurrentSessionContext} which scopes the notion
019: * of a current session to a JTA transaction. Because JTA gives us a nice
020: * tie-in to clean up after ourselves, this implementation will generate
021: * Sessions as needed provided a JTA transaction is in effect. If a session
022: * is not already associated with the current JTA transaction at the time
023: * {@link #currentSession()} is called, a new session will be opened and it
024: * will be associated with that JTA transaction.
025: * <p/>
026: * Note that the sessions returned from this method are automatically configured with
027: * both the {@link org.hibernate.cfg.Environment#FLUSH_BEFORE_COMPLETION auto-flush} and
028: * {@link org.hibernate.cfg.Environment#AUTO_CLOSE_SESSION auto-close} attributes set to
029: * true, meaning that the Session will be automatically flushed and closed
030: * as part of the lifecycle for the JTA transaction to which it is associated.
031: * Additionally, it will also be configured to aggressively release JDBC
032: * connections after each statement is executed. These settings are governed
033: * by the {@link #isAutoFlushEnabled()}, {@link #isAutoCloseEnabled()}, and
034: * {@link #getConnectionReleaseMode()} methods; these are provided (along with
035: * the {@link #buildOrObtainSession()} method) for easier subclassing for custom
036: * JTA-based session tracking logic (like maybe long-session semantics).
037: *
038: * @author <a href="mailto:steve@hibernate.org">Steve Ebersole </a>
039: */
040: public class JTASessionContext implements CurrentSessionContext {
041:
042: private static final Log log = LogFactory
043: .getLog(JTASessionContext.class);
044:
045: protected final SessionFactoryImplementor factory;
046: private transient Map currentSessionMap = new Hashtable();
047:
048: public JTASessionContext(SessionFactoryImplementor factory) {
049: this .factory = factory;
050: }
051:
052: public Session currentSession() throws HibernateException {
053: TransactionManager transactionManager = factory
054: .getTransactionManager();
055: if (transactionManager == null) {
056: throw new HibernateException(
057: "No TransactionManagerLookup specified");
058: }
059:
060: Transaction txn = null;
061: try {
062: txn = transactionManager.getTransaction();
063: if (txn == null) {
064: throw new HibernateException(
065: "Unable to locate current JTA transaction");
066: }
067: if (!JTAHelper.isInProgress(txn.getStatus())) {
068: // We could register the session against the transaction even though it is
069: // not started, but we'd have no guarentee of ever getting the map
070: // entries cleaned up (aside from spawning threads).
071: throw new HibernateException(
072: "Current transaction is not in progress");
073: }
074: } catch (HibernateException e) {
075: throw e;
076: } catch (Throwable t) {
077: throw new HibernateException(
078: "Problem locating/validating JTA transaction", t);
079: }
080:
081: Session currentSession = (Session) currentSessionMap.get(txn);
082:
083: if (currentSession == null) {
084: currentSession = buildOrObtainSession();
085:
086: try {
087: txn.registerSynchronization(buildCleanupSynch(txn));
088: } catch (Throwable t) {
089: try {
090: currentSession.close();
091: } catch (Throwable ignore) {
092: log
093: .debug(
094: "Unable to release generated current-session on failed synch registration",
095: ignore);
096: }
097: throw new HibernateException(
098: "Unable to register cleanup Synchronization with TransactionManager");
099: }
100:
101: currentSessionMap.put(txn, currentSession);
102: }
103:
104: return currentSession;
105: }
106:
107: private CleanupSynch buildCleanupSynch(Transaction txn) {
108: return new CleanupSynch(txn, this );
109: }
110:
111: /**
112: * Strictly provided for subclassing purposes; specifically to allow long-session
113: * support.
114: * <p/>
115: * This implementation always just opens a new session.
116: *
117: * @return the built or (re)obtained session.
118: */
119: protected Session buildOrObtainSession() {
120: return factory.openSession(null, isAutoFlushEnabled(),
121: isAutoCloseEnabled(), getConnectionReleaseMode());
122: }
123:
124: /**
125: * Mainly for subclass usage. This impl always returns true.
126: *
127: * @return Whether or not the the session should be closed by transaction completion.
128: */
129: protected boolean isAutoCloseEnabled() {
130: return true;
131: }
132:
133: /**
134: * Mainly for subclass usage. This impl always returns true.
135: *
136: * @return Whether or not the the session should be flushed prior transaction completion.
137: */
138: protected boolean isAutoFlushEnabled() {
139: return true;
140: }
141:
142: /**
143: * Mainly for subclass usage. This impl always returns after_statement.
144: *
145: * @return The connection release mode for any built sessions.
146: */
147: protected ConnectionReleaseMode getConnectionReleaseMode() {
148: return ConnectionReleaseMode.AFTER_STATEMENT;
149: }
150:
151: /**
152: * JTA transaction synch used for cleanup of the internal session map.
153: */
154: protected static class CleanupSynch implements Synchronization {
155: private Transaction txn;
156: private JTASessionContext context;
157:
158: public CleanupSynch(Transaction txn, JTASessionContext context) {
159: this .txn = txn;
160: this .context = context;
161: }
162:
163: public void beforeCompletion() {
164: }
165:
166: public void afterCompletion(int i) {
167: context.currentSessionMap.remove(txn);
168: }
169: }
170: }
|