From 5d8fac86d749c9ea98eb7eda58655fe5fa3616d0 Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Wed, 30 Oct 2013 22:50:01 -0400 Subject: [PATCH] Add timeout async request handling to OSIV components This change adds async web request timeout handling to OSIV filters and interceptors to ensure the session or entity manager is released. Issue: SPR-10874 --- .../support/AsyncRequestInterceptor.java | 109 +++++++++++++ .../support/OpenSessionInViewFilter.java | 52 ++---- .../support/OpenSessionInViewInterceptor.java | 39 +---- .../support/AsyncRequestInterceptor.java | 110 +++++++++++++ .../support/OpenSessionInViewFilter.java | 43 +---- .../support/OpenSessionInViewInterceptor.java | 38 +---- .../jpa/support/AsyncRequestInterceptor.java | 111 +++++++++++++ .../OpenEntityManagerInViewFilter.java | 40 +---- .../OpenEntityManagerInViewInterceptor.java | 35 +--- .../support/OpenSessionInViewTests.java | 152 +++++++++++++++--- .../support/OpenEntityManagerInViewTests.java | 108 ++++++++----- ...redResultProcessingInterceptorAdapter.java | 6 +- 12 files changed, 572 insertions(+), 271 deletions(-) create mode 100644 spring-orm-hibernate4/src/main/java/org/springframework/orm/hibernate4/support/AsyncRequestInterceptor.java create mode 100644 spring-orm/src/main/java/org/springframework/orm/hibernate3/support/AsyncRequestInterceptor.java create mode 100644 spring-orm/src/main/java/org/springframework/orm/jpa/support/AsyncRequestInterceptor.java diff --git a/spring-orm-hibernate4/src/main/java/org/springframework/orm/hibernate4/support/AsyncRequestInterceptor.java b/spring-orm-hibernate4/src/main/java/org/springframework/orm/hibernate4/support/AsyncRequestInterceptor.java new file mode 100644 index 0000000000..3887915854 --- /dev/null +++ b/spring-orm-hibernate4/src/main/java/org/springframework/orm/hibernate4/support/AsyncRequestInterceptor.java @@ -0,0 +1,109 @@ +/* + * Copyright 2002-2013 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.orm.hibernate4.support; + + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.hibernate.SessionFactory; +import org.springframework.orm.hibernate4.SessionFactoryUtils; +import org.springframework.orm.hibernate4.SessionHolder; +import org.springframework.transaction.support.TransactionSynchronizationManager; +import org.springframework.web.context.request.NativeWebRequest; +import org.springframework.web.context.request.async.CallableProcessingInterceptorAdapter; +import org.springframework.web.context.request.async.DeferredResult; +import org.springframework.web.context.request.async.DeferredResultProcessingInterceptor; + +import java.util.concurrent.Callable; + +/** + * An interceptor with asynchronous web requests used in OpenSessionInViewFilter and + * OpenSessionInViewInterceptor. + * + * Ensures the following: + * 1) The session is bound/unbound when "callable processing" is started + * 2) The session is closed if an async request times out + * + * @author Rossen Stoyanchev + * @since 3.2.5 + */ +public class AsyncRequestInterceptor extends CallableProcessingInterceptorAdapter + implements DeferredResultProcessingInterceptor { + + private static Log logger = LogFactory.getLog(AsyncRequestInterceptor.class); + + private final SessionFactory sessionFactory; + + private final SessionHolder sessionHolder; + + private volatile boolean timeoutInProgress; + + public AsyncRequestInterceptor(SessionFactory sessionFactory, SessionHolder sessionHolder) { + this.sessionFactory = sessionFactory; + this.sessionHolder = sessionHolder; + } + + @Override + public void preProcess(NativeWebRequest request, Callable task) { + bindSession(); + } + + public void bindSession() { + this.timeoutInProgress = false; + TransactionSynchronizationManager.bindResource(this.sessionFactory, this.sessionHolder); + } + + @Override + public void postProcess(NativeWebRequest request, Callable task, Object concurrentResult) { + TransactionSynchronizationManager.unbindResource(this.sessionFactory); + } + + @Override + public Object handleTimeout(NativeWebRequest request, Callable task) { + this.timeoutInProgress = true; + return RESULT_NONE; // give other interceptors a chance to handle the timeout + } + + @Override + public void afterCompletion(NativeWebRequest request, Callable task) throws Exception { + closeAfterTimeout(); + } + + private void closeAfterTimeout() { + if (this.timeoutInProgress) { + logger.debug("Closing Hibernate Session after async request timeout"); + SessionFactoryUtils.closeSession(sessionHolder.getSession()); + } + } + + // Implementation of DeferredResultProcessingInterceptor methods + + public void beforeConcurrentHandling(NativeWebRequest request, DeferredResult deferredResult) { } + public void preProcess(NativeWebRequest request, DeferredResult deferredResult) { } + public void postProcess(NativeWebRequest request, DeferredResult deferredResult, Object result) { } + + @Override + public boolean handleTimeout(NativeWebRequest request, DeferredResult deferredResult) { + this.timeoutInProgress = true; + return true; // give other interceptors a chance to handle the timeout + } + + @Override + public void afterCompletion(NativeWebRequest request, DeferredResult deferredResult) { + closeAfterTimeout(); + } +} diff --git a/spring-orm-hibernate4/src/main/java/org/springframework/orm/hibernate4/support/OpenSessionInViewFilter.java b/spring-orm-hibernate4/src/main/java/org/springframework/orm/hibernate4/support/OpenSessionInViewFilter.java index 0045a5d3f8..6ab5668293 100644 --- a/spring-orm-hibernate4/src/main/java/org/springframework/orm/hibernate4/support/OpenSessionInViewFilter.java +++ b/spring-orm-hibernate4/src/main/java/org/springframework/orm/hibernate4/support/OpenSessionInViewFilter.java @@ -16,14 +16,6 @@ package org.springframework.orm.hibernate4.support; -import java.io.IOException; -import java.util.concurrent.Callable; - -import javax.servlet.FilterChain; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - import org.hibernate.FlushMode; import org.hibernate.HibernateException; import org.hibernate.Session; @@ -33,13 +25,17 @@ 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.NativeWebRequest; -import org.springframework.web.context.request.async.CallableProcessingInterceptorAdapter; import org.springframework.web.context.request.async.WebAsyncManager; import org.springframework.web.context.request.async.WebAsyncUtils; import org.springframework.web.context.support.WebApplicationContextUtils; import org.springframework.web.filter.OncePerRequestFilter; +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + /** * Servlet 2.3 Filter that binds a Hibernate Session to the thread for the entire * processing of the request. Intended for the "Open Session in View" pattern, @@ -143,8 +139,9 @@ public class OpenSessionInViewFilter extends OncePerRequestFilter { SessionHolder sessionHolder = new SessionHolder(session); TransactionSynchronizationManager.bindResource(sessionFactory, sessionHolder); - asyncManager.registerCallableInterceptor(key, - new SessionBindingCallableInterceptor(sessionFactory, sessionHolder)); + AsyncRequestInterceptor interceptor = new AsyncRequestInterceptor(sessionFactory, sessionHolder); + asyncManager.registerCallableInterceptor(key, interceptor); + asyncManager.registerDeferredResultInterceptor(key, interceptor); } } @@ -216,37 +213,8 @@ public class OpenSessionInViewFilter extends OncePerRequestFilter { if (asyncManager.getCallableInterceptor(key) == null) { return false; } - ((SessionBindingCallableInterceptor) asyncManager.getCallableInterceptor(key)).initializeThread(); + ((AsyncRequestInterceptor) asyncManager.getCallableInterceptor(key)).bindSession(); return true; } - - /** - * Bind and unbind the Hibernate {@code Session} to the current thread. - */ - private static class SessionBindingCallableInterceptor extends CallableProcessingInterceptorAdapter { - - private final SessionFactory sessionFactory; - - private final SessionHolder sessionHolder; - - public SessionBindingCallableInterceptor(SessionFactory sessionFactory, SessionHolder sessionHolder) { - this.sessionFactory = sessionFactory; - this.sessionHolder = sessionHolder; - } - - @Override - public void preProcess(NativeWebRequest request, Callable task) { - initializeThread(); - } - - @Override - public void postProcess(NativeWebRequest request, Callable task, Object concurrentResult) { - TransactionSynchronizationManager.unbindResource(this.sessionFactory); - } - - private void initializeThread() { - TransactionSynchronizationManager.bindResource(this.sessionFactory, this.sessionHolder); - } - } } diff --git a/spring-orm-hibernate4/src/main/java/org/springframework/orm/hibernate4/support/OpenSessionInViewInterceptor.java b/spring-orm-hibernate4/src/main/java/org/springframework/orm/hibernate4/support/OpenSessionInViewInterceptor.java index 4d82160eb7..c1c46bc248 100644 --- a/spring-orm-hibernate4/src/main/java/org/springframework/orm/hibernate4/support/OpenSessionInViewInterceptor.java +++ b/spring-orm-hibernate4/src/main/java/org/springframework/orm/hibernate4/support/OpenSessionInViewInterceptor.java @@ -33,9 +33,7 @@ import org.springframework.ui.ModelMap; import org.springframework.web.context.request.AsyncWebRequestInterceptor; import org.springframework.web.context.request.NativeWebRequest; import org.springframework.web.context.request.WebRequest; -import org.springframework.web.context.request.async.CallableProcessingInterceptorAdapter; -import org.springframework.web.context.request.async.WebAsyncManager; -import org.springframework.web.context.request.async.WebAsyncUtils; +import org.springframework.web.context.request.async.*; /** * Spring web request interceptor that binds a Hibernate {@code Session} to the @@ -119,8 +117,10 @@ public class OpenSessionInViewInterceptor implements AsyncWebRequestInterceptor SessionHolder sessionHolder = new SessionHolder(session); TransactionSynchronizationManager.bindResource(getSessionFactory(), sessionHolder); - asyncManager.registerCallableInterceptor(participateAttributeName, - new SessionBindingCallableInterceptor(sessionHolder)); + AsyncRequestInterceptor asyncRequestInterceptor = + new AsyncRequestInterceptor(getSessionFactory(), sessionHolder); + asyncManager.registerCallableInterceptor(participateAttributeName, asyncRequestInterceptor); + asyncManager.registerDeferredResultInterceptor(participateAttributeName, asyncRequestInterceptor); } } @@ -200,35 +200,8 @@ public class OpenSessionInViewInterceptor implements AsyncWebRequestInterceptor if (asyncManager.getCallableInterceptor(key) == null) { return false; } - ((SessionBindingCallableInterceptor) asyncManager.getCallableInterceptor(key)).initializeThread(); + ((AsyncRequestInterceptor) asyncManager.getCallableInterceptor(key)).bindSession(); return true; } - - /** - * Bind and unbind the Hibernate {@code Session} to the current thread. - */ - private class SessionBindingCallableInterceptor extends CallableProcessingInterceptorAdapter { - - private final SessionHolder sessionHolder; - - public SessionBindingCallableInterceptor(SessionHolder sessionHolder) { - this.sessionHolder = sessionHolder; - } - - @Override - public void preProcess(NativeWebRequest request, Callable task) { - initializeThread(); - } - - @Override - public void postProcess(NativeWebRequest request, Callable task, Object concurrentResult) { - TransactionSynchronizationManager.unbindResource(getSessionFactory()); - } - - private void initializeThread() { - TransactionSynchronizationManager.bindResource(getSessionFactory(), this.sessionHolder); - } - } - } diff --git a/spring-orm/src/main/java/org/springframework/orm/hibernate3/support/AsyncRequestInterceptor.java b/spring-orm/src/main/java/org/springframework/orm/hibernate3/support/AsyncRequestInterceptor.java new file mode 100644 index 0000000000..4e161d87df --- /dev/null +++ b/spring-orm/src/main/java/org/springframework/orm/hibernate3/support/AsyncRequestInterceptor.java @@ -0,0 +1,110 @@ +/* + * Copyright 2002-2013 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.orm.hibernate3.support; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.hibernate.SessionFactory; +import org.springframework.orm.hibernate3.SessionFactoryUtils; +import org.springframework.orm.hibernate3.SessionHolder; +import org.springframework.transaction.support.TransactionSynchronizationManager; +import org.springframework.web.context.request.NativeWebRequest; +import org.springframework.web.context.request.async.CallableProcessingInterceptorAdapter; +import org.springframework.web.context.request.async.DeferredResult; +import org.springframework.web.context.request.async.DeferredResultProcessingInterceptor; + +import java.util.concurrent.Callable; + +/** + * An interceptor with asynchronous web requests used in OpenSessionInViewFilter and + * OpenSessionInViewInterceptor. + * + * Ensures the following: + * 1) The session is bound/unbound when "callable processing" is started + * 2) The session is closed if an async request times out + * + * @author Rossen Stoyanchev + * @since 3.2.5 + */ +class AsyncRequestInterceptor extends CallableProcessingInterceptorAdapter + implements DeferredResultProcessingInterceptor { + + private static final Log logger = LogFactory.getLog(AsyncRequestInterceptor.class); + + private final SessionFactory sessionFactory; + + private final SessionHolder sessionHolder; + + private volatile boolean timeoutInProgress; + + + public AsyncRequestInterceptor(SessionFactory sessionFactory, SessionHolder sessionHolder) { + this.sessionFactory = sessionFactory; + this.sessionHolder = sessionHolder; + } + + @Override + public void preProcess(NativeWebRequest request, Callable task) { + bindSession(); + } + + public void bindSession() { + this.timeoutInProgress = false; + TransactionSynchronizationManager.bindResource(this.sessionFactory, this.sessionHolder); + } + + @Override + public void postProcess(NativeWebRequest request, Callable task, Object concurrentResult) { + TransactionSynchronizationManager.unbindResource(this.sessionFactory); + } + + @Override + public Object handleTimeout(NativeWebRequest request, Callable task) { + this.timeoutInProgress = true; + return RESULT_NONE; // give other interceptors a chance to handle the timeout + } + + @Override + public void afterCompletion(NativeWebRequest request, Callable task) throws Exception { + closeAfterTimeout(); + } + + private void closeAfterTimeout() { + if (this.timeoutInProgress) { + logger.debug("Closing Hibernate Session after async request timeout"); + SessionFactoryUtils.closeSession(sessionHolder.getSession()); + } + } + + // Implementation of DeferredResultProcessingInterceptor methods + + public void beforeConcurrentHandling(NativeWebRequest request, DeferredResult deferredResult) {} + public void preProcess(NativeWebRequest request, DeferredResult deferredResult) {} + public void postProcess(NativeWebRequest request, DeferredResult deferredResult, Object result) {} + + @Override + public boolean handleTimeout(NativeWebRequest request, DeferredResult deferredResult) { + this.timeoutInProgress = true; + return true; // give other interceptors a chance to handle the timeout + } + + @Override + public void afterCompletion(NativeWebRequest request, DeferredResult deferredResult) { + closeAfterTimeout(); + } + +} 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 7aab7c8910..ced44d6f5e 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 @@ -17,7 +17,6 @@ package org.springframework.orm.hibernate3.support; import java.io.IOException; -import java.util.concurrent.Callable; import javax.servlet.FilterChain; import javax.servlet.ServletException; @@ -33,10 +32,7 @@ import org.springframework.orm.hibernate3.SessionHolder; import org.springframework.transaction.support.TransactionSynchronizationManager; import org.springframework.util.Assert; import org.springframework.web.context.WebApplicationContext; -import org.springframework.web.context.request.NativeWebRequest; -import org.springframework.web.context.request.async.CallableProcessingInterceptorAdapter; -import org.springframework.web.context.request.async.WebAsyncManager; -import org.springframework.web.context.request.async.WebAsyncUtils; +import org.springframework.web.context.request.async.*; import org.springframework.web.context.support.WebApplicationContextUtils; import org.springframework.web.filter.OncePerRequestFilter; @@ -212,8 +208,9 @@ public class OpenSessionInViewFilter extends OncePerRequestFilter { SessionHolder sessionHolder = new SessionHolder(session); TransactionSynchronizationManager.bindResource(sessionFactory, sessionHolder); - asyncManager.registerCallableInterceptor(key, - new SessionBindingCallableInterceptor(sessionFactory, sessionHolder)); + AsyncRequestInterceptor interceptor = new AsyncRequestInterceptor(sessionFactory, sessionHolder); + asyncManager.registerCallableInterceptor(key, interceptor); + asyncManager.registerDeferredResultInterceptor(key, interceptor); } } } @@ -319,38 +316,8 @@ public class OpenSessionInViewFilter extends OncePerRequestFilter { if (asyncManager.getCallableInterceptor(key) == null) { return false; } - ((SessionBindingCallableInterceptor) asyncManager.getCallableInterceptor(key)).initializeThread(); + ((AsyncRequestInterceptor) asyncManager.getCallableInterceptor(key)).bindSession(); return true; } - - /** - * Bind and unbind the Hibernate {@code Session} to the current thread. - */ - private static class SessionBindingCallableInterceptor extends CallableProcessingInterceptorAdapter { - - private final SessionFactory sessionFactory; - - private final SessionHolder sessionHolder; - - public SessionBindingCallableInterceptor(SessionFactory sessionFactory, SessionHolder sessionHolder) { - this.sessionFactory = sessionFactory; - this.sessionHolder = sessionHolder; - } - - @Override - public void preProcess(NativeWebRequest request, Callable task) { - initializeThread(); - } - - @Override - public void postProcess(NativeWebRequest request, Callable task, Object concurrentResult) { - TransactionSynchronizationManager.unbindResource(this.sessionFactory); - } - - private void initializeThread() { - TransactionSynchronizationManager.bindResource(this.sessionFactory, this.sessionHolder); - } - } - } 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 9daf211d50..d42ce03c12 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 @@ -29,9 +29,7 @@ import org.springframework.ui.ModelMap; import org.springframework.web.context.request.AsyncWebRequestInterceptor; import org.springframework.web.context.request.NativeWebRequest; import org.springframework.web.context.request.WebRequest; -import org.springframework.web.context.request.async.CallableProcessingInterceptorAdapter; -import org.springframework.web.context.request.async.WebAsyncManager; -import org.springframework.web.context.request.async.WebAsyncUtils; +import org.springframework.web.context.request.async.*; /** * Spring web request interceptor that binds a Hibernate {@code Session} to the @@ -173,8 +171,10 @@ public class OpenSessionInViewInterceptor extends HibernateAccessor implements A SessionHolder sessionHolder = new SessionHolder(session); TransactionSynchronizationManager.bindResource(getSessionFactory(), sessionHolder); - asyncManager.registerCallableInterceptor(participateAttributeName, - new SessionBindingCallableInterceptor(sessionHolder)); + AsyncRequestInterceptor asyncRequestInterceptor = + new AsyncRequestInterceptor(getSessionFactory(), sessionHolder); + asyncManager.registerCallableInterceptor(participateAttributeName, asyncRequestInterceptor); + asyncManager.registerDeferredResultInterceptor(participateAttributeName, asyncRequestInterceptor); } else { // deferred close mode @@ -272,34 +272,8 @@ public class OpenSessionInViewInterceptor extends HibernateAccessor implements A if (asyncManager.getCallableInterceptor(key) == null) { return false; } - ((SessionBindingCallableInterceptor) asyncManager.getCallableInterceptor(key)).initializeThread(); + ((AsyncRequestInterceptor) asyncManager.getCallableInterceptor(key)).bindSession(); return true; } - - /** - * Bind and unbind the Hibernate {@code Session} to the current thread. - */ - private class SessionBindingCallableInterceptor extends CallableProcessingInterceptorAdapter { - - private final SessionHolder sessionHolder; - - public SessionBindingCallableInterceptor(SessionHolder sessionHolder) { - this.sessionHolder = sessionHolder; - } - - @Override - public void preProcess(NativeWebRequest request, Callable task) { - initializeThread(); - } - - @Override - public void postProcess(NativeWebRequest request, Callable task, Object concurrentResult) { - TransactionSynchronizationManager.unbindResource(getSessionFactory()); - } - - private void initializeThread() { - TransactionSynchronizationManager.bindResource(getSessionFactory(), this.sessionHolder); - } - } } diff --git a/spring-orm/src/main/java/org/springframework/orm/jpa/support/AsyncRequestInterceptor.java b/spring-orm/src/main/java/org/springframework/orm/jpa/support/AsyncRequestInterceptor.java new file mode 100644 index 0000000000..046bd8f650 --- /dev/null +++ b/spring-orm/src/main/java/org/springframework/orm/jpa/support/AsyncRequestInterceptor.java @@ -0,0 +1,111 @@ +/* + * Copyright 2002-2013 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.orm.jpa.support; + + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.orm.jpa.EntityManagerFactoryUtils; +import org.springframework.orm.jpa.EntityManagerHolder; +import org.springframework.transaction.support.TransactionSynchronizationManager; +import org.springframework.web.context.request.NativeWebRequest; +import org.springframework.web.context.request.async.CallableProcessingInterceptorAdapter; +import org.springframework.web.context.request.async.DeferredResult; +import org.springframework.web.context.request.async.DeferredResultProcessingInterceptor; + +import javax.persistence.EntityManagerFactory; +import java.util.concurrent.Callable; + +/** + * An interceptor with asynchronous web requests used in OpenSessionInViewFilter and + * OpenSessionInViewInterceptor. + * + * Ensures the following: + * 1) The session is bound/unbound when "callable processing" is started + * 2) The session is closed if an async request times out + * + * @author Rossen Stoyanchev + * @since 3.2.5 + */ +public class AsyncRequestInterceptor extends CallableProcessingInterceptorAdapter + implements DeferredResultProcessingInterceptor { + + private static Log logger = LogFactory.getLog(AsyncRequestInterceptor.class); + + private final EntityManagerFactory emFactory; + + private final EntityManagerHolder emHolder; + + private volatile boolean timeoutInProgress; + + + public AsyncRequestInterceptor(EntityManagerFactory emFactory, EntityManagerHolder emHolder) { + this.emFactory = emFactory; + this.emHolder = emHolder; + } + + @Override + public void preProcess(NativeWebRequest request, Callable task) { + bindSession(); + } + + public void bindSession() { + this.timeoutInProgress = false; + TransactionSynchronizationManager.bindResource(this.emFactory, this.emHolder); + } + + @Override + public void postProcess(NativeWebRequest request, Callable task, Object concurrentResult) { + TransactionSynchronizationManager.unbindResource(this.emFactory); + } + + @Override + public Object handleTimeout(NativeWebRequest request, Callable task) { + this.timeoutInProgress = true; + return RESULT_NONE; // give other interceptors a chance to handle the timeout + } + + @Override + public void afterCompletion(NativeWebRequest request, Callable task) throws Exception { + closeAfterTimeout(); + } + + private void closeAfterTimeout() { + if (this.timeoutInProgress) { + logger.debug("Closing JPA EntityManager after async request timeout"); + EntityManagerFactoryUtils.closeEntityManager(emHolder.getEntityManager()); + } + } + + // Implementation of DeferredResultProcessingInterceptor methods + + public void beforeConcurrentHandling(NativeWebRequest request, DeferredResult deferredResult) { } + public void preProcess(NativeWebRequest request, DeferredResult deferredResult) { } + public void postProcess(NativeWebRequest request, DeferredResult deferredResult, Object result) { } + + @Override + public boolean handleTimeout(NativeWebRequest request, DeferredResult deferredResult) { + this.timeoutInProgress = true; + return true; // give other interceptors a chance to handle the timeout + } + + @Override + public void afterCompletion(NativeWebRequest request, DeferredResult deferredResult) { + closeAfterTimeout(); + } +} + 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 24febab251..b518fe262d 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 @@ -34,9 +34,7 @@ import org.springframework.transaction.support.TransactionSynchronizationManager import org.springframework.util.StringUtils; import org.springframework.web.context.WebApplicationContext; import org.springframework.web.context.request.NativeWebRequest; -import org.springframework.web.context.request.async.CallableProcessingInterceptorAdapter; -import org.springframework.web.context.request.async.WebAsyncManager; -import org.springframework.web.context.request.async.WebAsyncUtils; +import org.springframework.web.context.request.async.*; import org.springframework.web.context.support.WebApplicationContextUtils; import org.springframework.web.filter.OncePerRequestFilter; @@ -166,7 +164,9 @@ public class OpenEntityManagerInViewFilter extends OncePerRequestFilter { EntityManagerHolder emHolder = new EntityManagerHolder(em); TransactionSynchronizationManager.bindResource(emf, emHolder); - asyncManager.registerCallableInterceptor(key, new EntityManagerBindingCallableInterceptor(emf, emHolder)); + AsyncRequestInterceptor interceptor = new AsyncRequestInterceptor(emf, emHolder); + asyncManager.registerCallableInterceptor(key, interceptor); + asyncManager.registerDeferredResultInterceptor(key, interceptor); } catch (PersistenceException ex) { throw new DataAccessResourceFailureException("Could not create JPA EntityManager", ex); @@ -242,38 +242,8 @@ public class OpenEntityManagerInViewFilter extends OncePerRequestFilter { if (asyncManager.getCallableInterceptor(key) == null) { return false; } - ((EntityManagerBindingCallableInterceptor) asyncManager.getCallableInterceptor(key)).initializeThread(); + ((AsyncRequestInterceptor) asyncManager.getCallableInterceptor(key)).bindSession(); return true; } - /** - * Bind and unbind the {@code EntityManager} to the current thread. - */ - private static class EntityManagerBindingCallableInterceptor extends CallableProcessingInterceptorAdapter { - - private final EntityManagerFactory emFactory; - - private final EntityManagerHolder emHolder; - - - public EntityManagerBindingCallableInterceptor(EntityManagerFactory emFactory, EntityManagerHolder emHolder) { - this.emFactory = emFactory; - this.emHolder = emHolder; - } - - @Override - public void preProcess(NativeWebRequest request, Callable task) { - initializeThread(); - } - - @Override - public void postProcess(NativeWebRequest request, Callable task, Object concurrentResult) { - TransactionSynchronizationManager.unbindResource(this.emFactory); - } - - private void initializeThread() { - TransactionSynchronizationManager.bindResource(this.emFactory, this.emHolder); - } - } - } 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 fdebb8c8c8..dbc88b73e3 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 @@ -93,8 +93,9 @@ public class OpenEntityManagerInViewInterceptor extends EntityManagerFactoryAcce EntityManagerHolder emHolder = new EntityManagerHolder(em); TransactionSynchronizationManager.bindResource(getEntityManagerFactory(), emHolder); - asyncManager.registerCallableInterceptor(participateAttributeName, - new EntityManagerBindingCallableInterceptor(emHolder)); + AsyncRequestInterceptor interceptor = new AsyncRequestInterceptor(getEntityManagerFactory(), emHolder); + asyncManager.registerCallableInterceptor(participateAttributeName, interceptor); + asyncManager.registerDeferredResultInterceptor(participateAttributeName, interceptor); } catch (PersistenceException ex) { throw new DataAccessResourceFailureException("Could not create JPA EntityManager", ex); @@ -154,36 +155,8 @@ public class OpenEntityManagerInViewInterceptor extends EntityManagerFactoryAcce if (asyncManager.getCallableInterceptor(key) == null) { return false; } - ((EntityManagerBindingCallableInterceptor) asyncManager.getCallableInterceptor(key)).initializeThread(); + ((AsyncRequestInterceptor) asyncManager.getCallableInterceptor(key)).bindSession(); return true; } - - /** - * Bind and unbind the Hibernate {@code Session} to the current thread. - */ - private class EntityManagerBindingCallableInterceptor extends CallableProcessingInterceptorAdapter { - - private final EntityManagerHolder emHolder; - - - public EntityManagerBindingCallableInterceptor(EntityManagerHolder emHolder) { - this.emHolder = emHolder; - } - - @Override - public void preProcess(NativeWebRequest request, Callable task) { - initializeThread(); - } - - @Override - public void postProcess(NativeWebRequest request, Callable task, Object concurrentResult) { - TransactionSynchronizationManager.unbindResource(getEntityManagerFactory()); - } - - private void initializeThread() { - TransactionSynchronizationManager.bindResource(getEntityManagerFactory(), this.emHolder); - } - } - } 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 0777c3ae11..c57dbcfc1e 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 @@ -18,13 +18,11 @@ package org.springframework.orm.hibernate3.support; import java.io.IOException; import java.sql.Connection; +import java.util.List; import java.util.concurrent.Callable; import java.util.concurrent.atomic.AtomicInteger; -import javax.servlet.FilterChain; -import javax.servlet.ServletException; -import javax.servlet.ServletRequest; -import javax.servlet.ServletResponse; +import javax.servlet.*; import javax.transaction.TransactionManager; import org.hibernate.FlushMode; @@ -35,11 +33,7 @@ import org.hibernate.engine.SessionFactoryImplementor; import org.junit.Before; import org.junit.Test; import org.springframework.core.task.SimpleAsyncTaskExecutor; -import org.springframework.mock.web.test.MockFilterConfig; -import org.springframework.mock.web.test.MockHttpServletRequest; -import org.springframework.mock.web.test.MockHttpServletResponse; -import org.springframework.mock.web.test.MockServletContext; -import org.springframework.mock.web.test.PassThroughFilterChain; +import org.springframework.mock.web.test.*; import org.springframework.orm.hibernate3.HibernateAccessor; import org.springframework.orm.hibernate3.HibernateTransactionManager; import org.springframework.orm.hibernate3.SessionFactoryUtils; @@ -50,9 +44,11 @@ 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.StandardServletAsyncWebRequest; import org.springframework.web.context.request.async.WebAsyncManager; import org.springframework.web.context.request.async.WebAsyncUtils; import org.springframework.web.context.support.StaticWebApplicationContext; +import org.springframework.web.util.NestedServletException; import static org.junit.Assert.*; import static org.mockito.BDDMockito.*; @@ -79,6 +75,7 @@ public class OpenSessionInViewTests { public void setup() { this.sc = new MockServletContext(); this.request = new MockHttpServletRequest(sc); + this.request.setAsyncSupported(true); this.response = new MockHttpServletResponse(); this.webRequest = new ServletWebRequest(this.request); } @@ -142,12 +139,10 @@ public class OpenSessionInViewTests { interceptor.preHandle(this.webRequest); assertTrue(TransactionSynchronizationManager.hasResource(sf)); - AsyncWebRequest asyncWebRequest = mock(AsyncWebRequest.class); - + AsyncWebRequest asyncWebRequest = new StandardServletAsyncWebRequest(this.request, this.response); WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(this.request); asyncManager.setTaskExecutor(new SyncTaskExecutor()); asyncManager.setAsyncWebRequest(asyncWebRequest); - asyncManager.startCallableProcessing(new Callable() { @Override public String call() throws Exception { @@ -166,13 +161,58 @@ public class OpenSessionInViewTests { interceptor.postHandle(this.webRequest, null); assertTrue(TransactionSynchronizationManager.hasResource(sf)); + verify(session, never()).close(); + interceptor.afterCompletion(this.webRequest, null); assertFalse(TransactionSynchronizationManager.hasResource(sf)); verify(session).setFlushMode(FlushMode.MANUAL); - verify(asyncWebRequest, times(2)).addCompletionHandler(any(Runnable.class)); - verify(asyncWebRequest).addTimeoutHandler(any(Runnable.class)); - verify(asyncWebRequest).startAsync(); + verify(session).close(); + } + + @Test + public void testOpenSessionInViewInterceptorAsyncTimeoutScenario() throws Exception { + + // Initial request thread + + final SessionFactory sf = mock(SessionFactory.class); + Session session = mock(Session.class); + + OpenSessionInViewInterceptor interceptor = new OpenSessionInViewInterceptor(); + interceptor.setSessionFactory(sf); + + given(sf.openSession()).willReturn(session); + given(session.getSessionFactory()).willReturn(sf); + + interceptor.preHandle(this.webRequest); + assertTrue(TransactionSynchronizationManager.hasResource(sf)); + + AsyncWebRequest asyncWebRequest = new StandardServletAsyncWebRequest(this.request, this.response); + WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(this.request); + asyncManager.setTaskExecutor(new SyncTaskExecutor()); + asyncManager.setAsyncWebRequest(asyncWebRequest); + asyncManager.startCallableProcessing(new Callable() { + @Override + public String call() throws Exception { + return "anything"; + } + }); + + interceptor.afterConcurrentHandlingStarted(this.webRequest); + assertFalse(TransactionSynchronizationManager.hasResource(sf)); + verify(session, never()).close(); + + // Async request timeout + + MockAsyncContext asyncContext = (MockAsyncContext) this.request.getAsyncContext(); + for (AsyncListener listener : asyncContext.getListeners()) { + listener.onTimeout(new AsyncEvent(asyncContext)); + } + for (AsyncListener listener : asyncContext.getListeners()) { + listener.onComplete(new AsyncEvent(asyncContext)); + } + + verify(session).close(); } @Test @@ -376,9 +416,7 @@ public class OpenSessionInViewTests { } }; - AsyncWebRequest asyncWebRequest = mock(AsyncWebRequest.class); - given(asyncWebRequest.isAsyncStarted()).willReturn(true); - + AsyncWebRequest asyncWebRequest = new StandardServletAsyncWebRequest(this.request, this.response); WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(this.request); asyncManager.setTaskExecutor(new SyncTaskExecutor()); asyncManager.setAsyncWebRequest(asyncWebRequest); @@ -393,19 +431,89 @@ public class OpenSessionInViewTests { filter.doFilter(this.request, this.response, filterChain); assertFalse(TransactionSynchronizationManager.hasResource(sf)); assertEquals(1, count.get()); - + verify(session, never()).close(); // Async dispatch after concurrent handling produces result ... + this.request.setAsyncStarted(false); assertFalse(TransactionSynchronizationManager.hasResource(sf)); filter.doFilter(this.request, this.response, filterChain); assertFalse(TransactionSynchronizationManager.hasResource(sf)); assertEquals(2, count.get()); verify(session).setFlushMode(FlushMode.MANUAL); - verify(asyncWebRequest, times(2)).addCompletionHandler(any(Runnable.class)); - verify(asyncWebRequest).addTimeoutHandler(any(Runnable.class)); - verify(asyncWebRequest).startAsync(); + verify(session).close(); + + wac.close(); + } + + @Test + public void testOpenSessionInViewFilterAsyncTimeoutScenario() throws Exception { + + final SessionFactory sf = mock(SessionFactory.class); + Session session = mock(Session.class); + + // Initial request during which concurrent handling starts.. + + given(sf.openSession()).willReturn(session); + given(session.getSessionFactory()).willReturn(sf); + + 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 AtomicInteger count = new AtomicInteger(0); + final AsyncWebRequest asyncWebRequest = new StandardServletAsyncWebRequest(this.request, this.response); + final MockHttpServletRequest request = this.request; + + final FilterChain filterChain = new FilterChain() { + @Override + public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse) + throws NestedServletException { + + assertTrue(TransactionSynchronizationManager.hasResource(sf)); + count.incrementAndGet(); + + WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request); + asyncManager.setTaskExecutor(new SyncTaskExecutor()); + asyncManager.setAsyncWebRequest(asyncWebRequest); + try { + asyncManager.startCallableProcessing(new Callable() { + @Override + public String call() throws Exception { + return "anything"; + } + }); + } + catch (Exception e) { + throw new NestedServletException("", e); + } + } + }; + + assertFalse(TransactionSynchronizationManager.hasResource(sf)); + filter.doFilter(this.request, this.response, filterChain); + assertFalse(TransactionSynchronizationManager.hasResource(sf)); + assertEquals(1, count.get()); + verify(session, never()).close(); + + // Async request timeout ... + + MockAsyncContext asyncContext = (MockAsyncContext) this.request.getAsyncContext(); + for (AsyncListener listener : asyncContext.getListeners()) { + listener.onTimeout(new AsyncEvent(asyncContext)); + } + for (AsyncListener listener : asyncContext.getListeners()) { + listener.onComplete(new AsyncEvent(asyncContext)); + } + + verify(session).close(); wac.close(); } 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 901448e0a6..5ad164e9dc 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 @@ -21,25 +21,19 @@ import java.util.concurrent.Callable; import java.util.concurrent.atomic.AtomicInteger; import javax.persistence.EntityManager; import javax.persistence.EntityManagerFactory; -import javax.servlet.FilterChain; -import javax.servlet.ServletException; -import javax.servlet.ServletRequest; -import javax.servlet.ServletResponse; +import javax.servlet.*; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.springframework.core.task.SimpleAsyncTaskExecutor; -import org.springframework.mock.web.test.MockFilterConfig; -import org.springframework.mock.web.test.MockHttpServletRequest; -import org.springframework.mock.web.test.MockHttpServletResponse; -import org.springframework.mock.web.test.MockServletContext; -import org.springframework.mock.web.test.PassThroughFilterChain; +import org.springframework.mock.web.test.*; 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.StandardServletAsyncWebRequest; import org.springframework.web.context.request.async.WebAsyncManager; import org.springframework.web.context.request.async.WebAsyncUtils; import org.springframework.web.context.support.StaticWebApplicationContext; @@ -59,6 +53,12 @@ public class OpenEntityManagerInViewTests { private EntityManagerFactory factory; + private MockHttpServletRequest request; + + private MockHttpServletResponse response; + + private ServletWebRequest webRequest; + @Before public void setUp() throws Exception { @@ -66,6 +66,11 @@ public class OpenEntityManagerInViewTests { manager = mock(EntityManager.class); given(factory.createEntityManager()).willReturn(manager); + + this.request = new MockHttpServletRequest(); + this.request.setAsyncSupported(true); + this.response = new MockHttpServletResponse(); + this.webRequest = new ServletWebRequest(this.request); } @After @@ -79,13 +84,13 @@ public class OpenEntityManagerInViewTests { @Test public void testOpenEntityManagerInViewInterceptor() throws Exception { OpenEntityManagerInViewInterceptor interceptor = new OpenEntityManagerInViewInterceptor(); - interceptor.setEntityManagerFactory(factory); + interceptor.setEntityManagerFactory(this.factory); MockServletContext sc = new MockServletContext(); MockHttpServletRequest request = new MockHttpServletRequest(sc); interceptor.preHandle(new ServletWebRequest(request)); - assertTrue(TransactionSynchronizationManager.hasResource(factory)); + assertTrue(TransactionSynchronizationManager.hasResource(this.factory)); // check that further invocations simply participate interceptor.preHandle(new ServletWebRequest(request)); @@ -120,16 +125,13 @@ public class OpenEntityManagerInViewTests { OpenEntityManagerInViewInterceptor interceptor = new OpenEntityManagerInViewInterceptor(); interceptor.setEntityManagerFactory(factory); - MockServletContext sc = new MockServletContext(); - MockHttpServletRequest request = new MockHttpServletRequest(sc); - ServletWebRequest webRequest = new ServletWebRequest(request); + given(factory.createEntityManager()).willReturn(this.manager); - interceptor.preHandle(webRequest); + interceptor.preHandle(this.webRequest); assertTrue(TransactionSynchronizationManager.hasResource(factory)); - AsyncWebRequest asyncWebRequest = mock(AsyncWebRequest.class); - - WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(webRequest); + AsyncWebRequest asyncWebRequest = new StandardServletAsyncWebRequest(this.request, this.response); + WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(this.webRequest); asyncManager.setTaskExecutor(new SyncTaskExecutor()); asyncManager.setAsyncWebRequest(asyncWebRequest); asyncManager.startCallableProcessing(new Callable() { @@ -139,17 +141,12 @@ public class OpenEntityManagerInViewTests { } }); - verify(asyncWebRequest, times(2)).addCompletionHandler(any(Runnable.class)); - verify(asyncWebRequest).addTimeoutHandler(any(Runnable.class)); - verify(asyncWebRequest, times(2)).addCompletionHandler(any(Runnable.class)); - verify(asyncWebRequest).startAsync(); - - interceptor.afterConcurrentHandlingStarted(webRequest); + interceptor.afterConcurrentHandlingStarted(this.webRequest); assertFalse(TransactionSynchronizationManager.hasResource(factory)); // Async dispatch thread - interceptor.preHandle(webRequest); + interceptor.preHandle(this.webRequest); assertTrue(TransactionSynchronizationManager.hasResource(factory)); asyncManager.clearConcurrentResult(); @@ -168,15 +165,57 @@ public class OpenEntityManagerInViewTests { interceptor.postHandle(new ServletWebRequest(request), null); interceptor.afterCompletion(new ServletWebRequest(request), null); - interceptor.postHandle(webRequest, null); + interceptor.postHandle(this.webRequest, null); assertTrue(TransactionSynchronizationManager.hasResource(factory)); - given(manager.isOpen()).willReturn(true); + given(this.manager.isOpen()).willReturn(true); - interceptor.afterCompletion(webRequest, null); + interceptor.afterCompletion(this.webRequest, null); assertFalse(TransactionSynchronizationManager.hasResource(factory)); - verify(manager).close(); + verify(this.manager).close(); + } + + @Test + public void testOpenEntityManagerInViewInterceptorAsyncTimeoutScenario() throws Exception { + + // Initial request thread + + OpenEntityManagerInViewInterceptor interceptor = new OpenEntityManagerInViewInterceptor(); + interceptor.setEntityManagerFactory(factory); + + given(this.factory.createEntityManager()).willReturn(this.manager); + + interceptor.preHandle(this.webRequest); + assertTrue(TransactionSynchronizationManager.hasResource(this.factory)); + + AsyncWebRequest asyncWebRequest = new StandardServletAsyncWebRequest(this.request, this.response); + WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(this.request); + asyncManager.setTaskExecutor(new SyncTaskExecutor()); + asyncManager.setAsyncWebRequest(asyncWebRequest); + asyncManager.startCallableProcessing(new Callable() { + @Override + public String call() throws Exception { + return "anything"; + } + }); + + interceptor.afterConcurrentHandlingStarted(this.webRequest); + assertFalse(TransactionSynchronizationManager.hasResource(this.factory)); + + // Async request timeout + + given(this.manager.isOpen()).willReturn(true); + + MockAsyncContext asyncContext = (MockAsyncContext) this.request.getAsyncContext(); + for (AsyncListener listener : asyncContext.getListeners()) { + listener.onTimeout(new AsyncEvent(asyncContext)); + } + for (AsyncListener listener : asyncContext.getListeners()) { + listener.onComplete(new AsyncEvent(asyncContext)); + } + + verify(this.manager).close(); } @Test @@ -257,8 +296,6 @@ public class OpenEntityManagerInViewTests { 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"); @@ -297,7 +334,7 @@ public class OpenEntityManagerInViewTests { AsyncWebRequest asyncWebRequest = mock(AsyncWebRequest.class); given(asyncWebRequest.isAsyncStarted()).willReturn(true); - WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request); + WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(this.request); asyncManager.setTaskExecutor(new SyncTaskExecutor()); asyncManager.setAsyncWebRequest(asyncWebRequest); asyncManager.startCallableProcessing(new Callable() { @@ -309,13 +346,12 @@ public class OpenEntityManagerInViewTests { assertFalse(TransactionSynchronizationManager.hasResource(factory)); assertFalse(TransactionSynchronizationManager.hasResource(factory2)); - filter2.doFilter(request, response, filterChain3); + filter2.doFilter(this.request, this.response, filterChain3); assertFalse(TransactionSynchronizationManager.hasResource(factory)); assertFalse(TransactionSynchronizationManager.hasResource(factory2)); assertEquals(1, count.get()); assertEquals(1, count2.get()); assertNotNull(request.getAttribute("invoked")); - verify(asyncWebRequest, times(2)).addCompletionHandler(any(Runnable.class)); verify(asyncWebRequest).addTimeoutHandler(any(Runnable.class)); verify(asyncWebRequest, times(2)).addCompletionHandler(any(Runnable.class)); @@ -328,13 +364,13 @@ public class OpenEntityManagerInViewTests { assertFalse(TransactionSynchronizationManager.hasResource(factory)); assertFalse(TransactionSynchronizationManager.hasResource(factory2)); - filter.doFilter(request, response, filterChain3); + filter.doFilter(this.request, this.response, filterChain3); assertFalse(TransactionSynchronizationManager.hasResource(factory)); assertFalse(TransactionSynchronizationManager.hasResource(factory2)); assertEquals(2, count.get()); assertEquals(2, count2.get()); - verify(manager).close(); + verify(this.manager).close(); verify(manager2).close(); wac.close(); diff --git a/spring-web/src/main/java/org/springframework/web/context/request/async/DeferredResultProcessingInterceptorAdapter.java b/spring-web/src/main/java/org/springframework/web/context/request/async/DeferredResultProcessingInterceptorAdapter.java index 42409c6fe2..0298f7db4f 100644 --- a/spring-web/src/main/java/org/springframework/web/context/request/async/DeferredResultProcessingInterceptorAdapter.java +++ b/spring-web/src/main/java/org/springframework/web/context/request/async/DeferredResultProcessingInterceptorAdapter.java @@ -31,7 +31,8 @@ public abstract class DeferredResultProcessingInterceptorAdapter implements Defe * This implementation is empty. */ @Override - public void beforeConcurrentHandling(NativeWebRequest request, DeferredResult deferredResult) throws Exception { + public void beforeConcurrentHandling(NativeWebRequest request, DeferredResult deferredResult) + throws Exception { } /** @@ -50,7 +51,8 @@ public abstract class DeferredResultProcessingInterceptorAdapter implements Defe } /** - * This implementation returns {@code true} by default. + * This implementation returns {@code true} by default allowing other interceptors + * to be given a chance to handle the timeout. */ @Override public boolean handleTimeout(NativeWebRequest request, DeferredResult deferredResult) throws Exception {