Skip to content

Commit f9081be

Browse files
committed
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
1 parent 1ac8e48 commit f9081be

File tree

10 files changed

+371
-213
lines changed

10 files changed

+371
-213
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
/*
2+
* Copyright 2002-2013 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.orm.hibernate4.support;
18+
19+
20+
import org.apache.commons.logging.Log;
21+
import org.apache.commons.logging.LogFactory;
22+
import org.hibernate.SessionFactory;
23+
import org.springframework.orm.hibernate4.SessionFactoryUtils;
24+
import org.springframework.orm.hibernate4.SessionHolder;
25+
import org.springframework.transaction.support.TransactionSynchronizationManager;
26+
import org.springframework.web.context.request.NativeWebRequest;
27+
import org.springframework.web.context.request.async.CallableProcessingInterceptorAdapter;
28+
import org.springframework.web.context.request.async.DeferredResult;
29+
import org.springframework.web.context.request.async.DeferredResultProcessingInterceptor;
30+
31+
import java.util.concurrent.Callable;
32+
33+
/**
34+
* An interceptor with asynchronous web requests used in OpenSessionInViewFilter and
35+
* OpenSessionInViewInterceptor.
36+
*
37+
* Ensures the following:
38+
* 1) The session is bound/unbound when "callable processing" is started
39+
* 2) The session is closed if an async request times out
40+
*
41+
* @author Rossen Stoyanchev
42+
* @since 3.2.5
43+
*/
44+
public class AsyncRequestInterceptor extends CallableProcessingInterceptorAdapter
45+
implements DeferredResultProcessingInterceptor {
46+
47+
private static Log logger = LogFactory.getLog(AsyncRequestInterceptor.class);
48+
49+
private final SessionFactory sessionFactory;
50+
51+
private final SessionHolder sessionHolder;
52+
53+
private volatile boolean timeoutInProgress;
54+
55+
public AsyncRequestInterceptor(SessionFactory sessionFactory, SessionHolder sessionHolder) {
56+
this.sessionFactory = sessionFactory;
57+
this.sessionHolder = sessionHolder;
58+
}
59+
60+
@Override
61+
public <T> void preProcess(NativeWebRequest request, Callable<T> task) {
62+
bindSession();
63+
}
64+
65+
public void bindSession() {
66+
this.timeoutInProgress = false;
67+
TransactionSynchronizationManager.bindResource(this.sessionFactory, this.sessionHolder);
68+
}
69+
70+
@Override
71+
public <T> void postProcess(NativeWebRequest request, Callable<T> task, Object concurrentResult) {
72+
TransactionSynchronizationManager.unbindResource(this.sessionFactory);
73+
}
74+
75+
@Override
76+
public <T> Object handleTimeout(NativeWebRequest request, Callable<T> task) {
77+
this.timeoutInProgress = true;
78+
return RESULT_NONE; // give other interceptors a chance to handle the timeout
79+
}
80+
81+
@Override
82+
public <T> void afterCompletion(NativeWebRequest request, Callable<T> task) throws Exception {
83+
closeAfterTimeout();
84+
}
85+
86+
private void closeAfterTimeout() {
87+
if (this.timeoutInProgress) {
88+
logger.debug("Closing Hibernate Session after async request timeout");
89+
SessionFactoryUtils.closeSession(sessionHolder.getSession());
90+
}
91+
}
92+
93+
// Implementation of DeferredResultProcessingInterceptor methods
94+
95+
public <T> void beforeConcurrentHandling(NativeWebRequest request, DeferredResult<T> deferredResult) { }
96+
public <T> void preProcess(NativeWebRequest request, DeferredResult<T> deferredResult) { }
97+
public <T> void postProcess(NativeWebRequest request, DeferredResult<T> deferredResult, Object result) { }
98+
99+
@Override
100+
public <T> boolean handleTimeout(NativeWebRequest request, DeferredResult<T> deferredResult) {
101+
this.timeoutInProgress = true;
102+
return true; // give other interceptors a chance to handle the timeout
103+
}
104+
105+
@Override
106+
public <T> void afterCompletion(NativeWebRequest request, DeferredResult<T> deferredResult) {
107+
closeAfterTimeout();
108+
}
109+
}

spring-orm-hibernate4/src/main/java/org/springframework/orm/hibernate4/support/OpenSessionInViewFilter.java

Lines changed: 10 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,6 @@
1616

1717
package org.springframework.orm.hibernate4.support;
1818

19-
import java.io.IOException;
20-
import java.util.concurrent.Callable;
21-
22-
import javax.servlet.FilterChain;
23-
import javax.servlet.ServletException;
24-
import javax.servlet.http.HttpServletRequest;
25-
import javax.servlet.http.HttpServletResponse;
26-
2719
import org.hibernate.FlushMode;
2820
import org.hibernate.HibernateException;
2921
import org.hibernate.Session;
@@ -33,13 +25,17 @@
3325
import org.springframework.orm.hibernate4.SessionHolder;
3426
import org.springframework.transaction.support.TransactionSynchronizationManager;
3527
import org.springframework.web.context.WebApplicationContext;
36-
import org.springframework.web.context.request.NativeWebRequest;
37-
import org.springframework.web.context.request.async.CallableProcessingInterceptorAdapter;
3828
import org.springframework.web.context.request.async.WebAsyncManager;
3929
import org.springframework.web.context.request.async.WebAsyncUtils;
4030
import org.springframework.web.context.support.WebApplicationContextUtils;
4131
import org.springframework.web.filter.OncePerRequestFilter;
4232

