Skip to content

GH-3154: Support UriBuilderFactory.EncodingMode #3162

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Jan 30, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2020 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.
Expand All @@ -25,6 +25,7 @@
import org.springframework.integration.config.xml.AbstractOutboundChannelAdapterParser;
import org.springframework.integration.config.xml.IntegrationNamespaceUtils;
import org.springframework.integration.http.outbound.HttpRequestExecutingMessageHandler;
import org.springframework.integration.http.support.DefaultHttpHeaderMapper;
import org.springframework.util.StringUtils;

/**
Expand All @@ -46,7 +47,6 @@ protected AbstractBeanDefinition parseConsumer(Element element, ParserContext pa

builder.addPropertyValue("expectReply", false);
HttpAdapterParsingUtils.configureUrlConstructorArg(element, parserContext, builder);
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "encode-uri");
HttpAdapterParsingUtils.setHttpMethodOrExpression(element, parserContext, builder);

String headerMapper = element.getAttribute("header-mapper");
Expand All @@ -60,8 +60,8 @@ protected AbstractBeanDefinition parseConsumer(Element element, ParserContext pa
builder.addPropertyReference("headerMapper", headerMapper);
}
else if (StringUtils.hasText(mappedRequestHeaders)) {
BeanDefinitionBuilder headerMapperBuilder = BeanDefinitionBuilder.genericBeanDefinition(
"org.springframework.integration.http.support.DefaultHttpHeaderMapper");
BeanDefinitionBuilder headerMapperBuilder =
BeanDefinitionBuilder.genericBeanDefinition(DefaultHttpHeaderMapper.class);
IntegrationNamespaceUtils.setValueIfAttributeDefined(headerMapperBuilder, element,
"mapped-request-headers", "outboundHeaderNames");
builder.addPropertyValue("headerMapper", headerMapperBuilder.getBeanDefinition());
Expand Down Expand Up @@ -90,6 +90,8 @@ protected BeanDefinitionBuilder getBuilder(Element element, ParserContext parser
for (String referenceAttributeName : HttpAdapterParsingUtils.SYNC_REST_TEMPLATE_REFERENCE_ATTRIBUTES) {
IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, referenceAttributeName);
}
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "encode-uri");
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "encoding-mode");
}
return builder;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2020 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.
Expand Down Expand Up @@ -47,7 +47,6 @@ protected BeanDefinitionBuilder parseHandler(Element element, ParserContext pars
BeanDefinitionBuilder builder = getBuilder(element, parserContext);

HttpAdapterParsingUtils.configureUrlConstructorArg(element, parserContext, builder);
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "encode-uri");
HttpAdapterParsingUtils.setHttpMethodOrExpression(element, parserContext, builder);

String headerMapper = element.getAttribute("header-mapper");
Expand Down Expand Up @@ -103,6 +102,8 @@ protected BeanDefinitionBuilder getBuilder(Element element, ParserContext parser
for (String referenceAttributeName : HttpAdapterParsingUtils.SYNC_REST_TEMPLATE_REFERENCE_ATTRIBUTES) {
IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, referenceAttributeName);
}
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "encode-uri");
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "encoding-mode");
}
return builder;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2017-2019 the original author or authors.
* Copyright 2017-2020 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.
Expand Down Expand Up @@ -34,6 +34,7 @@
import org.springframework.integration.mapping.HeaderMapper;
import org.springframework.messaging.Message;
import org.springframework.util.Assert;
import org.springframework.web.util.DefaultUriBuilderFactory;

/**
* The base {@link MessageHandlerSpec} for {@link AbstractHttpRequestExecutingMessageHandler}s.
Expand Down Expand Up @@ -71,12 +72,25 @@ protected S expectReply(boolean expectReply) {
* expanding and before send request via underlying implementation. The default value is <code>true</code>.
* @param encodeUri true if the URI should be encoded.
* @return the spec
* @deprecated since 5.3 in favor of {@link #encodingMode}
*/
@Deprecated
public S encodeUri(boolean encodeUri) {
this.target.setEncodeUri(encodeUri);
return _this();
}

/**
* Specify a {@link DefaultUriBuilderFactory.EncodingMode} for uri construction.
* @param encodingMode to use for uri construction.
* @return the spec
* @since 5.3
*/
public S encodingMode(DefaultUriBuilderFactory.EncodingMode encodingMode) {
this.target.setEncodingMode(encodingMode);
return _this();
}

