Merge pull request #459 from garyrussell/INT-2570

* INT-2570a:
  INT-2570 Support url-expression on Outbound Http
This commit is contained in:
Oleg Zhurakousky
2012-05-25 12:02:40 -04:00
12 changed files with 344 additions and 92 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2011 the original author or authors.
* Copyright 2002-2012 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.
@@ -18,18 +18,21 @@ package org.springframework.integration.http.config;
import java.util.List;
import org.w3c.dom.Element;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.ManagedMap;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.expression.common.LiteralExpression;
import org.springframework.integration.config.ExpressionFactoryBean;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.util.xml.DomUtils;
import org.w3c.dom.Element;
/**
* @author Oleg Zhurakousky
* @author Mark Fisher
* @author Gary Russell
* @since 2.0.2
*/
abstract class HttpAdapterParsingUtils {
@@ -63,4 +66,26 @@ abstract class HttpAdapterParsingUtils {
}
}
static void configureUrlConstructorArg(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) {
String urlAttribute = element.getAttribute("url");
String urlExpressionAttribute = element.getAttribute("url-expression");
boolean hasUrlAttribute = StringUtils.hasText(urlAttribute);
boolean hasUrlExpressionAttribute = StringUtils.hasText(urlExpressionAttribute);
if (!(hasUrlAttribute ^ hasUrlExpressionAttribute)) {
parserContext.getReaderContext().error("Adapter must have exactly one of 'url' or 'url-expression'", element);
}
RootBeanDefinition expressionDef;
if (hasUrlAttribute) {
expressionDef = new RootBeanDefinition(LiteralExpression.class);
expressionDef.getConstructorArgumentValues().addGenericArgumentValue(urlAttribute);
}
else {
expressionDef = new RootBeanDefinition(ExpressionFactoryBean.class);
expressionDef.getConstructorArgumentValues().addGenericArgumentValue(urlExpressionAttribute);
}
builder.addConstructorArgValue(expressionDef);
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2011 the original author or authors.
* Copyright 2002-2012 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,20 +16,20 @@
package org.springframework.integration.http.config;
import org.w3c.dom.Element;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.integration.config.xml.AbstractOutboundChannelAdapterParser;
import org.springframework.integration.config.xml.IntegrationNamespaceUtils;
import org.springframework.util.StringUtils;
import org.w3c.dom.Element;
/**
* Parser for the 'outbound-channel-adapter' element of the http namespace.
*
*
* @author Mark Fisher
* @author Oleg Zhurakousky
* @author Gary Russell
* @since 2.0
*/
public class HttpOutboundChannelAdapterParser extends AbstractOutboundChannelAdapterParser {
@@ -39,9 +39,9 @@ public class HttpOutboundChannelAdapterParser extends AbstractOutboundChannelAda
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(
"org.springframework.integration.http.outbound.HttpRequestExecutingMessageHandler");
builder.addPropertyValue("expectReply", false);
builder.addConstructorArgValue(element.getAttribute("url"));
HttpAdapterParsingUtils.configureUrlConstructorArg(element, parserContext, builder);
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "http-method");
String restTemplate = element.getAttribute("rest-template");
if (StringUtils.hasText(restTemplate)) {
HttpAdapterParsingUtils.verifyNoRestTemplateAttributes(element, parserContext);
@@ -52,7 +52,7 @@ public class HttpOutboundChannelAdapterParser extends AbstractOutboundChannelAda
IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, referenceAttributeName);
}
}
String headerMapper = element.getAttribute("header-mapper");
String mappedRequestHeaders = element.getAttribute("mapped-request-headers");
if (StringUtils.hasText(headerMapper)) {
@@ -66,7 +66,7 @@ public class HttpOutboundChannelAdapterParser extends AbstractOutboundChannelAda
else if (StringUtils.hasText(mappedRequestHeaders)) {
BeanDefinitionBuilder headerMapperBuilder = BeanDefinitionBuilder.genericBeanDefinition(
"org.springframework.integration.http.support.DefaultHttpHeaderMapper");
IntegrationNamespaceUtils.setValueIfAttributeDefined(headerMapperBuilder, element, "mapped-request-headers", "outboundHeaderNames");
IntegrationNamespaceUtils.setValueIfAttributeDefined(headerMapperBuilder, element, "mapped-request-headers", "outboundHeaderNames");
builder.addPropertyValue("headerMapper", headerMapperBuilder.getBeanDefinition());
}
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "charset");

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2011 the original author or authors.
* Copyright 2002-2012 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,19 +16,19 @@
package org.springframework.integration.http.config;
import org.w3c.dom.Element;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.integration.config.xml.AbstractConsumerEndpointParser;
import org.springframework.integration.config.xml.IntegrationNamespaceUtils;
import org.springframework.util.StringUtils;
import org.w3c.dom.Element;
/**
* Parser for the 'outbound-gateway' element of the http namespace.
*
*
* @author Mark Fisher
* @author Oleg Zhurakousky
* @author Gary Russell
*/
public class HttpOutboundGatewayParser extends AbstractConsumerEndpointParser {
@@ -41,9 +41,9 @@ public class HttpOutboundGatewayParser extends AbstractConsumerEndpointParser {
protected BeanDefinitionBuilder parseHandler(Element element, ParserContext parserContext) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(
"org.springframework.integration.http.outbound.HttpRequestExecutingMessageHandler");
builder.addConstructorArgValue(element.getAttribute("url"));
HttpAdapterParsingUtils.configureUrlConstructorArg(element, parserContext, builder);
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "http-method");
String restTemplate = element.getAttribute("rest-template");
if (StringUtils.hasText(restTemplate)) {
HttpAdapterParsingUtils.verifyNoRestTemplateAttributes(element, parserContext);
@@ -54,7 +54,7 @@ public class HttpOutboundGatewayParser extends AbstractConsumerEndpointParser {
IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, referenceAttributeName);
}
}
String headerMapper = element.getAttribute("header-mapper");
String mappedRequestHeaders = element.getAttribute("mapped-request-headers");
String mappedResponseHeaders = element.getAttribute("mapped-response-headers");

