INT-3055: HTTP Outbound uri-variables-expression

JIRA: https://jira.springsource.org/browse/INT-3055

* Add to HTTP Outbound Endpoint `uri-variables-expression`
* Add mutually exclusive check for `uri-variables-expression` with `<uri-variable>`
* Tests and Docs

INT-3055: Fixes

* `ExpressionEvalMap` now can apply `Map<String, Object>`,
but the value must have a `String` or `Expression` type.

Polishing

INT-3055 Doc Polishing
This commit is contained in:
Artem Bilan
2013-10-26 15:35:52 +03:00
committed by Gary Russell
parent 8ab65c6dd3
commit a7cabc53b0
13 changed files with 384 additions and 351 deletions

View File

@@ -23,10 +23,13 @@ import java.util.Set;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.common.LiteralExpression;
import org.springframework.util.Assert;
/**
* <p>
* An immutable {@link AbstractMap} implementation that wraps a Map<String, Expression>
* An immutable {@link AbstractMap} implementation that wraps a {@code Map<String, Object>},
* where values must be instances of {@link String} or {@link Expression},
* and evaluates an {@code expression} for the provided {@code key} from the underlying
* {@code original} Map.
* </p>
@@ -38,16 +41,18 @@ import org.springframework.expression.Expression;
* <p>
* A {@link ExpressionEvalMapBuilder} must be used to instantiate this class
* via its {@link #from(Map)} method:
* <pre class="code">
* {@code
* ExpressionEvalMap evalMap = ExpressionEvalMap
* .from(expressions)
.usingCallback(new EvaluationCallback() {
Object evaluate(Expression expression) {
// return some expression evaluation
}
})
.build();
* }
*ExpressionEvalMap evalMap = ExpressionEvalMap
* .from(expressions)
* .usingCallback(new EvaluationCallback() {
* Object evaluate(Expression expression) {
* // return some expression evaluation
* }
* })
* .build();
*}
* </pre>
* </p>
* <p>
* Thread-safety depends on the original underlying Map.
@@ -68,11 +73,11 @@ public final class ExpressionEvalMap extends AbstractMap<String, Object> {
};
private final Map<String, Expression> original;
private final Map<String, ?> original;
private final EvaluationCallback evaluationCallback;
private ExpressionEvalMap(Map<String, Expression> original, EvaluationCallback evaluationCallback) {
private ExpressionEvalMap(Map<String, ?> original, EvaluationCallback evaluationCallback) {
this.original = original;
this.evaluationCallback = evaluationCallback;
}
@@ -83,8 +88,20 @@ public final class ExpressionEvalMap extends AbstractMap<String, Object> {
*/
@Override
public Object get(Object key) {
Expression expression = original.get(key);
if (expression != null) {
Object value = original.get(key);
if (value != null) {
Expression expression;
if (value instanceof Expression) {
expression = (Expression) value;
}
else if (value instanceof String) {
expression = new LiteralExpression((String) value);
}
else {
throw new IllegalArgumentException("Values must be "
+ "'java.lang.String' or 'org.springframework.expression.Expression'; the value type for key "
+ key + " is : " + value.getClass());
}
return this.evaluationCallback.evaluate(expression);
}
return null;
@@ -136,7 +153,7 @@ public final class ExpressionEvalMap extends AbstractMap<String, Object> {
}
@Override
public void putAll(Map<? extends String, ? extends Object> m) {
public void putAll(Map<? extends String, ?> m) {
throw new UnsupportedOperationException();
}
@@ -155,7 +172,13 @@ public final class ExpressionEvalMap extends AbstractMap<String, Object> {
throw new UnsupportedOperationException();
}
public static ExpressionEvalMapBuilder from(Map<String, Expression> expressions) {
@Override
public String toString() {
return this.original.toString();
}
public static ExpressionEvalMapBuilder from(Map<String, ?> expressions) {
Assert.notNull(expressions, "'expressions' must not be null.");
return new ExpressionEvalMapBuilder(expressions);
}
@@ -205,7 +228,7 @@ public final class ExpressionEvalMap extends AbstractMap<String, Object> {
*/
public static final class ExpressionEvalMapBuilder {
private final Map<String, Expression> expressions;
private final Map<String, ?> expressions;
private EvaluationCallback evaluationCallback;
@@ -219,7 +242,7 @@ public final class ExpressionEvalMap extends AbstractMap<String, Object> {
private final ExpressionEvalMapFinalBuilder finalBuilder = new ExpressionEvalMapFinalBuilderImpl();
private ExpressionEvalMapBuilder(Map<String, Expression> expressions) {
private ExpressionEvalMapBuilder(Map<String, ?> expressions) {
this.expressions = expressions;
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2012 the original author or authors.
* Copyright 2002-2013 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.
@@ -34,6 +34,7 @@ import org.springframework.util.xml.DomUtils;
* @author Oleg Zhurakousky
* @author Mark Fisher
* @author Gary Russell
* @author Artem Bilan
* @since 2.0.2
*/
abstract class HttpAdapterParsingUtils {
@@ -52,9 +53,23 @@ abstract class HttpAdapterParsingUtils {
}
}
static void configureUriVariableExpressions(BeanDefinitionBuilder builder, Element element) {
static void configureUriVariableExpressions(BeanDefinitionBuilder builder, ParserContext parserContext, Element element) {
String uriVariablesExpression = element.getAttribute("uri-variables-expression");
List<Element> uriVariableElements = DomUtils.getChildElementsByTagName(element, "uri-variable");
if (!CollectionUtils.isEmpty(uriVariableElements)) {
boolean hasUriVariableExpressions = !CollectionUtils.isEmpty(uriVariableElements);
if (StringUtils.hasText(uriVariablesExpression)) {
if (hasUriVariableExpressions) {
parserContext.getReaderContext().error("'uri-variables-expression' attribute " +
"and 'uri-variable' sub-elements are mutually exclusive.", element);
}
BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(ExpressionFactoryBean.class)
.addConstructorArgValue(uriVariablesExpression);
builder.addPropertyValue("uriVariablesExpression", beanDefinitionBuilder.getBeanDefinition());
}
if (hasUriVariableExpressions) {
ManagedMap<String, Object> uriVariableExpressions = new ManagedMap<String, Object>();
for (Element uriVariableElement : uriVariableElements) {
String name = uriVariableElement.getAttribute("name");

View File

@@ -77,7 +77,7 @@ public class HttpOutboundChannelAdapterParser extends AbstractOutboundChannelAda
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "charset");
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "extract-payload");
HttpAdapterParsingUtils.setExpectedResponseOrExpression(element, parserContext, builder);
HttpAdapterParsingUtils.configureUriVariableExpressions(builder, element);
HttpAdapterParsingUtils.configureUriVariableExpressions(builder, parserContext, element);
return builder.getBeanDefinition();
}

View File

@@ -87,7 +87,7 @@ public class HttpOutboundGatewayParser extends AbstractConsumerEndpointParser {
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "reply-timeout", "sendTimeout");
IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "reply-channel", "outputChannel");
HttpAdapterParsingUtils.configureUriVariableExpressions(builder, element);
HttpAdapterParsingUtils.configureUriVariableExpressions(builder, parserContext, element);
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "transfer-cookies");
return builder;
}

View File

@@ -111,6 +111,10 @@ public class HttpRequestExecutingMessageHandler extends AbstractReplyProducingMe
private volatile HeaderMapper<HttpHeaders> headerMapper = DefaultHttpHeaderMapper.outboundMapper();
private volatile Expression uriVariablesExpression;
/**
* Create a handler that will send requests to the provided URI.
*/
@@ -280,6 +284,15 @@ public class HttpRequestExecutingMessageHandler extends AbstractReplyProducingMe
}
}
/**
* Set the {@link Expression} to evaluate against the outbound message; the expression
* must evaluate to a Map of URI variable expressions to evaluate against the outbound message
* when replacing the variable placeholders in a URI template.
*/
public void setUriVariablesExpression(Expression uriVariablesExpression) {
this.uriVariablesExpression = uriVariablesExpression;
}
/**
* Set to true if you wish 'Set-Cookie' headers in responses to be
* transferred as 'Cookie' headers in subsequent interactions for
@@ -314,6 +327,7 @@ public class HttpRequestExecutingMessageHandler extends AbstractReplyProducingMe
}
private class ClassToStringConverter implements Converter<Class<?>, String> {
@Override
public String convert(Class<?> source) {
return source.getName();
}
@@ -326,6 +340,7 @@ public class HttpRequestExecutingMessageHandler extends AbstractReplyProducingMe
*
*/
private class ObjectToStringConverter implements Converter<Object, String> {
@Override
public String convert(Object source) {
if (source instanceof Class) {
return ((Class<?>) source).getName();
@@ -351,11 +366,7 @@ public class HttpRequestExecutingMessageHandler extends AbstractReplyProducingMe
Class<?> expectedResponseType = this.determineExpectedResponseType(requestMessage);
HttpEntity<?> httpRequest = this.generateHttpRequest(requestMessage, httpMethod);
Map<String, Object> uriVariables = ExpressionEvalMap
.from(this.uriVariableExpressions)
.usingEvaluationContext(this.evaluationContext)
.withRoot(requestMessage)
.build();
Map<String, ?> uriVariables = this.determineUriVariables(requestMessage);
UriComponents uriComponents = UriComponentsBuilder.fromUriString(uri).buildAndExpand(uriVariables);
URI realUri = this.encodeUri ? uriComponents.toUri() : new URI(uriComponents.toUriString());
ResponseEntity<?> httpResponse = this.restTemplate.exchange(realUri, httpMethod, httpRequest, expectedResponseType);
@@ -554,7 +565,6 @@ public class HttpRequestExecutingMessageHandler extends AbstractReplyProducingMe
return HttpMethod.valueOf(strHttpMethod);
}
private Class<?> determineExpectedResponseType(Message<?> requestMessage) throws Exception{
Class<?> expectedResponseType = null;
String expectedResponseTypeName = null;
@@ -568,4 +578,22 @@ public class HttpRequestExecutingMessageHandler extends AbstractReplyProducingMe
}
@SuppressWarnings("unchecked")
private Map<String, ?> determineUriVariables(Message<?> requestMessage) {
Map<String, ?> expressions;
if (this.uriVariablesExpression != null) {
expressions = this.uriVariablesExpression.getValue(this.evaluationContext, requestMessage, Map.class);
}
else {
expressions = this.uriVariableExpressions;
}
return ExpressionEvalMap.from(expressions)
.usingEvaluationContext(this.evaluationContext)
.withRoot(requestMessage)
.build();
}
}

View File

@@ -322,78 +322,14 @@
<xsd:annotation>
<xsd:documentation>
Specify an expression for URI variable placeholder within 'url'.
This element is mutually exclusive with 'uri-variables-expression' attribute.
</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element name="request-handler-advice-chain" type="integration:adviceChainType" minOccurs="0" maxOccurs="1" />
</xsd:choice>
<xsd:attributeGroup ref="integration:channelAdapterAttributes"/>
<xsd:attribute name="url" type="xsd:string" use="optional">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
URL to which the requests should be sent. It may include {placeholders} for
evaluation against uri-variables.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="url-expression" type="xsd:string" use="optional">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
SpEL Expression resolving to a URL to which the requests should be sent. The resolved
value may include {placeholders} for further evaluation against uri-variables.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="encode-uri" type="xsd:string" default="true">
<xsd:annotation>
<xsd:documentation>
When set to "false", the real URI won't be encoded before the request is sent. This may be useful
in some scenarios as it allows user control over the encoding, if needed,
for example by using the "url-expression". Default is "true".
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="http-method">
<xsd:annotation>
<xsd:documentation>
The HTTP method to use when executing requests with this adapter Default is POST.
This attribute cannot be provided if http-method-expression has a value.
</xsd:documentation>
</xsd:annotation>
<xsd:simpleType>
<xsd:union memberTypes="httpMethodEnumeration xsd:string" />
</xsd:simpleType>
</xsd:attribute>
<xsd:attribute name="http-method-expression" type="xsd:string">
<xsd:annotation>
<xsd:documentation>
The SpEL expression to determine HTTP method, use when executing requests with this adapter,
dynamically. This attribute cannot be provided if http-method has a value.
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="rest-template" type="xsd:string">
<xsd:annotation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:expected-type type="org.springframework.web.client.RestTemplate" />
</tool:annotation>
</xsd:appinfo>
<xsd:documentation>
The reference to org.springframework.web.client.RestTemplate bean to send the HTTP Request.
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="charset" type="xsd:string">
<xsd:annotation>
<xsd:documentation>
Specify the charset name to use for converting String-typed payloads to bytes.
The default is 'UTF-8'
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attributeGroup ref="httpOutboundCommonAttributes"/>
<xsd:attribute name="extract-payload" type="xsd:string" default="true">
<xsd:annotation>
<xsd:documentation>
@@ -404,93 +340,6 @@
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="expected-response-type" type="xsd:string">
<xsd:annotation>
<xsd:documentation>
The expected type to which the response body should be converted.
Default is 'org.springframework.http.ResponseEntity'.
This attribute cannot be provided if expected-response-type-expression has a value
</xsd:documentation>
<xsd:appinfo>
<tool:annotation kind="direct">
<tool:expected-type type="java.lang.Class" />
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="expected-response-type-expression" type="xsd:string">
<xsd:annotation>
<xsd:documentation>
SpEL expression to determine the type for the expected response to which the response body should be converted
The returned value of the expression could be an instance of java.lang.Class or
java.lang.String representing a fully qualified class name.
This attribute cannot be provided if expected-response-type has a value
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="message-converters" type="xsd:string">
<xsd:annotation>
<xsd:documentation>
Provide a reference to a list of HttpMessageConverter instances. If specified, these converters will replace
all of the default converters that would normally be present on the underlying RestTemplate.
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="header-mapper" type="xsd:string">
<xsd:annotation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:expected-type type="org.springframework.integration.mapping.HeaderMapper" />
</tool:annotation>
</xsd:appinfo>
<xsd:documentation>
Specify a reference to org.springframework.integration.mapping.HeaderMapper
implementation bean. Only one of 'header-mapper' or 'mapped-request-headers' attributes
can be provided.
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="mapped-request-headers" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
Comma-separated list of names of MessageHeaders to be mapped into the HttpHeaders of the HTTP request.
This can only be provided if the 'header-mapper' reference is not being set directly. The values in
this list can also be simple patterns to be matched against the header names (e.g. "foo*" or "*foo").
The String "HTTP_REQUEST_HEADERS" will match against any of the standard HTTP Request headers.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="request-factory" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
Reference to a ClientHttpRequestFactory to be used by the underlying RestTemplate.
]]></xsd:documentation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:expected-type type="org.springframework.http.client.ClientHttpRequestFactory" />
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="error-handler" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
Reference to a ResponseErrorHandler to be used by the underlying RestTemplate.
]]></xsd:documentation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:expected-type type="org.springframework.web.client.ResponseErrorHandler" />
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="order" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
Specifies the order for invocation when this adapter is connected as a subscriber to a SubscribableChannel.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
</xsd:complexType>
</xsd:element>
@@ -525,96 +374,6 @@
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="url" type="xsd:string" use="optional">
<xsd:annotation>
<xsd:documentation>
URL to which the requests should be sent. It may include {placeholders} for
evaluation against uri-variables.
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="url-expression" type="xsd:string" use="optional">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
SpEL Expression resolving to a URL to which the requests should be sent. The resolved
value may include {placeholders} for further evaluation against uri-variables.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="encode-uri" type="xsd:string" default="true">
<xsd:annotation>
<xsd:documentation>
When set to "false", the real URI won't be encoded before the request is sent. This may be useful
in some scenarios as it allows user control over the encoding, if needed,
for example by using the "url-expression". Default is "true".
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="http-method">
<xsd:annotation>
<xsd:documentation>
The HTTP method to use when executing requests with this adapter. Default is POST.
This attribute cannot be provided if http-method-expression has a value.
</xsd:documentation>
</xsd:annotation>
<xsd:simpleType>
<xsd:union memberTypes="httpMethodEnumeration xsd:string" />
</xsd:simpleType>
</xsd:attribute>
<xsd:attribute name="http-method-expression" type="xsd:string">
<xsd:annotation>
<xsd:documentation>
The SpEL expression to determine HTTP method, use when executing requests with this gateway,
dynamically. This attribute cannot be provided if http-method has a value.
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="message-converters" type="xsd:string">
<xsd:annotation>
<xsd:documentation>
Provide a reference to a list of HttpMessageConverter instances. If specified, these converters will replace
all of the default converters that would normally be present on the underlying RestTemplate.
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="header-mapper" type="xsd:string">
<xsd:annotation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:expected-type type="org.springframework.integration.mapping.HeaderMapper" />
</tool:annotation>
</xsd:appinfo>
<xsd:documentation>
Specifies a reference to org.springframework.integration.mapping.HeaderMapper
implementation bean. Only one of 'header-mapper' or 'mapped-request-headers'('mapped-response-headers')
attributes
can be provided.
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="rest-template" type="xsd:string">
<xsd:annotation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:expected-type type="org.springframework.web.client.RestTemplate" />
</tool:annotation>
<xsd:documentation>
The reference to org.springframework.web.client.RestTemplate bean to send the HTTP Request.
</xsd:documentation>
</xsd:appinfo>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="mapped-request-headers" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
Comma-separated list of names of MessageHeaders to be mapped into the HttpHeaders of the HTTP request.
This can only be provided if the 'header-mapper' reference is not being set directly. The values in
this list can also be simple patterns to be matched against the header names (e.g. "foo*" or "*foo").
The String "HTTP_REQUEST_HEADERS" will match against any of the standard HTTP Request headers.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="mapped-response-headers" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
@@ -626,76 +385,13 @@
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="extract-request-payload" type="xsd:string">
<xsd:annotation>
<xsd:documentation>
Specifies whether the outbound message's payload should be extracted
when preparing the request body. Otherwise the Message instance itself
will be serialized.
The default value is 'true'.
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="expected-response-type" type="xsd:string">
<xsd:annotation>
<xsd:documentation>
The expected type to which the response body should be converted.
Default is 'org.springframework.http.ResponseEntity'.
This attribute cannot be provided if expected-response-type-expression has a value
Specifies whether the outbound message's payload should be extracted
when preparing the request body. Otherwise the Message instance itself
will be serialized.
The default value is 'true'.
</xsd:documentation>
<xsd:appinfo>
<tool:annotation kind="direct">
<tool:expected-type type="java.lang.Class" />
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="expected-response-type-expression" type="xsd:string">
<xsd:annotation>
<xsd:documentation>
SpEL expression to determine the type for the expected response to which the response body should be converted
The returned value of the expression could be an instance of java.lang.Class or
java.lang.String representing a fully qualified class name.
This attribute cannot be provided if expected-response-type has a value
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="charset" type="xsd:string">
<xsd:annotation>
<xsd:documentation>
Specify the charset name to use for converting String-typed payloads to bytes.
The default is 'UTF-8'
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="request-factory" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
Reference to a ClientHttpRequestFactory to be used by the underlying RestTemplate.
]]></xsd:documentation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:expected-type type="org.springframework.http.client.ClientHttpRequestFactory" />
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="error-handler" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
Reference to a ResponseErrorHandler to be used by the underlying RestTemplate.
]]></xsd:documentation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:expected-type type="org.springframework.web.client.ResponseErrorHandler" />
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="order" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
Specifies the order for invocation when this gateway is connected as a subscriber to a SubscribableChannel.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="transfer-cookies" type="xsd:string" default="false">
@@ -730,6 +426,7 @@
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attributeGroup ref="httpOutboundCommonAttributes"/>
</xsd:extension>
</xsd:complexContent>
</xsd:complexType>
@@ -873,4 +570,168 @@
</xsd:attribute>
</xsd:complexType>
<xsd:attributeGroup name="httpOutboundCommonAttributes">
<xsd:attribute name="url" type="xsd:string" use="optional">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
URL to which the requests should be sent. It may include {placeholders} for
evaluation against uri-variables.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="url-expression" type="xsd:string" use="optional">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
SpEL Expression resolving to a URL to which the requests should be sent. The resolved
value may include {placeholders} for further evaluation against uri-variables.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="encode-uri" type="xsd:string" default="true">
<xsd:annotation>
<xsd:documentation>
When set to "false", the real URI won't be encoded before the request is sent. This may be useful
in some scenarios as it allows user control over the encoding, if needed,
for example by using the "url-expression". Default is "true".
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="http-method">
<xsd:annotation>
<xsd:documentation>
The HTTP method to use when executing requests with this adapter Default is POST.
This attribute cannot be provided if http-method-expression has a value.
</xsd:documentation>
</xsd:annotation>
<xsd:simpleType>
<xsd:union memberTypes="httpMethodEnumeration xsd:string" />
</xsd:simpleType>
</xsd:attribute>
<xsd:attribute name="http-method-expression" type="xsd:string">
<xsd:annotation>
<xsd:documentation>
The SpEL expression to determine HTTP method, use when executing requests with this adapter,
dynamically. This attribute cannot be provided if http-method has a value.
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="rest-template" type="xsd:string">
<xsd:annotation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:expected-type type="org.springframework.web.client.RestTemplate" />
</tool:annotation>
</xsd:appinfo>
<xsd:documentation>
The reference to org.springframework.web.client.RestTemplate bean to send the HTTP Request.
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="charset" type="xsd:string">
<xsd:annotation>
<xsd:documentation>
Specify the charset name to use for converting String-typed payloads to bytes.
The default is 'UTF-8'
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="expected-response-type" type="xsd:string">
<xsd:annotation>
<xsd:documentation>
The expected type to which the response body should be converted.
Default is 'org.springframework.http.ResponseEntity'.
This attribute cannot be provided if expected-response-type-expression has a value
</xsd:documentation>
<xsd:appinfo>
<tool:annotation kind="direct">
<tool:expected-type type="java.lang.Class" />
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="expected-response-type-expression" type="xsd:string">
<xsd:annotation>
<xsd:documentation>
SpEL expression to determine the type for the expected response to which the response body should be converted
The returned value of the expression could be an instance of java.lang.Class or
java.lang.String representing a fully qualified class name.
This attribute cannot be provided if expected-response-type has a value
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="message-converters" type="xsd:string">
<xsd:annotation>
<xsd:documentation>
Provide a reference to a list of HttpMessageConverter instances. If specified, these converters will replace
all of the default converters that would normally be present on the underlying RestTemplate.
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="header-mapper" type="xsd:string">
<xsd:annotation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:expected-type type="org.springframework.integration.mapping.HeaderMapper" />
</tool:annotation>
</xsd:appinfo>
<xsd:documentation>
Specify a reference to org.springframework.integration.mapping.HeaderMapper
implementation bean. Only one of 'header-mapper' or 'mapped-request-headers' attributes
can be provided.
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="mapped-request-headers" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
Comma-separated list of names of MessageHeaders to be mapped into the HttpHeaders of the HTTP request.
This can only be provided if the 'header-mapper' reference is not being set directly. The values in
this list can also be simple patterns to be matched against the header names (e.g. "foo*" or "*foo").
The String "HTTP_REQUEST_HEADERS" will match against any of the standard HTTP Request headers.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="request-factory" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
Reference to a ClientHttpRequestFactory to be used by the underlying RestTemplate.
]]></xsd:documentation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:expected-type type="org.springframework.http.client.ClientHttpRequestFactory" />
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="error-handler" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
Reference to a ResponseErrorHandler to be used by the underlying RestTemplate.
]]></xsd:documentation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:expected-type type="org.springframework.web.client.ResponseErrorHandler" />
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="order" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
Specifies the order for invocation when this adapter is connected as a subscriber to a SubscribableChannel.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="uri-variables-expression" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
Specifies the SpEL expression to be evaluate as a Map for URI variable placeholders within 'url'.
This attribute is mutually exclusive with 'uri-variable' sub-elements.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
</xsd:attributeGroup>
</xsd:schema>

