Skip to content

Commit df697c5

Browse files
artembilangaryrussell
authored andcommitted
GH-2806: Add generics support to HTTP Inbound
Fixes #2806 **Cherry-pick to 5.1.x**
1 parent 8d99039 commit df697c5

File tree

4 files changed

+137
-73
lines changed

4 files changed

+137
-73
lines changed

spring-integration-http/src/main/java/org/springframework/integration/http/inbound/BaseHttpInboundEndpoint.java

+18-14
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2017-2018 the original author or authors.
2+
* Copyright 2017-2019 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -66,23 +66,23 @@ public class BaseHttpInboundEndpoint extends MessagingGatewaySupport implements
6666

6767
protected final AtomicInteger activeCount = new AtomicInteger();
6868

69-
private volatile ResolvableType requestPayloadType = null;
69+
private ResolvableType requestPayloadType = null;
7070

71-
private volatile HeaderMapper<HttpHeaders> headerMapper = DefaultHttpHeaderMapper.inboundMapper();
71+
private HeaderMapper<HttpHeaders> headerMapper = DefaultHttpHeaderMapper.inboundMapper();
7272

73-
private volatile boolean extractReplyPayload = true;
73+
private boolean extractReplyPayload = true;
7474

75-
private volatile Expression statusCodeExpression;
75+
private Expression statusCodeExpression;
7676

77-
private volatile EvaluationContext evaluationContext;
77+
private EvaluationContext evaluationContext;
7878

79-
private volatile RequestMapping requestMapping = new RequestMapping();
79+
private RequestMapping requestMapping = new RequestMapping();
8080

81-
private volatile Expression payloadExpression;
81+
private Expression payloadExpression;
8282

83-
private volatile Map<String, Expression> headerExpressions;
83+
private Map<String, Expression> headerExpressions;
8484

85-
private volatile CrossOrigin crossOrigin;
85+
private CrossOrigin crossOrigin;
8686

