diff --git a/spring-integration-http/src/main/java/org/springframework/integration/http/config/HttpAdapterParsingUtils.java b/spring-integration-http/src/main/java/org/springframework/integration/http/config/HttpAdapterParsingUtils.java index bd2420ffe3..7be6243318 100644 --- a/spring-integration-http/src/main/java/org/springframework/integration/http/config/HttpAdapterParsingUtils.java +++ b/spring-integration-http/src/main/java/org/springframework/integration/http/config/HttpAdapterParsingUtils.java @@ -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); + + } + + } diff --git a/spring-integration-http/src/main/java/org/springframework/integration/http/config/HttpOutboundChannelAdapterParser.java b/spring-integration-http/src/main/java/org/springframework/integration/http/config/HttpOutboundChannelAdapterParser.java index 42bed9c4f3..d4342339e4 100644 --- a/spring-integration-http/src/main/java/org/springframework/integration/http/config/HttpOutboundChannelAdapterParser.java +++ b/spring-integration-http/src/main/java/org/springframework/integration/http/config/HttpOutboundChannelAdapterParser.java @@ -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"); diff --git a/spring-integration-http/src/main/java/org/springframework/integration/http/config/HttpOutboundGatewayParser.java b/spring-integration-http/src/main/java/org/springframework/integration/http/config/HttpOutboundGatewayParser.java index faea7c859c..8822aeb57f 100644 --- a/spring-integration-http/src/main/java/org/springframework/integration/http/config/HttpOutboundGatewayParser.java +++ b/spring-integration-http/src/main/java/org/springframework/integration/http/config/HttpOutboundGatewayParser.java @@ -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"); diff --git a/spring-integration-http/src/main/java/org/springframework/integration/http/outbound/HttpRequestExecutingMessageHandler.java b/spring-integration-http/src/main/java/org/springframework/integration/http/outbound/HttpRequestExecutingMessageHandler.java index bb5b0b14fb..58792442a2 100755 --- a/spring-integration-http/src/main/java/org/springframework/integration/http/outbound/HttpRequestExecutingMessageHandler.java +++ b/spring-integration-http/src/main/java/org/springframework/integration/http/outbound/HttpRequestExecutingMessageHandler.java @@ -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 uriVariables = new HashMap(); for (Map.Entry 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 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) content)) { if (this.isMultipart((Map)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((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 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; } diff --git a/spring-integration-http/src/main/resources/org/springframework/integration/http/config/spring-integration-http-2.2.xsd b/spring-integration-http/src/main/resources/org/springframework/integration/http/config/spring-integration-http-2.2.xsd index 6441ff2289..dbe6e31930 100644 --- a/spring-integration-http/src/main/resources/org/springframework/integration/http/config/spring-integration-http-2.2.xsd +++ b/spring-integration-http/src/main/resources/org/springframework/integration/http/config/spring-integration-http-2.2.xsd @@ -333,11 +333,21 @@ The String "HTTP_REQUEST_HEADERS" will match against any of the standard HTTP Re - + + + + + + + @@ -463,13 +473,23 @@ The String "HTTP_REQUEST_HEADERS" will match against any of the standard HTTP Re - + - 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. + + + + + + diff --git a/spring-integration-http/src/test/java/org/springframework/integration/http/config/HttpOutboundChannelAdapterParserTests-context.xml b/spring-integration-http/src/test/java/org/springframework/integration/http/config/HttpOutboundChannelAdapterParserTests-context.xml index b203204a64..77d0a29fbe 100644 --- a/spring-integration-http/src/test/java/org/springframework/integration/http/config/HttpOutboundChannelAdapterParserTests-context.xml +++ b/spring-integration-http/src/test/java/org/springframework/integration/http/config/HttpOutboundChannelAdapterParserTests-context.xml @@ -34,6 +34,16 @@ + + + + + + diff --git a/spring-integration-http/src/test/java/org/springframework/integration/http/config/HttpOutboundChannelAdapterParserTests-url-fail-context.xml b/spring-integration-http/src/test/java/org/springframework/integration/http/config/HttpOutboundChannelAdapterParserTests-url-fail-context.xml new file mode 100644 index 0000000000..a388424d88 --- /dev/null +++ b/spring-integration-http/src/test/java/org/springframework/integration/http/config/HttpOutboundChannelAdapterParserTests-url-fail-context.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + diff --git a/spring-integration-http/src/test/java/org/springframework/integration/http/config/HttpOutboundChannelAdapterParserTests.java b/spring-integration-http/src/test/java/org/springframework/integration/http/config/HttpOutboundChannelAdapterParserTests.java index 7e58590560..5a2c8b5947 100644 --- a/spring-integration-http/src/test/java/org/springframework/integration/http/config/HttpOutboundChannelAdapterParserTests.java +++ b/spring-integration-http/src/test/java/org/springframework/integration/http/config/HttpOutboundChannelAdapterParserTests.java @@ -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 { diff --git a/spring-integration-http/src/test/java/org/springframework/integration/http/config/HttpOutboundGatewayParserTests-context.xml b/spring-integration-http/src/test/java/org/springframework/integration/http/config/HttpOutboundGatewayParserTests-context.xml index 2267de88ab..b86162eb4a 100644 --- a/spring-integration-http/src/test/java/org/springframework/integration/http/config/HttpOutboundGatewayParserTests-context.xml +++ b/spring-integration-http/src/test/java/org/springframework/integration/http/config/HttpOutboundGatewayParserTests-context.xml @@ -40,6 +40,8 @@ + + diff --git a/spring-integration-http/src/test/java/org/springframework/integration/http/config/HttpOutboundGatewayParserTests.java b/spring-integration-http/src/test/java/org/springframework/integration/http/config/HttpOutboundGatewayParserTests.java index 15eef458bf..ef17ea5e2e 100644 --- a/spring-integration-http/src/test/java/org/springframework/integration/http/config/HttpOutboundGatewayParserTests.java +++ b/spring-integration-http/src/test/java/org/springframework/integration/http/config/HttpOutboundGatewayParserTests.java @@ -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 { diff --git a/spring-integration-http/src/test/java/org/springframework/integration/http/outbound/HttpRequestExecutingMessageHandlerTests.java b/spring-integration-http/src/test/java/org/springframework/integration/http/outbound/HttpRequestExecutingMessageHandlerTests.java index 951abb7621..52e5793013 100644 --- a/spring-integration-http/src/test/java/org/springframework/integration/http/outbound/HttpRequestExecutingMessageHandlerTests.java +++ b/spring-integration-http/src/test/java/org/springframework/integration/http/outbound/HttpRequestExecutingMessageHandlerTests.java @@ -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> lastRequestEntity = new AtomicReference>(); + private final AtomicReference actualUrl = new AtomicReference(); @Override public ResponseEntity exchange(String url, HttpMethod method, HttpEntity requestEntity, Class responseType, Map 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 { } } - } diff --git a/src/reference/docbook/http.xml b/src/reference/docbook/http.xml index ca7c48fc0e..6cbc38a038 100644 --- a/src/reference/docbook/http.xml +++ b/src/reference/docbook/http.xml @@ -279,6 +279,22 @@ In the case of the Outbound Gateway, the reply message produced by the gateway w order="3" auto-startup="false"/>]]> + + + 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. + + + 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. + + Mapping URI variables