View File

@@ -13,9 +13,9 @@
<si:channel id="requests"/>
<outbound-channel-adapter id="minimalConfig" url="http://localhost/test1" channel="requests"/>
<outbound-channel-adapter id="restTemplateConfig" url="http://localhost/test1" channel="requests" rest-template="customRestTemplate"/>
<beans:bean id="customRestTemplate" class="org.springframework.web.client.RestTemplate"/>
<outbound-channel-adapter id="fullConfig"
@@ -34,8 +34,14 @@
<uri-variable name="foo" expression="headers.bar"/>
</outbound-channel-adapter>
<util:map id="uriVariables">
<beans:entry key="foo1" value="bar1"/>
<beans:entry key="foo2" value="bar2"/>
</util:map>
<outbound-channel-adapter id="withUrlAndTemplate"
url="http://localhost/test1" channel="requests"
uri-variables-expression="@uriVariables"
rest-template="customRestTemplate"/>
<outbound-channel-adapter id="withUrlExpression" url-expression="'http://localhost/test1'" channel="requests"/>

View File

@@ -57,6 +57,7 @@ import org.springframework.web.client.RestTemplate;
* @author Mark Fisher
* @author Gary Russell
* @author Gunnar Hillert
* @author Artem Bilan
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
@@ -166,6 +167,7 @@ public class HttpOutboundChannelAdapterParserTests {
}
@Test
@SuppressWarnings("uchecked")
public void withUrlAndTemplate() {
DirectFieldAccessor endpointAccessor = new DirectFieldAccessor(this.withUrlAndTemplate);
RestTemplate restTemplate =
@@ -185,6 +187,14 @@ public class HttpOutboundChannelAdapterParserTests {
assertEquals(HttpMethod.POST.name(), TestUtils.getPropertyValue(handler, "httpMethodExpression", Expression.class).getExpressionString());
assertEquals("UTF-8", handlerAccessor.getPropertyValue("charset"));
assertEquals(true, handlerAccessor.getPropertyValue("extractPayload"));
//INT-3055
Object uriVariablesExpression = handlerAccessor.getPropertyValue("uriVariablesExpression");
assertNotNull(uriVariablesExpression);
assertEquals("@uriVariables", ((Expression) uriVariablesExpression).getExpressionString());
Object uriVariableExpressions = handlerAccessor.getPropertyValue("uriVariableExpressions");
assertNotNull(uriVariableExpressions);
assertTrue(((Map<?, ?>) uriVariableExpressions).isEmpty());
}
@Test

View File

@@ -40,7 +40,13 @@
<uri-variable name="foo" expression="headers.bar"/>
</outbound-gateway>
<outbound-gateway id="withUrlExpression" url-expression="'http://localhost/test1'" request-channel="requests"/>
<util:map id="uriVariables">
<beans:entry key="foo1" value="bar1"/>
<beans:entry key="foo2" value="bar2"/>
</util:map>
<outbound-gateway id="withUrlExpression" url-expression="'http://localhost/test1'" request-channel="requests"
uri-variables-expression="@uriVariables"/>
<outbound-gateway id="withAdvice" url-expression="'http://localhost/test1'" request-channel="requests">
<request-handler-advice-chain>

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2012 the original author or authors.
* Copyright 2002-2013 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.
@@ -170,6 +170,14 @@ public class HttpOutboundGatewayParserTests {
assertEquals("UTF-8", handlerAccessor.getPropertyValue("charset"));
assertEquals(true, handlerAccessor.getPropertyValue("extractPayload"));
assertEquals(false, handlerAccessor.getPropertyValue("transferCookies"));
//INT-3055
Object uriVariablesExpression = handlerAccessor.getPropertyValue("uriVariablesExpression");
assertNotNull(uriVariablesExpression);
assertEquals("@uriVariables", ((Expression) uriVariablesExpression).getExpressionString());
Object uriVariableExpressions = handlerAccessor.getPropertyValue("uriVariableExpressions");
assertNotNull(uriVariableExpressions);
assertTrue(((Map<?, ?>) uriVariableExpressions).isEmpty());
}
@Test

View File

@@ -17,6 +17,7 @@
package org.springframework.integration.http.outbound;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
import static org.mockito.Mockito.mock;
import java.io.IOException;
@@ -35,7 +36,9 @@ import org.springframework.http.HttpMethod;
import org.springframework.http.client.ClientHttpRequest;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.integration.Message;
import org.springframework.integration.expression.ExpressionEvalMap;
import org.springframework.integration.message.GenericMessage;
import org.springframework.integration.support.MessageBuilder;
/**
* @author Dave Syer
@@ -62,14 +65,13 @@ public class UriVariableExpressionTests {
handler.setBeanFactory(mock(BeanFactory.class));
handler.afterPropertiesSet();
Message<?> message = new GenericMessage<Object>("bar");
Exception exception = null;
try {
handler.handleMessage(message);
fail("Exception expected.");
}
catch (Exception e) {
exception = e;
assertEquals("intentional", e.getCause().getMessage());
}
assertEquals("intentional", exception.getCause().getMessage());
assertEquals("http://test/bar", uriHolder.get().toString());
}
@@ -92,15 +94,45 @@ public class UriVariableExpressionTests {
});
handler.setBeanFactory(mock(BeanFactory.class));
handler.afterPropertiesSet();
Message<?> message = new GenericMessage<Object>("bar");
Exception exception = null;
try {
handler.handleMessage(message);
handler.handleMessage(new GenericMessage<Object>("bar"));
fail("Exception expected.");
}
catch (Exception e) {
exception = e;
assertEquals("intentional", e.getCause().getMessage());
}
assertEquals("intentional", exception.getCause().getMessage());
assertEquals("http://test/bar", uriHolder.get().toString());
}
@Test
public void testInt3055UriVariablesExpression() throws Exception {
final AtomicReference<URI> uriHolder = new AtomicReference<URI>();
HttpRequestExecutingMessageHandler handler = new HttpRequestExecutingMessageHandler("http://test/{foo}");
handler.setRequestFactory(new SimpleClientHttpRequestFactory() {
@Override
public ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod) throws IOException {
uriHolder.set(uri);
throw new RuntimeException("intentional");
}
});
handler.setBeanFactory(mock(BeanFactory.class));
handler.setUriVariablesExpression(new SpelExpressionParser().parseExpression("headers.uriVariables"));
handler.afterPropertiesSet();
Map<String, String> expressions = new HashMap<String, String>();
expressions.put("foo", "bar");
Map<String, ?> expressionsMap = ExpressionEvalMap.from(expressions).usingSimpleCallback().build();
try {
handler.handleMessage(MessageBuilder.withPayload("test").setHeader("uriVariables", expressionsMap).build());
fail("Exception expected.");
}
catch (Exception e) {
assertEquals("intentional", e.getCause().getMessage());
}
assertEquals("http://test/bar", uriHolder.get().toString());
}

View File

@@ -520,6 +520,43 @@ By default the HTTP request will be generated using an instance of <classname>Si
method will be invoked on the payload object of the Message and the result
of that method will be used as the value for the URI variable named 'zipCode'.
</para>
<para>
Since <emphasis>Spring Integration 3.0</emphasis>, HTTP Outbound Endpoints support the
<code>uri-variables-expression</code> attribute to specify an <interfacename>Expression</interfacename>
which should be evaluated, resulting in a
<interfacename>Map</interfacename> for all URI variable placeholders within the URL template. It provides
a mechanism whereby different variable expressions can be used, based on the outbound message.
This attribute is mutually exclusive with the <code>&lt;uri-variable/&gt;</code> sub-element:
<programlisting language="xml"><![CDATA[<int-http:outbound-gateway
url="http://foo.host/{foo}/bars/{bar}"
request-channel="trafficChannel"
http-method="GET"
uri-variables-expression="@uriVariablesBean.populate(payload)"
expected-response-type="java.lang.String"/>]]></programlisting>
where <code>uriVariablesBean</code> might be:
<programlisting language="java"><![CDATA[public class UriVariablesBean {
private static final ExpressionParser EXPRESSION_PARSER = new SpelExpressionParser();
public Map<String, ?> populate(Object payload) {
Map<String, Object> variables = new HashMap<String, Object>();
if (payload instanceOf String.class)) {
variables.put("foo", "foo"));
}
else {
variables.put("foo", EXPRESSION_PARSER.parseExpression("headers.bar"));
}
return variables;
}
}]]></programlisting>
</para>
<note>
The <code>uri-variables-expression</code> must evaluate to a <interfacename>Map</interfacename>.
The values of the Map must be instances of
<interfacename>String</interfacename> or <interfacename>Expression</interfacename>.
This Map is provided to an <classname>ExpressionEvalMap</classname> for further resolution of URI variable placeholders
using those expressions in the context of the outbound <interfacename>Message</interfacename>.
</note>
<para>
<emphasis>Controlling URI Encoding</emphasis>
</para>

View File

@@ -431,6 +431,13 @@
<emphasis>#requestHeaders</emphasis> and <emphasis>#cookies</emphasis>. These variables are available in
both payload and header expressions.
</listitem>
<listitem>
<emphasis role="bold">Outbound Endpoint 'uri-variables-expression'</emphasis> - HTTP Outbound Endpoints
now support the <code>uri-variables-expression</code> attribute to specify
an <interfacename>Expression</interfacename> to evaluate a <interfacename>Map</interfacename>
for all URI variable placeholders within URL template. This allows selection of a different
map of expressions based on the outgoing message.
</listitem>
</itemizedlist>
</para>
<para>