View File

@@ -32,6 +32,7 @@ import org.springframework.context.expression.BeanFactoryResolver;
import org.springframework.context.expression.MapAccessor;
import org.springframework.core.convert.ConversionService;
import org.springframework.expression.Expression;
import org.springframework.expression.common.LiteralExpression;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.expression.spel.support.StandardTypeConverter;
import org.springframework.http.HttpEntity;
@@ -67,7 +68,7 @@ import org.springframework.web.client.RestTemplate;
* When there is a response body, the {@link HttpStatus} enum instance will instead be
* copied to the MessageHeaders of the reply. In both cases, the response headers will
* be mapped to the reply Message's headers by this handler's {@link HeaderMapper} instance.
*
*
* @author Mark Fisher
* @author Oleg Zhurakousky
* @author Gary Russell
@@ -75,7 +76,7 @@ import org.springframework.web.client.RestTemplate;
*/
public class HttpRequestExecutingMessageHandler extends AbstractReplyProducingMessageHandler {
private final String uri;
private final Expression uriExpression;
private volatile HttpMethod httpMethod = HttpMethod.POST;
@@ -84,7 +85,7 @@ public class HttpRequestExecutingMessageHandler extends AbstractReplyProducingMe
private volatile Class<?> expectedResponseType;
private volatile boolean extractPayload = true;
private volatile boolean extractPayloadExplicitlySet = false;
private volatile String charset = "UTF-8";
@@ -113,16 +114,39 @@ public class HttpRequestExecutingMessageHandler extends AbstractReplyProducingMe
this(uri, null);
}
/**
* Create a handler that will send requests to the provided URI Expression.
*/
public HttpRequestExecutingMessageHandler(Expression uriExpression) {
this(uriExpression, null);
}
/**
* Create a handler that will send requests to the provided URI using a provided RestTemplate
* @param uri
* @param restTemplate
*/
public HttpRequestExecutingMessageHandler(String uri, RestTemplate restTemplate) {
this(new LiteralExpression(uri), restTemplate);
/*
* We'd prefer to do this assertion first, but the compiler doesn't allow it. However,
* it's safe because the literal expression simply wraps the String variable, even
* when null.
*/
Assert.hasText(uri, "URI is required");
}
/**
* Create a handler that will send requests to the provided URI using a provided RestTemplate
* @param uriExpression A SpEL Expression that can be resolved against the message object and
* {@link BeanFactory}.
* @param restTemplate
*/
public HttpRequestExecutingMessageHandler(Expression uriExpression, RestTemplate restTemplate) {
Assert.notNull(uriExpression, "URI Expression is required");
this.restTemplate = (restTemplate == null ? new RestTemplate() : restTemplate);
this.restTemplate.getMessageConverters().add(0, new SerializingHttpMessageConverter());
this.uri = uri;
this.uriExpression = uriExpression;
StandardEvaluationContext sec = new StandardEvaluationContext();
sec.addPropertyAccessor(new MapAccessor());
this.evaluationContext = sec;
@@ -205,7 +229,7 @@ public class HttpRequestExecutingMessageHandler extends AbstractReplyProducingMe
public void setRequestFactory(ClientHttpRequestFactory requestFactory) {
this.restTemplate.setRequestFactory(requestFactory);
}
/**
* Set the Map of URI variable expressions to evaluate against the outbound message
* when replacing the variable placeholders in a URI template.
@@ -240,7 +264,7 @@ public class HttpRequestExecutingMessageHandler extends AbstractReplyProducingMe
}
if (!this.shouldIncludeRequestBody() && this.extractPayloadExplicitlySet){
if (logger.isWarnEnabled()){
logger.warn("The 'extractPayload' attribute has no meaning in the context of this handler since the provided HTTP Method is '" +
logger.warn("The 'extractPayload' attribute has no meaning in the context of this handler since the provided HTTP Method is '" +
this.httpMethod + "', and no request body will be sent for that method.");
}
}
@@ -248,6 +272,8 @@ public class HttpRequestExecutingMessageHandler extends AbstractReplyProducingMe
@Override
protected Object handleRequestMessage(Message<?> requestMessage) {
String uri = this.uriExpression.getValue(this.evaluationContext, requestMessage, String.class);
Assert.notNull(uri, "URI Expression evaluation cannot result in null");
try {
Map<String, Object> uriVariables = new HashMap<String, Object>();
for (Map.Entry<String, Expression> entry : this.uriVariableExpressions.entrySet()) {
@@ -255,7 +281,7 @@ public class HttpRequestExecutingMessageHandler extends AbstractReplyProducingMe
uriVariables.put(entry.getKey(), value);
}
HttpEntity<?> httpRequest = this.generateHttpRequest(requestMessage);
ResponseEntity<?> httpResponse = this.restTemplate.exchange(this.uri, this.httpMethod, httpRequest, this.expectedResponseType, uriVariables);
ResponseEntity<?> httpResponse = this.restTemplate.exchange(uri, this.httpMethod, httpRequest, this.expectedResponseType, uriVariables);
if (this.expectReply) {
HttpHeaders httpHeaders = httpResponse.getHeaders();
Map<String, Object> headers = this.headerMapper.toHeaders(httpHeaders);
@@ -281,7 +307,7 @@ public class HttpRequestExecutingMessageHandler extends AbstractReplyProducingMe
throw e;
}
catch (Exception e) {
throw new MessageHandlingException(requestMessage, "HTTP request execution failed for URI [" + this.uri + "]", e);
throw new MessageHandlingException(requestMessage, "HTTP request execution failed for URI [" + uri + "]", e);
}
}
@@ -367,7 +393,7 @@ public class HttpRequestExecutingMessageHandler extends AbstractReplyProducingMe
if (this.isFormData((Map<Object, ?>) content)) {
if (this.isMultipart((Map<String, ?>)content)) {
contentType = MediaType.MULTIPART_FORM_DATA;
}
}
else {
contentType = MediaType.APPLICATION_FORM_URLENCODED;
}
@@ -393,7 +419,7 @@ public class HttpRequestExecutingMessageHandler extends AbstractReplyProducingMe
Object value = simpleMap.get(key);
if (value instanceof Object[]) {
Object[] valueArray = (Object[]) value;
value = Arrays.asList(valueArray);
value = Arrays.asList(valueArray);
}
if (value instanceof Collection) {
multipartValueMap.put(key, new ArrayList<Object>((Collection<?>) value));
@@ -406,7 +432,7 @@ public class HttpRequestExecutingMessageHandler extends AbstractReplyProducingMe
}
/**
* If all keys are Strings, and some values are not Strings we'll consider
* If all keys are Strings, and some values are not Strings we'll consider
* the Map to be multipart/form-data
*/
private boolean isMultipart(Map<String, ?> map) {
@@ -414,7 +440,7 @@ public class HttpRequestExecutingMessageHandler extends AbstractReplyProducingMe
Object value = map.get(key);
if (value != null) {
if (value.getClass().isArray()) {
value = CollectionUtils.arrayToList(value);
value = CollectionUtils.arrayToList(value);
}
if (value instanceof Collection) {
Collection<?> cValues = (Collection<?>) value;
@@ -423,7 +449,7 @@ public class HttpRequestExecutingMessageHandler extends AbstractReplyProducingMe
return true;
}
}
}
}
else if (!(value instanceof String)) {
return true;
}
@@ -439,7 +465,7 @@ public class HttpRequestExecutingMessageHandler extends AbstractReplyProducingMe
for (Object key : map.keySet()) {
if (!(key instanceof String)) {
return false;
}
}
}
return true;
}

