diff --git a/spring-orm/src/main/java/org/springframework/orm/hibernate3/support/OpenSessionInViewFilter.java b/spring-orm/src/main/java/org/springframework/orm/hibernate3/support/OpenSessionInViewFilter.java index d5fe421631..fbcac64f64 100644 --- a/spring-orm/src/main/java/org/springframework/orm/hibernate3/support/OpenSessionInViewFilter.java +++ b/spring-orm/src/main/java/org/springframework/orm/hibernate3/support/OpenSessionInViewFilter.java @@ -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; + } + }; + } + } diff --git a/spring-orm/src/main/java/org/springframework/orm/hibernate3/support/OpenSessionInViewInterceptor.java b/spring-orm/src/main/java/org/springframework/orm/hibernate3/support/OpenSessionInViewInterceptor.java index 002f0c8238..7fdd151895 100644 --- a/spring-orm/src/main/java/org/springframework/orm/hibernate3/support/OpenSessionInViewInterceptor.java +++ b/spring-orm/src/main/java/org/springframework/orm/hibernate3/support/OpenSessionInViewInterceptor.java @@ -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 Session 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 SessionFactory @@ -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 Callable to bind the Hibernate 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 Session from the main thread but leave + * the Session 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 Session before view rendering, if necessary. *

Note that this just applies in {@link #isSingleSession() single session mode}! diff --git a/spring-orm/src/main/java/org/springframework/orm/hibernate4/support/OpenSessionInViewFilter.java b/spring-orm/src/main/java/org/springframework/orm/hibernate4/support/OpenSessionInViewFilter.java index 0e76d8c35f..c8dd223406 100644 --- a/spring-orm/src/main/java/org/springframework/orm/hibernate4/support/OpenSessionInViewFilter.java +++ b/spring-orm/src/main/java/org/springframework/orm/hibernate4/support/OpenSessionInViewFilter.java @@ -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; + } + }; + } + } diff --git a/spring-orm/src/main/java/org/springframework/orm/hibernate4/support/OpenSessionInViewInterceptor.java b/spring-orm/src/main/java/org/springframework/orm/hibernate4/support/OpenSessionInViewInterceptor.java index 1c15bd3b8c..66ceffaaf7 100644 --- a/spring-orm/src/main/java/org/springframework/orm/hibernate4/support/OpenSessionInViewInterceptor.java +++ b/spring-orm/src/main/java/org/springframework/orm/hibernate4/support/OpenSessionInViewInterceptor.java @@ -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 Session 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 SessionFactory @@ -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 Callable to bind the Hibernate 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 Session 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 Session from the thread and close it). * @see org.springframework.transaction.support.TransactionSynchronizationManager diff --git a/spring-orm/src/test/java/org/springframework/orm/hibernate3/support/OpenSessionInViewTests.java b/spring-orm/src/test/java/org/springframework/orm/hibernate3/support/OpenSessionInViewTests.java index 21b4e2289d..82779fc09b 100644 --- a/spring-orm/src/test/java/org/springframework/orm/hibernate3/support/OpenSessionInViewTests.java +++ b/spring-orm/src/test/java/org/springframework/orm/hibernate3/support/OpenSessionInViewTests.java @@ -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(sf.openSession()).andReturn(session); + expect(session.getSessionFactory()).andReturn(sf); + session.setFlushMode(FlushMode.MANUAL); + 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(this.webRequest); + assertEquals(session, SessionFactoryUtils.getSession(sf, false)); + + interceptor.preHandle(this.webRequest); + interceptor.postHandle(this.webRequest, null); + interceptor.afterCompletion(this.webRequest, null); - //expect(mockStorage.size()).andReturn(expectedValue); + interceptor.postHandle(this.webRequest, null); + interceptor.afterCompletion(this.webRequest, null); - //expect(sf.openSession()).andReturn(session); - sf.openSession(); - sfControl.setReturnValue(session, 1); - session.getSessionFactory(); - sessionControl.setReturnValue(sf, 2); - session.isOpen(); - sessionControl.setReturnValue(true, 1); + interceptor.preHandle(this.webRequest); + interceptor.postHandle(this.webRequest, null); + interceptor.afterCompletion(this.webRequest, null); + + 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 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); - sessionControl.setVoidCallable(1); - sfControl.replay(); - sessionControl.replay(); - interceptor.preHandle(new ServletWebRequest(request)); + replay(sf); + replay(session); + + interceptor.preHandle(this.webRequest); assertTrue(TransactionSynchronizationManager.hasResource(sf)); - // check that further invocations simply participate - interceptor.preHandle(new ServletWebRequest(request)); + AbstractDelegatingCallable asyncCallable = interceptor.getAsyncCallable(this.webRequest); + assertNotNull(asyncCallable); - assertEquals(session, SessionFactoryUtils.getSession(sf, false)); + interceptor.postHandleAsyncStarted(this.webRequest); + assertFalse(TransactionSynchronizationManager.hasResource(sf)); - interceptor.preHandle(new ServletWebRequest(request)); - interceptor.postHandle(new ServletWebRequest(request), null); - interceptor.afterCompletion(new ServletWebRequest(request), null); + verify(sf); + verify(session); - interceptor.postHandle(new ServletWebRequest(request), null); - interceptor.afterCompletion(new ServletWebRequest(request), null); + asyncCallable.setNextCallable(new Callable() { + public Object call() { + return null; + } + }); - interceptor.preHandle(new ServletWebRequest(request)); - interceptor.postHandle(new ServletWebRequest(request), null); - interceptor.afterCompletion(new ServletWebRequest(request), null); + asyncCallable.call(); + assertTrue("Session not bound to async thread", TransactionSynchronizationManager.hasResource(sf)); - 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 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(this.webRequest, null); + interceptor.afterCompletion(this.webRequest, null); - 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.preHandle(new ServletWebRequest(request)); - interceptor.postHandle(new ServletWebRequest(request), null); - interceptor.afterCompletion(new ServletWebRequest(request), null); + verify(sf); + verify(session); - sfControl.verify(); - sessionControl.verify(); + 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 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(); - - 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 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(this.webRequest); + 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.postHandle(this.webRequest, null); + interceptor.afterCompletion(this.webRequest, null); - 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.preHandle(new ServletWebRequest(request)); - interceptor.postHandle(new ServletWebRequest(request), null); - interceptor.afterCompletion(new ServletWebRequest(request), null); + verify(sf); + verify(session); - sfControl.verify(); - sessionControl.verify(); - sfControl.reset(); - sessionControl.reset(); + reset(sf); + reset(session); + expect(session.close()).andReturn(null); + replay(sf); + replay(session); - session.close(); - sessionControl.setReturnValue(null, 1); - sfControl.replay(); - sessionControl.replay(); + interceptor.postHandle(this.webRequest, null); + interceptor.afterCompletion(this.webRequest, null); - interceptor.postHandle(new ServletWebRequest(request), null); - interceptor.afterCompletion(new ServletWebRequest(request), null); - sfControl.verify(); - sessionControl.verify(); + 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(); - - sf.openSession(); - sfControl.setReturnValue(session, 1); - session.getSessionFactory(); - sessionControl.setReturnValue(sf); + 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); - sessionControl.setVoidCallable(1); - session.close(); - sessionControl.setReturnValue(null, 1); - sfControl.replay(); - sessionControl.replay(); - - MockControl sf2Control = MockControl.createControl(SessionFactory.class); - final SessionFactory sf2 = (SessionFactory) sf2Control.getMock(); - MockControl session2Control = MockControl.createControl(Session.class); - Session session2 = (Session) session2Control.getMock(); - - sf2.openSession(); - sf2Control.setReturnValue(session2, 1); - session2.getSessionFactory(); - session2Control.setReturnValue(sf); + expect(session.close()).andReturn(null); + replay(sf); + replay(session); + + final SessionFactory sf2 = createStrictMock(SessionFactory.class); + Session session2 = createStrictMock(Session.class); + + 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")); + + verify(sf); + verify(session); + verify(sf2); + verify(session2); + + wac.close(); + } + + @Test + public void testOpenSessionInViewFilterWithSingleSessionAsyncScenario() 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); + 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")); - sfControl.verify(); - sessionControl.verify(); - sf2Control.verify(); - session2Control.verify(); + verify(sf); + verify(session); + verify(asyncWebRequest); + + chain.setTaskExecutor(new SyncTaskExecutor()); + chain.setCallable(new Callable() { + 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 { - MockControl sfControl = MockControl.createControl(SessionFactory.class); - final SessionFactory sf = (SessionFactory) sfControl.getMock(); - MockControl sessionControl = MockControl.createControl(Session.class); - Session session = (Session) sessionControl.getMock(); - - sf.openSession(); - sfControl.setReturnValue(session, 1); - session.getSessionFactory(); - sessionControl.setReturnValue(sf); + 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); - sessionControl.setVoidCallable(1); - session.close(); - sessionControl.setReturnValue(null, 1); - sfControl.replay(); - sessionControl.replay(); + 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(); - - sf.openSession(); - sfControl.setReturnValue(session, 1); - session.getSessionFactory(); - sessionControl.setReturnValue(sf); - session.getFlushMode(); - sessionControl.setReturnValue(FlushMode.MANUAL, 1); + final SessionFactory sf = createStrictMock(SessionFactory.class); + final Session session = createStrictMock(Session.class); + + 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(); - - 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(); - - sf2.openSession(); - sf2Control.setReturnValue(session2, 1); - session2.beginTransaction(); - session2Control.setReturnValue(tx, 1); - session2.connection(); - session2Control.setReturnValue(con, 2); + replay(sf); + replay(session); + + final SessionFactory sf2 = createStrictMock(SessionFactory.class); + final Session session2 = createStrictMock(Session.class); + + 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")); + + verify(sf); + verify(session); - sfControl.verify(); - sessionControl.verify(); - sf2Control.verify(); - session2Control.verify(); - txControl.verify(); - conControl.verify(); + 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(); - - sf.openSession(); - sfControl.setReturnValue(session, 1); - session.getSessionFactory(); - sessionControl.setReturnValue(sf); - session.getFlushMode(); - sessionControl.setReturnValue(FlushMode.MANUAL, 1); + final SessionFactory sf = createStrictMock(SessionFactory.class); + final Session session = createStrictMock(Session.class); + + 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(); + } + } } diff --git a/spring-web/src/main/java/org/springframework/web/context/request/async/AsyncExecutionChain.java b/spring-web/src/main/java/org/springframework/web/context/request/async/AsyncExecutionChain.java index 489d2c6731..858845dcdb 100644 --- a/spring-web/src/main/java/org/springframework/web/context/request/async/AsyncExecutionChain.java +++ b/spring-web/src/main/java/org/springframework/web/context/request/async/AsyncExecutionChain.java @@ -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. diff --git a/spring-web/src/main/java/org/springframework/web/context/request/async/AsyncWebRequestInterceptor.java b/spring-web/src/main/java/org/springframework/web/context/request/async/AsyncWebRequestInterceptor.java new file mode 100644 index 0000000000..017e0bd7a4 --- /dev/null +++ b/spring-web/src/main/java/org/springframework/web/context/request/async/AsyncWebRequestInterceptor.java @@ -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. + * + *

This is the sequence of events on the main thread in an async scenario: + *

    + *
  1. {@link #preHandle(WebRequest)} + *
  2. {@link #getAsyncCallable(WebRequest)} + *
  3. ... handler execution + *
  4. {@link #postHandleAsyncStarted(WebRequest)} + *
+ * + *

This is the sequence of events on the async thread: + *

    + *
  1. Async {@link Callable#call()} (the {@code Callable} returned by {@code getAsyncCallable}) + *
  2. ... async handler execution + *
  3. {@link #postHandle(WebRequest, org.springframework.ui.ModelMap)} + *
  4. {@link #afterCompletion(WebRequest, Exception)} + *
+ * + * @author Rossen Stoyanchev + * @since 3.2 + */ +public interface AsyncWebRequestInterceptor extends WebRequestInterceptor { + + /** + * Invoked after {@link #preHandle(WebRequest)} and before + * 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. + *

Implementations can use this Callable to initialize + * ThreadLocal attributes on the async thread. + * @return a {@link Callable} instance or null + */ + AbstractDelegatingCallable getAsyncCallable(WebRequest request); + + /** + * Invoked after 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 + * postHandle method is invoked after the request is handled + * in the async thread. + *

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); + +} diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/AsyncHandlerInterceptor.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/AsyncHandlerInterceptor.java new file mode 100644 index 0000000000..df341c6489 --- /dev/null +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/AsyncHandlerInterceptor.java @@ -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. + * + *

This is the sequence of events on the main thread in an async scenario: + *

    + *
  1. {@link #preHandle(WebRequest)} + *
  2. {@link #getAsyncCallable(WebRequest)} + *
  3. ... handler execution + *
  4. {@link #postHandleAsyncStarted(WebRequest)} + *
+ * + *

This is the sequence of events on the async thread: + *

    + *
  1. Async {@link Callable#call()} (the {@code Callable} returned by {@code getAsyncCallable}) + *
  2. ... async handler execution + *
  3. {@link #postHandle(WebRequest, org.springframework.ui.ModelMap)} + *
  4. {@link #afterCompletion(WebRequest, Exception)} + *
+ * + * @author Rossen Stoyanchev + * @since 3.2 + */ +public interface AsyncHandlerInterceptor extends HandlerInterceptor { + + /** + * Invoked after {@link #preHandle(WebRequest)} and before + * 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. + *

Implementations can use this Callable 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 null + */ + AbstractDelegatingCallable getAsyncCallable(HttpServletRequest request, HttpServletResponse response, Object handler); + + /** + * Invoked after 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 + * postHandle method is invoked after the request is handled + * in the async thread. + *

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); + +} diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/DispatcherServlet.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/DispatcherServlet.java index 19c561a04c..7cc2edc5ba 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/DispatcherServlet.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/DispatcherServlet.java @@ -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; } diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/HandlerExecutionChain.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/HandlerExecutionChain.java index c66836c638..d078e1706f 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/HandlerExecutionChain.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/HandlerExecutionChain.java @@ -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 diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/WebRequestHandlerInterceptorAdapter.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/WebRequestHandlerInterceptorAdapter.java index 50eb0e2054..9585c63280 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/WebRequestHandlerInterceptorAdapter.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/WebRequestHandlerInterceptorAdapter.java @@ -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 { diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/HandlerExecutionChainTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/HandlerExecutionChainTests.java index 550afaa6b8..da53c2b22d 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/HandlerExecutionChainTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/HandlerExecutionChainTests.java @@ -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; + } + } + }