Merge pull request #480 from olegz/INT-1760

This commit is contained in:
Gary Russell
2012-06-07 16:27:29 -04:00
12 changed files with 320 additions and 27 deletions

View File

@@ -114,4 +114,30 @@ abstract class HttpAdapterParsingUtils {
}
}
static void setExpectedResponseOrExpression(Element element, ParserContext parserContext, BeanDefinitionBuilder builder){
String expectedResponseType = element.getAttribute("expected-response-type");
String expectedResponseTypeExpression = element.getAttribute("expected-response-type-expression");
boolean hasExpectedResponseType = StringUtils.hasText(expectedResponseType);
boolean hasExpectedResponseTypeExpression = StringUtils.hasText(expectedResponseTypeExpression);
if (hasExpectedResponseType && hasExpectedResponseTypeExpression){
parserContext.getReaderContext().error("The 'expected-response-type' and 'expected-response-type-expression' are mutually exclusive. " +
"You can only have one or the other", element);
}
RootBeanDefinition expressionDef = null;
if (hasExpectedResponseType) {
expressionDef = new RootBeanDefinition(LiteralExpression.class);
expressionDef.getConstructorArgumentValues().addGenericArgumentValue(expectedResponseType);
}
else if (hasExpectedResponseTypeExpression){
expressionDef = new RootBeanDefinition(ExpressionFactoryBean.class);
expressionDef.getConstructorArgumentValues().addGenericArgumentValue(expectedResponseTypeExpression);
}
if (expressionDef != null){
builder.addPropertyValue("expectedResponseTypeExpression", expressionDef);
}
}
}

View File

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

View File

@@ -79,7 +79,9 @@ public class HttpOutboundGatewayParser extends AbstractConsumerEndpointParser {
}
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "charset");
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "extract-request-payload", "extractPayload");
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "expected-response-type");
HttpAdapterParsingUtils.setExpectedResponseOrExpression(element, parserContext, builder);
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "reply-timeout", "sendTimeout");
IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "reply-channel", "outputChannel");
HttpAdapterParsingUtils.configureUriVariableExpressions(builder, element);

View File

