INT-2397 http-method-expression

Add support for http-method-expression attribute to the HTTP Outbound Gateway/Adapter

INT-2397 polishing

INT-2397 polishing based on PR comments

INT-2397 more polishing based on PR comments

INT-2397 polishing

INT-2397 plishing PR comments

INT-2397 polishing
This commit is contained in:
Oleg Zhurakousky
2012-05-31 20:53:23 -04:00
committed by Gary Russell
parent a011f9dda7
commit b105872bb7
12 changed files with 307 additions and 47 deletions

View File

@@ -18,6 +18,8 @@ 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;
@@ -27,7 +29,6 @@ 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
@@ -87,5 +88,30 @@ abstract class HttpAdapterParsingUtils {
}
static void setHttpMethodOrExpression(Element element, ParserContext parserContext, BeanDefinitionBuilder builder){
String httpMethod = element.getAttribute("http-method");
String httpMethodExpression = element.getAttribute("http-method-expression");
boolean hasHttpMethod = StringUtils.hasText(httpMethod);
boolean hasHttpMethodExpression = StringUtils.hasText(httpMethodExpression);
if (hasHttpMethod && hasHttpMethodExpression){
parserContext.getReaderContext().error("The 'http-method' and 'http-method-expression' are mutually exclusive. " +
"You can only have one or the other", element);
}
RootBeanDefinition expressionDef = null;
if (hasHttpMethod) {
expressionDef = new RootBeanDefinition(LiteralExpression.class);
expressionDef.getConstructorArgumentValues().addGenericArgumentValue(httpMethod);
}
else if (hasHttpMethodExpression){
expressionDef = new RootBeanDefinition(ExpressionFactoryBean.class);
expressionDef.getConstructorArgumentValues().addGenericArgumentValue(httpMethodExpression);
}
if (expressionDef != null){
builder.addPropertyValue("httpMethodExpression", expressionDef);
}
}
}

View File

