HanderInterceptor and OSIV async request changes

This change updates Open-Session-in-View filters and interceptors for
use in async requests mainly ensuring the open Hibernate session is
unbound from the main request processing thread and bound to the to
async thread.

Issue: SPR-8517
This commit is contained in:
Rossen Stoyanchev 2012-04-27 10:54:57 -04:00
parent 1eaaa9a446
commit e7506b50b2
12 changed files with 880 additions and 349 deletions

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2009 the original author or authors.
* Copyright 2002-2012 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -31,6 +31,8 @@ import org.springframework.orm.hibernate3.SessionFactoryUtils;
import org.springframework.orm.hibernate3.SessionHolder;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.request.async.AbstractDelegatingCallable;
import org.springframework.web.context.request.async.AsyncExecutionChain;
import org.springframework.web.context.support.WebApplicationContextUtils;
import org.springframework.web.filter.OncePerRequestFilter;
@ -168,6 +170,8 @@ public class OpenSessionInViewFilter extends OncePerRequestFilter {
HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
AsyncExecutionChain chain = AsyncExecutionChain.getForCurrentRequest(request);
SessionFactory sessionFactory = lookupSessionFactory(request);
boolean participate = false;
@ -180,7 +184,10 @@ public class OpenSessionInViewFilter extends OncePerRequestFilter {
else {
logger.debug("Opening single Hibernate Session in OpenSessionInViewFilter");
Session session = getSession(sessionFactory);
TransactionSynchronizationManager.bindResource(sessionFactory, new SessionHolder(session));
SessionHolder sessionHolder = new SessionHolder(session);
TransactionSynchronizationManager.bindResource(sessionFactory, sessionHolder);
chain.addDelegatingCallable(getAsyncCallable(request, sessionFactory, sessionHolder));
}
}
else {
@ -204,6 +211,9 @@ public class OpenSessionInViewFilter extends OncePerRequestFilter {
// single session mode
SessionHolder sessionHolder =
(SessionHolder) TransactionSynchronizationManager.unbindResource(sessionFactory);
if (chain.isAsyncStarted()) {
return;
}
logger.debug("Closing single Hibernate Session in OpenSessionInViewFilter");
closeSession(sessionHolder.getSession(), sessionFactory);
}
@ -279,4 +289,28 @@ public class OpenSessionInViewFilter extends OncePerRequestFilter {
SessionFactoryUtils.closeSession(session);
}
/**
* Create a Callable to extend the use of the open Hibernate Session to the
* async thread completing the request.
*/
private AbstractDelegatingCallable getAsyncCallable(final HttpServletRequest request,
final SessionFactory sessionFactory, final SessionHolder sessionHolder) {
return new AbstractDelegatingCallable() {
public Object call() throws Exception {
TransactionSynchronizationManager.bindResource(sessionFactory, sessionHolder);
try {
getNextCallable().call();
}
finally {
SessionHolder sessionHolder =
(SessionHolder) TransactionSynchronizationManager.unbindResource(sessionFactory);
logger.debug("Closing Hibernate Session in OpenSessionInViewFilter");
SessionFactoryUtils.closeSession(sessionHolder.getSession());
}
return null;
}
};
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2008 the original author or authors.
* Copyright 2002-2012 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -18,7 +18,6 @@ package org.springframework.orm.hibernate3.support;
import org.hibernate.HibernateException;
import org.hibernate.Session;
import org.springframework.dao.DataAccessException;
import org.springframework.orm.hibernate3.HibernateAccessor;
import org.springframework.orm.hibernate3.SessionFactoryUtils;
@ -26,7 +25,8 @@ import org.springframework.orm.hibernate3.SessionHolder;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import org.springframework.ui.ModelMap;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.context.request.WebRequestInterceptor;
import org.springframework.web.context.request.async.AbstractDelegatingCallable;
import org.springframework.web.context.request.async.AsyncWebRequestInterceptor;
/**
* Spring web request interceptor that binds a Hibernate <code>Session</code> to the
@ -89,7 +89,7 @@ import org.springframework.web.context.request.WebRequestInterceptor;
* @see org.springframework.orm.hibernate3.SessionFactoryUtils#getSession
* @see org.springframework.transaction.support.TransactionSynchronizationManager
*/
public class OpenSessionInViewInterceptor extends HibernateAccessor implements WebRequestInterceptor {
public class OpenSessionInViewInterceptor extends HibernateAccessor implements AsyncWebRequestInterceptor {
/**
* Suffix that gets appended to the <code>SessionFactory</code>
@ -155,7 +155,8 @@ public class OpenSessionInViewInterceptor extends HibernateAccessor implements W
Session session = SessionFactoryUtils.getSession(
getSessionFactory(), getEntityInterceptor(), getJdbcExceptionTranslator());
applyFlushMode(session, false);
TransactionSynchronizationManager.bindResource(getSessionFactory(), new SessionHolder(session));
SessionHolder sessionHolder = new SessionHolder(session);
TransactionSynchronizationManager.bindResource(getSessionFactory(), sessionHolder);
}
else {
// deferred close mode
@ -164,6 +165,39 @@ public class OpenSessionInViewInterceptor extends HibernateAccessor implements W
}
}
/**
* Create a <code>Callable</code> to bind the <code>Hibernate</code> session
* to the async request thread.
*/
public AbstractDelegatingCallable getAsyncCallable(WebRequest request) {
String attributeName = getParticipateAttributeName();
if ((request.getAttribute(attributeName, WebRequest.SCOPE_REQUEST) != null) || !isSingleSession()) {
return null;
}
final SessionHolder sessionHolder =
(SessionHolder) TransactionSynchronizationManager.getResource(getSessionFactory());
return new AbstractDelegatingCallable() {
public Object call() throws Exception {
TransactionSynchronizationManager.bindResource(getSessionFactory(), sessionHolder);
getNextCallable().call();
return null;
}
};
}
/**
* Unbind the Hibernate <code>Session</code> from the main thread but leave
* the <code>Session</code> open for further use from the async thread.
*/
public void postHandleAsyncStarted(WebRequest request) {
String attributeName = getParticipateAttributeName();
if ((request.getAttribute(attributeName, WebRequest.SCOPE_REQUEST) == null) && isSingleSession()) {
TransactionSynchronizationManager.unbindResource(getSessionFactory());
}
}
/**
* Flush the Hibernate <code>Session</code> before view rendering, if necessary.
* <p>Note that this just applies in {@link #isSingleSession() single session mode}!

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2011 the original author or authors.
* Copyright 2002-2012 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -32,6 +32,8 @@ import org.springframework.orm.hibernate4.SessionFactoryUtils;
import org.springframework.orm.hibernate4.SessionHolder;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.request.async.AbstractDelegatingCallable;
import org.springframework.web.context.request.async.AsyncExecutionChain;
import org.springframework.web.context.support.WebApplicationContextUtils;
import org.springframework.web.filter.OncePerRequestFilter;
@ -102,6 +104,8 @@ public class OpenSessionInViewFilter extends OncePerRequestFilter {
HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
AsyncExecutionChain chain = AsyncExecutionChain.getForCurrentRequest(request);
SessionFactory sessionFactory = lookupSessionFactory(request);
boolean participate = false;
@ -112,7 +116,10 @@ public class OpenSessionInViewFilter extends OncePerRequestFilter {
else {
logger.debug("Opening Hibernate Session in OpenSessionInViewFilter");
Session session = openSession(sessionFactory);
TransactionSynchronizationManager.bindResource(sessionFactory, new SessionHolder(session));
SessionHolder sessionHolder = new SessionHolder(session);
TransactionSynchronizationManager.bindResource(sessionFactory, sessionHolder);
chain.addDelegatingCallable(getAsyncCallable(request, sessionFactory, sessionHolder));
}
try {
@ -123,6 +130,9 @@ public class OpenSessionInViewFilter extends OncePerRequestFilter {
if (!participate) {
SessionHolder sessionHolder =
(SessionHolder) TransactionSynchronizationManager.unbindResource(sessionFactory);
if (chain.isAsyncStarted()) {
return;
}
logger.debug("Closing Hibernate Session in OpenSessionInViewFilter");
SessionFactoryUtils.closeSession(sessionHolder.getSession());
}
@ -177,4 +187,28 @@ public class OpenSessionInViewFilter extends OncePerRequestFilter {
}
}
/**
* Create a Callable to extend the use of the open Hibernate Session to the
* async thread completing the request.
*/
private AbstractDelegatingCallable getAsyncCallable(final HttpServletRequest request,
final SessionFactory sessionFactory, final SessionHolder sessionHolder) {
return new AbstractDelegatingCallable() {
public Object call() throws Exception {
TransactionSynchronizationManager.bindResource(sessionFactory, sessionHolder);
try {
getNextCallable().call();
}
finally {
SessionHolder sessionHolder =
(SessionHolder) TransactionSynchronizationManager.unbindResource(sessionFactory);
logger.debug("Closing Hibernate Session in OpenSessionInViewFilter");
SessionFactoryUtils.closeSession(sessionHolder.getSession());
}
return null;
}
};
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2011 the original author or authors.
* Copyright 2002-2012 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -22,7 +22,6 @@ import org.hibernate.FlushMode;
import org.hibernate.HibernateException;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.springframework.dao.DataAccessException;
import org.springframework.dao.DataAccessResourceFailureException;
import org.springframework.orm.hibernate4.SessionFactoryUtils;
@ -30,7 +29,9 @@ import org.springframework.orm.hibernate4.SessionHolder;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import org.springframework.ui.ModelMap;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.context.request.WebRequestInterceptor;
import org.springframework.web.context.request.async.AbstractDelegatingCallable;
import org.springframework.web.context.request.async.AsyncExecutionChain;
import org.springframework.web.context.request.async.AsyncWebRequestInterceptor;
/**
* Spring web request interceptor that binds a Hibernate <code>Session</code> to the
@ -72,7 +73,7 @@ import org.springframework.web.context.request.WebRequestInterceptor;
* @see org.springframework.orm.hibernate4.HibernateTransactionManager
* @see org.springframework.transaction.support.TransactionSynchronizationManager
*/
public class OpenSessionInViewInterceptor implements WebRequestInterceptor {
public class OpenSessionInViewInterceptor implements AsyncWebRequestInterceptor {
/**
* Suffix that gets appended to the <code>SessionFactory</code>
@ -112,13 +113,47 @@ public class OpenSessionInViewInterceptor implements WebRequestInterceptor {
else {
logger.debug("Opening Hibernate Session in OpenSessionInViewInterceptor");
Session session = openSession();
TransactionSynchronizationManager.bindResource(getSessionFactory(), new SessionHolder(session));
SessionHolder sessionHolder = new SessionHolder(session);
TransactionSynchronizationManager.bindResource(getSessionFactory(), sessionHolder);
}
}
public void postHandle(WebRequest request, ModelMap model) {
}
/**
* Create a <code>Callable</code> to bind the <code>Hibernate</code> session
* to the async request thread.
*/
public AbstractDelegatingCallable getAsyncCallable(WebRequest request) {
String attributeName = getParticipateAttributeName();
if (request.getAttribute(attributeName, WebRequest.SCOPE_REQUEST) != null) {
return null;
}
final SessionHolder sessionHolder =
(SessionHolder) TransactionSynchronizationManager.getResource(getSessionFactory());
return new AbstractDelegatingCallable() {
public Object call() throws Exception {
TransactionSynchronizationManager.bindResource(getSessionFactory(), sessionHolder);
getNextCallable().call();
return null;
}
};
}
/**
* Unbind the Hibernate <code>Session</code> from the main thread leaving
* it open for further use from an async thread.
*/
public void postHandleAsyncStarted(WebRequest request) {
String attributeName = getParticipateAttributeName();
if (request.getAttribute(attributeName, WebRequest.SCOPE_REQUEST) == null) {
TransactionSynchronizationManager.unbindResource(getSessionFactory());
}
}
/**
* Unbind the Hibernate <code>Session</code> from the thread and close it).
* @see org.springframework.transaction.support.TransactionSynchronizationManager

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2011 the original author or authors.
* Copyright 2002-2012 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -16,23 +16,35 @@
package org.springframework.orm.hibernate3.support;
import static org.easymock.EasyMock.createStrictMock;
import static org.easymock.EasyMock.expect;
import static org.easymock.EasyMock.replay;
import static org.easymock.EasyMock.reset;
import static org.easymock.EasyMock.verify;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import java.io.IOException;
import java.sql.Connection;
import java.util.concurrent.Callable;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.transaction.TransactionManager;
import junit.framework.TestCase;
import org.easymock.MockControl;
import org.hibernate.FlushMode;
import org.hibernate.HibernateException;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.classic.Session;
import org.hibernate.engine.SessionFactoryImplementor;
import org.junit.Before;
import org.junit.Test;
import org.springframework.core.task.SimpleAsyncTaskExecutor;
import org.springframework.mock.web.MockFilterConfig;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
@ -47,307 +59,341 @@ import org.springframework.transaction.support.DefaultTransactionDefinition;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.context.request.async.AbstractDelegatingCallable;
import org.springframework.web.context.request.async.AsyncExecutionChain;
import org.springframework.web.context.request.async.AsyncWebRequest;
import org.springframework.web.context.support.StaticWebApplicationContext;
/**
* @author Juergen Hoeller
* @author Rossen Stoyanchev
* @since 05.03.2005
*/
public class OpenSessionInViewTests extends TestCase {
public class OpenSessionInViewTests {
private MockServletContext sc;
private MockHttpServletRequest request;
private MockHttpServletResponse response;
private ServletWebRequest webRequest;
@Before
public void setup() {
this.sc = new MockServletContext();
this.request = new MockHttpServletRequest(sc);
this.response = new MockHttpServletResponse();
this.webRequest = new ServletWebRequest(this.request);
}
@Test
public void testOpenSessionInViewInterceptorWithSingleSession() throws Exception {
//SessionFactory sf = createMock(SessionFactory.class);
//Session session = createMock(Session.class);
MockControl sfControl = MockControl.createControl(SessionFactory.class);
final SessionFactory sf = (SessionFactory) sfControl.getMock();
MockControl sessionControl = MockControl.createControl(Session.class);
Session session = (Session) sessionControl.getMock();
SessionFactory sf = createStrictMock(SessionFactory.class);
Session session = createStrictMock(Session.class);
OpenSessionInViewInterceptor interceptor = new OpenSessionInViewInterceptor();
interceptor.setSessionFactory(sf);
MockServletContext sc = new MockServletContext();
MockHttpServletRequest request = new MockHttpServletRequest(sc);
//expect(mockStorage.size()).andReturn(expectedValue);
//expect(sf.openSession()).andReturn(session);
sf.openSession();
sfControl.setReturnValue(session, 1);
session.getSessionFactory();
sessionControl.setReturnValue(sf, 2);
session.isOpen();
sessionControl.setReturnValue(true, 1);
expect(sf.openSession()).andReturn(session);
expect(session.getSessionFactory()).andReturn(sf);
session.setFlushMode(FlushMode.MANUAL);
sessionControl.setVoidCallable(1);
sfControl.replay();
sessionControl.replay();
interceptor.preHandle(new ServletWebRequest(request));
expect(session.getSessionFactory()).andReturn(sf);
expect(session.isOpen()).andReturn(true);
replay(sf);
replay(session);
interceptor.preHandle(this.webRequest);
assertTrue(TransactionSynchronizationManager.hasResource(sf));
// check that further invocations simply participate
interceptor.preHandle(new ServletWebRequest(request));
interceptor.preHandle(this.webRequest);
assertEquals(session, SessionFactoryUtils.getSession(sf, false));
interceptor.preHandle(new ServletWebRequest(request));
interceptor.postHandle(new ServletWebRequest(request), null);
interceptor.afterCompletion(new ServletWebRequest(request), null);
interceptor.preHandle(this.webRequest);
interceptor.postHandle(this.webRequest, null);
interceptor.afterCompletion(this.webRequest, null);
interceptor.postHandle(new ServletWebRequest(request), null);
interceptor.afterCompletion(new ServletWebRequest(request), null);
interceptor.postHandle(this.webRequest, null);
interceptor.afterCompletion(this.webRequest, null);
interceptor.preHandle(new ServletWebRequest(request));
interceptor.postHandle(new ServletWebRequest(request), null);
interceptor.afterCompletion(new ServletWebRequest(request), null);
interceptor.preHandle(this.webRequest);
interceptor.postHandle(this.webRequest, null);
interceptor.afterCompletion(this.webRequest, null);
sfControl.verify();
sessionControl.verify();
verify(sf);
verify(session);
reset(sf);
reset(session);
replay(sf);
replay(session);
sfControl.reset();
sessionControl.reset();
sfControl.replay();
sessionControl.replay();
interceptor.postHandle(new ServletWebRequest(request), null);
interceptor.postHandle(this.webRequest, null);
assertTrue(TransactionSynchronizationManager.hasResource(sf));
sfControl.verify();
sessionControl.verify();
sfControl.reset();
sessionControl.reset();
session.close();
sessionControl.setReturnValue(null, 1);
sfControl.replay();
sessionControl.replay();
interceptor.afterCompletion(new ServletWebRequest(request), null);
verify(sf);
verify(session);
reset(sf);
reset(session);
expect(session.close()).andReturn(null);
replay(sf);
replay(session);
interceptor.afterCompletion(this.webRequest, null);
assertFalse(TransactionSynchronizationManager.hasResource(sf));
sfControl.verify();
sessionControl.verify();
verify(sf);
verify(session);
}
@Test
public void testOpenSessionInViewInterceptorAsyncScenario() throws Exception {
final SessionFactory sf = createStrictMock(SessionFactory.class);
Session session = createStrictMock(Session.class);
OpenSessionInViewInterceptor interceptor = new OpenSessionInViewInterceptor();
interceptor.setSessionFactory(sf);
expect(sf.openSession()).andReturn(session);
expect(session.getSessionFactory()).andReturn(sf);
session.setFlushMode(FlushMode.MANUAL);
replay(sf);
replay(session);
interceptor.preHandle(this.webRequest);
assertTrue(TransactionSynchronizationManager.hasResource(sf));
AbstractDelegatingCallable asyncCallable = interceptor.getAsyncCallable(this.webRequest);
assertNotNull(asyncCallable);
interceptor.postHandleAsyncStarted(this.webRequest);
assertFalse(TransactionSynchronizationManager.hasResource(sf));
verify(sf);
verify(session);
asyncCallable.setNextCallable(new Callable<Object>() {
public Object call() {
return null;
}
});
asyncCallable.call();
assertTrue("Session not bound to async thread", TransactionSynchronizationManager.hasResource(sf));
verify(sf);
verify(session);
reset(sf);
reset(session);
replay(sf);
replay(session);
interceptor.postHandle(this.webRequest, null);
assertTrue(TransactionSynchronizationManager.hasResource(sf));
verify(sf);
verify(session);
reset(sf);
reset(session);
expect(session.close()).andReturn(null);
replay(sf);
replay(session);
interceptor.afterCompletion(this.webRequest, null);
assertFalse(TransactionSynchronizationManager.hasResource(sf));
verify(sf);
verify(session);
}
@Test
public void testOpenSessionInViewInterceptorWithSingleSessionAndJtaTm() throws Exception {
MockControl sfControl = MockControl.createControl(SessionFactoryImplementor.class);
final SessionFactoryImplementor sf = (SessionFactoryImplementor) sfControl.getMock();
MockControl sessionControl = MockControl.createControl(Session.class);
Session session = (Session) sessionControl.getMock();
final SessionFactoryImplementor sf = createStrictMock(SessionFactoryImplementor.class);
Session session = createStrictMock(Session.class);
MockControl tmControl = MockControl.createControl(TransactionManager.class);
TransactionManager tm = (TransactionManager) tmControl.getMock();
tm.getTransaction();
tmControl.setReturnValue(null, 2);
TransactionManager tm = createStrictMock(TransactionManager.class);
expect(tm.getTransaction()).andReturn(null);
expect(tm.getTransaction()).andReturn(null);
replay(tm);
OpenSessionInViewInterceptor interceptor = new OpenSessionInViewInterceptor();
interceptor.setSessionFactory(sf);
MockServletContext sc = new MockServletContext();
MockHttpServletRequest request = new MockHttpServletRequest(sc);
MockHttpServletResponse response = new MockHttpServletResponse();
sf.getTransactionManager();
sfControl.setReturnValue(tm, 2);
sf.openSession();
sfControl.setReturnValue(session, 1);
session.isOpen();
sessionControl.setReturnValue(true, 1);
expect(sf.openSession()).andReturn(session);
expect(sf.getTransactionManager()).andReturn(tm);
session.setFlushMode(FlushMode.MANUAL);
sessionControl.setVoidCallable(1);
expect(sf.getTransactionManager()).andReturn(tm);
expect(session.isOpen()).andReturn(true);
tmControl.replay();
sfControl.replay();
sessionControl.replay();
replay(sf);
replay(session);
interceptor.preHandle(new ServletWebRequest(request));
interceptor.preHandle(this.webRequest);
assertTrue(TransactionSynchronizationManager.hasResource(sf));
// check that further invocations simply participate
interceptor.preHandle(new ServletWebRequest(request));
interceptor.preHandle(this.webRequest);
assertEquals(session, SessionFactoryUtils.getSession(sf, false));
interceptor.preHandle(new ServletWebRequest(request));
interceptor.postHandle(new ServletWebRequest(request), null);
interceptor.afterCompletion(new ServletWebRequest(request), null);
interceptor.preHandle(this.webRequest);
interceptor.postHandle(this.webRequest, null);
interceptor.afterCompletion(this.webRequest, null);
interceptor.postHandle(new ServletWebRequest(request), null);
interceptor.afterCompletion(new ServletWebRequest(request), null);
interceptor.postHandle(this.webRequest, null);
interceptor.afterCompletion(this.webRequest, null);
interceptor.preHandle(new ServletWebRequest(request));
interceptor.postHandle(new ServletWebRequest(request), null);
interceptor.afterCompletion(new ServletWebRequest(request), null);
interceptor.preHandle(this.webRequest);
interceptor.postHandle(this.webRequest, null);
interceptor.afterCompletion(this.webRequest, null);
sfControl.verify();
sessionControl.verify();
verify(sf);
verify(session);
sfControl.reset();
sessionControl.reset();
sfControl.replay();
sessionControl.replay();
interceptor.postHandle(new ServletWebRequest(request), null);
reset(sf);
reset(session);
replay(sf);
replay(session);
interceptor.postHandle(this.webRequest, null);
assertTrue(TransactionSynchronizationManager.hasResource(sf));
sfControl.verify();
sessionControl.verify();
sfControl.reset();
sessionControl.reset();
session.close();
sessionControl.setReturnValue(null, 1);
sfControl.replay();
sessionControl.replay();
interceptor.afterCompletion(new ServletWebRequest(request), null);
verify(sf);
verify(session);
reset(sf);
reset(session);
expect(session.close()).andReturn(null);
replay(sf);
replay(session);
interceptor.afterCompletion(this.webRequest, null);
assertFalse(TransactionSynchronizationManager.hasResource(sf));
sfControl.verify();
sessionControl.verify();
verify(sf);
verify(session);
}
@Test
public void testOpenSessionInViewInterceptorWithSingleSessionAndFlush() throws Exception {
MockControl sfControl = MockControl.createControl(SessionFactory.class);
final SessionFactory sf = (SessionFactory) sfControl.getMock();
MockControl sessionControl = MockControl.createControl(Session.class);
Session session = (Session) sessionControl.getMock();
SessionFactory sf = createStrictMock(SessionFactory.class);
Session session = createStrictMock(Session.class);
OpenSessionInViewInterceptor interceptor = new OpenSessionInViewInterceptor();
interceptor.setSessionFactory(sf);
interceptor.setFlushMode(HibernateAccessor.FLUSH_AUTO);
MockServletContext sc = new MockServletContext();
MockHttpServletRequest request = new MockHttpServletRequest(sc);
MockHttpServletResponse response = new MockHttpServletResponse();
sf.openSession();
sfControl.setReturnValue(session, 1);
session.getSessionFactory();
sessionControl.setReturnValue(sf);
sfControl.replay();
sessionControl.replay();
interceptor.preHandle(new ServletWebRequest(request));
expect(sf.openSession()).andReturn(session);
expect(session.getSessionFactory()).andReturn(sf);
replay(sf);
replay(session);
interceptor.preHandle(this.webRequest);
assertTrue(TransactionSynchronizationManager.hasResource(sf));
sfControl.verify();
sessionControl.verify();
verify(sf);
verify(session);
sfControl.reset();
sessionControl.reset();
reset(sf);
reset(session);
session.flush();
sessionControl.setVoidCallable(1);
sfControl.replay();
sessionControl.replay();
interceptor.postHandle(new ServletWebRequest(request), null);
replay(sf);
replay(session);
interceptor.postHandle(this.webRequest, null);
assertTrue(TransactionSynchronizationManager.hasResource(sf));
sfControl.verify();
sessionControl.verify();
verify(sf);
verify(session);
sfControl.reset();
sessionControl.reset();
session.close();
sessionControl.setReturnValue(null, 1);
sfControl.replay();
sessionControl.replay();
interceptor.afterCompletion(new ServletWebRequest(request), null);
reset(sf);
reset(session);
expect(session.close()).andReturn(null);
replay(sf);
replay(session);
interceptor.afterCompletion(this.webRequest, null);
assertFalse(TransactionSynchronizationManager.hasResource(sf));
sfControl.verify();
sessionControl.verify();
verify(sf);
verify(session);
}
@Test
public void testOpenSessionInViewInterceptorAndDeferredClose() throws Exception {
MockControl sfControl = MockControl.createControl(SessionFactory.class);
final SessionFactory sf = (SessionFactory) sfControl.getMock();
MockControl sessionControl = MockControl.createControl(Session.class);
Session session = (Session) sessionControl.getMock();
SessionFactory sf = createStrictMock(SessionFactory.class);
Session session = createStrictMock(Session.class);
OpenSessionInViewInterceptor interceptor = new OpenSessionInViewInterceptor();
interceptor.setSessionFactory(sf);
interceptor.setSingleSession(false);
MockServletContext sc = new MockServletContext();
MockHttpServletRequest request = new MockHttpServletRequest(sc);
MockHttpServletResponse response = new MockHttpServletResponse();
sf.openSession();
sfControl.setReturnValue(session, 1);
session.getSessionFactory();
sessionControl.setReturnValue(sf, 1);
expect(sf.openSession()).andReturn(session);
expect(session.getSessionFactory()).andReturn(sf);
session.setFlushMode(FlushMode.MANUAL);
sessionControl.setVoidCallable(1);
sfControl.replay();
sessionControl.replay();
replay(sf);
replay(session);
interceptor.preHandle(new ServletWebRequest(request));
interceptor.preHandle(this.webRequest);
org.hibernate.Session sess = SessionFactoryUtils.getSession(sf, true);
SessionFactoryUtils.releaseSession(sess, sf);
// check that further invocations simply participate
interceptor.preHandle(new ServletWebRequest(request));
interceptor.preHandle(this.webRequest);
interceptor.preHandle(new ServletWebRequest(request));
interceptor.postHandle(new ServletWebRequest(request), null);
interceptor.afterCompletion(new ServletWebRequest(request), null);
interceptor.preHandle(this.webRequest);
interceptor.postHandle(this.webRequest, null);
interceptor.afterCompletion(this.webRequest, null);
interceptor.postHandle(new ServletWebRequest(request), null);
interceptor.afterCompletion(new ServletWebRequest(request), null);
interceptor.postHandle(this.webRequest, null);
interceptor.afterCompletion(this.webRequest, null);
interceptor.preHandle(new ServletWebRequest(request));
interceptor.postHandle(new ServletWebRequest(request), null);
interceptor.afterCompletion(new ServletWebRequest(request), null);
interceptor.preHandle(this.webRequest);
interceptor.postHandle(this.webRequest, null);
interceptor.afterCompletion(this.webRequest, null);
sfControl.verify();
sessionControl.verify();
sfControl.reset();
sessionControl.reset();
verify(sf);
verify(session);
session.close();
sessionControl.setReturnValue(null, 1);
sfControl.replay();
sessionControl.replay();
reset(sf);
reset(session);
expect(session.close()).andReturn(null);
replay(sf);
replay(session);
interceptor.postHandle(new ServletWebRequest(request), null);
interceptor.afterCompletion(new ServletWebRequest(request), null);
sfControl.verify();
sessionControl.verify();
interceptor.postHandle(this.webRequest, null);
interceptor.afterCompletion(this.webRequest, null);
verify(sf);
verify(session);
}
@Test
public void testOpenSessionInViewFilterWithSingleSession() throws Exception {
MockControl sfControl = MockControl.createControl(SessionFactory.class);
final SessionFactory sf = (SessionFactory) sfControl.getMock();
MockControl sessionControl = MockControl.createControl(Session.class);
Session session = (Session) sessionControl.getMock();
final SessionFactory sf = createStrictMock(SessionFactory.class);
Session session = createStrictMock(Session.class);
sf.openSession();
sfControl.setReturnValue(session, 1);
session.getSessionFactory();
sessionControl.setReturnValue(sf);
expect(sf.openSession()).andReturn(session);
expect(session.getSessionFactory()).andReturn(sf);
session.setFlushMode(FlushMode.MANUAL);
sessionControl.setVoidCallable(1);
session.close();
sessionControl.setReturnValue(null, 1);
sfControl.replay();
sessionControl.replay();
expect(session.close()).andReturn(null);
replay(sf);
replay(session);
MockControl sf2Control = MockControl.createControl(SessionFactory.class);
final SessionFactory sf2 = (SessionFactory) sf2Control.getMock();
MockControl session2Control = MockControl.createControl(Session.class);
Session session2 = (Session) session2Control.getMock();
final SessionFactory sf2 = createStrictMock(SessionFactory.class);
Session session2 = createStrictMock(Session.class);
sf2.openSession();
sf2Control.setReturnValue(session2, 1);
session2.getSessionFactory();
session2Control.setReturnValue(sf);
expect(sf2.openSession()).andReturn(session2);
expect(session2.getSessionFactory()).andReturn(sf2);
session2.setFlushMode(FlushMode.AUTO);
session2Control.setVoidCallable(1);
session2.close();
session2Control.setReturnValue(null, 1);
sf2Control.replay();
session2Control.replay();
expect(session2.close()).andReturn(null);
replay(sf2);
replay(session2);
MockServletContext sc = new MockServletContext();
StaticWebApplicationContext wac = new StaticWebApplicationContext();
wac.setServletContext(sc);
wac.getDefaultListableBeanFactory().registerSingleton("sessionFactory", sf);
wac.getDefaultListableBeanFactory().registerSingleton("mySessionFactory", sf2);
wac.refresh();
sc.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, wac);
MockHttpServletRequest request = new MockHttpServletRequest(sc);
MockHttpServletResponse response = new MockHttpServletResponse();
MockFilterConfig filterConfig = new MockFilterConfig(wac.getServletContext(), "filter");
MockFilterConfig filterConfig2 = new MockFilterConfig(wac.getServletContext(), "filter2");
@ -378,44 +424,112 @@ public class OpenSessionInViewTests extends TestCase {
assertFalse(TransactionSynchronizationManager.hasResource(sf));
assertFalse(TransactionSynchronizationManager.hasResource(sf2));
filter2.doFilter(request, response, filterChain3);
filter2.doFilter(this.request, this.response, filterChain3);
assertFalse(TransactionSynchronizationManager.hasResource(sf));
assertFalse(TransactionSynchronizationManager.hasResource(sf2));
assertNotNull(request.getAttribute("invoked"));
assertNotNull(this.request.getAttribute("invoked"));
sfControl.verify();
sessionControl.verify();
sf2Control.verify();
session2Control.verify();
verify(sf);
verify(session);
verify(sf2);
verify(session2);
wac.close();
}
public void testOpenSessionInViewFilterWithSingleSessionAndPreBoundSession() throws Exception {
MockControl sfControl = MockControl.createControl(SessionFactory.class);
final SessionFactory sf = (SessionFactory) sfControl.getMock();
MockControl sessionControl = MockControl.createControl(Session.class);
Session session = (Session) sessionControl.getMock();
@Test
public void testOpenSessionInViewFilterWithSingleSessionAsyncScenario() throws Exception {
final SessionFactory sf = createStrictMock(SessionFactory.class);
Session session = createStrictMock(Session.class);
sf.openSession();
sfControl.setReturnValue(session, 1);
session.getSessionFactory();
sessionControl.setReturnValue(sf);
expect(sf.openSession()).andReturn(session);
expect(session.getSessionFactory()).andReturn(sf);
session.setFlushMode(FlushMode.MANUAL);
sessionControl.setVoidCallable(1);
session.close();
sessionControl.setReturnValue(null, 1);
sfControl.replay();
sessionControl.replay();
replay(sf);
replay(session);
StaticWebApplicationContext wac = new StaticWebApplicationContext();
wac.setServletContext(sc);
wac.getDefaultListableBeanFactory().registerSingleton("sessionFactory", sf);
wac.refresh();
sc.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, wac);
MockFilterConfig filterConfig = new MockFilterConfig(wac.getServletContext(), "filter");
final OpenSessionInViewFilter filter = new OpenSessionInViewFilter();
filter.init(filterConfig);
final FilterChain filterChain = new FilterChain() {
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse) {
assertTrue(TransactionSynchronizationManager.hasResource(sf));
servletRequest.setAttribute("invoked", Boolean.TRUE);
}
};
AsyncWebRequest asyncWebRequest = createStrictMock(AsyncWebRequest.class);
expect(asyncWebRequest.isAsyncStarted()).andReturn(true);
expect(asyncWebRequest.isAsyncStarted()).andReturn(true);
replay(asyncWebRequest);
AsyncExecutionChain chain = AsyncExecutionChain.getForCurrentRequest(this.request);
chain.setAsyncWebRequest(asyncWebRequest);
assertFalse(TransactionSynchronizationManager.hasResource(sf));
filter.doFilter(this.request, this.response, filterChain);
assertFalse(TransactionSynchronizationManager.hasResource(sf));
assertNotNull(this.request.getAttribute("invoked"));
verify(sf);
verify(session);
verify(asyncWebRequest);
chain.setTaskExecutor(new SyncTaskExecutor());
chain.setCallable(new Callable<Object>() {
public Object call() {
assertTrue(TransactionSynchronizationManager.hasResource(sf));
return null;
}
});
reset(asyncWebRequest);
asyncWebRequest.startAsync();
expect(asyncWebRequest.isAsyncCompleted()).andReturn(false);
asyncWebRequest.complete();
replay(asyncWebRequest);
reset(sf);
reset(session);
expect(session.close()).andReturn(null);
replay(sf);
replay(session);
chain.startCallableChainProcessing();
assertFalse(TransactionSynchronizationManager.hasResource(sf));
verify(sf);
verify(session);
verify(asyncWebRequest);
wac.close();
}
@Test
public void testOpenSessionInViewFilterWithSingleSessionAndPreBoundSession() throws Exception {
final SessionFactory sf = createStrictMock(SessionFactory.class);
Session session = createStrictMock(Session.class);
expect(sf.openSession()).andReturn(session);
expect(session.getSessionFactory()).andReturn(sf);
session.setFlushMode(FlushMode.MANUAL);
expect(session.close()).andReturn(null);
replay(sf);
replay(session);
MockServletContext sc = new MockServletContext();
StaticWebApplicationContext wac = new StaticWebApplicationContext();
wac.setServletContext(sc);
wac.getDefaultListableBeanFactory().registerSingleton("sessionFactory", sf);
wac.refresh();
sc.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, wac);
MockHttpServletRequest request = new MockHttpServletRequest(sc);
MockHttpServletResponse response = new MockHttpServletResponse();
MockFilterConfig filterConfig = new MockFilterConfig(wac.getServletContext(), "filter");
MockFilterConfig filterConfig2 = new MockFilterConfig(wac.getServletContext(), "filter2");
@ -424,7 +538,7 @@ public class OpenSessionInViewTests extends TestCase {
OpenSessionInViewInterceptor interceptor = new OpenSessionInViewInterceptor();
interceptor.setSessionFactory(sf);
interceptor.preHandle(new ServletWebRequest(request));
interceptor.preHandle(this.webRequest);
final OpenSessionInViewFilter filter = new OpenSessionInViewFilter();
filter.init(filterConfig);
@ -437,74 +551,57 @@ public class OpenSessionInViewTests extends TestCase {
};
assertTrue(TransactionSynchronizationManager.hasResource(sf));
filter.doFilter(request, response, filterChain);
filter.doFilter(this.request, this.response, filterChain);
assertTrue(TransactionSynchronizationManager.hasResource(sf));
assertNotNull(request.getAttribute("invoked"));
assertNotNull(this.request.getAttribute("invoked"));
interceptor.postHandle(new ServletWebRequest(request), null);
interceptor.afterCompletion(new ServletWebRequest(request), null);
interceptor.postHandle(this.webRequest, null);
interceptor.afterCompletion(this.webRequest, null);
sfControl.verify();
sessionControl.verify();
verify(sf);
verify(session);
wac.close();
}
@Test
public void testOpenSessionInViewFilterWithDeferredClose() throws Exception {
MockControl sfControl = MockControl.createControl(SessionFactory.class);
final SessionFactory sf = (SessionFactory) sfControl.getMock();
final MockControl sessionControl = MockControl.createControl(Session.class);
final Session session = (Session) sessionControl.getMock();
final SessionFactory sf = createStrictMock(SessionFactory.class);
final Session session = createStrictMock(Session.class);
sf.openSession();
sfControl.setReturnValue(session, 1);
session.getSessionFactory();
sessionControl.setReturnValue(sf);
session.getFlushMode();
sessionControl.setReturnValue(FlushMode.MANUAL, 1);
expect(sf.openSession()).andReturn(session);
expect(session.getSessionFactory()).andReturn(sf);
expect(session.getFlushMode()).andReturn(FlushMode.MANUAL);
session.setFlushMode(FlushMode.MANUAL);
sessionControl.setVoidCallable(1);
sfControl.replay();
sessionControl.replay();
replay(sf);
replay(session);
MockControl sf2Control = MockControl.createControl(SessionFactory.class);
final SessionFactory sf2 = (SessionFactory) sf2Control.getMock();
final MockControl session2Control = MockControl.createControl(Session.class);
final Session session2 = (Session) session2Control.getMock();
MockControl txControl = MockControl.createControl(Transaction.class);
Transaction tx = (Transaction) txControl.getMock();
MockControl conControl = MockControl.createControl(Connection.class);
Connection con = (Connection) conControl.getMock();
final SessionFactory sf2 = createStrictMock(SessionFactory.class);
final Session session2 = createStrictMock(Session.class);
sf2.openSession();
sf2Control.setReturnValue(session2, 1);
session2.beginTransaction();
session2Control.setReturnValue(tx, 1);
session2.connection();
session2Control.setReturnValue(con, 2);
Transaction tx = createStrictMock(Transaction.class);
Connection con = createStrictMock(Connection.class);
expect(sf2.openSession()).andReturn(session2);
expect(session2.connection()).andReturn(con);
expect(session2.beginTransaction()).andReturn(tx);
expect(session2.isConnected()).andReturn(true);
expect(session2.connection()).andReturn(con);
tx.commit();
txControl.setVoidCallable(1);
session2.isConnected();
session2Control.setReturnValue(true, 1);
con.isReadOnly();
conControl.setReturnValue(false, 1);
expect(con.isReadOnly()).andReturn(false);
session2.setFlushMode(FlushMode.MANUAL);
session2Control.setVoidCallable(1);
sf2Control.replay();
session2Control.replay();
txControl.replay();
conControl.replay();
replay(sf2);
replay(session2);
replay(tx);
replay(con);
MockServletContext sc = new MockServletContext();
StaticWebApplicationContext wac = new StaticWebApplicationContext();
wac.setServletContext(sc);
wac.getDefaultListableBeanFactory().registerSingleton("sessionFactory", sf);
wac.getDefaultListableBeanFactory().registerSingleton("mySessionFactory", sf2);
wac.refresh();
sc.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, wac);
MockHttpServletRequest request = new MockHttpServletRequest(sc);
MockHttpServletResponse response = new MockHttpServletResponse();
MockFilterConfig filterConfig = new MockFilterConfig(wac.getServletContext(), "filter");
MockFilterConfig filterConfig2 = new MockFilterConfig(wac.getServletContext(), "filter2");
@ -526,12 +623,11 @@ public class OpenSessionInViewTests extends TestCase {
SessionFactoryUtils.releaseSession(sess, sf);
tm.commit(ts);
sessionControl.verify();
sessionControl.reset();
verify(session);
reset(session);
session.close();
sessionControl.setReturnValue(null, 1);
sessionControl.replay();
expect(session.close()).andReturn(null);
replay(session);
servletRequest.setAttribute("invoked", Boolean.TRUE);
}
@ -545,12 +641,11 @@ public class OpenSessionInViewTests extends TestCase {
TransactionStatus ts = tm.getTransaction(new DefaultTransactionDefinition());
tm.commit(ts);
session2Control.verify();
session2Control.reset();
verify(session2);
reset(session2);
session2.close();
session2Control.setReturnValue(null, 1);
session2Control.replay();
expect(session2.close()).andReturn(null);
replay(session2);
filter.doFilter(servletRequest, servletResponse, filterChain);
}
@ -558,44 +653,48 @@ public class OpenSessionInViewTests extends TestCase {
FilterChain filterChain3 = new PassThroughFilterChain(filter2, filterChain2);
filter2.doFilter(request, response, filterChain3);
assertNotNull(request.getAttribute("invoked"));
filter2.doFilter(this.request, this.response, filterChain3);
assertNotNull(this.request.getAttribute("invoked"));
sfControl.verify();
sessionControl.verify();
sf2Control.verify();
session2Control.verify();
txControl.verify();
conControl.verify();
verify(sf);
verify(session);
verify(sf2);
verify(session2);
verify(tx);
verify(con);
wac.close();
}
@Test
public void testOpenSessionInViewFilterWithDeferredCloseAndAlreadyActiveDeferredClose() throws Exception {
MockControl sfControl = MockControl.createControl(SessionFactory.class);
final SessionFactory sf = (SessionFactory) sfControl.getMock();
final MockControl sessionControl = MockControl.createControl(Session.class);
final Session session = (Session) sessionControl.getMock();
final SessionFactory sf = createStrictMock(SessionFactory.class);
final Session session = createStrictMock(Session.class);
sf.openSession();
sfControl.setReturnValue(session, 1);
session.getSessionFactory();
sessionControl.setReturnValue(sf);
session.getFlushMode();
sessionControl.setReturnValue(FlushMode.MANUAL, 1);
expect(sf.openSession()).andReturn(session);
expect(session.getSessionFactory()).andReturn(sf);
expect(session.getFlushMode()).andReturn(FlushMode.MANUAL);
session.setFlushMode(FlushMode.MANUAL);
sessionControl.setVoidCallable(1);
sfControl.replay();
sessionControl.replay();
replay(sf);
replay(session);
// sf.openSession();
// sfControl.setReturnValue(session, 1);
// session.getSessionFactory();
// sessionControl.setReturnValue(sf);
// session.getFlushMode();
// sessionControl.setReturnValue(FlushMode.MANUAL, 1);
// session.setFlushMode(FlushMode.MANUAL);
// sessionControl.setVoidCallable(1);
// sfControl.replay();
// sessionControl.replay();
MockServletContext sc = new MockServletContext();
StaticWebApplicationContext wac = new StaticWebApplicationContext();
wac.setServletContext(sc);
wac.getDefaultListableBeanFactory().registerSingleton("sessionFactory", sf);
wac.refresh();
sc.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, wac);
MockHttpServletRequest request = new MockHttpServletRequest(sc);
MockHttpServletResponse response = new MockHttpServletResponse();
MockFilterConfig filterConfig = new MockFilterConfig(wac.getServletContext(), "filter");
MockFilterConfig filterConfig2 = new MockFilterConfig(wac.getServletContext(), "filter2");
@ -607,7 +706,7 @@ public class OpenSessionInViewTests extends TestCase {
interceptor.setSessionFactory(sf);
interceptor.setSingleSession(false);
interceptor.preHandle(new ServletWebRequest(request));
interceptor.preHandle(webRequest);
final OpenSessionInViewFilter filter = new OpenSessionInViewFilter();
filter.init(filterConfig);
@ -623,15 +722,14 @@ public class OpenSessionInViewTests extends TestCase {
SessionFactoryUtils.releaseSession(sess, sf);
tm.commit(ts);
sessionControl.verify();
sessionControl.reset();
verify(session);
reset(session);
try {
session.close();
expect(session.close()).andReturn(null);
}
catch (HibernateException ex) {
}
sessionControl.setReturnValue(null, 1);
sessionControl.replay();
replay(session);
servletRequest.setAttribute("invoked", Boolean.TRUE);
}
@ -644,16 +742,25 @@ public class OpenSessionInViewTests extends TestCase {
}
};
filter.doFilter(request, response, filterChain2);
assertNotNull(request.getAttribute("invoked"));
filter.doFilter(this.request, this.response, filterChain2);
assertNotNull(this.request.getAttribute("invoked"));
interceptor.postHandle(new ServletWebRequest(request), null);
interceptor.afterCompletion(new ServletWebRequest(request), null);
interceptor.postHandle(webRequest, null);
interceptor.afterCompletion(webRequest, null);
sfControl.verify();
sessionControl.verify();
verify(sf);
verify(session);
wac.close();
}
@SuppressWarnings("serial")
private static class SyncTaskExecutor extends SimpleAsyncTaskExecutor {
@Override
public void execute(Runnable task, long startTimeout) {
task.run();
}
}
}

View File

@ -25,6 +25,8 @@ import javax.servlet.ServletRequest;
import org.springframework.core.task.AsyncTaskExecutor;
import org.springframework.core.task.SimpleAsyncTaskExecutor;
import org.springframework.util.Assert;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.context.request.async.DeferredResult.DeferredResultHandler;
/**
@ -77,6 +79,20 @@ public final class AsyncExecutionChain {
return chain;
}
/**
* Obtain the AsyncExecutionChain for the current request.
* Or if not found, create an instance and associate it with the request.
*/
public static AsyncExecutionChain getForCurrentRequest(WebRequest request) {
int scope = RequestAttributes.SCOPE_REQUEST;
AsyncExecutionChain chain = (AsyncExecutionChain) request.getAttribute(CALLABLE_CHAIN_ATTRIBUTE, scope);
if (chain == null) {
chain = new AsyncExecutionChain();
request.setAttribute(CALLABLE_CHAIN_ATTRIBUTE, chain, scope);
}
return chain;
}
/**
* Provide an instance of an AsyncWebRequest.
* This property must be set before async request processing can begin.

View File

@ -0,0 +1,73 @@
/*
* Copyright 2002-2012 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.context.request.async;
import java.util.concurrent.Callable;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.context.request.WebRequestInterceptor;
/**
* Extends {@link WebRequestInterceptor} with lifecycle methods specific to async
* request processing.
*
* <p>This is the sequence of events on the main thread in an async scenario:
* <ol>
* <li>{@link #preHandle(WebRequest)}
* <li>{@link #getAsyncCallable(WebRequest)}
* <li>... <em>handler execution</em>
* <li>{@link #postHandleAsyncStarted(WebRequest)}
* </ol>
*
* <p>This is the sequence of events on the async thread:
* <ol>
* <li>Async {@link Callable#call()} (the {@code Callable} returned by {@code getAsyncCallable})
* <li>... <em>async handler execution</em>
* <li>{@link #postHandle(WebRequest, org.springframework.ui.ModelMap)}
* <li>{@link #afterCompletion(WebRequest, Exception)}
* </ol>
*
* @author Rossen Stoyanchev
* @since 3.2
*/
public interface AsyncWebRequestInterceptor extends WebRequestInterceptor {
/**
* Invoked <em>after</em> {@link #preHandle(WebRequest)} and <em>before</em>
* the handler is executed. The returned {@link Callable} is used only if
* handler execution leads to teh start of async processing. It is invoked
* the async thread before the request is handled fro.
* <p>Implementations can use this <code>Callable</code> to initialize
* ThreadLocal attributes on the async thread.
* @return a {@link Callable} instance or <code>null</code>
*/
AbstractDelegatingCallable getAsyncCallable(WebRequest request);
/**
* Invoked <em>after</em> the execution of a handler if the handler started
* async processing instead of handling the request. Effectively this method
* is invoked on the way out of the main processing thread instead of
* {@link #postHandle(WebRequest, org.springframework.ui.ModelMap)}. The
* <code>postHandle</code> method is invoked after the request is handled
* in the async thread.
* <p>Implementations of this method can ensure ThreadLocal attributes bound
* to the main thread are cleared and also prepare for binding them to the
* async thread.
*/
void postHandleAsyncStarted(WebRequest request);
}

View File

@ -0,0 +1,82 @@
/*
* Copyright 2002-2012 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.servlet;
import java.util.concurrent.Callable;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.context.request.async.AbstractDelegatingCallable;
/**
* Extends {@link HanderInterceptor} with lifecycle methods specific to async
* request processing.
*
* <p>This is the sequence of events on the main thread in an async scenario:
* <ol>
* <li>{@link #preHandle(WebRequest)}
* <li>{@link #getAsyncCallable(WebRequest)}
* <li>... <em>handler execution</em>
* <li>{@link #postHandleAsyncStarted(WebRequest)}
* </ol>
*
* <p>This is the sequence of events on the async thread:
* <ol>
* <li>Async {@link Callable#call()} (the {@code Callable} returned by {@code getAsyncCallable})
* <li>... <em>async handler execution</em>
* <li>{@link #postHandle(WebRequest, org.springframework.ui.ModelMap)}
* <li>{@link #afterCompletion(WebRequest, Exception)}
* </ol>
*
* @author Rossen Stoyanchev
* @since 3.2
*/
public interface AsyncHandlerInterceptor extends HandlerInterceptor {
/**
* Invoked <em>after</em> {@link #preHandle(WebRequest)} and <em>before</em>
* the handler is executed. The returned {@link Callable} is used only if
* handler execution leads to teh start of async processing. It is invoked
* the async thread before the request is handled fro.
* <p>Implementations can use this <code>Callable</code> to initialize
* ThreadLocal attributes on the async thread.
* @param request current HTTP request
* @param response current HTTP response
* @param handler chosen handler to execute, for type and/or instance examination
* @return a {@link Callable} instance or <code>null</code>
*/
AbstractDelegatingCallable getAsyncCallable(HttpServletRequest request, HttpServletResponse response, Object handler);
/**
* Invoked <em>after</em> the execution of a handler if the handler started
* async processing instead of handling the request. Effectively this method
* is invoked on the way out of the main processing thread instead of
* {@link #postHandle(WebRequest, org.springframework.ui.ModelMap)}. The
* <code>postHandle</code> method is invoked after the request is handled
* in the async thread.
* <p>Implementations of this method can ensure ThreadLocal attributes bound
* to the main thread are cleared and also prepare for binding them to the
* async thread.
* @param request current HTTP request
* @param response current HTTP response
* @param handler chosen handler to execute, for type and/or instance examination
*/
void postHandleAsyncStarted(HttpServletRequest request, HttpServletResponse response, Object handler);
}

View File

@ -941,6 +941,8 @@ public class DispatcherServlet extends FrameworkServlet {
return;
}
mappedHandler.addDelegatingCallables(processedRequest, response);
asyncChain.addDelegatingCallable(
getDispatchAsyncCallable(mappedHandler, request, response, processedRequest));
@ -948,6 +950,7 @@ public class DispatcherServlet extends FrameworkServlet {
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
if (asyncChain.isAsyncStarted()) {
mappedHandler.applyPostHandleAsyncStarted(processedRequest, response);
logger.debug("Exiting request thread and leaving the response open");
return;
}

View File

@ -26,6 +26,7 @@ import javax.servlet.http.HttpServletResponse;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.util.CollectionUtils;
import org.springframework.web.context.request.async.AsyncExecutionChain;
/**
* Handler execution chain, consisting of handler object and any handler interceptors.
@ -139,7 +140,7 @@ public class HandlerExecutionChain {
}
/**
* Apply preHandle methods of registered interceptors.
* Apply postHandle methods of registered interceptors.
*/
void applyPostHandle(HttpServletRequest request, HttpServletResponse response, ModelAndView mv)
throws Exception {
@ -153,6 +154,53 @@ public class HandlerExecutionChain {
}
}
/**
* Add delegating, async Callable instances to the {@link AsyncExecutionChain}
* for use in case of asynchronous request processing.
*/
void addDelegatingCallables(HttpServletRequest request, HttpServletResponse response)
throws Exception {
if (getInterceptors() == null) {
return;
}
for (int i = getInterceptors().length - 1; i >= 0; i--) {
HandlerInterceptor interceptor = getInterceptors()[i];
if (interceptor instanceof AsyncHandlerInterceptor) {
try {
AsyncHandlerInterceptor asyncInterceptor = (AsyncHandlerInterceptor) interceptor;
AsyncExecutionChain chain = AsyncExecutionChain.getForCurrentRequest(request);
chain.addDelegatingCallable(asyncInterceptor.getAsyncCallable(request, response, this.handler));
}
catch (Throwable ex) {
logger.error("HandlerInterceptor.addAsyncCallables threw exception", ex);
}
}
}
}
/**
* Trigger postHandleAsyncStarted callbacks on the mapped HandlerInterceptors.
*/
void applyPostHandleAsyncStarted(HttpServletRequest request, HttpServletResponse response)
throws Exception {
if (getInterceptors() == null) {
return;
}
for (int i = getInterceptors().length - 1; i >= 0; i--) {
HandlerInterceptor interceptor = getInterceptors()[i];
if (interceptor instanceof AsyncHandlerInterceptor) {
try {
((AsyncHandlerInterceptor) interceptor).postHandleAsyncStarted(request, response, this.handler);
}
catch (Throwable ex) {
logger.error("HandlerInterceptor.postHandleAsyncStarted threw exception", ex);
}
}
}
}
/**
* Trigger afterCompletion callbacks on the mapped HandlerInterceptors.
* Will just invoke afterCompletion for all interceptors whose preHandle invocation

View File

@ -21,7 +21,9 @@ import javax.servlet.http.HttpServletResponse;
import org.springframework.util.Assert;
import org.springframework.web.context.request.WebRequestInterceptor;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.context.request.async.AbstractDelegatingCallable;
import org.springframework.web.context.request.async.AsyncWebRequestInterceptor;
import org.springframework.web.servlet.AsyncHandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
/**
@ -33,7 +35,7 @@ import org.springframework.web.servlet.ModelAndView;
* @see org.springframework.web.context.request.WebRequestInterceptor
* @see org.springframework.web.servlet.HandlerInterceptor
*/
public class WebRequestHandlerInterceptorAdapter implements HandlerInterceptor {
public class WebRequestHandlerInterceptorAdapter implements AsyncHandlerInterceptor {
private final WebRequestInterceptor requestInterceptor;
@ -55,6 +57,25 @@ public class WebRequestHandlerInterceptorAdapter implements HandlerInterceptor {
return true;
}
public AbstractDelegatingCallable getAsyncCallable(HttpServletRequest request,
HttpServletResponse response, Object handler) {
if (this.requestInterceptor instanceof AsyncWebRequestInterceptor) {
AsyncWebRequestInterceptor asyncInterceptor = (AsyncWebRequestInterceptor) this.requestInterceptor;
DispatcherServletWebRequest webRequest = new DispatcherServletWebRequest(request, response);
return asyncInterceptor.getAsyncCallable(webRequest);
}
return null;
}
public void postHandleAsyncStarted(HttpServletRequest request, HttpServletResponse response, Object handler) {
if (this.requestInterceptor instanceof AsyncWebRequestInterceptor) {
AsyncWebRequestInterceptor asyncInterceptor = (AsyncWebRequestInterceptor) this.requestInterceptor;
DispatcherServletWebRequest webRequest = new DispatcherServletWebRequest(request, response);
asyncInterceptor.postHandleAsyncStarted(webRequest);
}
}
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView)
throws Exception {

View File

@ -16,7 +16,7 @@
package org.springframework.web.servlet;
import static org.easymock.EasyMock.createMock;
import static org.easymock.EasyMock.createStrictMock;
import static org.easymock.EasyMock.expect;
import static org.easymock.EasyMock.replay;
import static org.easymock.EasyMock.verify;
@ -26,6 +26,7 @@ import org.junit.Before;
import org.junit.Test;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.web.context.request.async.AbstractDelegatingCallable;
/**
* A test fixture with HandlerExecutionChain and mock handler interceptors.
@ -42,11 +43,11 @@ public class HandlerExecutionChainTests {
private MockHttpServletResponse response;
private HandlerInterceptor interceptor1;
private AsyncHandlerInterceptor interceptor1;
private HandlerInterceptor interceptor2;
private AsyncHandlerInterceptor interceptor2;
private HandlerInterceptor interceptor3;
private AsyncHandlerInterceptor interceptor3;
@Before
public void setup() {
@ -56,9 +57,9 @@ public class HandlerExecutionChainTests {
this.handler = new Object();
this.chain = new HandlerExecutionChain(this.handler);
this.interceptor1 = createMock(HandlerInterceptor.class);
this.interceptor2 = createMock(HandlerInterceptor.class);
this.interceptor3 = createMock(HandlerInterceptor.class);
this.interceptor1 = createStrictMock(AsyncHandlerInterceptor.class);
this.interceptor2 = createStrictMock(AsyncHandlerInterceptor.class);
this.interceptor3 = createStrictMock(AsyncHandlerInterceptor.class);
this.chain.addInterceptor(this.interceptor1);
this.chain.addInterceptor(this.interceptor2);
@ -91,7 +92,42 @@ public class HandlerExecutionChainTests {
}
@Test
public void earlyExit() throws Exception {
public void successAsyncScenario() throws Exception {
ModelAndView mav = new ModelAndView();
expect(this.interceptor1.preHandle(this.request, this.response, this.handler)).andReturn(true);
expect(this.interceptor2.preHandle(this.request, this.response, this.handler)).andReturn(true);
expect(this.interceptor3.preHandle(this.request, this.response, this.handler)).andReturn(true);
expect(this.interceptor1.getAsyncCallable(request, response, this.handler)).andReturn(new TestAsyncCallable());
expect(this.interceptor2.getAsyncCallable(request, response, this.handler)).andReturn(new TestAsyncCallable());
expect(this.interceptor3.getAsyncCallable(request, response, this.handler)).andReturn(new TestAsyncCallable());
this.interceptor1.postHandleAsyncStarted(request, response, this.handler);
this.interceptor2.postHandleAsyncStarted(request, response, this.handler);
this.interceptor3.postHandleAsyncStarted(request, response, this.handler);
this.interceptor1.postHandle(this.request, this.response, this.handler, mav);
this.interceptor2.postHandle(this.request, this.response, this.handler, mav);
this.interceptor3.postHandle(this.request, this.response, this.handler, mav);
this.interceptor3.afterCompletion(this.request, this.response, this.handler, null);
this.interceptor2.afterCompletion(this.request, this.response, this.handler, null);
this.interceptor1.afterCompletion(this.request, this.response, this.handler, null);
replay(this.interceptor1, this.interceptor2, this.interceptor3);
this.chain.applyPreHandle(request, response);
this.chain.addDelegatingCallables(request, response);
this.chain.applyPostHandleAsyncStarted(request, response);
this.chain.applyPostHandle(request, response, mav);
this.chain.triggerAfterCompletion(this.request, this.response, null);
verify(this.interceptor1, this.interceptor2, this.interceptor3);
}
@Test
public void earlyExitInPreHandle() throws Exception {
expect(this.interceptor1.preHandle(this.request, this.response, this.handler)).andReturn(true);
expect(this.interceptor2.preHandle(this.request, this.response, this.handler)).andReturn(false);
@ -155,4 +191,12 @@ public class HandlerExecutionChainTests {
verify(this.interceptor1, this.interceptor2, this.interceptor3);
}
private static class TestAsyncCallable extends AbstractDelegatingCallable {
public Object call() throws Exception {
return null;
}
}
}