Upgrade to the SF and SA BUILD-SNAPSHOTs

* Upgrade to Jackson-2.9.0 to align with SF
* Fix `ReactiveHttpRequestExecutingMessageHandler` to use latest `WebClient`
* Introduce `Supplier<URI> uriSupplier` to evaluate the `realUri` on demand.
In case of `WebClient` use `.uri(Function)` to perform `uriSupplier`
* Move `doNext()` for the `WebClientException` out of `one-way` block.
According the `RestTemplate` logic the erroneous response is handled
before returning from the `exchange()`.
Therefore both request/reply and one-way scenarios must have the same
error handling logic
* Some refactoring to the `AbstractHttpRequestExecutingMessageHandler`
and `AbstractHttpRequestExecutingMessageHandler` according a new `uriSupplier`
This commit is contained in:
Artem Bilan
2017-04-07 10:16:37 -04:00
committed by Gary Russell
parent f751398cfe
commit 9e175054d0
5 changed files with 98 additions and 117 deletions

View File

@@ -106,7 +106,7 @@ subprojects { subproject ->
hibernateVersion = '5.2.5.Final'
hsqldbVersion = '2.3.3'
h2Version = '1.4.180'
jackson2Version = '2.8.5'
jackson2Version = '2.9.0.pr2'
javaxActivationVersion = '1.1.1'
javaxMailVersion = '1.5.6'
jedisVersion = '2.9.0'
@@ -130,15 +130,15 @@ subprojects { subproject ->
servletApiVersion = '3.1.0'
slf4jVersion = "1.7.21"
smackVersion = '4.1.7'
springAmqpVersion = project.hasProperty('springAmqpVersion') ? project.springAmqpVersion : '2.0.0.M3'
springDataJpaVersion = '2.0.0.M2'
springDataMongoVersion = '2.0.0.M2'
springDataRedisVersion = '2.0.0.M2'
springGemfireVersion = '2.0.0.M2'
springSecurityVersion = '4.2.2.RELEASE'
springAmqpVersion = project.hasProperty('springAmqpVersion') ? project.springAmqpVersion : '2.0.0.BUILD-SNAPSHOT'
springDataJpaVersion = '2.0.0.BUILD-SNAPSHOT'
springDataMongoVersion = '2.0.0.BUILD-SNAPSHOT'
springDataRedisVersion = '2.0.0.BUILD-SNAPSHOT'
springGemfireVersion = '2.0.0.BUILD-SNAPSHOT'
springSecurityVersion = '5.0.0.BUILD-SNAPSHOT'
springSocialTwitterVersion = '2.0.0.M1'
springRetryVersion = '1.2.0.RELEASE'
springVersion = project.hasProperty('springVersion') ? project.springVersion : '5.0.0.M5'
springVersion = project.hasProperty('springVersion') ? project.springVersion : '5.0.0.BUILD-SNAPSHOT'
springWsVersion = '2.4.0.RELEASE'
tomcatVersion = "8.5.9"
xmlUnitVersion = '1.6'

View File

@@ -17,6 +17,7 @@
package org.springframework.integration.http.outbound;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Arrays;
@@ -25,6 +26,7 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.function.Supplier;
import javax.xml.transform.Source;
@@ -46,7 +48,6 @@ import org.springframework.integration.support.AbstractIntegrationMessageBuilder
import org.springframework.integration.support.MessageBuilderFactory;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageHandlingException;
import org.springframework.messaging.MessagingException;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.CollectionUtils;
@@ -253,46 +254,44 @@ public abstract class AbstractHttpRequestExecutingMessageHandler extends Abstrac
@Override
protected Object handleRequestMessage(Message<?> requestMessage) {
HttpMethod httpMethod = determineHttpMethod(requestMessage);
if (!shouldIncludeRequestBody(httpMethod) && this.extractPayloadExplicitlySet) {
if (logger.isWarnEnabled()) {
logger.warn("The 'extractPayload' attribute has no relevance for the current request " +
"since the HTTP Method is '" + httpMethod +
"', and no request body will be sent for that method.");
}
}
Object expectedResponseType = determineExpectedResponseType(requestMessage);
HttpEntity<?> httpRequest = generateHttpRequest(requestMessage, httpMethod);
return exchange(() -> generateUri(requestMessage), httpMethod, httpRequest, expectedResponseType,
requestMessage);
}
protected abstract Object exchange(Supplier<URI> uriSupplier, HttpMethod httpMethod, HttpEntity<?> httpRequest,
Object expectedResponseType, Message<?> requestMessage);
private URI generateUri(Message<?> requestMessage) {
Object uri = this.uriExpression.getValue(this.evaluationContext, requestMessage);
Assert.state(uri instanceof String || uri instanceof URI,
"'uriExpression' evaluation must result in a 'String' or 'URI' instance, not: "
+ (uri == null ? "null" : uri.getClass()));
URI realUri = null;
Map<String, ?> uriVariables = determineUriVariables(requestMessage);
UriComponentsBuilder uriComponentsBuilder = uri instanceof String
? UriComponentsBuilder.fromUriString((String) uri)
: UriComponentsBuilder.fromUri((URI) uri);
UriComponents uriComponents = uriComponentsBuilder.buildAndExpand(uriVariables);
try {
HttpMethod httpMethod = this.determineHttpMethod(requestMessage);
if (!shouldIncludeRequestBody(httpMethod) && this.extractPayloadExplicitlySet) {
if (logger.isWarnEnabled()) {
logger.warn("The 'extractPayload' attribute has no relevance for the current request " +
"since the HTTP Method is '" + httpMethod +
"', and no request body will be sent for that method.");
}
}
Object expectedResponseType = determineExpectedResponseType(requestMessage);
HttpEntity<?> httpRequest = generateHttpRequest(requestMessage, httpMethod);
Map<String, ?> uriVariables = this.determineUriVariables(requestMessage);
UriComponentsBuilder uriComponentsBuilder = uri instanceof String
? UriComponentsBuilder.fromUriString((String) uri)
: UriComponentsBuilder.fromUri((URI) uri);
UriComponents uriComponents = uriComponentsBuilder.buildAndExpand(uriVariables);
realUri = this.encodeUri ? uriComponents.toUri() : new URI(uriComponents.toUriString());
return exchange(realUri, httpMethod, httpRequest, expectedResponseType, requestMessage);
return this.encodeUri ? uriComponents.toUri() : new URI(uriComponents.toUriString());
}
catch (MessagingException e) {
throw e;
}
catch (Exception e) {
throw new MessageHandlingException(requestMessage, "HTTP request execution failed for URI ["
+ (realUri == null ? uri : realUri) + "]", e);
catch (URISyntaxException e) {
throw new MessageHandlingException(requestMessage, "Invalid URI [" + uri + "]", e);
}
}
protected abstract Object exchange(URI realUri, HttpMethod httpMethod, HttpEntity<?> httpRequest,
Object expectedResponseType, Message<?> requestMessage);
protected Object getReply(ResponseEntity<?> httpResponse) {
if (this.expectReply) {
HttpHeaders httpHeaders = httpResponse.getHeaders();
@@ -341,7 +340,7 @@ public abstract class AbstractHttpRequestExecutingMessageHandler extends Abstrac
}
}
private HttpEntity<?> generateHttpRequest(Message<?> message, HttpMethod httpMethod) throws Exception {
private HttpEntity<?> generateHttpRequest(Message<?> message, HttpMethod httpMethod) {
Assert.notNull(message, "message must not be null");
return (this.extractPayload) ? this.createHttpEntityFromPayload(message, httpMethod)
: this.createHttpEntityFromMessage(message, httpMethod);
@@ -355,7 +354,7 @@ public abstract class AbstractHttpRequestExecutingMessageHandler extends Abstrac
}
HttpHeaders httpHeaders = this.mapHeaders(message);
if (!shouldIncludeRequestBody(httpMethod)) {
return new HttpEntity<Object>(httpHeaders);
return new HttpEntity<>(httpHeaders);
}
// otherwise, we are creating a request with a body and need to deal with the content-type header as well
if (httpHeaders.getContentType() == null) {
@@ -370,7 +369,7 @@ public abstract class AbstractHttpRequestExecutingMessageHandler extends Abstrac
payload = this.convertToMultiValueMap((Map<?, ?>) payload);
}
}
return new HttpEntity<Object>(payload, httpHeaders);
return new HttpEntity<>(payload, httpHeaders);
}
private HttpEntity<?> createHttpEntityFromMessage(Message<?> message, HttpMethod httpMethod) {
@@ -379,7 +378,7 @@ public abstract class AbstractHttpRequestExecutingMessageHandler extends Abstrac
httpHeaders.setContentType(new MediaType("application", "x-java-serialized-object"));
return new HttpEntity<Object>(message, httpHeaders);
}
return new HttpEntity<Object>(httpHeaders);
return new HttpEntity<>(httpHeaders);
}
protected HttpHeaders mapHeaders(Message<?> message) {
@@ -471,7 +470,7 @@ public abstract class AbstractHttpRequestExecutingMessageHandler extends Abstrac
* If all keys and values are Strings, we'll consider the Map to be form data.
*/
private boolean isFormData(Map<Object, ?> map) {
for (Object key : map.keySet()) {
for (Object key : map.keySet()) {
if (!(key instanceof String)) {
return false;
}
@@ -498,7 +497,7 @@ public abstract class AbstractHttpRequestExecutingMessageHandler extends Abstrac
}
}
private Object determineExpectedResponseType(Message<?> requestMessage) throws Exception {
private Object determineExpectedResponseType(Message<?> requestMessage) {
Object expectedResponseType = null;
if (this.expectedResponseTypeExpression != null) {
expectedResponseType = this.expectedResponseTypeExpression.getValue(this.evaluationContext, requestMessage);
@@ -507,11 +506,17 @@ public abstract class AbstractHttpRequestExecutingMessageHandler extends Abstrac
Assert.state(expectedResponseType instanceof Class<?>
|| expectedResponseType instanceof String
|| expectedResponseType instanceof ParameterizedTypeReference,
"'expectedResponseType' can be an instance of 'Class<?>', 'String' or 'ParameterizedTypeReference<?>'; "
+ "evaluation resulted in a" + expectedResponseType.getClass() + ".");
"'expectedResponseType' can be an instance of 'Class<?>', 'String' " +
"or 'ParameterizedTypeReference<?>'; " +
"evaluation resulted in a" + expectedResponseType.getClass() + ".");
if (expectedResponseType instanceof String && StringUtils.hasText((String) expectedResponseType)) {
expectedResponseType = ClassUtils.forName((String) expectedResponseType,
getApplicationContext().getClassLoader());
try {
expectedResponseType = ClassUtils.forName((String) expectedResponseType,
getApplicationContext().getClassLoader());
}
catch (ClassNotFoundException e) {
throw new IllegalStateException("Cannot load class for name: " + expectedResponseType, e);
}
}
}
return expectedResponseType;
@@ -532,9 +537,9 @@ public abstract class AbstractHttpRequestExecutingMessageHandler extends Abstrac
}
return ExpressionEvalMap.from(expressions)
.usingEvaluationContext(this.evaluationContext)
.withRoot(requestMessage)
.build();
.usingEvaluationContext(this.evaluationContext)
.withRoot(requestMessage)
.build();
}

View File

@@ -18,6 +18,7 @@ package org.springframework.integration.http.outbound;
import java.net.URI;
import java.util.List;
import java.util.function.Supplier;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.core.ParameterizedTypeReference;
@@ -33,8 +34,10 @@ import org.springframework.integration.expression.ValueExpression;
import org.springframework.integration.mapping.HeaderMapper;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageHandler;
import org.springframework.messaging.MessageHandlingException;
import org.springframework.util.Assert;
import org.springframework.web.client.ResponseErrorHandler;
import org.springframework.web.client.RestClientException;
import org.springframework.web.client.RestTemplate;
/**
@@ -149,16 +152,24 @@ public class HttpRequestExecutingMessageHandler extends AbstractHttpRequestExecu
}
@Override
protected Object exchange(URI uri, HttpMethod httpMethod, HttpEntity<?> httpRequest, Object expectedResponseType,
Message<?> requestMessage) {
protected Object exchange(Supplier<URI> uriSupplier, HttpMethod httpMethod, HttpEntity<?> httpRequest,
Object expectedResponseType, Message<?> requestMessage) {
URI uri = uriSupplier.get();
ResponseEntity<?> httpResponse;
if (expectedResponseType instanceof ParameterizedTypeReference<?>) {
httpResponse = this.restTemplate.exchange(uri, httpMethod, httpRequest,
(ParameterizedTypeReference<?>) expectedResponseType);
try {
if (expectedResponseType instanceof ParameterizedTypeReference<?>) {
httpResponse = this.restTemplate.exchange(uri, httpMethod, httpRequest,
(ParameterizedTypeReference<?>) expectedResponseType);
}
else {
httpResponse = this.restTemplate.exchange(uri, httpMethod, httpRequest,
(Class<?>) expectedResponseType);
}
return getReply(httpResponse);
}
else {
httpResponse = this.restTemplate.exchange(uri, httpMethod, httpRequest, (Class<?>) expectedResponseType);
catch (RestClientException e) {
throw new MessageHandlingException(requestMessage,
"HTTP request execution failed for URI [" + uri + "]", e);
}
return getReply(httpResponse);
}
}

View File

@@ -17,6 +17,7 @@
package org.springframework.integration.http.outbound;
import java.net.URI;
import java.util.function.Supplier;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.core.ParameterizedTypeReference;
@@ -32,7 +33,6 @@ import org.springframework.messaging.Message;
import org.springframework.messaging.MessageHandler;
import org.springframework.util.Assert;
import org.springframework.web.reactive.function.BodyExtractors;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.client.ClientResponse;
import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.web.reactive.function.client.WebClientException;
@@ -111,53 +111,29 @@ public class ReactiveHttpRequestExecutingMessageHandler extends AbstractHttpRequ
}
@Override
protected Object exchange(URI uri, HttpMethod httpMethod, HttpEntity<?> httpRequest, Object expectedResponseType,
Message<?> requestMessage) {
WebClient.UriSpec uriSpec;
protected Object exchange(Supplier<URI> uriSupplier, HttpMethod httpMethod, HttpEntity<?> httpRequest,
Object expectedResponseType, Message<?> requestMessage) {
// TODO use WebClient.method(HttpMethod) in the future version
WebClient.RequestBodySpec requestSpec =
this.webClient.method(httpMethod)
.uri(b -> uriSupplier.get())
.headers(httpRequest.getHeaders());
switch (httpMethod) {
case GET:
uriSpec = this.webClient.get();
break;
case HEAD:
uriSpec = this.webClient.head();
break;
case POST:
uriSpec = this.webClient.post();
break;
case PUT:
uriSpec = this.webClient.put();
break;
case PATCH:
uriSpec = this.webClient.patch();
break;
case DELETE:
uriSpec = this.webClient.delete();
break;
case OPTIONS:
uriSpec = this.webClient.options();
break;
case TRACE:
throw new UnsupportedOperationException("WebClient doesn't support the TRACE HTTP method");
default:
throw new UnsupportedOperationException("Unsupported HTTP method");
}
WebClient.HeaderSpec spec = uriSpec.uri(uri)
.headers(httpRequest.getHeaders());
Mono<ClientResponse> responseMono;
if (httpRequest.hasBody()) {
responseMono = spec.exchange(BodyInserters.fromObject(httpRequest.getBody()));
}
else {
responseMono = spec.exchange();
requestSpec.body(httpRequest.getBody());
}
Mono<ClientResponse> responseMono = requestSpec.exchange()
.doOnNext(response -> {
HttpStatus httpStatus = response.statusCode();
if (httpStatus.is4xxClientError() || httpStatus.is5xxServerError()) {
throw new WebClientException(
"ClientResponse has erroneous status code: " + httpStatus.value() +
" " + httpStatus.getReasonPhrase());
}
});
if (isExpectReply()) {
ResolvableType responseType;
if (expectedResponseType instanceof ParameterizedTypeReference<?>) {
@@ -180,16 +156,7 @@ public class ReactiveHttpRequestExecutingMessageHandler extends AbstractHttpRequ
.map(this::getReply);
}
else {
responseMono
.doOnNext(response -> {
HttpStatus httpStatus = response.statusCode();
if (httpStatus.is4xxClientError() || httpStatus.is5xxServerError()) {
throw new WebClientException(
"ClientResponse has erroneous status code: " + httpStatus.value() +
" " + httpStatus.getReasonPhrase());
}
})
.subscribe(v -> { }, ex -> sendErrorMessage(requestMessage, ex));
responseMono.subscribe(v -> { }, ex -> sendErrorMessage(requestMessage, ex));
return null;
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2015 the original author or authors.
* Copyright 2002-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,9 +16,7 @@
package org.springframework.integration.http.outbound;
import static org.hamcrest.Matchers.containsString;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.fail;
import static org.mockito.Mockito.mock;
@@ -52,6 +50,7 @@ import org.springframework.messaging.support.GenericMessage;
* @author Wallace Wadge
* @author Gary Russell
* @author Artem Bilan
*
* @since 2.0
*/
public class UriVariableExpressionTests {
@@ -78,7 +77,6 @@ public class UriVariableExpressionTests {
}
catch (Exception e) {
assertEquals("intentional", e.getCause().getMessage());
assertThat(e.getMessage(), containsString("http://test/bar"));
}
assertEquals("http://test/bar", uriHolder.get().toString());
}