View File

@@ -333,11 +333,21 @@ The String "HTTP_REQUEST_HEADERS" will match against any of the standard HTTP Re
<xsd:element name="uri-variable" type="uriVariableType" minOccurs="0" maxOccurs="unbounded" />
</xsd:sequence>
<xsd:attribute name="id" type="xsd:string" />
<xsd:attribute name="url" type="xsd:string" use="required">
<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}.
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>
@@ -463,13 +473,23 @@ The String "HTTP_REQUEST_HEADERS" will match against any of the standard HTTP Re
<xsd:sequence>
<xsd:element name="uri-variable" type="uriVariableType" minOccurs="0" maxOccurs="unbounded" />
</xsd:sequence>
<xsd:attribute name="url" type="xsd:string" use="required">
<xsd:attribute name="url" type="xsd:string" use="optional">
<xsd:annotation>
<xsd:documentation>
URL to be used as a fallback for any request Message does not contain the request URL Message header.
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="http-method" default="POST">
<xsd:annotation>
<xsd:documentation>

View File

@@ -34,6 +34,16 @@
<uri-variable name="foo" expression="headers.bar"/>
</outbound-channel-adapter>
<outbound-channel-adapter id="withUrlAndTemplate"
url="http://localhost/test1" channel="requests"
rest-template="customRestTemplate"/>
<outbound-channel-adapter id="withUrlExpression" url-expression="'http://localhost/test1'" channel="requests"/>
<outbound-channel-adapter id="withUrlExpressionAndTemplate"
url-expression="'http://localhost/test1'" channel="requests"
rest-template="customRestTemplate"/>
<beans:bean id="testRequestFactory" class="org.springframework.http.client.SimpleClientHttpRequestFactory"/>
<beans:bean id="testErrorHandler" class="org.springframework.integration.http.config.HttpOutboundChannelAdapterParserTests$StubErrorHandler"/>

View File

@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans:beans
xmlns="http://www.springframework.org/schema/integration/http"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:beans="http://www.springframework.org/schema/beans"
xmlns:si="http://www.springframework.org/schema/integration"
xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/integration/http http://www.springframework.org/schema/integration/http/spring-integration-http.xsd
http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd">
<si:channel id="requests"/>
<!-- Can't have both url and url-expression -->
<outbound-channel-adapter id="minimalConfig" url="http://localhost/test1" url-expression="'foo'"
error-handler="testErrorHandler" rest-template="restTemplate" channel="requests"/>
<beans:bean id="restTemplate" class="org.springframework.web.client.RestTemplate"/>
<beans:bean id="testErrorHandler"
class="org.springframework.integration.http.config.HttpOutboundChannelAdapterParserTests$StubErrorHandler"/>
</beans:beans>