@@ -16,13 +16,15 @@
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.integration.http.outbound.HttpRequestExecutingMessageHandler;
import org.springframework.util.StringUtils;
import org.w3c.dom.Element;
/**
* Parser for the 'outbound-channel-adapter' element of the http namespace.
@@ -36,11 +38,11 @@ public class HttpOutboundChannelAdapterParser extends AbstractOutboundChannelAda
@Override
protected AbstractBeanDefinition parseConsumer(Element element, ParserContext parserContext) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(
"org.springframework.integration.http.outbound.HttpRequestExecutingMessageHandler");
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(HttpRequestExecutingMessageHandler.class);
builder.addPropertyValue("expectReply", false);
HttpAdapterParsingUtils.configureUrlConstructorArg(element, parserContext, builder);
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "http-method");
HttpAdapterParsingUtils.setHttpMethodOrExpression(element, parserContext, builder);
String restTemplate = element.getAttribute("rest-template");
if (StringUtils.hasText(restTemplate)) {

View File

@@ -16,12 +16,14 @@
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.integration.http.outbound.HttpRequestExecutingMessageHandler;
import org.springframework.util.StringUtils;
import org.w3c.dom.Element;
/**
* Parser for the 'outbound-gateway' element of the http namespace.
@@ -39,10 +41,11 @@ public class HttpOutboundGatewayParser extends AbstractConsumerEndpointParser {
@Override
protected BeanDefinitionBuilder parseHandler(Element element, ParserContext parserContext) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(
"org.springframework.integration.http.outbound.HttpRequestExecutingMessageHandler");
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(HttpRequestExecutingMessageHandler.class);
HttpAdapterParsingUtils.configureUrlConstructorArg(element, parserContext, builder);
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "http-method");
HttpAdapterParsingUtils.setHttpMethodOrExpression(element, parserContext, builder);
String restTemplate = element.getAttribute("rest-template");
if (StringUtils.hasText(restTemplate)) {

View File

@@ -56,6 +56,7 @@ import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.util.StringUtils;
import org.springframework.web.client.ResponseErrorHandler;
import org.springframework.web.client.RestTemplate;
@@ -76,9 +77,15 @@ import org.springframework.web.client.RestTemplate;
*/
public class HttpRequestExecutingMessageHandler extends AbstractReplyProducingMessageHandler {
private final Map<String, Expression> uriVariableExpressions = new HashMap<String, Expression>();
private final RestTemplate restTemplate;
private final StandardEvaluationContext evaluationContext;
private final Expression uriExpression;
private volatile HttpMethod httpMethod = HttpMethod.POST;
private volatile Expression httpMethodExpression = new LiteralExpression(HttpMethod.POST.name());
private volatile boolean expectReply = true;
@@ -94,12 +101,6 @@ public class HttpRequestExecutingMessageHandler extends AbstractReplyProducingMe
private volatile HeaderMapper<HttpHeaders> headerMapper = DefaultHttpHeaderMapper.outboundMapper();
private final Map<String, Expression> uriVariableExpressions = new HashMap<String, Expression>();
private final RestTemplate restTemplate;
private final StandardEvaluationContext evaluationContext;
/**
* Create a handler that will send requests to the provided URI.
*/
@@ -152,12 +153,21 @@ public class HttpRequestExecutingMessageHandler extends AbstractReplyProducingMe
this.evaluationContext = sec;
}
/**
* Specify the SpEL {@link Expression} to determine {@link HttpMethod} dynamically
*
* @param httpMethodExpression
*/
public void setHttpMethodExpression(Expression httpMethodExpression) {
Assert.notNull(httpMethodExpression, "'httpMethodExpression' must not be null");
this.httpMethodExpression = httpMethodExpression;
}
/**
* Specify the {@link HttpMethod} for requests. The default method will be POST.
*/
public void setHttpMethod(HttpMethod httpMethod) {
this.httpMethod = httpMethod;
this.httpMethodExpression = new LiteralExpression(httpMethod.name());
}
/**
@@ -262,12 +272,6 @@ public class HttpRequestExecutingMessageHandler extends AbstractReplyProducingMe
if (conversionService != null) {
this.evaluationContext.setTypeConverter(new StandardTypeConverter(conversionService));
}
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 '" +
this.httpMethod + "', and no request body will be sent for that method.");
}
}
}
@Override
@@ -280,8 +284,18 @@ public class HttpRequestExecutingMessageHandler extends AbstractReplyProducingMe
Object value = entry.getValue().getValue(this.evaluationContext, requestMessage, String.class);
uriVariables.put(entry.getKey(), value);
}
HttpEntity<?> httpRequest = this.generateHttpRequest(requestMessage);
ResponseEntity<?> httpResponse = this.restTemplate.exchange(uri, this.httpMethod, httpRequest, this.expectedResponseType, uriVariables);
HttpMethod httpMethod = this.determineHttpMethod(requestMessage);
if (!this.shouldIncludeRequestBody(httpMethod) && this.extractPayloadExplicitlySet){
if (logger.isWarnEnabled()){
logger.warn("The 'extractPayload' attribute has no relevance for the current request since the HTTP Method is '" +
httpMethod + "', and no request body will be sent for that method.");
}
}
HttpEntity<?> httpRequest = this.generateHttpRequest(requestMessage, httpMethod);
ResponseEntity<?> httpResponse = this.restTemplate.exchange(uri, httpMethod, httpRequest, this.expectedResponseType, uriVariables);
if (this.expectReply) {
HttpHeaders httpHeaders = httpResponse.getHeaders();
Map<String, Object> headers = this.headerMapper.toHeaders(httpHeaders);
@@ -332,20 +346,20 @@ public class HttpRequestExecutingMessageHandler extends AbstractReplyProducingMe
}
}
private HttpEntity<?> generateHttpRequest(Message<?> message) throws Exception {
private HttpEntity<?> generateHttpRequest(Message<?> message, HttpMethod httpMethod) throws Exception {
Assert.notNull(message, "message must not be null");
return (this.extractPayload) ? this.createHttpEntityFromPayload(message)
: this.createHttpEntityFromMessage(message);
return (this.extractPayload) ? this.createHttpEntityFromPayload(message, httpMethod)
: this.createHttpEntityFromMessage(message, httpMethod);
}
private HttpEntity<?> createHttpEntityFromPayload(Message<?> message) {
private HttpEntity<?> createHttpEntityFromPayload(Message<?> message, HttpMethod httpMethod) {
Object payload = message.getPayload();
if (payload instanceof HttpEntity<?>) {
// payload is already an HttpEntity, just return it as-is
return (HttpEntity<?>) payload;
}
HttpHeaders httpHeaders = this.mapHeaders(message);
if (!shouldIncludeRequestBody()) {
if (!shouldIncludeRequestBody(httpMethod)) {
return new HttpEntity<Object>(httpHeaders);
}
// otherwise, we are creating a request with a body and need to deal with the content-type header as well
@@ -363,9 +377,9 @@ public class HttpRequestExecutingMessageHandler extends AbstractReplyProducingMe
return new HttpEntity<Object>(payload, httpHeaders);
}
private HttpEntity<?> createHttpEntityFromMessage(Message<?> message) {
private HttpEntity<?> createHttpEntityFromMessage(Message<?> message, HttpMethod httpMethod) {
HttpHeaders httpHeaders = mapHeaders(message);
if (shouldIncludeRequestBody()) {
if (shouldIncludeRequestBody(httpMethod)) {
httpHeaders.setContentType(new MediaType("application", "x-java-serialized-object"));
return new HttpEntity<Object>(message, httpHeaders);
}
@@ -405,8 +419,8 @@ public class HttpRequestExecutingMessageHandler extends AbstractReplyProducingMe
return contentType;
}
private boolean shouldIncludeRequestBody() {
return !HttpMethod.GET.equals(this.httpMethod);
private boolean shouldIncludeRequestBody(HttpMethod httpMethod) {
return !HttpMethod.GET.equals(httpMethod);
}
private MediaType resolveContentType(String content, String charset) {
@@ -470,4 +484,10 @@ public class HttpRequestExecutingMessageHandler extends AbstractReplyProducingMe
return true;
}
private HttpMethod determineHttpMethod(Message<?> requestMessage) {
String strHttpMethod = httpMethodExpression.getValue(this.evaluationContext, requestMessage, String.class);
Assert.isTrue(StringUtils.hasText(strHttpMethod) && !Arrays.asList(HttpMethod.values()).contains(strHttpMethod),
"The 'httpMethodExpression' returned an invalid HTTP Method value: " + strHttpMethod);
return HttpMethod.valueOf(strHttpMethod);
}
}

View File

@@ -360,16 +360,25 @@ The String "HTTP_REQUEST_HEADERS" will match against any of the standard HTTP Re
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="http-method" default="POST">
<xsd:attribute name="http-method">
<xsd:annotation>
<xsd:documentation>
The HTTP method to use when executing requests with this adapter.
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="channel" type="xsd:string">
<xsd:annotation>
<xsd:appinfo>
@@ -499,16 +508,25 @@ The String "HTTP_REQUEST_HEADERS" will match against any of the standard HTTP Re
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="http-method" default="POST">
<xsd:attribute name="http-method">
<xsd:annotation>
<xsd:documentation>
The HTTP method to use when executing requests with this adapter.
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>