@@ -31,6 +31,8 @@ import org.springframework.beans.factory.BeanFactory;
import org.springframework.context.expression.BeanFactoryResolver;
import org.springframework.context.expression.MapAccessor;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.converter.Converter;
import org.springframework.core.convert.support.GenericConversionService;
import org.springframework.expression.Expression;
import org.springframework.expression.common.LiteralExpression;
import org.springframework.expression.spel.support.StandardEvaluationContext;
@@ -89,7 +91,7 @@ public class HttpRequestExecutingMessageHandler extends AbstractReplyProducingMe
private volatile boolean expectReply = true;
private volatile Class<?> expectedResponseType;
private volatile Expression expectedResponseTypeExpression;
private volatile boolean extractPayload = true;
@@ -198,13 +200,26 @@ public class HttpRequestExecutingMessageHandler extends AbstractReplyProducingMe
}
/**
* Specify the expected response type for the REST request.
* If this is null (the default), only the status code will be returned
* as the reply Message payload. To take advantage of the HttpMessageConverters
* Specify the expected response type for the REST request
* otherwise the default response type is {@link ResponseEntity} and will
* be returned as a payload of the reply Message.
* To take advantage of the HttpMessageConverters
* registered on this adapter, provide a different type).
* Also see {@link #setExpectedResponseTypeExpression(Expression)}
*/
public void setExpectedResponseType(Class<?> expectedResponseType) {
this.expectedResponseType = expectedResponseType;
Assert.notNull(expectedResponseType, "'expectedResponseType' must not be null");
this.expectedResponseTypeExpression = new LiteralExpression(expectedResponseType.getName());
}
/**
* Specify the {@link Expression} to determine the type for the expected response
* The returned value of the expression could be an instance of {@link Class} or
* {@link String} representing a fully qualified class name
* Also see {@link #setExpectedResponseTypeExpression(Expression)}
*/
public void setExpectedResponseTypeExpression(Expression expectedResponseTypeExpression) {
this.expectedResponseTypeExpression = expectedResponseTypeExpression;
}
/**
@@ -269,8 +284,19 @@ public class HttpRequestExecutingMessageHandler extends AbstractReplyProducingMe
this.evaluationContext.setBeanResolver(new BeanFactoryResolver(beanFactory));
}
ConversionService conversionService = this.getConversionService();
if (conversionService == null){
conversionService = new GenericConversionService();
}
Assert.isInstanceOf(GenericConversionService.class, conversionService);
GenericConversionService gConversionService = (GenericConversionService) conversionService;
gConversionService.addConverter(new Converter<Class<?>, String>() {
public String convert(Class<?> source) {
return source.getName();
}
});
if (conversionService != null) {
this.evaluationContext.setTypeConverter(new StandardTypeConverter(conversionService));
this.evaluationContext.setTypeConverter(new StandardTypeConverter(gConversionService));
}
}
@@ -294,8 +320,10 @@ public class HttpRequestExecutingMessageHandler extends AbstractReplyProducingMe
}
}
Class<?> expectedResponseType = this.determineExpectedResponseType(requestMessage);
HttpEntity<?> httpRequest = this.generateHttpRequest(requestMessage, httpMethod);
ResponseEntity<?> httpResponse = this.restTemplate.exchange(uri, httpMethod, httpRequest, this.expectedResponseType, uriVariables);
ResponseEntity<?> httpResponse = this.restTemplate.exchange(uri, httpMethod, httpRequest, expectedResponseType, uriVariables);
if (this.expectReply) {
HttpHeaders httpHeaders = httpResponse.getHeaders();
Map<String, Object> headers = this.headerMapper.toHeaders(httpHeaders);
@@ -310,7 +338,7 @@ public class HttpRequestExecutingMessageHandler extends AbstractReplyProducingMe
return replyBuilder.copyHeaders(headers).build();
}
else {
return MessageBuilder.withPayload(httpResponse.getStatusCode()).
return MessageBuilder.withPayload(httpResponse).
copyHeaders(headers).setHeader(org.springframework.integration.http.HttpHeaders.STATUS_CODE, httpResponse.getStatusCode()).
build();
}
@@ -490,4 +518,16 @@ public class HttpRequestExecutingMessageHandler extends AbstractReplyProducingMe
"The 'httpMethodExpression' returned an invalid HTTP Method value: " + strHttpMethod);
return HttpMethod.valueOf(strHttpMethod);
}
private Class<?> determineExpectedResponseType(Message<?> requestMessage) throws Exception{
Class<?> expectedResponseType = null;
String expectedResponseTypeName = null;
if (this.expectedResponseTypeExpression != null){
expectedResponseTypeName = this.expectedResponseTypeExpression.getValue(this.evaluationContext, requestMessage, String.class);
}
if (StringUtils.hasText(expectedResponseTypeName)){
expectedResponseType = Class.forName(expectedResponseTypeName);
}
return expectedResponseType;
}
}

View File

@@ -411,7 +411,9 @@ The String "HTTP_REQUEST_HEADERS" will match against any of the standard HTTP Re
<xsd:attribute name="expected-response-type" type="xsd:string">
<xsd:annotation>
<xsd:documentation>
The expected type to which the response body should be converted.
The expected type to which the response body should be converted.
Default is 'org.springframework.http.ResponseEntity'.
This attribute cannot be provided if expected-response-type-expression has a value
</xsd:documentation>
<xsd:appinfo>
<tool:annotation kind="direct">
@@ -420,6 +422,16 @@ The String "HTTP_REQUEST_HEADERS" will match against any of the standard HTTP Re
</xsd:appinfo>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="expected-response-type-expression" type="xsd:string">
<xsd:annotation>
<xsd:documentation>
SpEL expression to determine the type for the expected response to which the response body should be converted
The returned value of the expression could be an instance of java.lang.Class or
java.lang.String representing a fully qualified class name.
This attribute cannot be provided if expected-response-type has a value
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="message-converters" type="xsd:string">
<xsd:annotation>
<xsd:documentation>
@@ -595,7 +607,9 @@ The String "HTTP_REQUEST_HEADERS" will match against any of the standard HTTP Re
<xsd:attribute name="expected-response-type" type="xsd:string">
<xsd:annotation>
<xsd:documentation>
The expected type to which the response body should be converted.
The expected type to which the response body should be converted.
Default is 'org.springframework.http.ResponseEntity'.
This attribute cannot be provided if expected-response-type-expression has a value
</xsd:documentation>
<xsd:appinfo>
<tool:annotation kind="direct">
@@ -604,6 +618,16 @@ The String "HTTP_REQUEST_HEADERS" will match against any of the standard HTTP Re
</xsd:appinfo>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="expected-response-type-expression" type="xsd:string">
<xsd:annotation>
<xsd:documentation>
SpEL expression to determine the type for the expected response to which the response body should be converted
The returned value of the expression could be an instance of java.lang.Class or
java.lang.String representing a fully qualified class name.
This attribute cannot be provided if expected-response-type has a value
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="charset" type="xsd:string" />
<xsd:attribute name="request-factory" type="xsd:string">
<xsd:annotation>

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.
@@ -118,7 +118,7 @@ public class HttpOutboundChannelAdapterParserTests {
DirectFieldAccessor templateAccessor = new DirectFieldAccessor(handlerAccessor.getPropertyValue("restTemplate"));
ClientHttpRequestFactory requestFactory = (ClientHttpRequestFactory)
templateAccessor.getPropertyValue("requestFactory");
assertEquals(Boolean.class, handlerAccessor.getPropertyValue("expectedResponseType"));
assertEquals(Boolean.class.getName(), TestUtils.getPropertyValue(handler, "expectedResponseTypeExpression", Expression.class).getValue());
assertTrue(requestFactory instanceof SimpleClientHttpRequestFactory);
Object converterListBean = this.applicationContext.getBean("converterList");
assertEquals(converterListBean, templateAccessor.getPropertyValue("messageConverters"));

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2010 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.
@@ -108,7 +108,8 @@ public class HttpOutboundGatewayParserTests {
assertTrue(requestFactory instanceof SimpleClientHttpRequestFactory);
Object converterListBean = this.applicationContext.getBean("converterList");
assertEquals(converterListBean, templateAccessor.getPropertyValue("messageConverters"));
assertEquals(String.class, handlerAccessor.getPropertyValue("expectedResponseType"));
assertEquals(String.class.getName(), TestUtils.getPropertyValue(handler, "expectedResponseTypeExpression", Expression.class).getValue());
Expression uriExpression = (Expression) handlerAccessor.getPropertyValue("uriExpression");
assertEquals("http://localhost/test2", uriExpression.getValue());
assertEquals(HttpMethod.PUT.name(), TestUtils.getPropertyValue(handler, "httpMethodExpression", Expression.class).getExpressionString());

View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:int="http://www.springframework.org/schema/integration"
xmlns:int-http="http://www.springframework.org/schema/integration/http"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration.xsd
http://www.springframework.org/schema/integration/http http://www.springframework.org/schema/integration/http/spring-integration-http-2.2.xsd">
<int-http:outbound-gateway url="http://localhost:51235/testApps/outboundResponse"
request-channel="resTypeSetChannel"
reply-channel="replyChannel"
expected-response-type="java.lang.String"
expected-response-type-expression="payload"/>
<int:channel id="replyChannel">
<int:queue/>
</int:channel>
</beans>

View File

@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:int="http://www.springframework.org/schema/integration"
xmlns:int-http="http://www.springframework.org/schema/integration/http"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration.xsd
http://www.springframework.org/schema/integration/http http://www.springframework.org/schema/integration/http/spring-integration-http-2.2.xsd">
<int-http:outbound-gateway url="http://localhost:51235/testApps/outboundResponse"
request-channel="requestChannel"
reply-channel="replyChannel"/>
<int-http:outbound-gateway url="http://localhost:51235/testApps/outboundResponse"
request-channel="resTypeSetChannel"
reply-channel="replyChannel"
expected-response-type="java.lang.String"/>
<int-http:outbound-gateway url="http://localhost:51235/testApps/outboundResponse"
request-channel="resTypeExpressionSetChannel"
reply-channel="replyChannel"
expected-response-type-expression="payload"/>
<int:channel id="replyChannel">
<int:queue/>
</int:channel>
</beans>

View File

@@ -0,0 +1,154 @@
/*
* 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.integration.http.config;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import java.io.IOException;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import org.springframework.beans.factory.parsing.BeanDefinitionParsingException;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.http.ResponseEntity;
import org.springframework.integration.Message;
import org.springframework.integration.MessageChannel;
import org.springframework.integration.channel.QueueChannel;
import org.springframework.integration.message.GenericMessage;
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
import com.sun.net.httpserver.HttpServer;
/**
* @author Oleg Zhurakousky
*
* see https://jira.springsource.org/browse/INT-2397
*/
public class OutboundResponseTypeTests {
private static HttpServer server;
private static MyHandler httpHandler;
@BeforeClass
public static void createServer() throws Exception {
httpHandler = new MyHandler();
server = HttpServer.create(new InetSocketAddress(51235), 0);
server.createContext("/testApps/outboundResponse", httpHandler);
server.start();
}
@AfterClass
public static void stopServer() throws Exception {
server.stop(0);
}
@Test
public void testDefaultResponseType() throws Exception{
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(
"OutboundResponseTypeTests-context.xml", this.getClass());
MessageChannel channel = context.getBean("requestChannel", MessageChannel.class);
QueueChannel replyChannel = context.getBean("replyChannel", QueueChannel.class);
channel.send(new GenericMessage<String>("Hello"));
Message<?> message = replyChannel.receive(5000);
assertNotNull(message);
assertTrue(message.getPayload() instanceof ResponseEntity);
}
@Test
public void testWithResponseTypeSet() throws Exception{
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(
"OutboundResponseTypeTests-context.xml", this.getClass());
MessageChannel channel = context.getBean("resTypeSetChannel", MessageChannel.class);
QueueChannel replyChannel = context.getBean("replyChannel", QueueChannel.class);
channel.send(new GenericMessage<String>("Hello"));
Message<?> message = replyChannel.receive(5000);
assertNotNull(message);
assertTrue(message.getPayload() instanceof String);
}
@Test
public void testWithResponseTypeExpressionSet() throws Exception{
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(
"OutboundResponseTypeTests-context.xml", this.getClass());
MessageChannel channel = context.getBean("resTypeExpressionSetChannel", MessageChannel.class);
QueueChannel replyChannel = context.getBean("replyChannel", QueueChannel.class);
channel.send(new GenericMessage<String>("java.lang.String"));
Message<?> message = replyChannel.receive(5000);
assertNotNull(message);
assertTrue(message.getPayload() instanceof String);
}
@Test
public void testWithResponseTypeExpressionSetAsClass() throws Exception{
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(
"OutboundResponseTypeTests-context.xml", this.getClass());
MessageChannel channel = context.getBean("resTypeExpressionSetChannel", MessageChannel.class);
QueueChannel replyChannel = context.getBean("replyChannel", QueueChannel.class);
channel.send(new GenericMessage<Class<?>>(String.class));
Message<?> message = replyChannel.receive(5000);
assertNotNull(message);
assertTrue(message.getPayload() instanceof String);
}
@Test(expected=BeanDefinitionParsingException.class)
public void testMutuallyExclusivityInMethodAndMethodExpression() throws Exception{
new ClassPathXmlApplicationContext(
"OutboundResponseTypeTests-context-fail.xml", this.getClass());
}
static class MyHandler implements HttpHandler {
private String httpMethod = "POST";
public void setHttpMethod(String httpMethod){
this.httpMethod = httpMethod;
}
public void handle(HttpExchange t) throws IOException {
String requestMethod = t.getRequestMethod();
String response = null;
if (requestMethod.equalsIgnoreCase(this.httpMethod)){
response = httpMethod;
t.sendResponseHeaders(200, response.length());
}
else {
response = "Request is NOT valid";
t.sendResponseHeaders(404, 0);
}
OutputStream os = t.getResponseBody();
os.write(response.getBytes());
os.close();
}
}
}

