001: package org.hibernate.context;
002:
003: import java.io.IOException;
004: import java.io.ObjectInputStream;
005: import java.io.ObjectOutputStream;
006: import java.io.Serializable;
007: import java.lang.reflect.InvocationHandler;
008: import java.lang.reflect.InvocationTargetException;
009: import java.lang.reflect.Method;
010: import java.lang.reflect.Proxy;
011: import java.util.HashMap;
012: import java.util.Map;
013: import javax.transaction.Synchronization;
014:
015: import org.apache.commons.logging.Log;
016: import org.apache.commons.logging.LogFactory;
017: import org.hibernate.ConnectionReleaseMode;
018: import org.hibernate.HibernateException;
019: import org.hibernate.SessionFactory;
020: import org.hibernate.classic.Session;
021: import org.hibernate.engine.SessionFactoryImplementor;
022:
023: /**
024: * A {@link CurrentSessionContext} impl which scopes the notion of current
025: * session by the current thread of execution. Unlike the JTA counterpart,
026: * threads do not give us a nice hook to perform any type of cleanup making
027: * it questionable for this impl to actually generate Session instances. In
028: * the interest of usability, it was decided to have this default impl
029: * actually generate a session upon first request and then clean it up
030: * after the {@link org.hibernate.Transaction} associated with that session
031: * is committed/rolled-back. In order for ensuring that happens, the sessions
032: * generated here are unusable until after {@link Session#beginTransaction()}
033: * has been called. If <tt>close()</tt> is called on a session managed by
034: * this class, it will be automatically unbound.
035: * <p/>
036: * Additionally, the static {@link #bind} and {@link #unbind} methods are
037: * provided to allow application code to explicitly control opening and
038: * closing of these sessions. This, with some from of interception,
039: * is the preferred approach. It also allows easy framework integration
040: * and one possible approach for implementing long-sessions.
041: * <p/>
042: * The {@link #buildOrObtainSession}, {@link #isAutoCloseEnabled},
043: * {@link #isAutoFlushEnabled}, {@link #getConnectionReleaseMode}, and
044: * {@link #buildCleanupSynch} methods are all provided to allow easy
045: * subclassing (for long- running session scenarios, for example).
046: *
047: * @author <a href="mailto:steve@hibernate.org">Steve Ebersole </a>
048: */
049: public class ThreadLocalSessionContext implements CurrentSessionContext {
050:
051: private static final Log log = LogFactory
052: .getLog(ThreadLocalSessionContext.class);
053: private static final Class[] SESS_PROXY_INTERFACES = new Class[] {
054: org.hibernate.classic.Session.class,
055: org.hibernate.engine.SessionImplementor.class,
056: org.hibernate.jdbc.JDBCContext.Context.class,
057: org.hibernate.event.EventSource.class };
058:
059: /**
060: * A ThreadLocal maintaining current sessions for the given execution thread.
061: * The actual ThreadLocal variable is a java.util.Map to account for
062: * the possibility for multiple SessionFactorys being used during execution
063: * of the given thread.
064: */
065: private static final ThreadLocal context = new ThreadLocal();
066:
067: protected final SessionFactoryImplementor factory;
068:
069: public ThreadLocalSessionContext(SessionFactoryImplementor factory) {
070: this .factory = factory;
071: }
072:
073: public final Session currentSession() throws HibernateException {
074: Session current = existingSession(factory);
075: if (current == null) {
076: current = buildOrObtainSession();
077: // register a cleanup synch
078: current.getTransaction().registerSynchronization(
079: buildCleanupSynch());
080: // wrap the session in the transaction-protection proxy
081: if (needsWrapping(current)) {
082: current = wrap(current);
083: }
084: // then bind it
085: doBind(current, factory);
086: }
087: return current;
088: }
089:
090: private boolean needsWrapping(Session session) {
091: // try to make sure we don't wrap and already wrapped session
092: return session != null
093: && !Proxy.isProxyClass(session.getClass())
094: || (Proxy.getInvocationHandler(session) != null && !(Proxy
095: .getInvocationHandler(session) instanceof TransactionProtectionWrapper));
096: }
097:
098: protected SessionFactoryImplementor getFactory() {
099: return factory;
100: }
101:
102: /**
103: * Strictly provided for subclassing purposes; specifically to allow long-session
104: * support.
105: * <p/>
106: * This implementation always just opens a new session.
107: *
108: * @return the built or (re)obtained session.
109: */
110: protected Session buildOrObtainSession() {
111: return factory.openSession(null, isAutoFlushEnabled(),
112: isAutoCloseEnabled(), getConnectionReleaseMode());
113: }
114:
115: protected CleanupSynch buildCleanupSynch() {
116: return new CleanupSynch(factory);
117: }
118:
119: /**
120: * Mainly for subclass usage. This impl always returns true.
121: *
122: * @return Whether or not the the session should be closed by transaction completion.
123: */
124: protected boolean isAutoCloseEnabled() {
125: return true;
126: }
127:
128: /**
129: * Mainly for subclass usage. This impl always returns true.
130: *
131: * @return Whether or not the the session should be flushed prior transaction completion.
132: */
133: protected boolean isAutoFlushEnabled() {
134: return true;
135: }
136:
137: /**
138: * Mainly for subclass usage. This impl always returns after_transaction.
139: *
140: * @return The connection release mode for any built sessions.
141: */
142: protected ConnectionReleaseMode getConnectionReleaseMode() {
143: return factory.getSettings().getConnectionReleaseMode();
144: }
145:
146: protected Session wrap(Session session) {
147: TransactionProtectionWrapper wrapper = new TransactionProtectionWrapper(
148: session);
149: Session wrapped = (Session) Proxy.newProxyInstance(
150: Session.class.getClassLoader(), SESS_PROXY_INTERFACES,
151: wrapper);
152: // yick! need this for proper serialization/deserialization handling...
153: wrapper.setWrapped(wrapped);
154: return wrapped;
155: }
156:
157: /**
158: * Associates the given session with the current thread of execution.
159: *
160: * @param session The session to bind.
161: */
162: public static void bind(org.hibernate.Session session) {
163: SessionFactory factory = session.getSessionFactory();
164: cleanupAnyOrphanedSession(factory);
165: doBind(session, factory);
166: }
167:
168: private static void cleanupAnyOrphanedSession(SessionFactory factory) {
169: Session orphan = doUnbind(factory, false);
170: if (orphan != null) {
171: log
172: .warn("Already session bound on call to bind(); make sure you clean up your sessions!");
173: try {
174: if (orphan.getTransaction() != null
175: && orphan.getTransaction().isActive()) {
176: try {
177: orphan.getTransaction().rollback();
178: } catch (Throwable t) {
179: log
180: .debug(
181: "Unable to rollback transaction for orphaned session",
182: t);
183: }
184: }
185: orphan.close();
186: } catch (Throwable t) {
187: log.debug("Unable to close orphaned session", t);
188: }
189: }
190: }
191:
192: /**
193: * Unassociate a previously bound session from the current thread of execution.
194: *
195: * @return The session which was unbound.
196: */
197: public static Session unbind(SessionFactory factory) {
198: return doUnbind(factory, true);
199: }
200:
201: private static Session existingSession(SessionFactory factory) {
202: Map sessionMap = sessionMap();
203: if (sessionMap == null) {
204: return null;
205: } else {
206: return (Session) sessionMap.get(factory);
207: }
208: }
209:
210: protected static Map sessionMap() {
211: return (Map) context.get();
212: }
213:
214: private static void doBind(org.hibernate.Session session,
215: SessionFactory factory) {
216: Map sessionMap = sessionMap();
217: if (sessionMap == null) {
218: sessionMap = new HashMap();
219: context.set(sessionMap);
220: }
221: sessionMap.put(factory, session);
222: }
223:
224: private static Session doUnbind(SessionFactory factory,
225: boolean releaseMapIfEmpty) {
226: Map sessionMap = sessionMap();
227: Session session = null;
228: if (sessionMap != null) {
229: session = (Session) sessionMap.remove(factory);
230: if (releaseMapIfEmpty && sessionMap.isEmpty()) {
231: context.set(null);
232: }
233: }
234: return session;
235: }
236:
237: /**
238: * JTA transaction synch used for cleanup of the internal session map.
239: */
240: protected static class CleanupSynch implements Synchronization,
241: Serializable {
242: protected final SessionFactory factory;
243:
244: public CleanupSynch(SessionFactory factory) {
245: this .factory = factory;
246: }
247:
248: public void beforeCompletion() {
249: }
250:
251: public void afterCompletion(int i) {
252: unbind(factory);
253: }
254: }
255:
256: private class TransactionProtectionWrapper implements
257: InvocationHandler, Serializable {
258: private final Session realSession;
259: private Session wrappedSession;
260:
261: public TransactionProtectionWrapper(Session realSession) {
262: this .realSession = realSession;
263: }
264:
265: public Object invoke(Object proxy, Method method, Object[] args)
266: throws Throwable {
267: try {
268: // If close() is called, guarantee unbind()
269: if ("close".equals(method.getName())) {
270: unbind(realSession.getSessionFactory());
271: } else if ("toString".equals(method.getName())
272: || "equals".equals(method.getName())
273: || "hashCode".equals(method.getName())
274: || "getStatistics".equals(method.getName())
275: || "isOpen".equals(method.getName())) {
276: // allow these to go through the the real session no matter what
277: } else if (!realSession.isOpen()) {
278: // essentially, if the real session is closed allow any
279: // method call to pass through since the real session
280: // will complain by throwing an appropriate exception;
281: // NOTE that allowing close() above has the same basic effect,
282: // but we capture that there simply to perform the unbind...
283: } else if (!realSession.getTransaction().isActive()) {
284: // limit the methods available if no transaction is active
285: if ("beginTransaction".equals(method.getName())
286: || "getTransaction"
287: .equals(method.getName())
288: || "isTransactionInProgress".equals(method
289: .getName())
290: || "setFlushMode".equals(method.getName())
291: || "getSessionFactory".equals(method
292: .getName())) {
293: log.trace("allowing method ["
294: + method.getName()
295: + "] in non-transacted context");
296: } else if ("reconnect".equals(method.getName())
297: || "disconnect".equals(method.getName())) {
298: // allow these (deprecated) methods to pass through
299: } else {
300: throw new HibernateException(
301: method.getName()
302: + " is not valid without active transaction");
303: }
304: }
305: log.trace("allowing proxied method ["
306: + method.getName()
307: + "] to proceed to real session");
308: return method.invoke(realSession, args);
309: } catch (InvocationTargetException e) {
310: if (e.getTargetException() instanceof RuntimeException) {
311: throw (RuntimeException) e.getTargetException();
312: } else {
313: throw e;
314: }
315: }
316: }
317:
318: public void setWrapped(Session wrapped) {
319: this .wrappedSession = wrapped;
320: }
321:
322: // serialization ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
323:
324: private void writeObject(ObjectOutputStream oos)
325: throws IOException {
326: // if a ThreadLocalSessionContext-bound session happens to get
327: // serialized, to be completely correct, we need to make sure
328: // that unbinding of that session occurs.
329: oos.defaultWriteObject();
330: if (existingSession(factory) == wrappedSession) {
331: unbind(factory);
332: }
333: }
334:
335: private void readObject(ObjectInputStream ois)
336: throws IOException, ClassNotFoundException {
337: // on the inverse, it makes sense that if a ThreadLocalSessionContext-
338: // bound session then gets deserialized to go ahead and re-bind it to
339: // the ThreadLocalSessionContext session map.
340: ois.defaultReadObject();
341: realSession.getTransaction().registerSynchronization(
342: buildCleanupSynch());
343: doBind(wrappedSession, factory);
344: }
345: }
346: }
|