8787
public BaseHttpInboundEndpoint(boolean expectReply) {
8888
super(expectReply);
@@ -279,8 +279,13 @@ private void validateSupportedMethods() {
279279
}
280280

281281
protected HttpStatus evaluateHttpStatus(HttpEntity<?> httpEntity) {
282-
Object value = this.statusCodeExpression.getValue(this.evaluationContext, httpEntity);
283-
return buildHttpStatus(value);
282+
if (this.statusCodeExpression != null) {
283+
Object value = this.statusCodeExpression.getValue(this.evaluationContext, httpEntity);
284+
return buildHttpStatus(value);
285+
}
286+
else {
287+
return HttpStatus.INTERNAL_SERVER_ERROR;
288+
}
284289
}
285290

286291
protected HttpStatus resolveHttpStatusFromHeaders(MessageHeaders headers) {
@@ -328,8 +333,7 @@ public String getComponentType() {
328333
* @return true or false if HTTP request can contain the body
329334
*/
330335
protected boolean isReadable(HttpRequest request) {
331-
HttpMethod method = request.getMethod();
332-
return method == null ? false : !(CollectionUtils.containsInstance(nonReadableBodyHttpMethods, method));
336+
return !(CollectionUtils.containsInstance(nonReadableBodyHttpMethods, request.getMethod()));
333337
}
334338

335339
}

spring-integration-http/src/main/java/org/springframework/integration/http/inbound/HttpRequestHandlingEndpointSupport.java

+56-53
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package org.springframework.integration.http.inbound;
1818

1919
import java.io.IOException;
20+
import java.lang.reflect.Type;
2021
import java.util.ArrayList;
2122
import java.util.HashMap;
2223
import java.util.List;
@@ -39,6 +40,7 @@
3940
import org.springframework.http.MediaType;
4041
import org.springframework.http.RequestEntity;
4142
import org.springframework.http.converter.ByteArrayHttpMessageConverter;
43+
import org.springframework.http.converter.GenericHttpMessageConverter;
4244
import org.springframework.http.converter.HttpMessageConverter;
4345
import org.springframework.http.converter.ResourceHttpMessageConverter;
4446
import org.springframework.http.converter.StringHttpMessageConverter;
@@ -93,7 +95,7 @@
9395
* In a request-reply scenario, the reply Message's payload will be extracted prior
9496
* to generating a response by default.
9597
* To have the entire serialized Message available for the response, switch the
96-
* {@link #extractReplyPayload} value to {@code false}.
98+
* {@code extractReplyPayload} value to {@code false}.
9799
*
98100
* @author Mark Fisher
99101
* @author Oleg Zhurakousky
@@ -146,27 +148,21 @@ public HttpRequestHandlingEndpointSupport(boolean expectReply) {
146148
stringHttpMessageConverter.setWriteAcceptCharset(false);
147149
this.defaultMessageConverters.add(stringHttpMessageConverter);
148150
this.defaultMessageConverters.add(new ResourceHttpMessageConverter());
149-
SourceHttpMessageConverter<Source> sourceConverter = new SourceHttpMessageConverter<Source>();
151+
SourceHttpMessageConverter<Source> sourceConverter = new SourceHttpMessageConverter<>();
150152
this.defaultMessageConverters.add(sourceConverter);
151153
if (jaxb2Present) {
152154
this.defaultMessageConverters.add(new Jaxb2RootElementHttpMessageConverter());
153-
if (logger.isDebugEnabled()) {
154-
logger.debug("'Jaxb2RootElementHttpMessageConverter' was added to the 'defaultMessageConverters'.");
155-
}
155+
logger.debug("'Jaxb2RootElementHttpMessageConverter' was added to the 'defaultMessageConverters'.");
156156
}
157157
if (JacksonPresent.isJackson2Present()) {
158158
this.defaultMessageConverters.add(new MappingJackson2HttpMessageConverter());
159-
if (logger.isDebugEnabled()) {
160-
logger.debug("'MappingJackson2HttpMessageConverter' was added to the 'defaultMessageConverters'.");
161-
}
159+
logger.debug("'MappingJackson2HttpMessageConverter' was added to the 'defaultMessageConverters'.");
162160
}
163161
if (romeToolsPresent) {
164162
this.defaultMessageConverters.add(new AtomFeedHttpMessageConverter());
165163
this.defaultMessageConverters.add(new RssChannelHttpMessageConverter());
166-
if (logger.isDebugEnabled()) {
167-
logger.debug("'AtomFeedHttpMessageConverter' was added to the 'defaultMessageConverters'.");
168-
logger.debug("'RssChannelHttpMessageConverter' was added to the 'defaultMessageConverters'.");
169-
}
164+
logger.debug("'AtomFeedHttpMessageConverter' was added to the 'defaultMessageConverters'.");
165+
logger.debug("'RssChannelHttpMessageConverter' was added to the 'defaultMessageConverters'.");
170166
}
171167
}
172168

@@ -176,8 +172,9 @@ public HttpRequestHandlingEndpointSupport(boolean expectReply) {
176172
* @param messageConverters The message converters.
177173
*/
178174
public void setMessageConverters(List<HttpMessageConverter<?>> messageConverters) {
175+
Assert.notNull(messageConverters, "'messageConverters' must not be null");
179176
Assert.noNullElements(messageConverters.toArray(), "'messageConverters' must not contain null entries");
180-
List<HttpMessageConverter<?>> localConverters = new ArrayList<HttpMessageConverter<?>>(messageConverters);
177+
List<HttpMessageConverter<?>> localConverters = new ArrayList<>(messageConverters);
181178
if (this.mergeWithDefaultConverters) {
182179
localConverters.addAll(this.defaultMessageConverters);
183180
this.convertersMerged = true;
@@ -266,14 +263,13 @@ private Message<?> actualDoHandleRequest(HttpServletRequest servletRequest, Requ
266263

267264
this.activeCount.incrementAndGet();
268265
try {
269-
StandardEvaluationContext evaluationContext = this.createEvaluationContext();
266+
StandardEvaluationContext evaluationContext = createEvaluationContext();
270267
evaluationContext.setRootObject(httpEntity);
271268

272269
evaluationContext.setVariable("requestAttributes", RequestContextHolder.currentRequestAttributes());
273270

274-
MultiValueMap<String, String> requestParams = this.convertParameterMap(servletRequest.getParameterMap());
271+
MultiValueMap<String, String> requestParams = convertParameterMap(servletRequest.getParameterMap());
275272
evaluationContext.setVariable("requestParams", requestParams);
276-
277273
evaluationContext.setVariable("requestHeaders", new ServletServerHttpRequest(servletRequest).getHeaders());
278274

279275
Cookie[] requestCookies = servletRequest.getCookies();
@@ -335,12 +331,16 @@ private Message<?> actualDoHandleRequest(HttpServletRequest servletRequest, Requ
335331
AbstractIntegrationMessageBuilder<?> messageBuilder = null;
336332

337333
if (payload instanceof Message<?>) {
338-
messageBuilder = this.getMessageBuilderFactory().fromMessage((Message<?>) payload)
339-
.copyHeadersIfAbsent(headers);
334+
messageBuilder =
335+
getMessageBuilderFactory()
336+
.fromMessage((Message<?>) payload)
337+
.copyHeadersIfAbsent(headers);
340338
}
341339
else {
342340
Assert.state(payload != null, "payload cannot be null");
343-
messageBuilder = this.getMessageBuilderFactory().withPayload(payload).copyHeaders(headers);
341+
messageBuilder = getMessageBuilderFactory()
342+
.withPayload(payload)
343+
.copyHeaders(headers);
344344
}
345345

346346
HttpMethod method = httpEntity.getMethod();
@@ -359,30 +359,24 @@ private Message<?> actualDoHandleRequest(HttpServletRequest servletRequest, Requ
359359
Message<?> reply = null;
360360
if (this.expectReply) {
361361
try {
362-
reply = this.sendAndReceiveMessage(message);
362+
reply = sendAndReceiveMessage(message);
363363
}
364364
catch (MessageTimeoutException e) {
365-
if (getStatusCodeExpression() != null) {
366-
reply = getMessageBuilderFactory().withPayload(e.getMessage())
367-
.setHeader(org.springframework.integration.http.HttpHeaders.STATUS_CODE,
368-
evaluateHttpStatus(httpEntity))
369-
.build();
370-
}
371-
else {
372-
reply = getMessageBuilderFactory().withPayload(e.getMessage())
373-
.setHeader(org.springframework.integration.http.HttpHeaders.STATUS_CODE,
374-
HttpStatus.INTERNAL_SERVER_ERROR)
375-
.build();
376-
}
365+
reply =
366+
getMessageBuilderFactory()
367+
.withPayload(e.getMessage())
368+
.setHeader(org.springframework.integration.http.HttpHeaders.STATUS_CODE,
369+
evaluateHttpStatus(httpEntity))
370+
.build();
377371
}
378372
}
379373
else {
380-
this.send(message);
374+
send(message);
381375
}
382376
return reply;
383377
}
384378
finally {
385-
this.postProcessRequest(servletRequest);
379+
postProcessRequest(servletRequest);
386380
this.activeCount.decrementAndGet();
387381
}
388382
}
@@ -391,7 +385,8 @@ private Message<?> createServiceUnavailableResponse() {
391385
if (logger.isDebugEnabled()) {
392386
logger.debug("Endpoint is stopped; returning status " + HttpStatus.SERVICE_UNAVAILABLE);
393387
}
394-
return this.getMessageBuilderFactory().withPayload("Endpoint is stopped")
388+
return getMessageBuilderFactory()
389+
.withPayload("Endpoint is stopped")
395390
.setHeader(org.springframework.integration.http.HttpHeaders.STATUS_CODE, HttpStatus.SERVICE_UNAVAILABLE)
396391
.build();
397392
}
@@ -401,7 +396,7 @@ private Message<?> createServiceUnavailableResponse() {
401396
* sets up the {@link ServletServerHttpResponse}.
402397
* @param response The ServletServerHttpResponse.
403398
* @param replyMessage The reply message.
404-
* @return The message payload (if {@link #extractReplyPayload}) otherwise the message.
399+
* @return The message payload (if {@code extractReplyPayload}) otherwise the message.
405400
*/
406401
protected final Object setupResponseAndConvertReply(ServletServerHttpResponse response, Message<?> replyMessage) {
407402
getHeaderMapper().fromHeaders(replyMessage.getHeaders(), response.getHeaders());
@@ -461,7 +456,7 @@ private void postProcessRequest(HttpServletRequest request) {
461456
* Converts a servlet request's parameterMap to a {@link MultiValueMap}.
462457
*/
463458
private MultiValueMap<String, String> convertParameterMap(Map<String, String[]> parameterMap) {
464-
MultiValueMap<String, String> convertedMap = new LinkedMultiValueMap<String, String>(parameterMap.size());
459+
MultiValueMap<String, String> convertedMap = new LinkedMultiValueMap<>(parameterMap.size());
465460
for (Map.Entry<String, String[]> entry : parameterMap.entrySet()) {
466461
String[] values = entry.getValue();
467462
for (String value : values) {
@@ -487,29 +482,37 @@ protected Object extractRequestBody(ServletServerHttpRequest request) throws IOE
487482
contentType = MediaType.APPLICATION_OCTET_STREAM;
488483
}
489484
ResolvableType requestPayloadType = getRequestPayloadType();
490-
Class<?> expectedType;
491485
if (requestPayloadType == null) {
492-
expectedType = "text".equals(contentType.getType()) ? String.class : byte[].class;
493-
}
494-
else {
495-
expectedType = requestPayloadType.resolve();
486+
requestPayloadType =
487+
ResolvableType.forClass(
488+
"text".equals(contentType.getType())
489+
? String.class
490+
: byte[].class);
496491
}
497492

498-
/*
499-
* TODO: resolve() can return null, which is not valid for canRead().
500-
* Perhaps we should coerce to String/byte[] instead of attempting
501-
* to convert. However this might be a breaking change - 5.2?
502-
* Hence NOSONAR below.
503-
*/
493+
Type targetType = requestPayloadType.getType();
494+
Class<?> targetClass = requestPayloadType.toClass();
495+
504496
for (HttpMessageConverter<?> converter : this.messageConverters) {
505-
if (converter.canRead(expectedType, contentType)) {
506-
return converter.read((Class) expectedType, request);
497+
GenericHttpMessageConverter<?> genericConverter =
498+
converter instanceof GenericHttpMessageConverter
499+
? (GenericHttpMessageConverter<?>) converter
500+
: null;
501+
if (genericConverter != null
502+
? genericConverter.canRead(targetType, null, contentType) :
503+
(converter.canRead(targetClass, contentType))) {
504+
505+
if (genericConverter != null) {
506+
return genericConverter.read(targetType, null, request);
507+
}
508+
else {
509+
return converter.read((Class) targetClass, request);
510+
}
507511
}
508512
}
509-
throw new MessagingException(// NOSONAR might be null; see comment above.
513+
throw new MessagingException(
510514
"Could not convert request: no suitable HttpMessageConverter found for expected type ["
511-
+ expectedType != null ? expectedType.getName() : "null"
512-
+ "] and content type [" + contentType + "]");
515+
+ requestPayloadType + "] and content type [" + contentType + "]");
513516
}
514517

515518
}

spring-integration-http/src/main/java/org/springframework/integration/http/inbound/HttpRequestHandlingMessagingGateway.java

+5-4
Original file line numberDiff line numberDiff line change
@@ -44,10 +44,11 @@
4444
* (e.g. 200 OK).
4545
* <p>
4646
* The default supported request methods are GET and POST, but the list of values can be configured with the
47-
* {@link RequestMapping#methods} property. The payload generated from a GET request (or HEAD or OPTIONS if supported) will
48-
* be a {@link org.springframework.util.MultiValueMap} containing the parameter values. For a request containing a body
49-
* (e.g. a POST), the type
50-
* of the payload is determined by the {@link #setRequestPayloadTypeClass(Class)} request payload type}.
47+
* {@link RequestMapping#getMethods()} property.
48+
* The payload generated from a GET request (or HEAD or OPTIONS if supported) will
49+
* be a {@link org.springframework.util.MultiValueMap} containing the parameter values.
50+
* For a request containing a body (e.g. a POST), the type of the payload is determined
51+
* by the {@link #setRequestPayloadTypeClass(Class)} request payload type}.
5152
* <p>
5253
* If the HTTP request is a multipart and a "multipartResolver" bean has been defined in the context, then it will be
5354
* converted by the

0 commit comments

Comments
 (0)