33+
import javax.servlet.FilterChain;
34+
import javax.servlet.ServletException;
35+
import javax.servlet.http.HttpServletRequest;
36+
import javax.servlet.http.HttpServletResponse;
37+
import java.io.IOException;
38+
4339
/**
4440
* Servlet 2.3 Filter that binds a Hibernate Session to the thread for the entire
4541
* processing of the request. Intended for the "Open Session in View" pattern,
@@ -143,8 +139,9 @@ protected void doFilterInternal(
143139
SessionHolder sessionHolder = new SessionHolder(session);
144140
TransactionSynchronizationManager.bindResource(sessionFactory, sessionHolder);
145141

146-
asyncManager.registerCallableInterceptor(key,
147-
new SessionBindingCallableInterceptor(sessionFactory, sessionHolder));
142+
AsyncRequestInterceptor interceptor = new AsyncRequestInterceptor(sessionFactory, sessionHolder);
143+
asyncManager.registerCallableInterceptor(key, interceptor);
144+
asyncManager.registerDeferredResultInterceptor(key, interceptor);
148145
}
149146
}
150147

@@ -216,37 +213,8 @@ private boolean applySessionBindingInterceptor(WebAsyncManager asyncManager, Str
216213
if (asyncManager.getCallableInterceptor(key) == null) {
217214
return false;
218215
}
219-
((SessionBindingCallableInterceptor) asyncManager.getCallableInterceptor(key)).initializeThread();
216+
((AsyncRequestInterceptor) asyncManager.getCallableInterceptor(key)).bindSession();
220217
return true;
221218
}
222219

223-
224-
/**
225-
* Bind and unbind the Hibernate {@code Session} to the current thread.
226-
*/
227-
private static class SessionBindingCallableInterceptor extends CallableProcessingInterceptorAdapter {
228-
229-
private final SessionFactory sessionFactory;
230-
231-
private final SessionHolder sessionHolder;
232-
233-
public SessionBindingCallableInterceptor(SessionFactory sessionFactory, SessionHolder sessionHolder) {
234-
this.sessionFactory = sessionFactory;
235-
this.sessionHolder = sessionHolder;
236-
}
237-
238-
@Override
239-
public <T> void preProcess(NativeWebRequest request, Callable<T> task) {
240-
initializeThread();
241-
}
242-
243-
@Override
244-
public <T> void postProcess(NativeWebRequest request, Callable<T> task, Object concurrentResult) {
245-
TransactionSynchronizationManager.unbindResource(this.sessionFactory);
246-
}
247-
248-
private void initializeThread() {
249-
TransactionSynchronizationManager.bindResource(this.sessionFactory, this.sessionHolder);
250-
}
251-
}
252220
}

spring-orm-hibernate4/src/main/java/org/springframework/orm/hibernate4/support/OpenSessionInViewInterceptor.java

Lines changed: 6 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,7 @@
3333
import org.springframework.web.context.request.AsyncWebRequestInterceptor;
3434
import org.springframework.web.context.request.NativeWebRequest;
3535
import org.springframework.web.context.request.WebRequest;
36-
import org.springframework.web.context.request.async.CallableProcessingInterceptorAdapter;
37-
import org.springframework.web.context.request.async.WebAsyncManager;
38-
import org.springframework.web.context.request.async.WebAsyncUtils;
36+
import org.springframework.web.context.request.async.*;
3937

