Merge pull request #73 from rstoyanchev/mvc-async

HanderInterceptor and OSIV async request changes
This commit is contained in:
Rossen Stoyanchev 2012-05-04 13:16:18 -07:00
commit 158b3c6af8
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;
}
}
}