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 127924c7bd..a325d602a2 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 @@ -166,7 +166,6 @@ public class OpenSessionInViewFilter extends OncePerRequestFilter { return this.flushMode; } - /** * The default value is "true" so that the filter may re-bind the opened * {@code Session} to each asynchronously dispatched thread and postpone @@ -186,6 +185,7 @@ public class OpenSessionInViewFilter extends OncePerRequestFilter { boolean participate = false; WebAsyncManager asyncManager = AsyncWebUtils.getAsyncManager(request); + boolean isFirstRequest = !isAsyncDispatch(request); String key = getAlreadyFilteredAttributeName(); if (isSingleSession()) { @@ -195,7 +195,7 @@ public class OpenSessionInViewFilter extends OncePerRequestFilter { participate = true; } else { - if (!isAsyncDispatch(request) || !asyncManager.initializeAsyncThread(key)) { + if (isFirstRequest || !asyncManager.initializeAsyncThread(key)) { logger.debug("Opening single Hibernate Session in OpenSessionInViewFilter"); Session session = getSession(sessionFactory); SessionHolder sessionHolder = new SessionHolder(session); @@ -240,19 +240,6 @@ public class OpenSessionInViewFilter extends OncePerRequestFilter { } } - private WebAsyncThreadInitializer createAsyncThreadInitializer(final SessionFactory sessionFactory, - final SessionHolder sessionHolder) { - - return new WebAsyncThreadInitializer() { - public void initialize() { - TransactionSynchronizationManager.bindResource(sessionFactory, sessionHolder); - } - public void reset() { - TransactionSynchronizationManager.unbindResource(sessionFactory); - } - }; - } - /** * Look up the SessionFactory that this filter should use, * taking the current HTTP request as argument. @@ -317,4 +304,17 @@ public class OpenSessionInViewFilter extends OncePerRequestFilter { SessionFactoryUtils.closeSession(session); } + private WebAsyncThreadInitializer createAsyncThreadInitializer(final SessionFactory sessionFactory, + final SessionHolder sessionHolder) { + + return new WebAsyncThreadInitializer() { + public void initialize() { + TransactionSynchronizationManager.bindResource(sessionFactory, sessionHolder); + } + public void reset() { + TransactionSynchronizationManager.unbindResource(sessionFactory); + } + }; + } + } 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 929af55bd4..d5d21f05a5 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 @@ -99,7 +99,6 @@ public class OpenSessionInViewFilter extends OncePerRequestFilter { return this.sessionFactoryBeanName; } - /** * The default value is "true" so that the filter may re-bind the opened * {@code Session} to each asynchronously dispatched thread and postpone @@ -119,6 +118,7 @@ public class OpenSessionInViewFilter extends OncePerRequestFilter { boolean participate = false; WebAsyncManager asyncManager = AsyncWebUtils.getAsyncManager(request); + boolean isFirstRequest = !isAsyncDispatch(request); String key = getAlreadyFilteredAttributeName(); if (TransactionSynchronizationManager.hasResource(sessionFactory)) { @@ -126,7 +126,7 @@ public class OpenSessionInViewFilter extends OncePerRequestFilter { participate = true; } else { - if (!isAsyncDispatch(request) || !asyncManager.initializeAsyncThread(key)) { + if (isFirstRequest || !asyncManager.initializeAsyncThread(key)) { logger.debug("Opening Hibernate Session in OpenSessionInViewFilter"); Session session = openSession(sessionFactory); SessionHolder sessionHolder = new SessionHolder(session); @@ -153,19 +153,6 @@ public class OpenSessionInViewFilter extends OncePerRequestFilter { } } - private WebAsyncThreadInitializer createAsyncThreadInitializer(final SessionFactory sessionFactory, - final SessionHolder sessionHolder) { - - return new WebAsyncThreadInitializer() { - public void initialize() { - TransactionSynchronizationManager.bindResource(sessionFactory, sessionHolder); - } - public void reset() { - TransactionSynchronizationManager.unbindResource(sessionFactory); - } - }; - } - /** * Look up the SessionFactory that this filter should use, * taking the current HTTP request as argument. @@ -214,4 +201,17 @@ public class OpenSessionInViewFilter extends OncePerRequestFilter { } } + private WebAsyncThreadInitializer createAsyncThreadInitializer(final SessionFactory sessionFactory, + final SessionHolder sessionHolder) { + + return new WebAsyncThreadInitializer() { + public void initialize() { + TransactionSynchronizationManager.bindResource(sessionFactory, sessionHolder); + } + public void reset() { + TransactionSynchronizationManager.unbindResource(sessionFactory); + } + }; + } + } 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 2a2678bd6f..84cd22f001 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 @@ -105,9 +105,9 @@ public class OpenSessionInViewInterceptor implements AsyncWebRequestInterceptor */ public void preHandle(WebRequest request) throws DataAccessException { - WebAsyncManager asyncManager = AsyncWebUtils.getAsyncManager(request); String participateAttributeName = getParticipateAttributeName(); + WebAsyncManager asyncManager = AsyncWebUtils.getAsyncManager(request); if (asyncManager.hasConcurrentResult()) { if (asyncManager.initializeAsyncThread(participateAttributeName)) { return; @@ -148,12 +148,6 @@ public class OpenSessionInViewInterceptor implements AsyncWebRequestInterceptor } } - public void afterConcurrentHandlingStarted(WebRequest request) { - if (!decrementParticipateCount(request)) { - TransactionSynchronizationManager.unbindResource(getSessionFactory()); - } - } - private boolean decrementParticipateCount(WebRequest request) { String participateAttributeName = getParticipateAttributeName(); Integer count = (Integer) request.getAttribute(participateAttributeName, WebRequest.SCOPE_REQUEST); @@ -170,6 +164,12 @@ public class OpenSessionInViewInterceptor implements AsyncWebRequestInterceptor return true; } + public void afterConcurrentHandlingStarted(WebRequest request) { + if (!decrementParticipateCount(request)) { + TransactionSynchronizationManager.unbindResource(getSessionFactory()); + } + } + /** * Open a Session for the SessionFactory that this interceptor uses. *

The default implementation delegates to the diff --git a/spring-orm/src/main/java/org/springframework/orm/jpa/support/OpenEntityManagerInViewFilter.java b/spring-orm/src/main/java/org/springframework/orm/jpa/support/OpenEntityManagerInViewFilter.java index 895ef6573d..d2117f5ab4 100644 --- a/spring-orm/src/main/java/org/springframework/orm/jpa/support/OpenEntityManagerInViewFilter.java +++ b/spring-orm/src/main/java/org/springframework/orm/jpa/support/OpenEntityManagerInViewFilter.java @@ -17,6 +17,7 @@ package org.springframework.orm.jpa.support; import java.io.IOException; + import javax.persistence.EntityManager; import javax.persistence.EntityManagerFactory; import javax.persistence.PersistenceException; @@ -31,6 +32,9 @@ import org.springframework.orm.jpa.EntityManagerHolder; import org.springframework.transaction.support.TransactionSynchronizationManager; import org.springframework.util.StringUtils; import org.springframework.web.context.WebApplicationContext; +import org.springframework.web.context.request.async.AsyncWebUtils; +import org.springframework.web.context.request.async.WebAsyncManager; +import org.springframework.web.context.request.async.WebAsyncManager.WebAsyncThreadInitializer; import org.springframework.web.context.support.WebApplicationContextUtils; import org.springframework.web.filter.OncePerRequestFilter; @@ -119,6 +123,15 @@ public class OpenEntityManagerInViewFilter extends OncePerRequestFilter { return this.persistenceUnitName; } + /** + * The default value is "true" so that the filter may re-bind the opened + * {@code EntityManager} to each asynchronously dispatched thread and postpone + * closing it until the very last asynchronous dispatch. + */ + @Override + protected boolean shouldFilterAsyncDispatches() { + return true; + } @Override protected void doFilterInternal( @@ -128,18 +141,28 @@ public class OpenEntityManagerInViewFilter extends OncePerRequestFilter { EntityManagerFactory emf = lookupEntityManagerFactory(request); boolean participate = false; + WebAsyncManager asyncManager = AsyncWebUtils.getAsyncManager(request); + boolean isFirstRequest = !isAsyncDispatch(request); + String key = getAlreadyFilteredAttributeName(); + if (TransactionSynchronizationManager.hasResource(emf)) { // Do not modify the EntityManager: just set the participate flag. participate = true; } else { - logger.debug("Opening JPA EntityManager in OpenEntityManagerInViewFilter"); - try { - EntityManager em = createEntityManager(emf); - TransactionSynchronizationManager.bindResource(emf, new EntityManagerHolder(em)); - } - catch (PersistenceException ex) { - throw new DataAccessResourceFailureException("Could not create JPA EntityManager", ex); + if (isFirstRequest || !asyncManager.initializeAsyncThread(key)) { + logger.debug("Opening JPA EntityManager in OpenEntityManagerInViewFilter"); + try { + EntityManager em = createEntityManager(emf); + EntityManagerHolder emHolder = new EntityManagerHolder(em); + TransactionSynchronizationManager.bindResource(emf, emHolder); + + WebAsyncThreadInitializer initializer = createAsyncThreadInitializer(emf, emHolder); + asyncManager.registerAsyncThreadInitializer(key, initializer); + } + catch (PersistenceException ex) { + throw new DataAccessResourceFailureException("Could not create JPA EntityManager", ex); + } } } @@ -151,8 +174,10 @@ public class OpenEntityManagerInViewFilter extends OncePerRequestFilter { if (!participate) { EntityManagerHolder emHolder = (EntityManagerHolder) TransactionSynchronizationManager.unbindResource(emf); - logger.debug("Closing JPA EntityManager in OpenEntityManagerInViewFilter"); - EntityManagerFactoryUtils.closeEntityManager(emHolder.getEntityManager()); + if (isLastRequestThread(request)) { + logger.debug("Closing JPA EntityManager in OpenEntityManagerInViewFilter"); + EntityManagerFactoryUtils.closeEntityManager(emHolder.getEntityManager()); + } } } } @@ -205,4 +230,17 @@ public class OpenEntityManagerInViewFilter extends OncePerRequestFilter { return emf.createEntityManager(); } + private WebAsyncThreadInitializer createAsyncThreadInitializer(final EntityManagerFactory emFactory, + final EntityManagerHolder emHolder) { + + return new WebAsyncThreadInitializer() { + public void initialize() { + TransactionSynchronizationManager.bindResource(emFactory, emHolder); + } + public void reset() { + TransactionSynchronizationManager.unbindResource(emFactory); + } + }; + } + } diff --git a/spring-orm/src/main/java/org/springframework/orm/jpa/support/OpenEntityManagerInViewInterceptor.java b/spring-orm/src/main/java/org/springframework/orm/jpa/support/OpenEntityManagerInViewInterceptor.java index d7abd61db2..7b30b81830 100644 --- a/spring-orm/src/main/java/org/springframework/orm/jpa/support/OpenEntityManagerInViewInterceptor.java +++ b/spring-orm/src/main/java/org/springframework/orm/jpa/support/OpenEntityManagerInViewInterceptor.java @@ -22,12 +22,15 @@ import javax.persistence.PersistenceException; import org.springframework.dao.DataAccessException; import org.springframework.dao.DataAccessResourceFailureException; import org.springframework.orm.jpa.EntityManagerFactoryAccessor; -import org.springframework.orm.jpa.EntityManagerHolder; import org.springframework.orm.jpa.EntityManagerFactoryUtils; +import org.springframework.orm.jpa.EntityManagerHolder; 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.AsyncWebRequestInterceptor; +import org.springframework.web.context.request.async.AsyncWebUtils; +import org.springframework.web.context.request.async.WebAsyncManager; +import org.springframework.web.context.request.async.WebAsyncManager.WebAsyncThreadInitializer; /** * Spring web request interceptor that binds a JPA EntityManager to the @@ -56,7 +59,7 @@ import org.springframework.web.context.request.WebRequestInterceptor; * @see org.springframework.orm.jpa.SharedEntityManagerCreator * @see org.springframework.transaction.support.TransactionSynchronizationManager */ -public class OpenEntityManagerInViewInterceptor extends EntityManagerFactoryAccessor implements WebRequestInterceptor { +public class OpenEntityManagerInViewInterceptor extends EntityManagerFactoryAccessor implements AsyncWebRequestInterceptor { /** * Suffix that gets appended to the EntityManagerFactory toString @@ -68,9 +71,18 @@ public class OpenEntityManagerInViewInterceptor extends EntityManagerFactoryAcce public void preHandle(WebRequest request) throws DataAccessException { + + String participateAttributeName = getParticipateAttributeName(); + + WebAsyncManager asyncManager = AsyncWebUtils.getAsyncManager(request); + if (asyncManager.hasConcurrentResult()) { + if (asyncManager.initializeAsyncThread(participateAttributeName)) { + return; + } + } + if (TransactionSynchronizationManager.hasResource(getEntityManagerFactory())) { // do not modify the EntityManager: just mark the request accordingly - String participateAttributeName = getParticipateAttributeName(); Integer count = (Integer) request.getAttribute(participateAttributeName, WebRequest.SCOPE_REQUEST); int newCount = (count != null ? count + 1 : 1); request.setAttribute(getParticipateAttributeName(), newCount, WebRequest.SCOPE_REQUEST); @@ -79,7 +91,11 @@ public class OpenEntityManagerInViewInterceptor extends EntityManagerFactoryAcce logger.debug("Opening JPA EntityManager in OpenEntityManagerInViewInterceptor"); try { EntityManager em = createEntityManager(); - TransactionSynchronizationManager.bindResource(getEntityManagerFactory(), new EntityManagerHolder(em)); + EntityManagerHolder emHolder = new EntityManagerHolder(em); + TransactionSynchronizationManager.bindResource(getEntityManagerFactory(), emHolder); + + WebAsyncThreadInitializer asyncThreadInitializer = createThreadInitializer(emHolder); + asyncManager.registerAsyncThreadInitializer(participateAttributeName, asyncThreadInitializer); } catch (PersistenceException ex) { throw new DataAccessResourceFailureException("Could not create JPA EntityManager", ex); @@ -91,18 +107,7 @@ public class OpenEntityManagerInViewInterceptor extends EntityManagerFactoryAcce } public void afterCompletion(WebRequest request, Exception ex) throws DataAccessException { - String participateAttributeName = getParticipateAttributeName(); - Integer count = (Integer) request.getAttribute(participateAttributeName, WebRequest.SCOPE_REQUEST); - if (count != null) { - // Do not modify the EntityManager: just clear the marker. - if (count > 1) { - request.setAttribute(participateAttributeName, count - 1, WebRequest.SCOPE_REQUEST); - } - else { - request.removeAttribute(participateAttributeName, WebRequest.SCOPE_REQUEST); - } - } - else { + if (!decrementParticipateCount(request)) { EntityManagerHolder emHolder = (EntityManagerHolder) TransactionSynchronizationManager.unbindResource(getEntityManagerFactory()); logger.debug("Closing JPA EntityManager in OpenEntityManagerInViewInterceptor"); @@ -110,6 +115,28 @@ public class OpenEntityManagerInViewInterceptor extends EntityManagerFactoryAcce } } + private boolean decrementParticipateCount(WebRequest request) { + String participateAttributeName = getParticipateAttributeName(); + Integer count = (Integer) request.getAttribute(participateAttributeName, WebRequest.SCOPE_REQUEST); + if (count == null) { + return false; + } + // Do not modify the Session: just clear the marker. + if (count > 1) { + request.setAttribute(participateAttributeName, count - 1, WebRequest.SCOPE_REQUEST); + } + else { + request.removeAttribute(participateAttributeName, WebRequest.SCOPE_REQUEST); + } + return true; + } + + public void afterConcurrentHandlingStarted(WebRequest request) { + if (!decrementParticipateCount(request)) { + TransactionSynchronizationManager.unbindResource(getEntityManagerFactory()); + } + } + /** * Return the name of the request attribute that identifies that a request is * already filtered. Default implementation takes the toString representation @@ -120,4 +147,15 @@ public class OpenEntityManagerInViewInterceptor extends EntityManagerFactoryAcce return getEntityManagerFactory().toString() + PARTICIPATE_SUFFIX; } + private WebAsyncThreadInitializer createThreadInitializer(final EntityManagerHolder emHolder) { + return new WebAsyncThreadInitializer() { + public void initialize() { + TransactionSynchronizationManager.bindResource(getEntityManagerFactory(), emHolder); + } + public void reset() { + TransactionSynchronizationManager.unbindResource(getEntityManagerFactory()); + } + }; + } + } 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 9a845214d5..087020ee3f 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 @@ -460,7 +460,7 @@ public class OpenSessionInViewTests { final SessionFactory sf = createStrictMock(SessionFactory.class); Session session = createStrictMock(Session.class); - // Initial request during which concurrent handler execution starts.. + // Initial request during which concurrent handling starts.. expect(sf.openSession()).andReturn(session); expect(session.getSessionFactory()).andReturn(sf); @@ -516,11 +516,10 @@ public class OpenSessionInViewTests { reset(session); reset(asyncWebRequest); - // Async dispatch after concurrent handler execution results ready.. + // Async dispatch after concurrent handling produces result ... expect(session.close()).andReturn(null); - expect(asyncWebRequest.isAsyncStarted()).andReturn(false); - expectLastCall().anyTimes(); + expect(asyncWebRequest.isAsyncStarted()).andReturn(false).anyTimes(); replay(sf); replay(session); diff --git a/spring-orm/src/test/java/org/springframework/orm/jpa/support/OpenEntityManagerInViewTests.java b/spring-orm/src/test/java/org/springframework/orm/jpa/support/OpenEntityManagerInViewTests.java index 1bae592894..dbccafd7f2 100644 --- a/spring-orm/src/test/java/org/springframework/orm/jpa/support/OpenEntityManagerInViewTests.java +++ b/spring-orm/src/test/java/org/springframework/orm/jpa/support/OpenEntityManagerInViewTests.java @@ -16,7 +16,21 @@ package org.springframework.orm.jpa.support; +import static org.easymock.EasyMock.anyObject; +import static org.easymock.EasyMock.createMock; +import static org.easymock.EasyMock.createStrictMock; +import static org.easymock.EasyMock.expect; +import static org.easymock.EasyMock.expectLastCall; +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 java.io.IOException; +import java.util.concurrent.Callable; +import java.util.concurrent.atomic.AtomicInteger; + import javax.persistence.EntityManager; import javax.persistence.EntityManagerFactory; import javax.servlet.FilterChain; @@ -25,7 +39,6 @@ import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import junit.framework.TestCase; -import org.easymock.MockControl; import org.springframework.mock.web.MockFilterConfig; import org.springframework.mock.web.MockHttpServletRequest; @@ -36,6 +49,9 @@ import org.springframework.orm.jpa.JpaTemplate; 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.AsyncWebRequest; +import org.springframework.web.context.request.async.AsyncWebUtils; +import org.springframework.web.context.request.async.WebAsyncManager; import org.springframework.web.context.support.StaticWebApplicationContext; /** @@ -44,8 +60,6 @@ import org.springframework.web.context.support.StaticWebApplicationContext; * @author Chris Beams */ public class OpenEntityManagerInViewTests extends TestCase { - - private MockControl factoryControl, managerControl; private EntityManager manager; @@ -56,15 +70,13 @@ public class OpenEntityManagerInViewTests extends TestCase { @Override protected void setUp() throws Exception { - factoryControl = MockControl.createControl(EntityManagerFactory.class); - factory = (EntityManagerFactory) factoryControl.getMock(); - managerControl = MockControl.createControl(EntityManager.class); - manager = (EntityManager) managerControl.getMock(); + factory = createMock(EntityManagerFactory.class); + manager = createMock(EntityManager.class); template = new JpaTemplate(factory); template.afterPropertiesSet(); - factoryControl.expectAndReturn(factory.createEntityManager(), manager); + expect(factory.createEntityManager()).andReturn(manager); } @Override @@ -82,8 +94,7 @@ public class OpenEntityManagerInViewTests extends TestCase { MockServletContext sc = new MockServletContext(); MockHttpServletRequest request = new MockHttpServletRequest(sc); - managerControl.replay(); - factoryControl.replay(); + replay(manager, factory); interceptor.preHandle(new ServletWebRequest(request)); assertTrue(TransactionSynchronizationManager.hasResource(factory)); @@ -102,55 +113,131 @@ public class OpenEntityManagerInViewTests extends TestCase { interceptor.postHandle(new ServletWebRequest(request), null); interceptor.afterCompletion(new ServletWebRequest(request), null); - factoryControl.verify(); - managerControl.verify(); + verify(manager, factory); - managerControl.reset(); - factoryControl.reset(); - managerControl.replay(); - factoryControl.replay(); + reset(manager, factory); + replay(manager, factory); interceptor.postHandle(new ServletWebRequest(request), null); assertTrue(TransactionSynchronizationManager.hasResource(factory)); - factoryControl.verify(); - managerControl.verify(); + verify(manager, factory); - managerControl.reset(); - factoryControl.reset(); + reset(manager, factory); - managerControl.expectAndReturn(manager.isOpen(), true); + expect(manager.isOpen()).andReturn(true); manager.close(); - managerControl.replay(); - factoryControl.replay(); + replay(manager, factory); interceptor.afterCompletion(new ServletWebRequest(request), null); assertFalse(TransactionSynchronizationManager.hasResource(factory)); - factoryControl.verify(); - managerControl.verify(); + verify(manager, factory); + } + + public void testOpenEntityManagerInViewInterceptorAsyncScenario() throws Exception { + + // Initial request thread + + OpenEntityManagerInViewInterceptor interceptor = new OpenEntityManagerInViewInterceptor(); + interceptor.setEntityManagerFactory(factory); + + MockServletContext sc = new MockServletContext(); + MockHttpServletRequest request = new MockHttpServletRequest(sc); + ServletWebRequest webRequest = new ServletWebRequest(request); + + replay(manager, factory); + + interceptor.preHandle(webRequest); + assertTrue(TransactionSynchronizationManager.hasResource(factory)); + + verify(manager, factory); + + AsyncWebRequest asyncWebRequest = createStrictMock(AsyncWebRequest.class); + asyncWebRequest.addCompletionHandler((Runnable) anyObject()); + asyncWebRequest.startAsync(); + replay(asyncWebRequest); + + WebAsyncManager asyncManager = AsyncWebUtils.getAsyncManager(webRequest); + asyncManager.setAsyncWebRequest(asyncWebRequest); + + asyncManager.startCallableProcessing(new Callable() { + public String call() throws Exception { + return "anything"; + } + }); + + verify(asyncWebRequest); + reset(asyncWebRequest); + replay(asyncWebRequest); + + interceptor.afterConcurrentHandlingStarted(webRequest); + assertFalse(TransactionSynchronizationManager.hasResource(factory)); + + // Async dispatch thread + + reset(manager, factory); + replay(manager, factory); + + interceptor.preHandle(webRequest); + assertTrue(TransactionSynchronizationManager.hasResource(factory)); + + verify(manager, factory); + reset(manager, factory); + replay(manager, factory); + + asyncManager.clearConcurrentResult(); + + // check that further invocations simply participate + interceptor.preHandle(new ServletWebRequest(request)); + + interceptor.preHandle(new ServletWebRequest(request)); + interceptor.postHandle(new ServletWebRequest(request), null); + interceptor.afterCompletion(new ServletWebRequest(request), null); + + interceptor.postHandle(new ServletWebRequest(request), null); + interceptor.afterCompletion(new ServletWebRequest(request), null); + + interceptor.preHandle(new ServletWebRequest(request)); + interceptor.postHandle(new ServletWebRequest(request), null); + interceptor.afterCompletion(new ServletWebRequest(request), null); + + verify(manager, factory); + reset(manager, factory); + replay(manager, factory); + + interceptor.postHandle(webRequest, null); + assertTrue(TransactionSynchronizationManager.hasResource(factory)); + + verify(manager, factory); + reset(manager, factory); + + expect(manager.isOpen()).andReturn(true); + manager.close(); + + replay(manager, factory); + + interceptor.afterCompletion(webRequest, null); + assertFalse(TransactionSynchronizationManager.hasResource(factory)); + + verify(manager, factory); } public void testOpenEntityManagerInViewFilter() throws Exception { - managerControl.expectAndReturn(manager.isOpen(), true); + expect(manager.isOpen()).andReturn(true); manager.close(); - managerControl.replay(); - factoryControl.replay(); + replay(manager, factory); - MockControl factoryControl2 = MockControl.createControl(EntityManagerFactory.class); - final EntityManagerFactory factory2 = (EntityManagerFactory) factoryControl2.getMock(); + final EntityManagerFactory factory2 = (EntityManagerFactory) createMock(EntityManagerFactory.class); + final EntityManager manager2 = (EntityManager) createMock(EntityManager.class); - MockControl managerControl2 = MockControl.createControl(EntityManager.class); - EntityManager manager2 = (EntityManager) managerControl2.getMock(); - - factoryControl2.expectAndReturn(factory2.createEntityManager(), manager2); - managerControl2.expectAndReturn(manager2.isOpen(), true); + expect(factory2.createEntityManager()).andReturn(manager2); + expect(manager2.isOpen()).andReturn(true); manager2.close(); - factoryControl2.replay(); - managerControl2.replay(); + replay(factory2, manager2); MockServletContext sc = new MockServletContext(); StaticWebApplicationContext wac = new StaticWebApplicationContext(); @@ -195,10 +282,109 @@ public class OpenEntityManagerInViewTests extends TestCase { assertFalse(TransactionSynchronizationManager.hasResource(factory2)); assertNotNull(request.getAttribute("invoked")); - factoryControl.verify(); - managerControl.verify(); - factoryControl2.verify(); - managerControl2.verify(); + verify(manager, factory); + verify(factory2, manager2); + + wac.close(); + } + + public void testOpenEntityManagerInViewFilterAsyncScenario() throws Exception { + expect(manager.isOpen()).andReturn(true); + manager.close(); + + replay(manager, factory); + + final EntityManagerFactory factory2 = (EntityManagerFactory) createMock(EntityManagerFactory.class); + final EntityManager manager2 = (EntityManager) createMock(EntityManager.class); + + expect(factory2.createEntityManager()).andReturn(manager2); + expect(manager2.isOpen()).andReturn(true); + manager2.close(); + + replay(factory2, manager2); + + MockServletContext sc = new MockServletContext(); + StaticWebApplicationContext wac = new StaticWebApplicationContext(); + wac.setServletContext(sc); + wac.getDefaultListableBeanFactory().registerSingleton("entityManagerFactory", factory); + wac.getDefaultListableBeanFactory().registerSingleton("myEntityManagerFactory", factory2); + 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"); + filterConfig2.addInitParameter("entityManagerFactoryBeanName", "myEntityManagerFactory"); + + final OpenEntityManagerInViewFilter filter = new OpenEntityManagerInViewFilter(); + filter.init(filterConfig); + final OpenEntityManagerInViewFilter filter2 = new OpenEntityManagerInViewFilter(); + filter2.init(filterConfig2); + + final AtomicInteger count = new AtomicInteger(0); + + final FilterChain filterChain = new FilterChain() { + public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse) { + assertTrue(TransactionSynchronizationManager.hasResource(factory)); + servletRequest.setAttribute("invoked", Boolean.TRUE); + count.incrementAndGet(); + } + }; + + final AtomicInteger count2 = new AtomicInteger(0); + + final FilterChain filterChain2 = new FilterChain() { + public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse) + throws IOException, ServletException { + assertTrue(TransactionSynchronizationManager.hasResource(factory2)); + filter.doFilter(servletRequest, servletResponse, filterChain); + count2.incrementAndGet(); + } + }; + + FilterChain filterChain3 = new PassThroughFilterChain(filter2, filterChain2); + + AsyncWebRequest asyncWebRequest = createStrictMock(AsyncWebRequest.class); + asyncWebRequest.addCompletionHandler((Runnable) anyObject()); + asyncWebRequest.startAsync(); + expect(asyncWebRequest.isAsyncStarted()).andReturn(true); + expectLastCall().anyTimes(); + replay(asyncWebRequest); + + WebAsyncManager asyncManager = AsyncWebUtils.getAsyncManager(request); + asyncManager.setAsyncWebRequest(asyncWebRequest); + asyncManager.startCallableProcessing(new Callable() { + public String call() throws Exception { + return "anything"; + } + }); + + assertFalse(TransactionSynchronizationManager.hasResource(factory)); + assertFalse(TransactionSynchronizationManager.hasResource(factory2)); + filter2.doFilter(request, response, filterChain3); + assertFalse(TransactionSynchronizationManager.hasResource(factory)); + assertFalse(TransactionSynchronizationManager.hasResource(factory2)); + assertEquals(1, count.get()); + assertEquals(1, count2.get()); + assertNotNull(request.getAttribute("invoked")); + + // Async dispatch after concurrent handling produces result ... + + reset(asyncWebRequest); + expect(asyncWebRequest.isAsyncStarted()).andReturn(false).anyTimes(); + replay(asyncWebRequest); + + assertFalse(TransactionSynchronizationManager.hasResource(factory)); + assertFalse(TransactionSynchronizationManager.hasResource(factory2)); + filter.doFilter(request, response, filterChain3); + assertFalse(TransactionSynchronizationManager.hasResource(factory)); + assertFalse(TransactionSynchronizationManager.hasResource(factory2)); + assertEquals(2, count.get()); + assertEquals(2, count2.get()); + + verify(manager, factory); + verify(factory2, manager2); wac.close(); } diff --git a/spring-web/src/main/java/org/springframework/web/context/request/Log4jNestedDiagnosticContextInterceptor.java b/spring-web/src/main/java/org/springframework/web/context/request/Log4jNestedDiagnosticContextInterceptor.java index 2bf99cbe2a..e8283f8846 100644 --- a/spring-web/src/main/java/org/springframework/web/context/request/Log4jNestedDiagnosticContextInterceptor.java +++ b/spring-web/src/main/java/org/springframework/web/context/request/Log4jNestedDiagnosticContextInterceptor.java @@ -18,8 +18,8 @@ package org.springframework.web.context.request; import org.apache.log4j.Logger; import org.apache.log4j.NDC; - import org.springframework.ui.ModelMap; +import org.springframework.web.context.request.async.AsyncWebUtils; /** * Request logging interceptor that adds a request context message to the @@ -60,6 +60,11 @@ public class Log4jNestedDiagnosticContextInterceptor implements WebRequestInterc * Adds a message the Log4J NDC before the request is processed. */ public void preHandle(WebRequest request) throws Exception { + + if (AsyncWebUtils.getAsyncManager(request).hasConcurrentResult()) { + return; + } + NDC.push(getNestedDiagnosticContextMessage(request)); } diff --git a/spring-web/src/main/java/org/springframework/web/filter/AbstractRequestLoggingFilter.java b/spring-web/src/main/java/org/springframework/web/filter/AbstractRequestLoggingFilter.java index 7bdef199ab..4872cbf8ff 100644 --- a/spring-web/src/main/java/org/springframework/web/filter/AbstractRequestLoggingFilter.java +++ b/spring-web/src/main/java/org/springframework/web/filter/AbstractRequestLoggingFilter.java @@ -198,19 +198,15 @@ public abstract class AbstractRequestLoggingFilter extends OncePerRequestFilter protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { - boolean isAsyncDispatch = isAsyncDispatch(request); + boolean isFirstRequest = !isAsyncDispatch(request); if (isIncludePayload()) { - if (isAsyncDispatch) { - request = WebUtils.getNativeRequest(request, RequestCachingRequestWrapper.class); - Assert.notNull(request, "Expected wrapped request"); - } - else { + if (isFirstRequest) { request = new RequestCachingRequestWrapper(request); } } - if (!isAsyncDispatch) { + if (isFirstRequest) { beforeRequest(request, getBeforeMessage(request)); } try { diff --git a/spring-web/src/main/java/org/springframework/web/filter/ShallowEtagHeaderFilter.java b/spring-web/src/main/java/org/springframework/web/filter/ShallowEtagHeaderFilter.java index 6f0e33c6a9..6852e12624 100644 --- a/spring-web/src/main/java/org/springframework/web/filter/ShallowEtagHeaderFilter.java +++ b/spring-web/src/main/java/org/springframework/web/filter/ShallowEtagHeaderFilter.java @@ -66,25 +66,25 @@ public class ShallowEtagHeaderFilter extends OncePerRequestFilter { protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { - ShallowEtagResponseWrapper responseWrapper; + boolean isFirstRequest = !isAsyncDispatch(request); - if (isAsyncDispatch(request)) { - responseWrapper = WebUtils.getNativeResponse(response, ShallowEtagResponseWrapper.class); - Assert.notNull(responseWrapper, "Expected wrapped response"); - } - else { - responseWrapper = new ShallowEtagResponseWrapper(response); + if (isFirstRequest) { + response = new ShallowEtagResponseWrapper(response); } - filterChain.doFilter(request, responseWrapper); + filterChain.doFilter(request, response); if (isLastRequestThread(request)) { - updateResponse(request, response, responseWrapper); + updateResponse(request, response); } } - private void updateResponse(HttpServletRequest request, HttpServletResponse response, - ShallowEtagResponseWrapper responseWrapper) throws IOException { + private void updateResponse(HttpServletRequest request, HttpServletResponse response) throws IOException { + + ShallowEtagResponseWrapper responseWrapper = WebUtils.getNativeResponse(response, ShallowEtagResponseWrapper.class); + Assert.notNull(responseWrapper, "ShallowEtagResponseWrapper not found"); + + response = (HttpServletResponse) responseWrapper.getResponse(); byte[] body = responseWrapper.toByteArray(); int statusCode = responseWrapper.getStatusCode();