View File

@@ -22,9 +22,6 @@ import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
@@ -34,10 +31,6 @@ import java.util.concurrent.atomic.AtomicReference;
import javax.xml.transform.Source;
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
import com.sun.net.httpserver.HttpServer;
import org.junit.Assert;
import org.junit.Test;
import org.springframework.beans.DirectFieldAccessor;
import org.springframework.context.ApplicationContext;
@@ -671,7 +664,7 @@ public class HttpRequestExecutingMessageHandlerTests {
PollableChannel output = ctx.getBean("replyChannel", PollableChannel.class);
Message<?> receive = output.receive();
assertEquals(HttpStatus.OK, receive.getPayload());
assertEquals(HttpStatus.OK, ((ResponseEntity<?>)receive.getPayload()).getStatusCode());
}

View File

@@ -246,8 +246,8 @@ In the case of the Outbound Gateway, the reply message produced by the gateway w
<para>
To configure the outbound gateway you can use the namespace support as well. The following code snippet shows the different configuration options for an outbound Http gateway. Most importantly, notice that the 'http-method' and 'expected-response-type' are provided. Those are two of the most commonly configured values. The
default http-method is POST, and the default response type is <emphasis>null</emphasis>. With a null response type, the payload of the reply Message would only
contain the status code (e.g. 200) as long as it's a successful status (non-successful status codes will throw Exceptions). If you are expecting a different
default http-method is POST, and the default response type is <emphasis>null</emphasis>. With a null response type, the payload of the reply Message would
contain the ResponseEntity as long as it's http status is a success (non-successful status codes will throw Exceptions). If you are expecting a different
type, such as a <classname>String</classname>, then provide that fully-qualified class name as shown below.
</para>
<programlisting language="xml"><![CDATA[<int-http:outbound-gateway id="example"
@@ -268,12 +268,14 @@ In the case of the Outbound Gateway, the reply message produced by the gateway w
<para>
Beginning with Spring Integration 2.2 you can also determine the HTTP Method dynamically using SpEL and the <emphasis>http-method-expression</emphasis> attribute.
Note that this attribute is obviously murually exclusive with <emphasis>http-method</emphasis>
You can also use <code>expected-response-type-expression</code> attribute instead of <code>expected-response-type</code> and
provide any valid SpEL expression that determines the type of the response.
<programlisting language="xml"><![CDATA[<int-http:outbound-gateway id="example"
request-channel="requests"
url="http://localhost/test"
http-method-expression="headers.httpMethod"
extract-request-payload="false"
expected-response-type="java.lang.String"
expected-response-type-expression="payload"
charset="UTF-8"
request-factory="requestFactory"
reply-timeout="1234"