View File

@@ -18,7 +18,9 @@ package org.springframework.integration.http.config;
import static junit.framework.Assert.assertNotSame;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import java.io.IOException;
@@ -33,6 +35,7 @@ import org.springframework.beans.factory.parsing.BeanDefinitionParsingException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.spel.standard.SpelExpression;
import org.springframework.http.HttpMethod;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.http.client.ClientHttpResponse;
@@ -58,13 +61,22 @@ public class HttpOutboundChannelAdapterParserTests {
@Autowired @Qualifier("fullConfig")
private AbstractEndpoint fullConfig;
@Autowired @Qualifier("restTemplateConfig")
private AbstractEndpoint restTemplateConfig;
@Autowired @Qualifier("customRestTemplate")
private RestTemplate customRestTemplate;
@Autowired @Qualifier("withUrlAndTemplate")
private AbstractEndpoint withUrlAndTemplate;
@Autowired @Qualifier("withUrlExpression")
private AbstractEndpoint withUrlExpression;
@Autowired @Qualifier("withUrlExpressionAndTemplate")
private AbstractEndpoint withUrlExpressionAndTemplate;
@Autowired
private ApplicationContext applicationContext;
@@ -72,7 +84,7 @@ public class HttpOutboundChannelAdapterParserTests {
@Test
public void minimalConfig() {
DirectFieldAccessor endpointAccessor = new DirectFieldAccessor(this.minimalConfig);
RestTemplate restTemplate =
RestTemplate restTemplate =
TestUtils.getPropertyValue(this.minimalConfig, "handler.restTemplate", RestTemplate.class);
assertNotSame(customRestTemplate, restTemplate);
HttpRequestExecutingMessageHandler handler = (HttpRequestExecutingMessageHandler) endpointAccessor.getPropertyValue("handler");
@@ -128,10 +140,10 @@ public class HttpOutboundChannelAdapterParserTests {
assertTrue(ObjectUtils.containsElement(mappedRequestHeaders, "requestHeader1"));
assertTrue(ObjectUtils.containsElement(mappedRequestHeaders, "requestHeader2"));
}
@Test
public void restTemplateConfig() {
RestTemplate restTemplate =
RestTemplate restTemplate =
TestUtils.getPropertyValue(this.restTemplateConfig, "handler.restTemplate", RestTemplate.class);
assertEquals(customRestTemplate, restTemplate);
}
@@ -141,6 +153,77 @@ public class HttpOutboundChannelAdapterParserTests {
new ClassPathXmlApplicationContext("HttpOutboundChannelAdapterParserTests-fail-context.xml", this.getClass());
}
@Test
public void withUrlAndTemplate() {
DirectFieldAccessor endpointAccessor = new DirectFieldAccessor(this.withUrlAndTemplate);
RestTemplate restTemplate =
TestUtils.getPropertyValue(this.withUrlAndTemplate, "handler.restTemplate", RestTemplate.class);
assertSame(customRestTemplate, restTemplate);
HttpRequestExecutingMessageHandler handler = (HttpRequestExecutingMessageHandler) endpointAccessor.getPropertyValue("handler");
DirectFieldAccessor handlerAccessor = new DirectFieldAccessor(handler);
assertEquals(false, handlerAccessor.getPropertyValue("expectReply"));
assertEquals(this.applicationContext.getBean("requests"), endpointAccessor.getPropertyValue("inputChannel"));
assertNull(handlerAccessor.getPropertyValue("outputChannel"));
DirectFieldAccessor templateAccessor = new DirectFieldAccessor(handlerAccessor.getPropertyValue("restTemplate"));
ClientHttpRequestFactory requestFactory = (ClientHttpRequestFactory)
templateAccessor.getPropertyValue("requestFactory");
assertTrue(requestFactory instanceof SimpleClientHttpRequestFactory);
assertEquals("http://localhost/test1", handlerAccessor.getPropertyValue("uri"));
assertEquals(HttpMethod.POST, handlerAccessor.getPropertyValue("httpMethod"));
assertEquals("UTF-8", handlerAccessor.getPropertyValue("charset"));
assertEquals(true, handlerAccessor.getPropertyValue("extractPayload"));
}
@Test
public void withUrlExpression() {
DirectFieldAccessor endpointAccessor = new DirectFieldAccessor(this.withUrlExpression);
RestTemplate restTemplate =
TestUtils.getPropertyValue(this.withUrlExpression, "handler.restTemplate", RestTemplate.class);
assertNotSame(customRestTemplate, restTemplate);
HttpRequestExecutingMessageHandler handler = (HttpRequestExecutingMessageHandler) endpointAccessor.getPropertyValue("handler");
DirectFieldAccessor handlerAccessor = new DirectFieldAccessor(handler);
assertEquals(false, handlerAccessor.getPropertyValue("expectReply"));
assertEquals(this.applicationContext.getBean("requests"), endpointAccessor.getPropertyValue("inputChannel"));
assertNull(handlerAccessor.getPropertyValue("outputChannel"));
DirectFieldAccessor templateAccessor = new DirectFieldAccessor(handlerAccessor.getPropertyValue("restTemplate"));
ClientHttpRequestFactory requestFactory = (ClientHttpRequestFactory)
templateAccessor.getPropertyValue("requestFactory");
assertTrue(requestFactory instanceof SimpleClientHttpRequestFactory);
SpelExpression expression = (SpelExpression) handlerAccessor.getPropertyValue("uriExpression");
assertNotNull(expression);
assertEquals("'http://localhost/test1'", expression.getExpressionString());
assertEquals(HttpMethod.POST, handlerAccessor.getPropertyValue("httpMethod"));
assertEquals("UTF-8", handlerAccessor.getPropertyValue("charset"));
assertEquals(true, handlerAccessor.getPropertyValue("extractPayload"));
}
@Test
public void withUrlExpressionAndTemplate() {
DirectFieldAccessor endpointAccessor = new DirectFieldAccessor(this.withUrlExpressionAndTemplate);
RestTemplate restTemplate =
TestUtils.getPropertyValue(this.withUrlExpressionAndTemplate, "handler.restTemplate", RestTemplate.class);
assertSame(customRestTemplate, restTemplate);
HttpRequestExecutingMessageHandler handler = (HttpRequestExecutingMessageHandler) endpointAccessor.getPropertyValue("handler");
DirectFieldAccessor handlerAccessor = new DirectFieldAccessor(handler);
assertEquals(false, handlerAccessor.getPropertyValue("expectReply"));
assertEquals(this.applicationContext.getBean("requests"), endpointAccessor.getPropertyValue("inputChannel"));
assertNull(handlerAccessor.getPropertyValue("outputChannel"));
DirectFieldAccessor templateAccessor = new DirectFieldAccessor(handlerAccessor.getPropertyValue("restTemplate"));
ClientHttpRequestFactory requestFactory = (ClientHttpRequestFactory)
templateAccessor.getPropertyValue("requestFactory");
assertTrue(requestFactory instanceof SimpleClientHttpRequestFactory);
SpelExpression expression = (SpelExpression) handlerAccessor.getPropertyValue("uriExpression");
assertNotNull(expression);
assertEquals("'http://localhost/test1'", expression.getExpressionString());
assertEquals(HttpMethod.POST, handlerAccessor.getPropertyValue("httpMethod"));
assertEquals("UTF-8", handlerAccessor.getPropertyValue("charset"));
assertEquals(true, handlerAccessor.getPropertyValue("extractPayload"));
}
@Test(expected=BeanDefinitionParsingException.class)
public void failWithUrlAndExpression() {
new ClassPathXmlApplicationContext("HttpOutboundChannelAdapterParserTests-url-fail-context.xml", this.getClass());
}
public static class StubErrorHandler implements ResponseErrorHandler {

View File

@@ -40,6 +40,8 @@
<uri-variable name="foo" expression="headers.bar"/>
</outbound-gateway>
<outbound-gateway id="withUrlExpression" url-expression="'http://localhost/test1'" request-channel="requests"/>
<beans:bean id="testRequestFactory" class="org.springframework.http.client.SimpleClientHttpRequestFactory"/>
<beans:bean id="testErrorHandler" class="org.springframework.integration.http.config.HttpOutboundGatewayParserTests$StubErrorHandler"/>

View File

@@ -26,12 +26,12 @@ import java.util.Map;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.DirectFieldAccessor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.ApplicationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.spel.standard.SpelExpression;
import org.springframework.http.HttpMethod;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.http.client.ClientHttpResponse;
@@ -57,6 +57,9 @@ public class HttpOutboundGatewayParserTests {
@Autowired @Qualifier("fullConfig")
private AbstractEndpoint fullConfigEndpoint;
@Autowired @Qualifier("withUrlExpression")
private AbstractEndpoint withUrlExpressionEndpoint;
@Autowired
private ApplicationContext applicationContext;
@@ -129,6 +132,29 @@ public class HttpOutboundGatewayParserTests {
assertEquals(true, handlerAccessor.getPropertyValue("transferCookies"));
}
@Test
public void withUrlExpression() {
HttpRequestExecutingMessageHandler handler = (HttpRequestExecutingMessageHandler) new DirectFieldAccessor(
this.withUrlExpressionEndpoint).getPropertyValue("handler");
MessageChannel requestChannel = (MessageChannel) new DirectFieldAccessor(
this.withUrlExpressionEndpoint).getPropertyValue("inputChannel");
assertEquals(this.applicationContext.getBean("requests"), requestChannel);
DirectFieldAccessor handlerAccessor = new DirectFieldAccessor(handler);
Object replyChannel = handlerAccessor.getPropertyValue("outputChannel");
assertNull(replyChannel);
DirectFieldAccessor templateAccessor = new DirectFieldAccessor(handlerAccessor.getPropertyValue("restTemplate"));
ClientHttpRequestFactory requestFactory = (ClientHttpRequestFactory)
templateAccessor.getPropertyValue("requestFactory");
assertTrue(requestFactory instanceof SimpleClientHttpRequestFactory);
SpelExpression expression = (SpelExpression) handlerAccessor.getPropertyValue("uriExpression");
assertNotNull(expression);
assertEquals("'http://localhost/test1'", expression.getExpressionString());
assertEquals(HttpMethod.POST, handlerAccessor.getPropertyValue("httpMethod"));
assertEquals("UTF-8", handlerAccessor.getPropertyValue("charset"));
assertEquals(true, handlerAccessor.getPropertyValue("extractPayload"));
assertEquals(false, handlerAccessor.getPropertyValue("transferCookies"));
}
public static class StubErrorHandler implements ResponseErrorHandler {

View File

@@ -34,6 +34,7 @@ import org.junit.Test;
import org.springframework.beans.DirectFieldAccessor;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
@@ -51,9 +52,10 @@ import org.springframework.web.client.RestTemplate;
* @author Mark Fisher
* @author Oleg Zhurakousky
* @author Artem Bilan
* @author Gary Russell
*/
public class HttpRequestExecutingMessageHandlerTests {
@Test
public void simpleStringKeyStringValueFormData() throws Exception {
HttpRequestExecutingMessageHandler handler = new HttpRequestExecutingMessageHandler("http://www.springsource.org/spring-integration");
@@ -85,7 +87,7 @@ public class HttpRequestExecutingMessageHandlerTests {
assertEquals("3", map.get("c").iterator().next());
assertEquals(MediaType.APPLICATION_FORM_URLENCODED, request.getHeaders().getContentType());
}
@Test
public void simpleStringKeyObjectValueFormData() throws Exception {
HttpRequestExecutingMessageHandler handler = new HttpRequestExecutingMessageHandler("http://www.springsource.org/spring-integration");
@@ -116,7 +118,7 @@ public class HttpRequestExecutingMessageHandlerTests {
assertEquals("Mohnton", map.get("c").get(0).toString());
assertEquals(MediaType.MULTIPART_FORM_DATA, request.getHeaders().getContentType());
}
@Test
public void simpleObjectKeyObjectValueFormData() throws Exception {
HttpRequestExecutingMessageHandler handler = new HttpRequestExecutingMessageHandler("http://www.springsource.org/spring-integration");
@@ -173,13 +175,13 @@ public class HttpRequestExecutingMessageHandlerTests {
Object body = request.getBody();
assertTrue(body instanceof MultiValueMap<?, ?>);
MultiValueMap<?, ?> map = (MultiValueMap <?, ?>) body;
List<?> aValue = map.get("a");
assertEquals(3, aValue.size());
assertEquals("1", aValue.get(0));
assertEquals("2", aValue.get(1));
assertEquals("3", aValue.get(2));
List<?> bValue = map.get("b");
assertEquals(1, bValue.size());
assertEquals("4", bValue.get(0));
@@ -187,13 +189,13 @@ public class HttpRequestExecutingMessageHandlerTests {
List<?> cValue = map.get("c");
assertEquals(1, cValue.size());
assertEquals("5", cValue.get(0));
List<?> dValue = map.get("d");
assertEquals(1, dValue.size());
assertEquals("6", dValue.get(0));
assertEquals(MediaType.APPLICATION_FORM_URLENCODED, request.getHeaders().getContentType());
}
@Test
public void stringKeyPrimitiveArrayValueMixedFormData() throws Exception {
HttpRequestExecutingMessageHandler handler = new HttpRequestExecutingMessageHandler("http://www.springsource.org/spring-integration");
@@ -218,7 +220,7 @@ public class HttpRequestExecutingMessageHandlerTests {
Object body = request.getBody();
assertTrue(body instanceof MultiValueMap<?, ?>);
MultiValueMap<?, ?> map = (MultiValueMap <?, ?>) body;
List<?> aValue = map.get("a");
assertEquals(1, aValue.size());
Object value = aValue.get(0);
@@ -227,7 +229,7 @@ public class HttpRequestExecutingMessageHandlerTests {
assertEquals(1, y[0]);
assertEquals(2, y[1]);
assertEquals(3, y[2]);
List<?> bValue = map.get("b");
assertEquals(1, bValue.size());
assertEquals("4", bValue.get(0));
@@ -235,7 +237,7 @@ public class HttpRequestExecutingMessageHandlerTests {
List<?> cValue = map.get("c");
assertEquals(1, cValue.size());
assertEquals("5", cValue.get(0));
List<?> dValue = map.get("d");
assertEquals(1, dValue.size());
assertEquals("6", dValue.get(0));
@@ -263,13 +265,13 @@ public class HttpRequestExecutingMessageHandlerTests {
Object body = request.getBody();
assertTrue(body instanceof MultiValueMap<?, ?>);
MultiValueMap<?, ?> map = (MultiValueMap <?, ?>) body;
List<?> aValue = map.get("a");
assertEquals(3, aValue.size());
assertNull(aValue.get(0));
assertEquals(4, aValue.get(1));
assertNull(aValue.get(2));
List<?> bValue = map.get("b");
assertEquals(1, bValue.size());
assertEquals("4", bValue.get(0));
@@ -278,8 +280,8 @@ public class HttpRequestExecutingMessageHandlerTests {
}
/**
* This test and the one below might look identical, but they are not.
* This test injected "5" into the list as String resulting in
* the Content-TYpe being application/x-www-form-urlencoded
* This test injected "5" into the list as String resulting in
* the Content-TYpe being application/x-www-form-urlencoded
* @throws Exception
*/
@Test
@@ -308,13 +310,13 @@ public class HttpRequestExecutingMessageHandlerTests {
Object body = request.getBody();
assertTrue(body instanceof MultiValueMap<?, ?>);
MultiValueMap<?, ?> map = (MultiValueMap <?, ?>) body;
List<?> aValue = map.get("a");
assertEquals(3, aValue.size());
assertNull(aValue.get(0));
assertEquals("5", aValue.get(1));
assertNull(aValue.get(2));
List<?> bValue = map.get("b");
assertEquals(1, bValue.size());
assertEquals("4", bValue.get(0));
@@ -323,7 +325,7 @@ public class HttpRequestExecutingMessageHandlerTests {
}
/**
* This test and the one above might look identical, but they are not.
* This test injected 5 into the list as int resulting in
* This test injected 5 into the list as int resulting in
* Content-type being multipart/form-data
* @throws Exception
*/
@@ -353,13 +355,13 @@ public class HttpRequestExecutingMessageHandlerTests {
Object body = request.getBody();
assertTrue(body instanceof MultiValueMap<?, ?>);
MultiValueMap<?, ?> map = (MultiValueMap <?, ?>) body;
List<?> aValue = map.get("a");
assertEquals(3, aValue.size());
assertNull(aValue.get(0));
assertEquals(5, aValue.get(1));
assertNull(aValue.get(2));
List<?> bValue = map.get("b");
assertEquals(1, bValue.size());
assertEquals("4", bValue.get(0));
@@ -393,23 +395,23 @@ public class HttpRequestExecutingMessageHandlerTests {
Object body = request.getBody();
assertTrue(body instanceof MultiValueMap<?, ?>);
MultiValueMap<?, ?> map = (MultiValueMap <?, ?>) body;
List<?> aValue = map.get("a");
assertEquals(2, aValue.size());
assertEquals("1", aValue.get(0));
assertEquals("2", aValue.get(1));
List<?> bValue = map.get("b");
assertEquals(0, bValue.size());
List<?> cValue = map.get("c");
assertEquals(1, cValue.size());
assertEquals("3", cValue.get(0));
assertEquals(MediaType.APPLICATION_FORM_URLENCODED, request.getHeaders().getContentType());
}
@Test
public void stringKeyObjectCollectionValueFormData() throws Exception {
HttpRequestExecutingMessageHandler handler = new HttpRequestExecutingMessageHandler("http://www.springsource.org/spring-integration");
@@ -436,20 +438,20 @@ public class HttpRequestExecutingMessageHandlerTests {
Object body = request.getBody();
assertTrue(body instanceof MultiValueMap<?, ?>);
MultiValueMap<?, ?> map = (MultiValueMap <?, ?>) body;
List<?> aValue = map.get("a");
assertEquals(2, aValue.size());
assertEquals("Philadelphia", aValue.get(0).toString());
assertEquals("Ambler", aValue.get(1).toString());
List<?> bValue = map.get("b");
assertEquals(0, bValue.size());
List<?> cValue = map.get("c");
assertEquals(1, cValue.size());
assertEquals("Mohnton", cValue.get(0).toString());
assertEquals(MediaType.MULTIPART_FORM_DATA, request.getHeaders().getContentType());
}
@@ -486,7 +488,7 @@ public class HttpRequestExecutingMessageHandlerTests {
assertNull(map.get("c").get(0));
assertEquals(MediaType.APPLICATION_FORM_URLENCODED, request.getHeaders().getContentType());
}
@SuppressWarnings("cast")
@Test
public void contentAsByteArray() throws Exception {
@@ -494,7 +496,7 @@ public class HttpRequestExecutingMessageHandlerTests {
MockRestTemplate template = new MockRestTemplate();
new DirectFieldAccessor(handler).setPropertyValue("restTemplate", template);
handler.setHttpMethod(HttpMethod.POST);
byte[] bytes = "Hello World".getBytes();
Message<?> message = MessageBuilder.withPayload(bytes).build();
Exception exception = null;
@@ -511,14 +513,14 @@ public class HttpRequestExecutingMessageHandlerTests {
assertEquals("Hello World", new String((byte[])bytes));
assertEquals(MediaType.APPLICATION_OCTET_STREAM, request.getHeaders().getContentType());
}
@Test
public void contentAsXmlSource() throws Exception {
HttpRequestExecutingMessageHandler handler = new HttpRequestExecutingMessageHandler("http://www.springsource.org/spring-integration");
MockRestTemplate template = new MockRestTemplate();
new DirectFieldAccessor(handler).setPropertyValue("restTemplate", template);
handler.setHttpMethod(HttpMethod.POST);
Message<?> message = MessageBuilder.withPayload(mock(Source.class)).build();
Exception exception = null;
try {
@@ -533,28 +535,28 @@ public class HttpRequestExecutingMessageHandlerTests {
assertTrue(body instanceof Source);
assertEquals(MediaType.TEXT_XML, request.getHeaders().getContentType());
}
@Test // no asertions just a warn message in a log
public void testWarnMessageForNonPostPutAndExtractPayload() throws Exception {
// should see a warn message
HttpRequestExecutingMessageHandler handler = new HttpRequestExecutingMessageHandler("http://www.springsource.org/spring-integration");
MockRestTemplate template = new MockRestTemplate();
new DirectFieldAccessor(handler).setPropertyValue("restTemplate", template);
handler.setHttpMethod(HttpMethod.GET);
handler.setExtractPayload(true);
handler.afterPropertiesSet();
// should not see a warn message since 'setExtractPayload' is not set explicitly
handler = new HttpRequestExecutingMessageHandler("http://www.springsource.org/spring-integration");
template = new MockRestTemplate();
new DirectFieldAccessor(handler).setPropertyValue("restTemplate", template);
handler.setHttpMethod(HttpMethod.GET);
handler.afterPropertiesSet();
// should not see a warn message since HTTP method is not GET
handler = new HttpRequestExecutingMessageHandler("http://www.springsource.org/spring-integration");
template = new MockRestTemplate();
new DirectFieldAccessor(handler).setPropertyValue("restTemplate", template);
@@ -562,7 +564,7 @@ public class HttpRequestExecutingMessageHandlerTests {
handler.setExtractPayload(true);
handler.afterPropertiesSet();
}
@Test
public void contentTypeIsNotSetForGetRequest() throws Exception {
//GET
@@ -570,7 +572,7 @@ public class HttpRequestExecutingMessageHandlerTests {
MockRestTemplate template = new MockRestTemplate();
new DirectFieldAccessor(handler).setPropertyValue("restTemplate", template);
handler.setHttpMethod(HttpMethod.GET);
Message<?> message = MessageBuilder.withPayload(mock(Source.class)).build();
Exception exception = null;
try {
@@ -582,18 +584,18 @@ public class HttpRequestExecutingMessageHandlerTests {
assertEquals("intentional", exception.getCause().getMessage());
HttpEntity<?> request = template.lastRequestEntity.get();
assertNull(request.getHeaders().getContentType());
/* TODO: reconsider the inclusion of content-type for various HttpMethods (only ignoring for GET as of 2.0.5)
* uncomment code below accordingly (see INT-1951)
*/
*/
/*
//HEAD
handler = new HttpRequestExecutingMessageHandler("http://www.springsource.org/spring-integration");
template = new MockRestTemplate();
new DirectFieldAccessor(handler).setPropertyValue("restTemplate", template);
handler.setHttpMethod(HttpMethod.HEAD);
message = MessageBuilder.withPayload(mock(Source.class)).build();
exception = null;
try {
@@ -605,13 +607,13 @@ public class HttpRequestExecutingMessageHandlerTests {
assertEquals("intentional", exception.getCause().getMessage());
request = template.lastRequestEntity.get();
assertNull(request.getHeaders().getContentType());
//DELETE
handler = new HttpRequestExecutingMessageHandler("http://www.springsource.org/spring-integration");
template = new MockRestTemplate();
new DirectFieldAccessor(handler).setPropertyValue("restTemplate", template);
handler.setHttpMethod(HttpMethod.DELETE);
message = MessageBuilder.withPayload(mock(Source.class)).build();
exception = null;
try {
@@ -623,13 +625,13 @@ public class HttpRequestExecutingMessageHandlerTests {
assertEquals("intentional", exception.getCause().getMessage());
request = template.lastRequestEntity.get();
assertNull(request.getHeaders().getContentType());
//TRACE
handler = new HttpRequestExecutingMessageHandler("http://www.springsource.org/spring-integration");
template = new MockRestTemplate();
new DirectFieldAccessor(handler).setPropertyValue("restTemplate", template);
handler.setHttpMethod(HttpMethod.TRACE);
message = MessageBuilder.withPayload(mock(Source.class)).build();
exception = null;
try {
@@ -652,11 +654,27 @@ public class HttpRequestExecutingMessageHandlerTests {
// It's just enough if it was sent successfully from chain without any failures
}
@Test
public void testUriExpression() {
MockRestTemplate restTemplate = new MockRestTemplate();
HttpRequestExecutingMessageHandler handler = new HttpRequestExecutingMessageHandler(
new SpelExpressionParser().parseExpression("headers['foo']"),
restTemplate);
String theURL = "http://bar/baz";
Message<?> message = MessageBuilder.withPayload("").setHeader("foo", theURL).build();
try {
handler.handleRequestMessage(message);
}
catch (Exception e) {}
assertEquals(theURL, restTemplate.actualUrl.get());
}
public static class City{
private String name;
public City(String name){
this.name = name;
}
@Override
public String toString(){
return name;
}
@@ -665,15 +683,18 @@ public class HttpRequestExecutingMessageHandlerTests {
private static class MockRestTemplate extends RestTemplate {
private final AtomicReference<HttpEntity<?>> lastRequestEntity = new AtomicReference<HttpEntity<?>>();
private final AtomicReference<String> actualUrl = new AtomicReference<String>();
@Override
public <T> ResponseEntity<T> exchange(String url, HttpMethod method, HttpEntity<?> requestEntity,
Class<T> responseType, Map<String, ?> uriVariables) throws RestClientException {
this.actualUrl.set(url);
this.lastRequestEntity.set(requestEntity);
throw new RuntimeException("intentional");
}
}
@SuppressWarnings("unused")
private static class MockRestTemplate2 extends RestTemplate {
@Override
@@ -683,5 +704,4 @@ public class HttpRequestExecutingMessageHandlerTests {
}
}
}

View File

@@ -279,6 +279,22 @@ In the case of the Outbound Gateway, the reply message produced by the gateway w
order="3"
auto-startup="false"/>]]></programlisting>
</para>
<note>
<para>
To specify the URL; you can use either the 'url' attribute
or the 'url-expression' attribute. The 'url' is a simple string (with placedholders for
URI variables, as
described below); the 'url-expression' is a SpEL expression, with the Message as the
root object, enabling dynamic urls. The url resulting from the expression evaluation can
still have placeholders for URI variables.
</para>
<para>
In previous releases, some users used the place holders to replace the entire URL with a URI variable.
Changes in Spring 3.1 can cause some issues with escaped characters, such as '?'. For this
reason, it is recommended that if you wish to generate the URL entirely at runtime, you
use the 'url-expression' attribute.
</para>
</note>
<para>
<emphasis>Mapping URI variables</emphasis>
</para>