/**
* Specify the SpEL {@link Expression} to determine {@link HttpMethod} at runtime.
* @param httpMethodExpression The method expression.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2017-2019 the original author or authors.
* Copyright 2017-2020 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.
Expand All @@ -17,7 +17,6 @@
package org.springframework.integration.http.outbound;

import java.net.URI;
import java.net.URISyntaxException;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
Expand All @@ -27,7 +26,6 @@
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.function.Supplier;

import javax.xml.transform.Source;

Expand Down Expand Up @@ -56,14 +54,13 @@
import org.springframework.integration.support.MessageBuilderFactory;
import org.springframework.lang.Nullable;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageHandlingException;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.CollectionUtils;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.util.StringUtils;
import org.springframework.web.util.UriComponents;
import org.springframework.web.util.DefaultUriBuilderFactory;
import org.springframework.web.util.UriComponentsBuilder;

/**
Expand All @@ -85,6 +82,8 @@ public abstract class AbstractHttpRequestExecutingMessageHandler extends Abstrac
private static final List<HttpMethod> NO_BODY_HTTP_METHODS =
Arrays.asList(HttpMethod.GET, HttpMethod.HEAD, HttpMethod.TRACE);

protected final DefaultUriBuilderFactory uriFactory = new DefaultUriBuilderFactory(); // NOSONAR - final

private final Map<String, Expression> uriVariableExpressions = new HashMap<>();

private final Expression uriExpression;
Expand All @@ -95,8 +94,6 @@ public abstract class AbstractHttpRequestExecutingMessageHandler extends Abstrac

private boolean trustedSpel;

private boolean encodeUri = true;

private Expression httpMethodExpression = new ValueExpression<>(HttpMethod.POST);

private boolean expectReply = true;
Expand Down Expand Up @@ -127,9 +124,27 @@ public AbstractHttpRequestExecutingMessageHandler(Expression uriExpression) {
* <code>true</code>.
* @param encodeUri true if the URI should be encoded.
* @see UriComponentsBuilder
* @deprecated since 5.3 in favor of {@link #setEncodingMode}
*/
@Deprecated
public void setEncodeUri(boolean encodeUri) {
this.encodeUri = encodeUri;
setEncodingMode(
encodeUri
? DefaultUriBuilderFactory.EncodingMode.TEMPLATE_AND_VALUES
: DefaultUriBuilderFactory.EncodingMode.NONE);
}

/**
* Set the encoding mode to use.
* By default this is set to {@link DefaultUriBuilderFactory.EncodingMode#TEMPLATE_AND_VALUES}.
* For more complicated scenarios consider to configure an {@link org.springframework.web.util.UriTemplateHandler}
* on an externally provided {@link org.springframework.web.client.RestTemplate}.
* @param encodingMode the mode to use for uri encoding
* @since 5.3
*/
public void setEncodingMode(DefaultUriBuilderFactory.EncodingMode encodingMode) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not setUriBuilderFactory ?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right. I'm going to document that for more complex scenarios it would be better to configure an external RestTemplate and already use its setUriTemplateHandler().

I would even go with all the RestTempalte options as an external reference. I'm not a fun of mutating internal objects.
But that would be too drastic even in the point release...

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, makes sense; proceed with the remaining work 😄

Assert.notNull(encodingMode, "'encodingMode' must not be null");
this.uriFactory.setEncodingMode(encodingMode);
}

