19
19
import java .io .ByteArrayInputStream ;
20
20
import java .io .IOException ;
21
21
import java .io .InputStream ;
22
- import java .lang .reflect .Method ;
23
22
import java .net .URI ;
24
23
import java .nio .charset .StandardCharsets ;
25
24
import java .time .ZoneId ;
28
27
import java .util .Arrays ;
29
28
import java .util .Collections ;
30
29
import java .util .Date ;
30
+ import java .util .Optional ;
31
31
32
32
import org .hamcrest .Matchers ;
33
33
import org .junit .Before ;
56
56
import org .springframework .web .HttpMediaTypeNotSupportedException ;
57
57
import org .springframework .web .bind .annotation .RequestMapping ;
58
58
import org .springframework .web .context .request .ServletWebRequest ;
59
+ import org .springframework .web .method .ResolvableMethod ;
59
60
import org .springframework .web .method .support .ModelAndViewContainer ;
60
61
61
- import static java .time .Instant .* ;
62
- import static java .time .format .DateTimeFormatter .* ;
62
+ import static java .time .Instant .ofEpochMilli ;
63
+ import static java .time .format .DateTimeFormatter .RFC_1123_DATE_TIME ;
63
64
import static org .junit .Assert .*;
64
65
import static org .mockito .BDDMockito .*;
65
- import static org .springframework .http .MediaType .*;
66
- import static org .springframework .web .servlet .HandlerMapping .*;
66
+ import static org .springframework .http .MediaType .APPLICATION_OCTET_STREAM ;
67
+ import static org .springframework .http .MediaType .TEXT_PLAIN ;
68
+ import static org .springframework .web .method .ResolvableMethod .on ;
69
+ import static org .springframework .web .servlet .HandlerMapping .PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE ;
67
70
68
71
/**
69
72
* Test fixture for {@link HttpEntityMethodProcessor} delegating to a mock
@@ -91,26 +94,6 @@ public class HttpEntityMethodProcessorMockTests {
91
94
92
95
private HttpMessageConverter <Object > resourceRegionMessageConverter ;
93
96
94
- private MethodParameter paramHttpEntity ;
95
-
96
- private MethodParameter paramRequestEntity ;
97
-
98
- private MethodParameter paramResponseEntity ;
99
-
100
- private MethodParameter paramInt ;
101
-
102
- private MethodParameter returnTypeResponseEntity ;
103
-
104
- private MethodParameter returnTypeResponseEntityProduces ;
105
-
106
- private MethodParameter returnTypeResponseEntityResource ;
107
-
108
- private MethodParameter returnTypeHttpEntity ;
109
-
110
- private MethodParameter returnTypeHttpEntitySubclass ;
111
-
112
- private MethodParameter returnTypeInt ;
113
-
114
97
private ModelAndViewContainer mavContainer ;
115
98
116
99
private MockHttpServletRequest servletRequest ;
@@ -119,6 +102,12 @@ public class HttpEntityMethodProcessorMockTests {
119
102
120
103
private ServletWebRequest webRequest ;
121
104
105
+ private MethodParameter returnTypeResponseEntity =
106
+ on (TestHandler .class ).resolveReturnType (ResponseEntity .class , String .class );
107
+
108
+ private MethodParameter returnTypeResponseEntityResource =
109
+ on (TestHandler .class ).resolveReturnType (ResponseEntity .class , Resource .class );
110
+
122
111
123
112
@ Before
124
113
@ SuppressWarnings ("unchecked" )
@@ -139,20 +128,6 @@ public void setup() throws Exception {
139
128
processor = new HttpEntityMethodProcessor (Arrays .asList (
140
129
stringHttpMessageConverter , resourceMessageConverter , resourceRegionMessageConverter ));
141
130
142
- Method handle1 = getClass ().getMethod ("handle1" , HttpEntity .class , ResponseEntity .class ,
143
- Integer .TYPE , RequestEntity .class );
144
-
145
- paramHttpEntity = new MethodParameter (handle1 , 0 );
146
- paramRequestEntity = new MethodParameter (handle1 , 3 );
147
- paramResponseEntity = new MethodParameter (handle1 , 1 );
148
- paramInt = new MethodParameter (handle1 , 2 );
149
- returnTypeResponseEntity = new MethodParameter (handle1 , -1 );
150
- returnTypeResponseEntityProduces = new MethodParameter (getClass ().getMethod ("handle4" ), -1 );
151
- returnTypeHttpEntity = new MethodParameter (getClass ().getMethod ("handle2" , HttpEntity .class ), -1 );
152
- returnTypeHttpEntitySubclass = new MethodParameter (getClass ().getMethod ("handle2x" , HttpEntity .class ), -1 );
153
- returnTypeInt = new MethodParameter (getClass ().getMethod ("handle3" ), -1 );
154
- returnTypeResponseEntityResource = new MethodParameter (getClass ().getMethod ("handle5" ), -1 );
155
-
156
131
mavContainer = new ModelAndViewContainer ();
157
132
servletRequest = new MockHttpServletRequest ("GET" , "/foo" );
158
133
servletResponse = new MockHttpServletResponse ();
@@ -162,25 +137,39 @@ public void setup() throws Exception {
162
137
163
138
@ Test
164
139
public void supportsParameter () {
165
- assertTrue ("HttpEntity parameter not supported" , processor .supportsParameter (paramHttpEntity ));
166
- assertTrue ("RequestEntity parameter not supported" , processor .supportsParameter (paramRequestEntity ));
167
- assertFalse ("ResponseEntity parameter supported" , processor .supportsParameter (paramResponseEntity ));
168
- assertFalse ("non-entity parameter supported" , processor .supportsParameter (paramInt ));
140
+ ResolvableMethod method = on (TestHandler .class ).named ("entityParams" ).build ();
141
+ assertTrue ("HttpEntity parameter not supported" ,
142
+ processor .supportsParameter (method .arg (HttpEntity .class , String .class )));
143
+ assertTrue ("RequestEntity parameter not supported" ,
144
+ processor .supportsParameter (method .arg (RequestEntity .class , String .class )));
145
+ assertFalse ("ResponseEntity parameter supported" ,
146
+ processor .supportsParameter (method .arg (ResponseEntity .class , String .class )));
147
+ assertFalse ("non-entity parameter supported" ,
148
+ processor .supportsParameter (method .arg (Integer .class )));
169
149
}
170
150
171
151
@ Test
172
152
public void supportsReturnType () {
173
- assertTrue ("ResponseEntity return type not supported" , processor .supportsReturnType (returnTypeResponseEntity ));
174
- assertTrue ("HttpEntity return type not supported" , processor .supportsReturnType (returnTypeHttpEntity ));
175
- assertTrue ("Custom HttpEntity subclass not supported" , processor .supportsReturnType (returnTypeHttpEntitySubclass ));
176
- assertFalse ("RequestEntity parameter supported" ,
177
- processor .supportsReturnType (paramRequestEntity ));
178
- assertFalse ("non-ResponseBody return type supported" , processor .supportsReturnType (returnTypeInt ));
153
+ assertTrue ("ResponseEntity return type not supported" ,
154
+ processor .supportsReturnType (on (TestHandler .class ).resolveReturnType (ResponseEntity .class , String .class )));
155
+ assertTrue ("HttpEntity return type not supported" ,
156
+ processor .supportsReturnType (on (TestHandler .class ).resolveReturnType (HttpEntity .class )));
157
+ assertTrue ("Custom HttpEntity subclass not supported" ,
158
+ processor .supportsReturnType (on (TestHandler .class ).resolveReturnType (CustomHttpEntity .class )));
159
+ assertTrue ("Optional ResponseEntity not supported" ,
160
+ processor .supportsReturnType (on (TestHandler .class ).resolveReturnType (Optional .class , ResponseEntity .class )));
161
+ assertFalse ("RequestEntity return type supported" ,
162
+ processor .supportsReturnType (on (TestHandler .class )
163
+ .named ("entityParams" ).build ().arg (RequestEntity .class , String .class )));
164
+ assertFalse ("non-ResponseBody return type supported" ,
165
+ processor .supportsReturnType (on (TestHandler .class ).resolveReturnType (Integer .class )));
179
166
}
180
167
181
168
@ Test
182
169
public void shouldResolveHttpEntityArgument () throws Exception {
183
170
String body = "Foo" ;
171
+ MethodParameter paramHttpEntity = on (TestHandler .class ).named ("entityParams" )
172
+ .build ().arg (HttpEntity .class , String .class );
184
173
185
174
MediaType contentType = TEXT_PLAIN ;
186
175
servletRequest .addHeader ("Content-Type" , contentType .toString ());
@@ -199,6 +188,8 @@ public void shouldResolveHttpEntityArgument() throws Exception {
199
188
@ Test
200
189
public void shouldResolveRequestEntityArgument () throws Exception {
201
190
String body = "Foo" ;
191
+ MethodParameter paramRequestEntity = on (TestHandler .class ).named ("entityParams" )
192
+ .build ().arg (RequestEntity .class , String .class );
202
193
203
194
MediaType contentType = TEXT_PLAIN ;
204
195
servletRequest .addHeader ("Content-Type" , contentType .toString ());
@@ -225,6 +216,8 @@ public void shouldResolveRequestEntityArgument() throws Exception {
225
216
226
217
@ Test
227
218
public void shouldFailResolvingWhenConverterCannotRead () throws Exception {
219
+ MethodParameter paramHttpEntity = on (TestHandler .class ).named ("entityParams" )
220
+ .build ().arg (HttpEntity .class , String .class );
228
221
MediaType contentType = TEXT_PLAIN ;
229
222
servletRequest .setMethod ("POST" );
230
223
servletRequest .addHeader ("Content-Type" , contentType .toString ());
@@ -238,6 +231,8 @@ public void shouldFailResolvingWhenConverterCannotRead() throws Exception {
238
231
239
232
@ Test
240
233
public void shouldFailResolvingWhenContentTypeNotSupported () throws Exception {
234
+ MethodParameter paramHttpEntity = on (TestHandler .class ).named ("entityParams" )
235
+ .build ().arg (HttpEntity .class , String .class );
241
236
servletRequest .setMethod ("POST" );
242
237
servletRequest .setContent ("some content" .getBytes (StandardCharsets .UTF_8 ));
243
238
this .thrown .expect (HttpMediaTypeNotSupportedException .class );
@@ -260,13 +255,14 @@ public void shouldHandleReturnValue() throws Exception {
260
255
261
256
@ Test
262
257
public void shouldHandleReturnValueWithProducibleMediaType () throws Exception {
258
+ MethodParameter returnType = on (TestHandler .class ).resolveReturnType (ResponseEntity .class , CharSequence .class );
263
259
String body = "Foo" ;
264
260
ResponseEntity <String > returnValue = new ResponseEntity <>(body , HttpStatus .OK );
265
261
servletRequest .addHeader ("Accept" , "text/*" );
266
262
servletRequest .setAttribute (PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE , Collections .singleton (MediaType .TEXT_HTML ));
267
263
given (stringHttpMessageConverter .canWrite (String .class , MediaType .TEXT_HTML )).willReturn (true );
268
264
269
- processor .handleReturnValue (returnValue , returnTypeResponseEntityProduces , mavContainer , webRequest );
265
+ processor .handleReturnValue (returnValue , returnType , mavContainer , webRequest );
270
266
271
267
assertTrue (mavContainer .isRequestHandled ());
272
268
verify (stringHttpMessageConverter ).write (eq (body ), eq (MediaType .TEXT_HTML ), isA (HttpOutputMessage .class ));
@@ -311,6 +307,7 @@ public void shouldFailHandlingWhenContentTypeNotSupported() throws Exception {
311
307
312
308
@ Test
313
309
public void shouldFailHandlingWhenConverterCannotWrite () throws Exception {
310
+ MethodParameter returnType = on (TestHandler .class ).resolveReturnType (ResponseEntity .class , CharSequence .class );
314
311
String body = "Foo" ;
315
312
ResponseEntity <String > returnValue = new ResponseEntity <>(body , HttpStatus .OK );
316
313
MediaType accepted = TEXT_PLAIN ;
@@ -322,7 +319,7 @@ public void shouldFailHandlingWhenConverterCannotWrite() throws Exception {
322
319
given (stringHttpMessageConverter .canWrite (String .class , accepted )).willReturn (false );
323
320
324
321
this .thrown .expect (HttpMediaTypeNotAcceptableException .class );
325
- processor .handleReturnValue (returnValue , returnTypeResponseEntityProduces , mavContainer , webRequest );
322
+ processor .handleReturnValue (returnValue , returnType , mavContainer , webRequest );
326
323
}
327
324
328
325
@ Test // SPR-9142
@@ -361,6 +358,16 @@ public void shouldHandleResponseHeaderAndBody() throws Exception {
361
358
assertEquals ("headerValue" , outputMessage .getValue ().getHeaders ().get ("header" ).get (0 ));
362
359
}
363
360
361
+ @ Test // SPR-13281
362
+ public void shouldReturnNotFoundWhenEmptyOptional () throws Exception {
363
+ MethodParameter returnType = on (TestHandler .class ).resolveReturnType (Optional .class , ResponseEntity .class );
364
+ Optional <ResponseEntity > returnValue = Optional .empty ();
365
+
366
+ processor .handleReturnValue (returnValue , returnType , mavContainer , webRequest );
367
+ assertTrue (mavContainer .isRequestHandled ());
368
+ assertEquals (HttpStatus .NOT_FOUND .value (), servletResponse .getStatus ());
369
+ }
370
+
364
371
@ Test
365
372
public void shouldHandleLastModifiedWithHttp304 () throws Exception {
366
373
long currentTime = new Date ().getTime ();
@@ -697,38 +704,46 @@ private void assertConditionalResponse(HttpStatus status, String body, String et
697
704
}
698
705
}
699
706
707
+ private static class TestHandler {
700
708
701
- @ SuppressWarnings ("unused" )
702
- public ResponseEntity <String > handle1 (HttpEntity <String > httpEntity , ResponseEntity <String > entity ,
703
- int i , RequestEntity <String > requestEntity ) {
709
+ @ SuppressWarnings ("unused" )
710
+ public ResponseEntity <String > entityParams (HttpEntity <String > httpEntity , ResponseEntity <String > entity ,
711
+ Integer i , RequestEntity <String > requestEntity ) {
704
712
705
- return entity ;
706
- }
713
+ return entity ;
714
+ }
707
715
708
- @ SuppressWarnings ("unused" )
709
- public HttpEntity <?> handle2 (HttpEntity <?> entity ) {
710
- return entity ;
711
- }
716
+ @ SuppressWarnings ("unused" )
717
+ public HttpEntity <?> httpEntity (HttpEntity <?> entity ) {
718
+ return entity ;
719
+ }
712
720
713
- @ SuppressWarnings ("unused" )
714
- public CustomHttpEntity handle2x (HttpEntity <?> entity ) {
715
- return new CustomHttpEntity ();
716
- }
721
+ @ SuppressWarnings ("unused" )
722
+ public CustomHttpEntity customHttpEntity (HttpEntity <?> entity ) {
723
+ return new CustomHttpEntity ();
724
+ }
717
725
718
- @ SuppressWarnings ("unused" )
719
- public int handle3 () {
720
- return 42 ;
721
- }
726
+ @ SuppressWarnings ("unused" )
727
+ public Optional < ResponseEntity > handleOptionalEntity () {
728
+ return null ;
729
+ }
722
730
723
- @ SuppressWarnings ("unused" )
724
- @ RequestMapping (produces = {"text/html" , "application/xhtml+xml" })
725
- public ResponseEntity <String > handle4 () {
726
- return null ;
727
- }
731
+ @ SuppressWarnings ("unused" )
732
+ @ RequestMapping (produces = {"text/html" , "application/xhtml+xml" })
733
+ public ResponseEntity <CharSequence > produces () {
734
+ return null ;
735
+ }
736
+
737
+ @ SuppressWarnings ("unused" )
738
+ public ResponseEntity <Resource > resourceResponseEntity () {
739
+ return null ;
740
+ }
741
+
742
+ @ SuppressWarnings ("unused" )
743
+ public Integer integer () {
744
+ return 42 ;
745
+ }
728
746
729
- @ SuppressWarnings ("unused" )
730
- public ResponseEntity <Resource > handle5 () {
731
- return null ;
732
747
}
733
748
734
749
@ SuppressWarnings ("unused" )
0 commit comments