Skip to content

Commit 5d04ef4

Browse files
committed
Support selective filtering of error dispatches
OncePerRequestFilter now allows sub-classes to choose whether they should ever get involved in processing an error dispatch. Issue: SPR-9895
1 parent d952da2 commit 5d04ef4

File tree

8 files changed

+137
-46
lines changed

8 files changed

+137
-46
lines changed

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

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,15 @@ protected boolean shouldNotFilterAsyncDispatch() {
178178
return false;
179179
}
180180

181+
/**
182+
* Returns "false" so that the filter may provide a Hibernate
183+
* {@code Session} to each error dispatches.
184+
*/
185+
@Override
186+
protected boolean shouldNotFilterErrorDispatch() {
187+
return false;
188+
}
189+
181190
@Override
182191
protected void doFilterInternal(
183192
HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
@@ -187,7 +196,6 @@ protected void doFilterInternal(
187196
boolean participate = false;
188197

189198
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
190-
boolean isFirstRequest = !asyncManager.hasConcurrentResult();
191199
String key = getAlreadyFilteredAttributeName();
192200

193201
if (isSingleSession()) {
@@ -197,6 +205,7 @@ protected void doFilterInternal(
197205
participate = true;
198206
}
199207
else {
208+
boolean isFirstRequest = !isAsyncDispatch(request);
200209
if (isFirstRequest || !applySessionBindingInterceptor(asyncManager, key)) {
201210
logger.debug("Opening single Hibernate Session in OpenSessionInViewFilter");
202211
Session session = getSession(sessionFactory);
@@ -210,8 +219,7 @@ protected void doFilterInternal(
210219
}
211220
else {
212221
// deferred close mode
213-
Assert.state(!asyncManager.isConcurrentHandlingStarted(),
214-
"Deferred close mode is not supported on async dispatches");
222+
Assert.state(!isAsyncStarted(request), "Deferred close mode is not supported on async dispatches");
215223
if (SessionFactoryUtils.isDeferredCloseActive(sessionFactory)) {
216224
// Do not modify deferred close: just set the participate flag.
217225
participate = true;
@@ -230,7 +238,7 @@ protected void doFilterInternal(
230238
// single session mode
231239
SessionHolder sessionHolder =
232240
(SessionHolder) TransactionSynchronizationManager.unbindResource(sessionFactory);
233-
if (!asyncManager.isConcurrentHandlingStarted()) {
241+
if (!isAsyncStarted(request)) {
234242
logger.debug("Closing single Hibernate Session in OpenSessionInViewFilter");
235243
closeSession(sessionHolder.getSession(), sessionFactory);
236244
}

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

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ protected String getSessionFactoryBeanName() {
102102
}
103103

104104
/**
105-
* The default value is "false" so that the filter may re-bind the opened
105+
* Returns "false" so that the filter may re-bind the opened Hibernate
106106
* {@code Session} to each asynchronously dispatched thread and postpone
107107
* closing it until the very last asynchronous dispatch.
108108
*/
@@ -111,6 +111,15 @@ protected boolean shouldNotFilterAsyncDispatch() {
111111
return false;
112112
}
113113

114+
/**
115+
* Returns "false" so that the filter may provide a Hibernate
116+
* {@code Session} to each error dispatches.
117+
*/
118+
@Override
119+
protected boolean shouldNotFilterErrorDispatch() {
120+
return false;
121+
}
122+
114123
@Override
115124
protected void doFilterInternal(
116125
HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
@@ -120,14 +129,14 @@ protected void doFilterInternal(
120129
boolean participate = false;
121130

122131
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
123-
boolean isFirstRequest = !asyncManager.hasConcurrentResult();
124132
String key = getAlreadyFilteredAttributeName();
125133

126134
if (TransactionSynchronizationManager.hasResource(sessionFactory)) {
127135
// Do not modify the Session: just set the participate flag.
128136
participate = true;
129137
}
130138
else {
139+
boolean isFirstRequest = !isAsyncDispatch(request);
131140
if (isFirstRequest || !applySessionBindingInterceptor(asyncManager, key)) {
132141
logger.debug("Opening Hibernate Session in OpenSessionInViewFilter");
133142
Session session = openSession(sessionFactory);
@@ -147,7 +156,7 @@ protected void doFilterInternal(
147156
if (!participate) {
148157
SessionHolder sessionHolder =
149158
(SessionHolder) TransactionSynchronizationManager.unbindResource(sessionFactory);
150-
if (!asyncManager.isConcurrentHandlingStarted()) {
159+
if (!isAsyncStarted(request)) {
151160
logger.debug("Closing Hibernate Session in OpenSessionInViewFilter");
152161
SessionFactoryUtils.closeSession(sessionHolder.getSession());
153162
}

spring-orm/src/main/java/org/springframework/orm/jpa/support/OpenEntityManagerInViewFilter.java

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ protected String getPersistenceUnitName() {
126126
}
127127

128128
/**
129-
* The default value is "false" so that the filter may re-bind the opened
129+
* Returns "false" so that the filter may re-bind the opened
130130
* {@code EntityManager} to each asynchronously dispatched thread and postpone
131131
* closing it until the very last asynchronous dispatch.
132132
*/
@@ -135,6 +135,15 @@ protected boolean shouldNotFilterAsyncDispatch() {
135135
return false;
136136
}
137137

138+
/**
139+
* Returns "false" so that the filter may provide an {@code EntityManager}
140+
* to each error dispatches.
141+
*/
142+
@Override
143+
protected boolean shouldNotFilterErrorDispatch() {
144+
return false;
145+
}
146+
138147
@Override
139148
protected void doFilterInternal(
140149
HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
@@ -144,14 +153,14 @@ protected void doFilterInternal(
144153
boolean participate = false;
145154

146155
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
147-
boolean isFirstRequest = !asyncManager.hasConcurrentResult();
148156
String key = getAlreadyFilteredAttributeName();
149157

150158
if (TransactionSynchronizationManager.hasResource(emf)) {
151159
// Do not modify the EntityManager: just set the participate flag.
152160
participate = true;
153161
}
154162
else {
163+
boolean isFirstRequest = !isAsyncDispatch(request);
155164
if (isFirstRequest || !applyEntityManagerBindingInterceptor(asyncManager, key)) {
156165
logger.debug("Opening JPA EntityManager in OpenEntityManagerInViewFilter");
157166
try {
@@ -175,7 +184,7 @@ protected void doFilterInternal(
175184
if (!participate) {
176185
EntityManagerHolder emHolder = (EntityManagerHolder)
177186
TransactionSynchronizationManager.unbindResource(emf);
178-
if (!asyncManager.isConcurrentHandlingStarted()) {
187+
if (!isAsyncStarted(request)) {
179188
logger.debug("Closing JPA EntityManager in OpenEntityManagerInViewFilter");
180189
EntityManagerFactoryUtils.closeEntityManager(emHolder.getEntityManager());
181190
}

spring-web/src/main/java/org/springframework/web/filter/AbstractRequestLoggingFilter.java

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,6 @@
3232

3333
import org.springframework.util.Assert;
3434
import org.springframework.util.StringUtils;
35-
import org.springframework.web.context.request.async.WebAsyncManager;
36-
import org.springframework.web.context.request.async.WebAsyncUtils;
3735
import org.springframework.web.util.WebUtils;
3836

3937
/**
@@ -200,8 +198,7 @@ protected boolean shouldNotFilterAsyncDispatch() {
200198
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
201199
throws ServletException, IOException {
202200

203-
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
204-
boolean isFirstRequest = !asyncManager.hasConcurrentResult();
201+
boolean isFirstRequest = !isAsyncDispatch(request);
205202

206203
if (isIncludePayload()) {
207204
if (isFirstRequest) {
@@ -216,7 +213,7 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse
216213
filterChain.doFilter(request, response);
217214
}
218215
finally {
219-
if (!asyncManager.isConcurrentHandlingStarted()) {
216+
if (!isAsyncStarted(request)) {
220217
afterRequest(request, getAfterMessage(request));
221218
}
222219
}

spring-web/src/main/java/org/springframework/web/filter/OncePerRequestFilter.java

Lines changed: 80 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -27,21 +27,39 @@
2727

2828
import org.springframework.web.context.request.async.WebAsyncManager;
2929
import org.springframework.web.context.request.async.WebAsyncUtils;
30+
import org.springframework.web.util.WebUtils;
3031

3132
/**
32-
* Filter base class that guarantees to be just executed once per request,
33-
* on any servlet container. It provides a {@link #doFilterInternal}
33+
* Filter base class that aims to guarantee a single execution per request
34+
* dispatch, on any servlet container. It provides a {@link #doFilterInternal}
3435
* method with HttpServletRequest and HttpServletResponse arguments.
3536
*
36-
* <p>In an async scenario a filter may be invoked again in additional threads
37-
* as part of an {@linkplain javax.servlet.DispatcherType.ASYNC ASYNC} dispatch.
38-
* Sub-classes may decide whether to be invoked once per request or once per
39-
* request thread for as long as the same request is being processed.
40-
* See {@link #shouldNotFilterAsyncDispatch()}.
37+
* <p>As of Servlet 3.0, a filter may be invoked as part of a
38+
* {@link javax.servlet.DispatcherType.REQUEST REQUEST} or
39+
* {@link javax.servlet.DispatcherType.ASYNC ASYNC} dispatches that occur in
40+
* separate threads. A filter can be configured in {@code web.xml} whether it
41+
* should be involved in async dispatches. However, in some cases servlet
42+
* containers assume different default configuration. Therefore sub-classes can
43+
* override the method {@link #shouldNotFilterAsyncDispatch()} to declare
44+
* statically if they shouuld indeed be invoked, <em>once</em>, during both types
45+
* of dispatches in order to provide thread initialization, logging, security,
46+
* and so on. This mechanism complements and does not replace the need to
47+
* configure a filter in {@code web.xml} with dispatcher types.
4148
*
42-
* <p>The {@link #getAlreadyFilteredAttributeName} method determines how
43-
* to identify that a request is already filtered. The default implementation
44-
* is based on the configured name of the concrete filter instance.
49+
* <p>Sub-classes may use {@link #isAsyncDispatch(HttpServletRequest)} to
50+
* determine when a filter is invoked as part of an async dispatch, and
51+
* use {@link #isAsyncStarted(HttpServletRequest)} to determine when the
52+
* request has been placed in async mode and therefore the current dispatch
53+
* won't be the last one.
54+
*
55+
* <p>Yet another dispatch type that also occurs in its own thread is
56+
* {@link javax.servlet.DispatcherType.ERROR ERROR}. Sub-classes can override
57+
* {@link #shouldNotFilterErrorDispatch()} if they wish to declare statically
58+
* if they should be invoked <em>once</em> during error dispatches.
59+
*
60+
* <p>The {@link #getAlreadyFilteredAttributeName} method determines how to
61+
* identify that a request is already filtered. The default implementation is
62+
* based on the configured name of the concrete filter instance.
4563
*
4664
* @author Juergen Hoeller
4765
* @author Rossen Stoyanchev
@@ -74,13 +92,10 @@ public final void doFilter(ServletRequest request, ServletResponse response, Fil
7492
HttpServletRequest httpRequest = (HttpServletRequest) request;
7593
HttpServletResponse httpResponse = (HttpServletResponse) response;
7694

77-
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
78-
boolean processAsyncDispatch = asyncManager.hasConcurrentResult() && !shouldNotFilterAsyncDispatch();
79-
8095
String alreadyFilteredAttributeName = getAlreadyFilteredAttributeName();
8196
boolean hasAlreadyFilteredAttribute = request.getAttribute(alreadyFilteredAttributeName) != null;
8297

83-
if ((hasAlreadyFilteredAttribute && (!processAsyncDispatch)) || shouldNotFilter(httpRequest)) {
98+
if (hasAlreadyFilteredAttribute || skipDispatch(httpRequest) || shouldNotFilter(httpRequest)) {
8499

85100
// Proceed without invoking this filter...
86101
filterChain.doFilter(request, response);
@@ -92,14 +107,46 @@ public final void doFilter(ServletRequest request, ServletResponse response, Fil
92107
doFilterInternal(httpRequest, httpResponse, filterChain);
93108
}
94109
finally {
95-
if (!asyncManager.isConcurrentHandlingStarted()) {
96-
// Remove the "already filtered" request attribute for this request.
97-
request.removeAttribute(alreadyFilteredAttributeName);
98-
}
110+
// Remove the "already filtered" request attribute for this request.
111+
request.removeAttribute(alreadyFilteredAttributeName);
99112
}
100113
}
101114
}
102115

116+
private boolean skipDispatch(HttpServletRequest request) {
117+
if (isAsyncDispatch(request) && shouldNotFilterAsyncDispatch()) {
118+
return true;
119+
}
120+
if ((request.getAttribute(WebUtils.ERROR_REQUEST_URI_ATTRIBUTE) != null) && shouldNotFilterErrorDispatch()) {
121+
return true;
122+
}
123+
return false;
124+
}
125+
126+
/**
127+
* The dispatcher type {@code javax.servlet.DispatcherType.ASYNC} introduced
128+
* in Servlet 3.0 means a filter can be invoked in more than one thread over
129+
* the course of a single request. This method returns {@code true} if the
130+
* filter is currently executing within an asynchronous dispatch.
131+
*
132+
* @param request the current request
133+
* @see WebAsyncManager#hasConcurrentResult()
134+
*/
135+
protected boolean isAsyncDispatch(HttpServletRequest request) {
136+
return WebAsyncUtils.getAsyncManager(request).hasConcurrentResult();
137+
}
138+
139+
/**
140+
* Whether request processing is in asynchronous mode meaning that the
141+
* response will not be committed after the current thread is exited.
142+
*
143+
* @param request the current request
144+
* @see WebAsyncManager#isConcurrentHandlingStarted()
145+
*/
146+
protected boolean isAsyncStarted(HttpServletRequest request) {
147+
return WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted();
148+
}
149+
103150
/**
104151
* Return the name of the request attribute that identifies that a request
105152
* is already filtered.
@@ -130,18 +177,18 @@ protected boolean shouldNotFilter(HttpServletRequest request) throws ServletExce
130177
}
131178

132179
/**
133-
* Whether to filter async dispatches, which occur in a different thread.
134180
* The dispatcher type {@code javax.servlet.DispatcherType.ASYNC} introduced
135-
* in Servlet 3.0 means a filter can be invoked in more than one thread (and
136-
* exited) over the course of a single request. Some filters only need to
137-
* filter the initial thread (e.g. request wrapping) while others may need
181+
* in Servlet 3.0 means a filter can be invoked in more than one thread
182+
* over the course of a single request. Some filters only need to filter
183+
* the initial thread (e.g. request wrapping) while others may need
138184
* to be invoked at least once in each additional thread for example for
139185
* setting up thread locals or to perform final processing at the very end.
140186
* <p>Note that although a filter can be mapped to handle specific dispatcher
141187
* types via {@code web.xml} or in Java through the {@code ServletContext},
142188
* servlet containers may enforce different defaults with regards to
143189
* dispatcher types. This flag enforces the design intent of the filter.
144-
* <p>The default setting is "true", which means the filter will not be
190+
*
191+
* <p>The default return value is "true", which means the filter will not be
145192
* invoked during subsequent async dispatches. If "false", the filter will
146193
* be invoked during async dispatches with the same guarantees of being
147194
* invoked only once during a request within a single thread.
@@ -150,6 +197,16 @@ protected boolean shouldNotFilterAsyncDispatch() {
150197
return true;
151198
}
152199

200+
/**
201+
* Whether to filter error dispatches such as when the servlet container
202+
* processes and error mapped in {@code web.xml}. The default return value
203+
* is "true", which means the filter will not be invoked in case of an error
204+
* dispatch.
205+
*/
206+
protected boolean shouldNotFilterErrorDispatch() {
207+
return true;
208+
}
209+
153210
/**
154211
* Same contract as for <code>doFilter</code>, but guaranteed to be
155212
* just invoked once per request within a single request thread.

spring-web/src/main/java/org/springframework/web/filter/RequestContextFilter.java

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -69,16 +69,24 @@ public void setThreadContextInheritable(boolean threadContextInheritable) {
6969
this.threadContextInheritable = threadContextInheritable;
7070
}
7171

72-
7372
/**
74-
* The default value is "false" in which case the filter will set up the request
75-
* context in each asynchronously dispatched thread.
73+
* Returns "false" so that the filter may set up the request context in each
74+
* asynchronously dispatched thread.
7675
*/
7776
@Override
7877
protected boolean shouldNotFilterAsyncDispatch() {
7978
return false;
8079
}
8180

81+
/**
82+
* Returns "false" so that the filter may set up the request context in an
83+
* error dispatch.
84+
*/
85+
@Override
86+
protected boolean shouldNotFilterErrorDispatch() {
87+
return false;
88+
}
89+
8290
@Override
8391
protected void doFilterInternal(
8492
HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)

spring-web/src/main/java/org/springframework/web/filter/ShallowEtagHeaderFilter.java

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -68,16 +68,13 @@ protected boolean shouldNotFilterAsyncDispatch() {
6868
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
6969
throws ServletException, IOException {
7070

71-
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
72-
boolean isFirstRequest = !asyncManager.hasConcurrentResult();
73-
74-
if (isFirstRequest) {
71+
if (!isAsyncDispatch(request)) {
7572
response = new ShallowEtagResponseWrapper(response);
7673
}
7774

7875
filterChain.doFilter(request, response);
7976

80-
if (!asyncManager.isConcurrentHandlingStarted()) {
77+
if (!isAsyncStarted(request)) {
8178
updateResponse(request, response);
8279
}
8380
}

0 commit comments

Comments
 (0)