4038
/**
4139
* Spring web request interceptor that binds a Hibernate {@code Session} to the
@@ -118,8 +116,10 @@ public void preHandle(WebRequest request) throws DataAccessException {
118116
SessionHolder sessionHolder = new SessionHolder(session);
119117
TransactionSynchronizationManager.bindResource(getSessionFactory(), sessionHolder);
120118

121-
asyncManager.registerCallableInterceptor(participateAttributeName,
122-
new SessionBindingCallableInterceptor(sessionHolder));
119+
AsyncRequestInterceptor asyncRequestInterceptor =
120+
new AsyncRequestInterceptor(getSessionFactory(), sessionHolder);
121+
asyncManager.registerCallableInterceptor(participateAttributeName, asyncRequestInterceptor);
122+
asyncManager.registerDeferredResultInterceptor(participateAttributeName, asyncRequestInterceptor);
123123
}
124124
}
125125

@@ -196,35 +196,8 @@ private boolean applySessionBindingInterceptor(WebAsyncManager asyncManager, Str
196196
if (asyncManager.getCallableInterceptor(key) == null) {
197197
return false;
198198
}
199-
((SessionBindingCallableInterceptor) asyncManager.getCallableInterceptor(key)).initializeThread();
199+
((AsyncRequestInterceptor) asyncManager.getCallableInterceptor(key)).bindSession();
200200
return true;
201201
}
202202

203-
204-
/**
205-
* Bind and unbind the Hibernate {@code Session} to the current thread.
206-
*/
207-
private class SessionBindingCallableInterceptor extends CallableProcessingInterceptorAdapter {
208-
209-
private final SessionHolder sessionHolder;
210-
211-
public SessionBindingCallableInterceptor(SessionHolder sessionHolder) {
212-
this.sessionHolder = sessionHolder;
213-
}
214-
215-
@Override
216-
public <T> void preProcess(NativeWebRequest request, Callable<T> task) {
217-
initializeThread();
218-
}
219-
220-
@Override
221-
public <T> void postProcess(NativeWebRequest request, Callable<T> task, Object concurrentResult) {
222-
TransactionSynchronizationManager.unbindResource(getSessionFactory());
223-
}
224-
225-
private void initializeThread() {
226-
TransactionSynchronizationManager.bindResource(getSessionFactory(), this.sessionHolder);
227-
}
228-
}
229-
230203
}
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
/*
2+
* Copyright 2002-2013 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.orm.hibernate3.support;
18+
19+
import org.apache.commons.logging.Log;
20+
import org.apache.commons.logging.LogFactory;
21+
import org.hibernate.SessionFactory;
22+
import org.springframework.orm.hibernate3.SessionFactoryUtils;
23+
import org.springframework.orm.hibernate3.SessionHolder;
24+
import org.springframework.transaction.support.TransactionSynchronizationManager;
25+
import org.springframework.web.context.request.NativeWebRequest;
26+
import org.springframework.web.context.request.async.CallableProcessingInterceptorAdapter;
27+
import org.springframework.web.context.request.async.DeferredResult;
28+
import org.springframework.web.context.request.async.DeferredResultProcessingInterceptor;
29+
30+
import java.util.concurrent.Callable;
31+
32+
/**
33+
* An interceptor with asynchronous web requests used in OpenSessionInViewFilter and
34+
* OpenSessionInViewInterceptor.
35+
*
36+
* Ensures the following:
37+
* 1) The session is bound/unbound when "callable processing" is started
38+
* 2) The session is closed if an async request times out
39+
*
40+
* @author Rossen Stoyanchev
41+
* @since 3.2.5
42+
*/
43+
class AsyncRequestInterceptor extends CallableProcessingInterceptorAdapter
44+
implements DeferredResultProcessingInterceptor {
45+
46+
private static final Log logger = LogFactory.getLog(AsyncRequestInterceptor.class);
47+
48+
private final SessionFactory sessionFactory;
49+
50+
private final SessionHolder sessionHolder;
51+
52+
private volatile boolean timeoutInProgress;
53+
54+
55+
public AsyncRequestInterceptor(SessionFactory sessionFactory, SessionHolder sessionHolder) {
56+
this.sessionFactory = sessionFactory;
57+
this.sessionHolder = sessionHolder;
58+
}
59+
60+
@Override
61+
public <T> void preProcess(NativeWebRequest request, Callable<T> task) {
62+
bindSession();
63+
}
64+
65+
public void bindSession() {
66+
this.timeoutInProgress = false;
67+
TransactionSynchronizationManager.bindResource(this.sessionFactory, this.sessionHolder);
68+
}
69+
70+
@Override
71+
public <T> void postProcess(NativeWebRequest request, Callable<T> task, Object concurrentResult) {
72+
TransactionSynchronizationManager.unbindResource(this.sessionFactory);
73+
}
74+
75+
@Override
76+
public <T> Object handleTimeout(NativeWebRequest request, Callable<T> task) {
77+
this.timeoutInProgress = true;
78+
return RESULT_NONE; // give other interceptors a chance to handle the timeout
79+
}
80+
81+
@Override
82+
public <T> void afterCompletion(NativeWebRequest request, Callable<T> task) throws Exception {
83+
closeAfterTimeout();
84+
}
85+
86+
private void closeAfterTimeout() {
87+
if (this.timeoutInProgress) {
88+
logger.debug("Closing Hibernate Session after async request timeout");
89+
SessionFactoryUtils.closeSession(sessionHolder.getSession());
90+
}
91+
}
92+
93+
// Implementation of DeferredResultProcessingInterceptor methods
94+
95+
public <T> void beforeConcurrentHandling(NativeWebRequest request, DeferredResult<T> deferredResult) {}
96+
public <T> void preProcess(NativeWebRequest request, DeferredResult<T> deferredResult) {}
97+
public <T> void postProcess(NativeWebRequest request, DeferredResult<T> deferredResult, Object result) {}
98+
99+
@Override
100+
public <T> boolean handleTimeout(NativeWebRequest request, DeferredResult<T> deferredResult) {
101+
this.timeoutInProgress = true;
102+
return true; // give other interceptors a chance to handle the timeout
103+
}
104+
105+
@Override
106+
public <T> void afterCompletion(NativeWebRequest request, DeferredResult<T> deferredResult) {
107+
closeAfterTimeout();
108+
}
109+
110+
}

0 commit comments

Comments
 (0)