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 {