• Views
  • Iteration Report
  • My Iteration Report
  •  
OMERO.server
  • Login
  • Help/Guide
  • About Trac
  • Preferences
  • Wiki
  • Timeline
  • Roadmap
  • Browse Source
  • View Tickets
  • Search

Context Navigation

  • Last Change
  • Annotate
  • Revision Log

root/trunk/components/server/src/ome/tools/hibernate/SessionHandler.java

Revision 2677, 13.6 kB (checked in by jmoore, 5 months ago)

ticket:1013 - Delaying stateful session close to ServiceHandler

  • Property svn:keywords set to
    Date
    Revision
    Id
    URL
Line 
1/*
2 *   $Id$
3 *
4 *   Copyright 2006 University of Dundee. All rights reserved.
5 *   Use is subject to license terms supplied in LICENSE.txt
6 */
7package ome.tools.hibernate;
8
9import java.lang.reflect.InvocationHandler;
10import java.lang.reflect.Method;
11import java.lang.reflect.Proxy;
12import java.util.Collections;
13import java.util.Map;
14import java.util.WeakHashMap;
15
16import javax.sql.DataSource;
17
18import ome.api.StatefulServiceInterface;
19import ome.conditions.ApiUsageException;
20import ome.conditions.InternalException;
21import ome.services.messages.RegisterServiceCleanupMessage;
22import ome.system.OmeroContext;
23
24import org.aopalliance.intercept.MethodInterceptor;
25import org.aopalliance.intercept.MethodInvocation;
26import org.apache.commons.logging.Log;
27import org.apache.commons.logging.LogFactory;
28import org.hibernate.FlushMode;
29import org.hibernate.HibernateException;
30import org.hibernate.Session;
31import org.hibernate.SessionFactory;
32import org.springframework.beans.BeansException;
33import org.springframework.context.ApplicationContext;
34import org.springframework.context.ApplicationContextAware;
35import org.springframework.orm.hibernate3.HibernateInterceptor;
36import org.springframework.orm.hibernate3.SessionHolder;
37import org.springframework.transaction.support.TransactionSynchronizationManager;
38
39/**
40 * holder for Hibernate sessions in stateful servics. A count of calls is kept.
41 *
42 * @author Josh Moore &nbsp;&nbsp;&nbsp;&nbsp; <a
43 *         href="mailto:josh.moore@gmx.de">josh.moore@gmx.de</a>
44 * @version 3.0 <small> (<b>Internal version:</b> $Rev$ $Date$) </small>
45 * @since 3.0
46 */
47class SessionStatus {
48
49    int calls = 0;
50
51    Session session;
52
53    SessionStatus(Session session) {
54        if (null == session) {
55            throw new IllegalArgumentException("No null sessions.");
56        }
57
58        this.session = session;
59    }
60
61}
62
63/**
64 * interceptor which delegates to
65 * {@link org.springframework.orm.hibernate3.HibernateInterceptor} for stateless
66 * services but which keeps a {@link java.util.WeakHashMap} of sessions keyed by
67 * the stateful service reference.
68 *
69 * original idea from:
70 * http://opensource2.atlassian.com/confluence/spring/pages/viewpage.action?pageId=1447
71 *
72 * See also: http://sourceforge.net/forum/message.php?msg_id=2455707
73 * http://forum.springframework.org/archive/index.php/t-10344.html
74 * http://opensource2.atlassian.com/projects/spring/browse/SPR-746
75 *
76 * and these: http://www.hibernate.org/43.html#A5
77 * http://www.carbonfive.com/community/archives/2005/07/ive_been_meanin.html
78 * http://www.hibernate.org/377.html
79 *
80 * @author Josh Moore &nbsp;&nbsp;&nbsp;&nbsp; <a
81 *         href="mailto:josh.moore@gmx.de">josh.moore@gmx.de</a>
82 * @version 3.0 <small> (<b>Internal version:</b> $Rev$ $Date$) </small>
83 * @since 3.0
84 */
85public class SessionHandler implements MethodInterceptor,
86        ApplicationContextAware {
87
88    /**
89     * used by the SessionHandler to test for the end of the stateful service's
90     * life. Using reflection so we get a bit more type safety.
91     */
92    private final Method close;
93
94    private final static Log log = LogFactory.getLog(SessionHandler.class);
95
96    private final Map<Object, SessionStatus> sessions = Collections
97            .synchronizedMap(new WeakHashMap<Object, SessionStatus>());
98
99    private OmeroContext ctx;
100
101    private final SessionFactory factory;
102
103    private final static SessionHolder DUMMY = new EmptySessionHolder();
104
105    final private static String CTOR_MSG = "Both arguments to the SessionHandler"
106            + " constructor should be not null.";
107
108    /**
109     * constructor taking a {@link DataSource} and a {@link SessionFactory}. A
110     * new {@link HibernateInterceptor} will be created.
111     *
112     * @param dataSource
113     *            Not null.
114     * @param factory
115     *            Not null.
116     */
117    public SessionHandler(SessionFactory factory) {
118        if (factory == null) {
119            throw new ApiUsageException(CTOR_MSG);
120        }
121
122        this.factory = factory;
123
124        try {
125            close = StatefulServiceInterface.class.getMethod("close");
126        } catch (Exception e) {
127            throw new InternalException(
128                    "Can't get StatefulServiceInterface.close method.");
129        }
130
131    }
132
133    public void setApplicationContext(ApplicationContext applicationContext)
134            throws BeansException {
135        this.ctx = (OmeroContext) applicationContext;
136    }
137
138    public void cleanThread() {
139        if (TransactionSynchronizationManager.hasResource(factory)) {
140            SessionHolder holder = (SessionHolder) TransactionSynchronizationManager
141                    .getResource(factory);
142            if (holder == null) {
143                throw new IllegalStateException("Can't be null.");
144            } else if (holder == DUMMY) {
145                TransactionSynchronizationManager.unbindResource(factory);
146            } else {
147                throw new IllegalStateException("Thread corrupted.");
148            }
149        }
150    }
151
152    /**
153     * delegates to {@link HibernateInterceptor} or manages sessions internally,
154     * based on the type of service.
155     */
156    public Object invoke(final MethodInvocation invocation) throws Throwable {
157        // Stateless; normal semantics.
158        if (!StatefulServiceInterface.class.isAssignableFrom(invocation
159                .getThis().getClass())) {
160            throw new InternalException(
161                    "Stateless service configured as stateful.");
162        }
163
164        // Stateful; let's get to work.
165        debug("Performing action in stateful session.");
166        return doStateful(invocation);
167    }
168
169    private Object doStateful(final MethodInvocation invocation)
170            throws Throwable {
171        Object result = null;
172        SessionStatus status = null;
173        try {
174            // Need to open even if "closing" because the service may need
175            // to perform cleanup in its close() method.
176            status = newOrRestoredSession(invocation);
177            status.session.setFlushMode(FlushMode.COMMIT);
178            // changing MANUAL to COMMIT for ticket:557. the appserver
179            // won't allow us to commit here anyway, and setting to COMMIT
180            // prevents Spring from automatically re-writing the flushMode
181            // as AUTO
182            result = invocation.proceed();
183            return result;
184        } finally {
185            // TODO do we need to check for disconnected or closed session here?
186            // The newOrRestoredSession method does not attempt to close the
187            // session before throwing the dirty session exception. We must do
188            // it here.
189            try {
190                if (isCloseSession(invocation)) {
191                    ctx.publishMessage(new RegisterServiceCleanupMessage(this,
192                            invocation.getThis()) {
193                        @Override
194                        public void close() {
195                            SessionStatus status = sessions.remove(invocation
196                                    .getThis());
197                            status.session.disconnect();
198                            status.session.close();
199                        }
200                    });
201                } else {
202                    if (status != null) {
203                        // Guarantee that no one has changed the FlushMode
204                        status.session.setFlushMode(FlushMode.MANUAL);
205                        status.session.disconnect();
206                        status.calls--;
207                    }
208                }
209            } catch (Exception e) {
210
211                log.error("Error while closing/disconnecting session.", e);
212
213            } finally {
214
215                try {
216                    resetThreadSession();
217                } catch (Exception e) {
218                    log.error("Could not cleanup thread session.", e);
219                    throw e;
220                }
221
222            }
223
224        }
225    }
226
227    private SessionStatus newOrRestoredSession(MethodInvocation invocation)
228            throws HibernateException {
229
230        SessionStatus status = sessions.get(invocation.getThis());
231        Session previousSession = nullOrSessionBoundToThread();
232
233        // a session is currently running.
234        // something has gone wrong (e.g. with cleanup) abort!
235        if (previousSession != null) {
236            String msg = "Dirty Hibernate Session " + previousSession
237                    + " found in Thread " + Thread.currentThread();
238
239            // If it is closeSession, then this will be handled by
240            // the finally{} block of doStateful
241            if (!isCloseSession(invocation)) {
242                previousSession.close();
243            }
244            throw new InternalException(msg);
245        }
246
247        // we may or may not be in a session, but if we haven't yet bound
248        // it to This, then we need to.
249        else if (status == null || !status.session.isOpen()) {
250            Session currentSession = acquireAndBindSession();
251            status = new SessionStatus(currentSession);
252            sessions.put(invocation.getThis(), status);
253        }
254
255        // the session bound to This is already currently being called. abort!
256        else if (status.calls > 1) {
257            throw new InternalException(
258                    "Hibernate session is not re-entrant.\n"
259                            + "Either you have two threads operating on the same "
260                            + "stateful object (don't do this)\n or you have a "
261                            + "recursive call (recurse on the unwrapped object). ");
262        }
263
264        // all is fine.
265        else {
266            debug("Binding and reconnecting session.");
267            // TODO doesn't make sense to check, because hibernate always
268            // says "yes" if it has a connectionProvider
269            // if (status.session.isConnected())
270            // {
271            // throw new InternalException("Session already connected!");
272            // }
273            bindSession(status.session);
274            // Connection connection =
275            // DataSourceUtils.getConnection(dataSource);
276            // status.session.reconnect(connection);
277        }
278
279        // It's ready to be used. Increment.
280        status.calls++;
281        return status;
282
283    }
284
285    // ~ SESSIONS
286    // =========================================================================
287
288    private boolean isCloseSession(MethodInvocation invocation) {
289        return close.getName().equals(invocation.getMethod().getName());
290    }
291
292    private Session acquireAndBindSession() throws HibernateException {
293        debug("Opening and binding session.");
294        Session session = factory.openSession();
295        bindSession(session);
296        return session;
297    }
298
299    private void bindSession(Session session) {
300        debug("Binding session to thread.");
301        SessionHolder sessionHolder = new SessionHolder(session);
302        sessionHolder.setTransaction(sessionHolder.getSession()
303                .beginTransaction()); // FIXME TODO
304        // If we reach this point, it's ok to bind the new SessionHolder,
305        // however the DUMMY EmptySessionHolder may be present so unbind
306        // just in case.
307        if (TransactionSynchronizationManager.hasResource(factory)) {
308            TransactionSynchronizationManager.unbindResource(factory);
309        }
310        TransactionSynchronizationManager.bindResource(factory, sessionHolder);
311        if (!TransactionSynchronizationManager.isSynchronizationActive()) {
312            throw new InternalException("Synchronization not active for "
313                    + "TransactionSynchronizationManager");
314        }
315    }
316
317    private Session nullOrSessionBoundToThread() {
318        SessionHolder holder = null;
319        if (TransactionSynchronizationManager.hasResource(factory)) {
320            holder = (SessionHolder) TransactionSynchronizationManager
321                    .getResource(factory);
322            // A bit tricky. Works in coordinate with resetThreadSession
323            // since the DUMMY would be replaced anyway.
324            if (holder != null && holder.isEmpty()) {
325                holder = null;
326            }
327        }
328        return holder == null ? null : holder.getSession();
329    }
330
331    private boolean isSessionBoundToThread() {
332        return nullOrSessionBoundToThread() != null;
333    }
334
335    private void resetThreadSession() {
336        if (isSessionBoundToThread()) {
337            debug("Session bound to thread. Reseting.");
338            TransactionSynchronizationManager.unbindResource(factory);
339            TransactionSynchronizationManager.bindResource(factory, DUMMY);
340        } else {
341            debug("Session not bound to thread. No need to reset.");
342        }
343    }
344
345    private void debug(String message) {
346        if (log.isDebugEnabled()) {
347            log.debug(message);
348        }
349    }
350
351}
352
353class EmptySessionHolder extends SessionHolder {
354    public EmptySessionHolder() {
355        super((Session) Proxy.newProxyInstance(Session.class.getClassLoader(),
356                new Class[] { Session.class }, new InvocationHandler() {
357                    public Object invoke(Object proxy, Method method,
358                            Object[] args) throws Throwable {
359                        String name = method.getName();
360                        if (name.equals("toString")) {
361                            return "NULL SESSION PROXY";
362                        }
363
364                        else if (name.equals("hashCode")) {
365                            return 0;
366                        } else if (name.equals("equals")) {
367                            return args[0] == null ? false : proxy == args[0];
368                        } else {
369                            throw new RuntimeException("No methods allowed");
370                        }
371                    }
372                }));
373    }
374
375    @Override
376    public boolean isEmpty() {
377        return true;
378    }
379}
Note: See TracBrowser for help on using the browser.

Download in other formats:

  • Plain Text
  • Original Format

Trac Powered

Powered by Trac 0.11
By Edgewall Software.

Visit the Trac open source project at
http://trac.edgewall.org/