30
30
31
31
import javax .xml .transform .Source ;
32
32
33
+ import org .reactivestreams .Publisher ;
34
+
35
+ import org .springframework .beans .factory .BeanFactory ;
33
36
import org .springframework .core .ParameterizedTypeReference ;
34
37
import org .springframework .expression .EvaluationContext ;
35
38
import org .springframework .expression .Expression ;
48
51
import org .springframework .integration .mapping .HeaderMapper ;
49
52
import org .springframework .integration .support .AbstractIntegrationMessageBuilder ;
50
53
import org .springframework .integration .support .MessageBuilderFactory ;
54
+ import org .springframework .lang .Nullable ;
51
55
import org .springframework .messaging .Message ;
52
56
import org .springframework .messaging .MessageHandlingException ;
53
57
import org .springframework .util .Assert ;
74
78
*/
75
79
public abstract class AbstractHttpRequestExecutingMessageHandler extends AbstractReplyProducingMessageHandler {
76
80
77
- private static final List <HttpMethod > noBodyHttpMethods =
81
+ private static final List <HttpMethod > NO_BODY_HTTP_METHODS =
78
82
Arrays .asList (HttpMethod .GET , HttpMethod .HEAD , HttpMethod .TRACE );
79
83
80
84
private final Map <String , Expression > uriVariableExpressions = new HashMap <>();
@@ -140,7 +144,7 @@ public void setHttpMethodExpression(Expression httpMethodExpression) {
140
144
*/
141
145
public void setHttpMethod (HttpMethod httpMethod ) {
142
146
Assert .notNull (httpMethod , "'httpMethod' must not be null" );
143
- this .httpMethodExpression = new ValueExpression <HttpMethod >(httpMethod );
147
+ this .httpMethodExpression = new ValueExpression <>(httpMethod );
144
148
}
145
149
146
150
/**
@@ -193,7 +197,7 @@ public void setExpectReply(boolean expectReply) {
193
197
*/
194
198
public void setExpectedResponseType (Class <?> expectedResponseType ) {
195
199
Assert .notNull (expectedResponseType , "'expectedResponseType' must not be null" );
196
- this . expectedResponseTypeExpression = new ValueExpression <Class <?>> (expectedResponseType );
200
+ setExpectedResponseTypeExpression ( new ValueExpression <> (expectedResponseType ) );
197
201
}
198
202
199
203
/**
@@ -261,20 +265,18 @@ public void setTrustedSpel(boolean trustedSpel) {
261
265
262
266
@ Override
263
267
protected void doInit () {
264
- this .evaluationContext = ExpressionUtils .createStandardEvaluationContext (this .getBeanFactory ());
265
- this .simpleEvaluationContext = ExpressionUtils .createSimpleEvaluationContext (this .getBeanFactory ());
268
+ BeanFactory beanFactory = getBeanFactory ();
269
+ this .evaluationContext = ExpressionUtils .createStandardEvaluationContext (beanFactory );
270
+ this .simpleEvaluationContext = ExpressionUtils .createSimpleEvaluationContext (beanFactory );
266
271
}
267
272
268
273
@ Override
269
274
protected Object handleRequestMessage (Message <?> requestMessage ) {
270
275
HttpMethod httpMethod = determineHttpMethod (requestMessage );
271
-
272
- if (!shouldIncludeRequestBody (httpMethod ) && this .extractPayloadExplicitlySet ) {
273
- if (logger .isWarnEnabled ()) {
274
- logger .warn ("The 'extractPayload' attribute has no relevance for the current request " +
275
- "since the HTTP Method is '" + httpMethod +
276
- "', and no request body will be sent for that method." );
277
- }
276
+ if (this .extractPayloadExplicitlySet && logger .isWarnEnabled () && !shouldIncludeRequestBody (httpMethod )) {
277
+ logger .warn ("The 'extractPayload' attribute has no relevance for the current request " +
278
+ "since the HTTP Method is '" + httpMethod +
279
+ "', and no request body will be sent for that method." );
278
280
}
279
281
280
282
Object expectedResponseType = determineExpectedResponseType (requestMessage );
@@ -293,9 +295,10 @@ private URI generateUri(Message<?> requestMessage) {
293
295
"'uriExpression' evaluation must result in a 'String' or 'URI' instance, not: "
294
296
+ (uri == null ? "null" : uri .getClass ()));
295
297
Map <String , ?> uriVariables = determineUriVariables (requestMessage );
296
- UriComponentsBuilder uriComponentsBuilder = uri instanceof String
297
- ? UriComponentsBuilder .fromUriString ((String ) uri )
298
- : UriComponentsBuilder .fromUri ((URI ) uri );
298
+ UriComponentsBuilder uriComponentsBuilder =
299
+ uri instanceof String
300
+ ? UriComponentsBuilder .fromUriString ((String ) uri )
301
+ : UriComponentsBuilder .fromUri ((URI ) uri );
299
302
UriComponents uriComponents = uriComponentsBuilder .buildAndExpand (uriVariables );
300
303
try {
301
304
return this .encodeUri ? uriComponents .toUri () : new URI (uriComponents .toUriString ());
@@ -310,7 +313,7 @@ protected Object getReply(ResponseEntity<?> httpResponse) {
310
313
HttpHeaders httpHeaders = httpResponse .getHeaders ();
311
314
Map <String , Object > headers = this .headerMapper .toHeaders (httpHeaders );
312
315
if (this .transferCookies ) {
313
- this . doConvertSetCookie (headers );
316
+ doConvertSetCookie (headers );
314
317
}
315
318
316
319
AbstractIntegrationMessageBuilder <?> replyBuilder = null ;
@@ -355,8 +358,9 @@ private void doConvertSetCookie(Map<String, Object> headers) {
355
358
356
359
private HttpEntity <?> generateHttpRequest (Message <?> message , HttpMethod httpMethod ) {
357
360
Assert .notNull (message , "message must not be null" );
358
- return (this .extractPayload ) ? this .createHttpEntityFromPayload (message , httpMethod )
359
- : this .createHttpEntityFromMessage (message , httpMethod );
361
+ return this .extractPayload
362
+ ? createHttpEntityFromPayload (message , httpMethod )
363
+ : createHttpEntityFromMessage (message , httpMethod );
360
364
}
361
365
362
366
private HttpEntity <?> createHttpEntityFromPayload (Message <?> message , HttpMethod httpMethod ) {
@@ -371,16 +375,20 @@ private HttpEntity<?> createHttpEntityFromPayload(Message<?> message, HttpMethod
371
375
}
372
376
// otherwise, we are creating a request with a body and need to deal with the content-type header as well
373
377
if (httpHeaders .getContentType () == null ) {
374
- MediaType contentType = (payload instanceof String )
375
- ? resolveContentType ((String ) payload , this .charset )
376
- : resolveContentType (payload );
378
+ MediaType contentType =
379
+ payload instanceof String
380
+ ? new MediaType ("text" , "plain" , this .charset )
381
+ : resolveContentType (payload );
377
382
httpHeaders .setContentType (contentType );
378
383
}
379
- if (MediaType .APPLICATION_FORM_URLENCODED .equals (httpHeaders .getContentType ()) ||
380
- MediaType .MULTIPART_FORM_DATA .equals (httpHeaders .getContentType ())) {
381
- if (!(payload instanceof MultiValueMap )) {
382
- payload = this .convertToMultiValueMap ((Map <?, ?>) payload );
383
- }
384
+ if ((MediaType .APPLICATION_FORM_URLENCODED .equals (httpHeaders .getContentType ()) ||
385
+ MediaType .MULTIPART_FORM_DATA .equals (httpHeaders .getContentType ()))
386
+ && !(payload instanceof MultiValueMap )) {
387
+
388
+ Assert .isInstanceOf (Map .class , payload ,
389
+ () -> "For " + MediaType .APPLICATION_FORM_URLENCODED + " and " +
390
+ MediaType .MULTIPART_FORM_DATA + " media types the payload must be an instance of a Map." );
391
+ payload = convertToMultiValueMap ((Map <?, ?>) payload );
384
392
}
385
393
return new HttpEntity <>(payload , httpHeaders );
386
394
}
@@ -409,42 +417,36 @@ private MediaType resolveContentType(Object content) {
409
417
else if (content instanceof Source ) {
410
418
contentType = MediaType .TEXT_XML ;
411
419
}
412
- else if (content instanceof Map ) {
420
+ else if (content instanceof Map && isFormData (( Map < Object , ?>) content ) ) {
413
421
// We need to check separately for MULTIPART as well as URLENCODED simply because
414
422
// MultiValueMap<Object, Object> is actually valid content for serialization
415
- if (this .isFormData ((Map <Object , ?>) content )) {
416
- if (this .isMultipart ((Map <String , ?>) content )) {
417
- contentType = MediaType .MULTIPART_FORM_DATA ;
418
- }
419
- else {
420
- contentType = MediaType .APPLICATION_FORM_URLENCODED ;
421
- }
423
+ if (isMultipart ((Map <String , ?>) content )) {
424
+ contentType = MediaType .MULTIPART_FORM_DATA ;
425
+ }
426
+ else {
427
+ contentType = MediaType .APPLICATION_FORM_URLENCODED ;
422
428
}
423
429
}
424
- if (contentType == null ) {
430
+ if (contentType == null && !( content instanceof Publisher <?>) ) {
425
431
contentType = new MediaType ("application" , "x-java-serialized-object" );
426
432
}
427
433
return contentType ;
428
434
}
429
435
430
436
private boolean shouldIncludeRequestBody (HttpMethod httpMethod ) {
431
- return !(CollectionUtils .containsInstance (noBodyHttpMethods , httpMethod ));
432
- }
433
-
434
- private MediaType resolveContentType (String content , Charset charset ) {
435
- return new MediaType ("text" , "plain" , charset );
437
+ return !(CollectionUtils .containsInstance (NO_BODY_HTTP_METHODS , httpMethod ));
436
438
}
437
439
438
440
private MultiValueMap <Object , Object > convertToMultiValueMap (Map <?, ?> simpleMap ) {
439
- LinkedMultiValueMap <Object , Object > multipartValueMap = new LinkedMultiValueMap <Object , Object >();
441
+ LinkedMultiValueMap <Object , Object > multipartValueMap = new LinkedMultiValueMap <>();
440
442
for (Entry <?, ?> entry : simpleMap .entrySet ()) {
441
443
Object key = entry .getKey ();
442
444
Object value = entry .getValue ();
443
445
if (value instanceof Object []) {
444
446
value = Arrays .asList ((Object []) value );
445
447
}
446
448
if (value instanceof Collection ) {
447
- multipartValueMap .put (key , new ArrayList <Object >((Collection <?>) value ));
449
+ multipartValueMap .put (key , new ArrayList <>((Collection <?>) value ));
448
450
}
449
451
else {
450
452
multipartValueMap .add (key , value );
@@ -453,6 +455,18 @@ private MultiValueMap<Object, Object> convertToMultiValueMap(Map<?, ?> simpleMap
453
455
return multipartValueMap ;
454
456
}
455
457
458
+ /**
459
+ * If all keys and values are Strings, we'll consider the Map to be form data.
460
+ */
461
+ private boolean isFormData (Map <Object , ?> map ) {
462
+ for (Object key : map .keySet ()) {
463
+ if (!(key instanceof String )) {
464
+ return false ;
465
+ }
466
+ }
467
+ return true ;
468
+ }
469
+
456
470
/**
457
471
* If all keys are Strings, and some values are not Strings we'll consider
458
472
* the Map to be multipart/form-data
@@ -479,21 +493,9 @@ else if (!(value instanceof String)) {
479
493
return false ;
480
494
}
481
495
482
- /**
483
- * If all keys and values are Strings, we'll consider the Map to be form data.
484
- */
485
- private boolean isFormData (Map <Object , ?> map ) {
486
- for (Object key : map .keySet ()) {
487
- if (!(key instanceof String )) {
488
- return false ;
489
- }
490
- }
491
- return true ;
492
- }
493
-
494
496
private HttpMethod determineHttpMethod (Message <?> requestMessage ) {
495
497
Object httpMethod = this .httpMethodExpression .getValue (this .evaluationContext , requestMessage );
496
- Assert .state (httpMethod != null && (httpMethod instanceof String || httpMethod instanceof HttpMethod ),
498
+ Assert .state ((httpMethod instanceof String || httpMethod instanceof HttpMethod ), () ->
497
499
"'httpMethodExpression' evaluation must result in an 'HttpMethod' enum or its String representation, " +
498
500
"not: " + (httpMethod == null ? "null" : httpMethod .getClass ()));
499
501
if (httpMethod instanceof HttpMethod ) {
@@ -503,36 +505,43 @@ private HttpMethod determineHttpMethod(Message<?> requestMessage) {
503
505
try {
504
506
return HttpMethod .valueOf ((String ) httpMethod );
505
507
}
506
- catch (Exception e ) {
508
+ catch (Exception ex ) {
507
509
throw new IllegalStateException ("The 'httpMethodExpression' returned an invalid HTTP Method value: "
508
- + httpMethod );
510
+ + httpMethod , ex );
509
511
}
510
512
}
511
513
}
512
514
513
515
private Object determineExpectedResponseType (Message <?> requestMessage ) {
514
- Object expectedResponseType = null ;
515
- if (this .expectedResponseTypeExpression != null ) {
516
- expectedResponseType = this .expectedResponseTypeExpression .getValue (this .evaluationContext , requestMessage );
516
+ return evaluateTypeFromExpression (requestMessage , this .expectedResponseTypeExpression , "expectedResponseType" );
517
+ }
518
+
519
+ @ Nullable
520
+ protected Object evaluateTypeFromExpression (Message <?> requestMessage , @ Nullable Expression expression ,
521
+ String property ) {
522
+
523
+ Object type = null ;
524
+ if (expression != null ) {
525
+ type = expression .getValue (this .evaluationContext , requestMessage );
517
526
}
518
- if (expectedResponseType != null ) {
519
- Assert .state (expectedResponseType instanceof Class <?>
520
- || expectedResponseType instanceof String
521
- || expectedResponseType instanceof ParameterizedTypeReference ,
522
- "'expectedResponseType' can be an instance of 'Class<?>', 'String' " +
527
+ if (type != null ) {
528
+ Class <?> typeClass = type .getClass ();
529
+ Assert .state (type instanceof Class <?>
530
+ || type instanceof String
531
+ || type instanceof ParameterizedTypeReference ,
532
+ () -> "The '" + property + "' can be an instance of 'Class<?>', 'String' " +
523
533
"or 'ParameterizedTypeReference<?>'; " +
524
- "evaluation resulted in a" + expectedResponseType . getClass () + "." );
525
- if (expectedResponseType instanceof String && StringUtils .hasText ((String ) expectedResponseType )) {
534
+ "evaluation resulted in a " + typeClass + "." );
535
+ if (type instanceof String && StringUtils .hasText ((String ) type )) {
526
536
try {
527
- expectedResponseType = ClassUtils .forName ((String ) expectedResponseType ,
528
- getApplicationContext ().getClassLoader ());
537
+ type = ClassUtils .forName ((String ) type , getApplicationContext ().getClassLoader ());
529
538
}
530
539
catch (ClassNotFoundException e ) {
531
- throw new IllegalStateException ("Cannot load class for name: " + expectedResponseType , e );
540
+ throw new IllegalStateException ("Cannot load class for name: " + type , e );
532
541
}
533
542
}
534
543
}
535
- return expectedResponseType ;
544
+ return type ;
536
545
}
537
546
538
547
@ SuppressWarnings ("unchecked" )
0 commit comments