Skip to content

Commit cfc560c

Browse files
committed
Leniently accept custom DeferredResult etc subclasses for null values
Issue: SPR-14423
1 parent 498d896 commit cfc560c

File tree

3 files changed

+63
-29
lines changed

3 files changed

+63
-29
lines changed

spring-messaging/src/main/java/org/springframework/messaging/handler/invocation/InvocableHandlerMethod.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -283,10 +283,10 @@ public Class<?> getParameterType() {
283283
if (this.returnValue != null) {
284284
return this.returnValue.getClass();
285285
}
286-
if (ResolvableType.NONE.equals(this.returnType)) {
287-
throw new IllegalArgumentException("Expected Future-like type with generic parameter");
286+
if (!ResolvableType.NONE.equals(this.returnType)) {
287+
return this.returnType.getRawClass();
288288
}
289-
return this.returnType.getRawClass();
289+
return super.getParameterType();
290290
}
291291

292292
@Override

spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ServletInvocableHandlerMethod.java

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -281,16 +281,10 @@ public Class<?> getParameterType() {
281281
if (this.returnValue != null) {
282282
return this.returnValue.getClass();
283283
}
284-
Class<?> parameterType = super.getParameterType();
285-
if (ResponseBodyEmitter.class.isAssignableFrom(parameterType) ||
286-
StreamingResponseBody.class.isAssignableFrom(parameterType)) {
287-
return parameterType;
284+
if (!ResolvableType.NONE.equals(this.returnType)) {
285+
return this.returnType.getRawClass();
288286
}
289-
if (ResolvableType.NONE.equals(this.returnType)) {
290-
throw new IllegalArgumentException("Expected one of Callable, DeferredResult, or ListenableFuture: " +
291-
super.getParameterType());
292-
}
293-
return this.returnType.getRawClass();
287+
return super.getParameterType();
294288
}
295289

296290
@Override

spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ServletInvocableHandlerMethodTests.java

Lines changed: 57 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@
5656
*
5757
* @author Rossen Stoyanchev
5858
* @author Sam Brannen
59+
* @author Juergen Hoeller
5960
*/
6061
public class ServletInvocableHandlerMethodTests {
6162

@@ -126,9 +127,7 @@ public void invokeAndHandle_VoidRequestNotModified() throws Exception {
126127
this.mavContainer.isRequestHandled());
127128
}
128129

129-
// SPR-9159
130-
131-
@Test
130+
@Test // SPR-9159
132131
public void invokeAndHandle_NotVoidWithResponseStatusAndReason() throws Exception {
133132
ServletInvocableHandlerMethod handlerMethod = getHandlerMethod(new Handler(), "responseStatusWithReason");
134133
handlerMethod.invokeAndHandle(this.webRequest, this.mavContainer);
@@ -138,7 +137,7 @@ public void invokeAndHandle_NotVoidWithResponseStatusAndReason() throws Exceptio
138137
assertEquals("400 Bad Request", this.response.getErrorMessage());
139138
}
140139

141-
@Test(expected=HttpMessageNotWritableException.class)
140+
@Test(expected = HttpMessageNotWritableException.class)
142141
public void invokeAndHandle_Exception() throws Exception {
143142
this.returnValueHandlers.addHandler(new ExceptionRaisingReturnValueHandler());
144143

@@ -169,23 +168,47 @@ public void invokeAndHandle_DynamicReturnValue() throws Exception {
169168

170169
@Test
171170
public void wrapConcurrentResult_MethodLevelResponseBody() throws Exception {
172-
wrapConcurrentResult_ResponseBody(new MethodLevelResponseBodyHandler());
171+
wrapConcurrentResult_ResponseBody(new MethodLevelResponseBodyHandler(), "bar", String.class);
172+
}
173+
174+
@Test
175+
public void wrapConcurrentResult_MethodLevelResponseBodyEmpty() throws Exception {
176+
wrapConcurrentResult_ResponseBody(new MethodLevelResponseBodyHandler(), null, String.class);
173177
}
174178

175179
@Test
176180
public void wrapConcurrentResult_TypeLevelResponseBody() throws Exception {
177-
wrapConcurrentResult_ResponseBody(new TypeLevelResponseBodyHandler());
181+
wrapConcurrentResult_ResponseBody(new TypeLevelResponseBodyHandler(), "bar", String.class);
182+
}
183+
184+
@Test
185+
public void wrapConcurrentResult_TypeLevelResponseBodyEmpty() throws Exception {
186+
wrapConcurrentResult_ResponseBody(new TypeLevelResponseBodyHandler(), null, String.class);
187+
}
188+
189+
@Test
190+
public void wrapConcurrentResult_DeferredResultSubclass() throws Exception {
191+
wrapConcurrentResult_ResponseBody(new DeferredResultSubclassHandler(), "bar", String.class);
178192
}
179193

180-
private void wrapConcurrentResult_ResponseBody(Object handler) throws Exception {
194+
@Test
195+
public void wrapConcurrentResult_DeferredResultSubclassEmpty() throws Exception {
196+
wrapConcurrentResult_ResponseBody(new DeferredResultSubclassHandler(), null, CustomDeferredResult.class);
197+
}
198+
199+
private void wrapConcurrentResult_ResponseBody(Object handler, Object result, Class<?> expectedReturnType)
200+
throws Exception {
201+
181202
List<HttpMessageConverter<?>> converters = new ArrayList<>();
182203
converters.add(new StringHttpMessageConverter());
204+
this.returnValueHandlers.addHandler(new ModelAndViewMethodReturnValueHandler());
183205
this.returnValueHandlers.addHandler(new RequestResponseBodyMethodProcessor(converters));
184206
ServletInvocableHandlerMethod handlerMethod = getHandlerMethod(handler, "handle");
185-
handlerMethod = handlerMethod.wrapConcurrentResult("bar");
186-
handlerMethod.invokeAndHandle(this.webRequest, this.mavContainer);
187207

188-
assertEquals("bar", this.response.getContentAsString());
208+
handlerMethod = handlerMethod.wrapConcurrentResult(result);
209+
handlerMethod.invokeAndHandle(this.webRequest, this.mavContainer);
210+
assertEquals((result != null ? result.toString() : ""), this.response.getContentAsString());
211+
assertEquals(expectedReturnType, handlerMethod.getReturnValueType(result).getParameterType());
189212
}
190213

191214
@Test
@@ -200,9 +223,7 @@ public void wrapConcurrentResult_ResponseEntity() throws Exception {
200223
assertEquals("bar", this.response.getContentAsString());
201224
}
202225

203-
// SPR-12287
204-
205-
@Test
226+
@Test // SPR-12287
206227
public void wrapConcurrentResult_ResponseEntityNullBody() throws Exception {
207228
List<HttpMessageConverter<?>> converters = new ArrayList<>();
208229
converters.add(new StringHttpMessageConverter());
@@ -256,9 +277,7 @@ public void wrapConcurrentResult_StreamingResponseBody() throws Exception {
256277
assertEquals("", this.response.getContentAsString());
257278
}
258279

259-
// SPR-12287 (16/Oct/14 comments)
260-
261-
@Test
280+
@Test // SPR-12287 (16/Oct/14 comments)
262281
public void responseEntityRawTypeWithNullBody() throws Exception {
263282
List<HttpMessageConverter<?>> converters = Collections.singletonList(new StringHttpMessageConverter());
264283
List<Object> advice = Collections.singletonList(mock(ResponseBodyAdvice.class));
@@ -281,6 +300,7 @@ private ServletInvocableHandlerMethod getHandlerMethod(Object controller,
281300
return handlerMethod;
282301
}
283302

303+
284304
@SuppressWarnings("unused")
285305
@ResponseStatus
286306
@Retention(RetentionPolicy.RUNTIME)
@@ -290,6 +310,7 @@ private ServletInvocableHandlerMethod getHandlerMethod(Object controller,
290310
HttpStatus responseStatus() default HttpStatus.INTERNAL_SERVER_ERROR;
291311
}
292312

313+
293314
@SuppressWarnings("unused")
294315
private static class Handler {
295316

@@ -321,6 +342,7 @@ public Object dynamicReturnValue(@RequestParam(required=false) String param) {
321342
}
322343
}
323344

345+
324346
@SuppressWarnings("unused")
325347
@ResponseStatus(HttpStatus.BAD_REQUEST)
326348
private static class ResponseStatusHandler {
@@ -329,6 +351,7 @@ public void handle() {
329351
}
330352
}
331353

354+
332355
private static class MethodLevelResponseBodyHandler {
333356

334357
@ResponseBody
@@ -337,6 +360,7 @@ public DeferredResult<String> handle() {
337360
}
338361
}
339362

363+
340364
@SuppressWarnings("unused")
341365
@ResponseBody
342366
private static class TypeLevelResponseBodyHandler {
@@ -346,6 +370,20 @@ public DeferredResult<String> handle() {
346370
}
347371
}
348372

373+
374+
private static class DeferredResultSubclassHandler {
375+
376+
@ResponseBody
377+
public CustomDeferredResult handle() {
378+
return new CustomDeferredResult();
379+
}
380+
}
381+
382+
383+
private static class CustomDeferredResult extends DeferredResult<String> {
384+
}
385+
386+
349387
@SuppressWarnings("unused")
350388
private static class ResponseEntityHandler {
351389

@@ -358,6 +396,7 @@ public ResponseEntity<Void> handleRawType() {
358396
}
359397
}
360398

399+
361400
private static class ExceptionRaisingReturnValueHandler implements HandlerMethodReturnValueHandler {
362401

363402
@Override
@@ -372,6 +411,7 @@ public void handleReturnValue(Object returnValue, MethodParameter returnType,
372411
}
373412
}
374413

414+
375415
@SuppressWarnings("unused")
376416
private static class AsyncHandler {
377417

@@ -384,4 +424,4 @@ public StreamingResponseBody handleWithStreaming() {
384424
}
385425
}
386426

387-
}
427+
}

0 commit comments

Comments
 (0)