/**
Expand Down Expand Up @@ -291,32 +306,23 @@ protected Object handleRequestMessage(Message<?> requestMessage) {
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()));
Map<String, ?> uriVariables = determineUriVariables(requestMessage);
UriComponentsBuilder uriComponentsBuilder =
uri instanceof String
? UriComponentsBuilder.fromUriString((String) uri)
: UriComponentsBuilder.fromUri((URI) uri);
UriComponents uriComponents = uriComponentsBuilder.buildAndExpand(uriVariables);
try {
return this.encodeUri ? uriComponents.encode().toUri() : new URI(uriComponents.toUriString());
}
catch (URISyntaxException e) {
throw new MessageHandlingException(requestMessage, "Invalid URI [" + uri + "] in the [" + this + ']', e);

Map<String, ?> uriVariables = null;

if (uri instanceof String) {
uriVariables = determineUriVariables(requestMessage);
}

return exchange(uri, httpMethod, httpRequest, expectedResponseType, requestMessage, uriVariables);
}

protected abstract Object exchange(Object uri, HttpMethod httpMethod, HttpEntity<?> httpRequest,
Object expectedResponseType, Message<?> requestMessage, Map<String, ?> uriVariables);

protected Object getReply(ResponseEntity<?> httpResponse) {
if (this.expectReply) {
HttpHeaders httpHeaders = httpResponse.getHeaders();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2020 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.
Expand All @@ -18,7 +18,7 @@

import java.net.URI;
import java.util.List;
import java.util.function.Supplier;
import java.util.Map;

import org.springframework.core.ParameterizedTypeReference;
import org.springframework.expression.Expression;
Expand All @@ -29,12 +29,14 @@
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.integration.expression.ValueExpression;
import org.springframework.lang.Nullable;
import org.springframework.messaging.Message;
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;
import org.springframework.web.util.DefaultUriBuilderFactory;

/**
* A {@link org.springframework.messaging.MessageHandler}
Expand Down Expand Up @@ -64,6 +66,8 @@ public class HttpRequestExecutingMessageHandler extends AbstractHttpRequestExecu

private final RestTemplate restTemplate;

private final boolean restTemplateExplicitlySet;

/**
* Create a handler that will send requests to the provided URI.
* @param uri The URI.
Expand Down Expand Up @@ -109,14 +113,24 @@ public HttpRequestExecutingMessageHandler(String uri, RestTemplate restTemplate)
* {@link org.springframework.beans.factory.BeanFactory}.
* @param restTemplate The rest template.
*/
public HttpRequestExecutingMessageHandler(Expression uriExpression, RestTemplate restTemplate) {
public HttpRequestExecutingMessageHandler(Expression uriExpression, @Nullable RestTemplate restTemplate) {
super(uriExpression);
this.restTemplate = (restTemplate == null ? new RestTemplate() : restTemplate);
this.restTemplateExplicitlySet = restTemplate != null;
this.restTemplate = (this.restTemplateExplicitlySet ? restTemplate : new RestTemplate());
if (!this.restTemplateExplicitlySet) {
this.restTemplate.setUriTemplateHandler(this.uriFactory);
}
}

@Override
public String getComponentType() {
return (this.isExpectReply() ? "http:outbound-gateway" : "http:outbound-channel-adapter");
return (isExpectReply() ? "http:outbound-gateway" : "http:outbound-channel-adapter");
}

private void assertLocalRestTemplate(String option) {
Assert.isTrue(!this.restTemplateExplicitlySet,
() -> "The option '" + option + "' must be provided on the externally configured RestTemplate: "
+ this.restTemplate);
}

/**
Expand All @@ -125,6 +139,7 @@ public String getComponentType() {
* @see RestTemplate#setErrorHandler(ResponseErrorHandler)
*/
public void setErrorHandler(ResponseErrorHandler errorHandler) {
assertLocalRestTemplate("errorHandler");
this.restTemplate.setErrorHandler(errorHandler);
}

Expand All @@ -135,6 +150,7 @@ public void setErrorHandler(ResponseErrorHandler errorHandler) {
* @see RestTemplate#setMessageConverters(java.util.List)
*/
public void setMessageConverters(List<HttpMessageConverter<?>> messageConverters) {
assertLocalRestTemplate("messageConverters");
this.restTemplate.setMessageConverters(messageConverters);
}

Expand All @@ -144,24 +160,43 @@ public void setMessageConverters(List<HttpMessageConverter<?>> messageConverters
* @see RestTemplate#setRequestFactory(ClientHttpRequestFactory)
*/
public void setRequestFactory(ClientHttpRequestFactory requestFactory) {
assertLocalRestTemplate("requestFactory");
this.restTemplate.setRequestFactory(requestFactory);
}

@Override
protected Object exchange(Supplier<URI> uriSupplier, HttpMethod httpMethod, HttpEntity<?> httpRequest,
Object expectedResponseType, Message<?> requestMessage) {
public void setEncodingMode(DefaultUriBuilderFactory.EncodingMode encodingMode) {
assertLocalRestTemplate("encodingMode on UriTemplateHandler");
super.setEncodingMode(encodingMode);
}

@Override
protected Object exchange(Object uri, HttpMethod httpMethod, HttpEntity<?> httpRequest,
Object expectedResponseType, Message<?> requestMessage, Map<String, ?> uriVariables) {

URI uri = uriSupplier.get();
ResponseEntity<?> httpResponse;
try {
if (expectedResponseType instanceof ParameterizedTypeReference<?>) {
httpResponse = this.restTemplate.exchange(uri, httpMethod, httpRequest,
(ParameterizedTypeReference<?>) expectedResponseType);
if (uri instanceof URI) {
if (expectedResponseType instanceof ParameterizedTypeReference<?>) {
httpResponse = this.restTemplate.exchange((URI) uri, httpMethod, httpRequest,
(ParameterizedTypeReference<?>) expectedResponseType);
}
else {
httpResponse = this.restTemplate.exchange((URI) uri, httpMethod, httpRequest,
(Class<?>) expectedResponseType);
}
}
else {
httpResponse = this.restTemplate.exchange(uri, httpMethod, httpRequest,
(Class<?>) expectedResponseType);
if (expectedResponseType instanceof ParameterizedTypeReference<?>) {
httpResponse = this.restTemplate.exchange((String) uri, httpMethod, httpRequest,
(ParameterizedTypeReference<?>) expectedResponseType, uriVariables);
}
else {
httpResponse = this.restTemplate.exchange((String) uri, httpMethod, httpRequest,
(Class<?>) expectedResponseType, uriVariables);
}
}

return getReply(httpResponse);
}
catch (RestClientException e) {
Expand Down
Loading