From 6a9cb75668aec1d5ab913e90a8245cb5b5beae81 Mon Sep 17 00:00:00 2001 From: Artem Bilan Date: Tue, 21 Aug 2012 18:04:45 +0300 Subject: [PATCH] INT-2312 Add HTTP RequestMapping support The general idea is to use Spring-MVC as much as possible. * Introduce `RequestMapping`, `IntegrationRequestMappingHandlerMapping` * Introduce XSD nested element `` for HTTP Inbound Endpoints * Introduce `inboundCommonAttributes` XSD attributeGroup for HTTP Inbound Endpoints * Introduce `IntegrationNamespaceUtils#createExpressionDefIfAttributeDefined` & `IntegrationNamespaceUtils#createDirectChannel` * Remove deprecated `name` attribute * Remove `UriPathHandlerMapping` as superseded by `IntegrationRequestMappingHandlerMapping` * Add documentation for `` - Add description to Reference Manual about `` * Add documentation to section 'What's new' * Add additional test for `` JIRA: https://jira.springsource.org/browse/INT-2312, https://jira.springsource.org/browse/INT-2619 Additional changes: * INT-2528 Remove deprecations in HTTP module - JIRA: https://jira.springsource.org/browse/INT-2528 * Add Jackson 2 support for HTTP-inbound * Using Jackson 2 HttpMessageConverter if Jackson 2 is available in classpath * Make `RequestMapping` public * Introduce `HttpContextUtils` and move `HANDLER_MAPPING_BEAN_NAME` to it * Revert and deprecate public API * Improve JavaDocs * Improve Reference Manual Thanks also to Biju Kunjummen for his incorporated commit. --- .../config/xml/IntegrationNamespaceUtils.java | 55 +- .../config/HttpInboundEndpointParser.java | 159 +++-- .../http/config/HttpNamespaceHandler.java | 2 +- .../HttpRequestHandlingEndpointSupport.java | 170 +++-- .../HttpRequestHandlingMessagingGateway.java | 2 +- ...tegrationRequestMappingHandlerMapping.java | 145 ++++ .../http/inbound/RequestMapping.java | 104 +++ .../http/inbound/UriPathHandlerMapping.java | 46 -- .../http/support/HttpContextUtils.java | 31 + .../config/spring-integration-http-3.0.xsd | 659 ++++++++++-------- .../http/HttpProxyScenarioTests-context.xml | 2 - .../http/HttpProxyScenarioTests.java | 3 +- ...boundChannelAdapterParserTests-context.xml | 58 +- .../HttpInboundChannelAdapterParserTests.java | 108 +-- .../HttpInboundGatewayParserTests-context.xml | 63 +- .../config/HttpInboundGatewayParserTests.java | 9 +- ...gMessagingGatewayWithPathMappingTests.java | 47 +- ...RequestMappingIntegrationTests-context.xml | 86 +++ ...Int2312RequestMappingIntegrationTests.java | 190 +++++ ...tpHeaderMapperFromMessageInboundTests.java | 7 +- .../RetrievingJpaOutboundGatewayParser.java | 3 +- .../MongoDbInboundChannelAdapterParser.java | 6 +- .../mongodb/config/MongoParserUtils.java | 6 +- ...RedisStoreInboundChannelAdapterParser.java | 4 +- src/reference/docbook/http.xml | 207 +++--- src/reference/docbook/whats-new.xml | 17 +- 26 files changed, 1428 insertions(+), 761 deletions(-) create mode 100644 spring-integration-http/src/main/java/org/springframework/integration/http/inbound/IntegrationRequestMappingHandlerMapping.java create mode 100644 spring-integration-http/src/main/java/org/springframework/integration/http/inbound/RequestMapping.java delete mode 100644 spring-integration-http/src/main/java/org/springframework/integration/http/inbound/UriPathHandlerMapping.java create mode 100644 spring-integration-http/src/main/java/org/springframework/integration/http/support/HttpContextUtils.java create mode 100644 spring-integration-http/src/test/java/org/springframework/integration/http/inbound/Int2312RequestMappingIntegrationTests-context.xml create mode 100644 spring-integration-http/src/test/java/org/springframework/integration/http/inbound/Int2312RequestMappingIntegrationTests.java diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/xml/IntegrationNamespaceUtils.java b/spring-integration-core/src/main/java/org/springframework/integration/config/xml/IntegrationNamespaceUtils.java index 9fbf517132..0c15ed6248 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/config/xml/IntegrationNamespaceUtils.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/config/xml/IntegrationNamespaceUtils.java @@ -20,7 +20,6 @@ import java.util.List; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; - import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanDefinitionHolder; import org.springframework.beans.factory.config.RuntimeBeanReference; @@ -40,6 +39,8 @@ import org.springframework.integration.endpoint.AbstractPollingEndpoint; import org.springframework.transaction.interceptor.DefaultTransactionAttribute; import org.springframework.transaction.interceptor.MatchAlwaysTransactionAttributeSource; import org.springframework.transaction.interceptor.TransactionInterceptor; +import org.springframework.beans.factory.support.BeanDefinitionReaderUtils; +import org.springframework.integration.channel.DirectChannel; import org.springframework.util.Assert; import org.springframework.util.StringUtils; import org.springframework.util.xml.DomUtils; @@ -58,11 +59,11 @@ import org.springframework.util.xml.DomUtils; */ public abstract class IntegrationNamespaceUtils { - static final String BASE_PACKAGE = "org.springframework.integration"; - static final String REF_ATTRIBUTE = "ref"; - static final String METHOD_ATTRIBUTE = "method"; - static final String ORDER = "order"; - static final String EXPRESSION_ATTRIBUTE = "expression"; + public static final String BASE_PACKAGE = "org.springframework.integration"; + public static final String REF_ATTRIBUTE = "ref"; + public static final String METHOD_ATTRIBUTE = "method"; + public static final String ORDER = "order"; + public static final String EXPRESSION_ATTRIBUTE = "expression"; public static final String HANDLER_ALIAS_SUFFIX = ".handler"; public static final String REQUEST_HANDLER_ADVICE_CHAIN = "request-handler-advice-chain"; public static final String AUTO_STARTUP = "auto-startup"; @@ -303,7 +304,8 @@ public abstract class IntegrationNamespaceUtils { /** * Utility method to configure HeaderMapper for Inbound and Outbound channel adapters/gateway */ - public static void configureHeaderMapper(Element element, BeanDefinitionBuilder rootBuilder, ParserContext parserContext, Class headerMapperClass, String replyHeaderValue){ + public static void configureHeaderMapper(Element element, BeanDefinitionBuilder rootBuilder, + ParserContext parserContext, Class headerMapperClass, String replyHeaderValue) { String defaultMappedReplyHeadersAttributeName = "mapped-reply-headers"; if (!StringUtils.hasText(replyHeaderValue)){ replyHeaderValue = defaultMappedReplyHeadersAttributeName; @@ -429,11 +431,11 @@ public abstract class IntegrationNamespaceUtils { return adviceChain; } - public static RootBeanDefinition createExpressionDefinitionFromValueOrExpression(String valueElementName, + public static BeanDefinition createExpressionDefinitionFromValueOrExpression(String valueElementName, String expressionElementName, ParserContext parserContext, Element element, boolean oneRequired) { Assert.hasText(valueElementName, "'valueElementName' must not be empty"); - Assert.hasText(expressionElementName, "'expressionElementName' must no be empty"); + Assert.hasText(expressionElementName, "'expressionElementName' must not be empty"); String valueElementValue = element.getAttribute(valueElementName); String expressionElementValue = element.getAttribute(expressionElementName); @@ -450,19 +452,16 @@ public abstract class IntegrationNamespaceUtils { parserContext.getReaderContext().error("One of '" + valueElementName + "' or '" + expressionElementName + "' is required", element); } - RootBeanDefinition expressionDef = null; + BeanDefinition expressionDef = null; if (hasAttributeValue) { expressionDef = new RootBeanDefinition(LiteralExpression.class); expressionDef.getConstructorArgumentValues().addGenericArgumentValue(valueElementValue); } - else if (hasAttributeExpression){ - expressionDef = new RootBeanDefinition(ExpressionFactoryBean.class); - expressionDef.getConstructorArgumentValues().addGenericArgumentValue(expressionElementValue); + else { + expressionDef = createExpressionDefIfAttributeDefined(expressionElementName, element); } return expressionDef; } - - public static void registerSpelFunctionBean(BeanDefinitionRegistry registry, String functionId, String className, String methodSignature) { BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(SpelFunctionFactoryBean.class) @@ -470,4 +469,30 @@ public abstract class IntegrationNamespaceUtils { .addConstructorArgValue(methodSignature); registry.registerBeanDefinition(functionId, builder.getBeanDefinition()); } + public static BeanDefinition createExpressionDefIfAttributeDefined(String expressionElementName, Element element) { + + Assert.hasText(expressionElementName, "'expressionElementName' must no be empty"); + + String expressionElementValue = element.getAttribute(expressionElementName); + + if (StringUtils.hasText(expressionElementValue)){ + BeanDefinitionBuilder expressionDefBuilder = BeanDefinitionBuilder.genericBeanDefinition(ExpressionFactoryBean.class); + expressionDefBuilder.addConstructorArgValue(expressionElementValue); + return expressionDefBuilder.getRawBeanDefinition(); + } + return null; + } + + public static String createDirectChannel(Element element, ParserContext parserContext) { + String channelId = element.getAttribute(ID_ATTRIBUTE); + if (!StringUtils.hasText(channelId)) { + parserContext.getReaderContext().error("The channel-adapter's 'id' attribute is required when no 'channel' " + + "reference has been provided, because that 'id' would be used for the created channel.", element); + } + BeanDefinitionBuilder channelBuilder = BeanDefinitionBuilder.genericBeanDefinition(DirectChannel.class); + BeanDefinitionHolder holder = new BeanDefinitionHolder(channelBuilder.getBeanDefinition(), channelId); + BeanDefinitionReaderUtils.registerBeanDefinition(holder, parserContext.getRegistry()); + return channelId; + } + } diff --git a/spring-integration-http/src/main/java/org/springframework/integration/http/config/HttpInboundEndpointParser.java b/spring-integration-http/src/main/java/org/springframework/integration/http/config/HttpInboundEndpointParser.java index 26cc330366..d60a447358 100644 --- a/spring-integration-http/src/main/java/org/springframework/integration/http/config/HttpInboundEndpointParser.java +++ b/spring-integration-http/src/main/java/org/springframework/integration/http/config/HttpInboundEndpointParser.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,48 +17,51 @@ package org.springframework.integration.http.config; import java.util.List; +import org.w3c.dom.Element; import org.springframework.beans.factory.BeanDefinitionStoreException; -import org.springframework.beans.factory.config.BeanDefinitionHolder; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.parsing.BeanComponentDefinition; import org.springframework.beans.factory.support.AbstractBeanDefinition; import org.springframework.beans.factory.support.BeanDefinitionBuilder; import org.springframework.beans.factory.support.BeanDefinitionReaderUtils; import org.springframework.beans.factory.support.ManagedMap; -import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser; import org.springframework.beans.factory.xml.ParserContext; -import org.springframework.expression.common.LiteralExpression; -import org.springframework.integration.channel.DirectChannel; -import org.springframework.integration.config.ExpressionFactoryBean; import org.springframework.integration.config.xml.IntegrationNamespaceUtils; import org.springframework.integration.http.inbound.HttpRequestHandlingController; import org.springframework.integration.http.inbound.HttpRequestHandlingMessagingGateway; +import org.springframework.integration.http.inbound.IntegrationRequestMappingHandlerMapping; +import org.springframework.integration.http.inbound.RequestMapping; +import org.springframework.integration.http.support.DefaultHttpHeaderMapper; +import org.springframework.integration.http.support.HttpContextUtils; import org.springframework.util.CollectionUtils; import org.springframework.util.StringUtils; import org.springframework.util.xml.DomUtils; -import org.w3c.dom.Element; /** * Parser for the 'inbound-channel-adapter' and 'inbound-gateway' elements * of the 'http' namespace. The constructor's boolean value specifies whether * a reply is to be expected. This value should be 'false' for the * 'inbound-channel-adapter' and 'true' for the 'inbound-gateway'. + * This parser also registers a global Spring-MVC infrastructure bean for + * {@link IntegrationRequestMappingHandlerMapping}; + * see {@link #registerRequestMappingHandlerMappingIfNecessary}. * * @author Mark Fisher * @author Oleg Zhurakousky * @author Gary Russell * @author Biju Kunjummen + * @author Artem Bilan */ public class HttpInboundEndpointParser extends AbstractSingleBeanDefinitionParser { private final boolean expectReply; - public HttpInboundEndpointParser(boolean expectReply) { this.expectReply = expectReply; } - @Override protected String getBeanClassName(Element element) { return element.hasAttribute("view-name") || element.hasAttribute("view-expression") @@ -70,13 +73,10 @@ public class HttpInboundEndpointParser extends AbstractSingleBeanDefinitionParse protected String resolveId(Element element, AbstractBeanDefinition definition, ParserContext parserContext) throws BeanDefinitionStoreException { String id = super.resolveId(element, definition, parserContext); - if (!StringUtils.hasText(id)) { - id = element.getAttribute("name"); - } else { - if (!element.hasAttribute(getInputChannelAttributeName())) { - // the created channel will get the 'id', so the adapter's bean name includes a suffix - id = id + ".adapter"; - } + + if (!element.hasAttribute(getInputChannelAttributeName())) { + // the created channel will get the 'id', so the adapter's bean name includes a suffix + id = id + ".adapter"; } if (!StringUtils.hasText(id)) { id = BeanDefinitionReaderUtils.generateBeanName(definition, parserContext.getRegistry()); @@ -94,20 +94,18 @@ public class HttpInboundEndpointParser extends AbstractSingleBeanDefinitionParse if (this.expectReply) { parserContext.getReaderContext().error( "a '" + inputChannelAttributeName + "' reference is required", element); - } else { - inputChannelRef = createDirectChannel(element, parserContext); + } + else { + inputChannelRef = IntegrationNamespaceUtils.createDirectChannel(element, parserContext); } } builder.addPropertyReference("requestChannel", inputChannelRef); IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "error-channel"); - - IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "path"); - String payloadExpression = element.getAttribute("payload-expression"); - if (StringUtils.hasText(payloadExpression)) { - RootBeanDefinition expressionDef = new RootBeanDefinition(ExpressionFactoryBean.class); - expressionDef.getConstructorArgumentValues().addGenericArgumentValue(payloadExpression); - builder.addPropertyValue("payloadExpression", expressionDef); + BeanDefinition payloadExpressionDef = + IntegrationNamespaceUtils.createExpressionDefIfAttributeDefined("payload-expression", element); + if (payloadExpressionDef != null) { + builder.addPropertyValue("payloadExpression", payloadExpressionDef); } List headerElements = DomUtils.getChildElementsByTagName(element, "header"); @@ -115,12 +113,12 @@ public class HttpInboundEndpointParser extends AbstractSingleBeanDefinitionParse if (!CollectionUtils.isEmpty(headerElements)) { ManagedMap headerElementsMap = new ManagedMap(); for (Element headerElement : headerElements) { - String name = headerElement.getAttribute("name"); - String expression = headerElement.getAttribute("expression"); - if (StringUtils.hasText(expression)){ - RootBeanDefinition expressionDef = new RootBeanDefinition(ExpressionFactoryBean.class); - expressionDef.getConstructorArgumentValues().addGenericArgumentValue(expression); - headerElementsMap.put(name, expressionDef); + String name = headerElement.getAttribute(NAME_ATTRIBUTE); + BeanDefinition headerExpressionDef = + IntegrationNamespaceUtils.createExpressionDefIfAttributeDefined(IntegrationNamespaceUtils.EXPRESSION_ATTRIBUTE, + headerElement); + if (headerExpressionDef != null) { + headerElementsMap.put(name, headerExpressionDef); } } builder.addPropertyValue("headerExpressions", headerElementsMap); @@ -135,71 +133,100 @@ public class HttpInboundEndpointParser extends AbstractSingleBeanDefinitionParse IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "convert-exceptions"); } else { - IntegrationNamespaceUtils.setValueIfAttributeDefined( - builder, element, "send-timeout", "requestTimeout"); + IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "send-timeout", "requestTimeout"); } - IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "supported-methods", "supportedMethodNames"); - IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "request-payload-type"); - String viewName = element.getAttribute("view-name"); - String viewExpression = element.getAttribute("view-expression"); - boolean hasViewName = StringUtils.hasText(viewName); - boolean hasViewExpression = StringUtils.hasText(viewExpression); - if (hasViewName ? hasViewExpression : false) { - parserContext.getReaderContext().error("Only one of 'view' or 'view-expression' is allowed", element); - } - if (hasViewName) { - RootBeanDefinition expressionDef = new RootBeanDefinition(LiteralExpression.class); - expressionDef.getConstructorArgumentValues().addGenericArgumentValue(viewName); - builder.addPropertyValue("viewExpression", expressionDef); - } - else if (hasViewExpression) { - RootBeanDefinition expressionDef = new RootBeanDefinition(ExpressionFactoryBean.class); - expressionDef.getConstructorArgumentValues().addGenericArgumentValue(viewExpression); + + BeanDefinition expressionDef = + IntegrationNamespaceUtils.createExpressionDefinitionFromValueOrExpression("view-name", "view-expression", + parserContext, element, false); + if (expressionDef != null) { builder.addPropertyValue("viewExpression", expressionDef); } + IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "errors-key"); IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "error-code"); IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "message-converters"); IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "merge-with-default-converters"); - - //IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "header-mapper"); String headerMapper = element.getAttribute("header-mapper"); + String mappedRequestHeaders = element.getAttribute("mapped-request-headers"); String mappedResponseHeaders = element.getAttribute("mapped-response-headers"); + boolean hasMappedRequestHeaders = StringUtils.hasText(mappedRequestHeaders); + boolean hasMappedResponseHeaders = StringUtils.hasText(mappedResponseHeaders); if (StringUtils.hasText(headerMapper)) { - if (StringUtils.hasText(mappedRequestHeaders) || StringUtils.hasText(mappedResponseHeaders)) { + if (hasMappedRequestHeaders || hasMappedResponseHeaders) { parserContext.getReaderContext().error("Neither 'mappped-request-headers' or 'mapped-response-headers' " + "attributes are allowed when a 'header-mapper' has been specified.", parserContext.extractSource(element)); } builder.addPropertyReference("headerMapper", headerMapper); } else { - BeanDefinitionBuilder headerMapperBuilder = BeanDefinitionBuilder.genericBeanDefinition( - "org.springframework.integration.http.support.DefaultHttpHeaderMapper"); + BeanDefinitionBuilder headerMapperBuilder = BeanDefinitionBuilder.genericBeanDefinition(DefaultHttpHeaderMapper.class); headerMapperBuilder.setFactoryMethod("inboundMapper"); - IntegrationNamespaceUtils.setValueIfAttributeDefined(headerMapperBuilder, element, "mapped-request-headers", "inboundHeaderNames"); - IntegrationNamespaceUtils.setValueIfAttributeDefined(headerMapperBuilder, element, "mapped-response-headers", "outboundHeaderNames"); + if (hasMappedRequestHeaders) { + headerMapperBuilder.addPropertyValue("inboundHeaderNames", mappedRequestHeaders); + } + if (hasMappedResponseHeaders) { + headerMapperBuilder.addPropertyValue("outboundHeaderNames", mappedResponseHeaders); + } + builder.addPropertyValue("headerMapper", headerMapperBuilder.getBeanDefinition()); } + + BeanDefinition requestMappingDef = this.createRequestMapping(element); + builder.addPropertyValue("requestMapping", requestMappingDef); + + IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "request-payload-type", "requestPayloadType"); + + this.registerRequestMappingHandlerMappingIfNecessary(parserContext); } private String getInputChannelAttributeName() { return this.expectReply ? "request-channel" : "channel"; } - private String createDirectChannel(Element element, ParserContext parserContext) { - String channelId = element.getAttribute("id"); - if (!StringUtils.hasText(channelId)) { - parserContext.getReaderContext().error("The channel-adapter's 'id' attribute is required when no 'channel' " - + "reference has been provided, because that 'id' would be used for the created channel.", element); + private BeanDefinition createRequestMapping(Element element) { + BeanDefinitionBuilder requestMappingDefBuilder = BeanDefinitionBuilder.genericBeanDefinition(RequestMapping.class); + + String methods = element.getAttribute("supported-methods"); + if (StringUtils.hasText(methods)) { + requestMappingDefBuilder.addPropertyValue("methods", methods.toUpperCase()); } - BeanDefinitionBuilder channelBuilder = BeanDefinitionBuilder.genericBeanDefinition(DirectChannel.class); - BeanDefinitionHolder holder = new BeanDefinitionHolder(channelBuilder.getBeanDefinition(), channelId); - BeanDefinitionReaderUtils.registerBeanDefinition(holder, parserContext.getRegistry()); - return channelId; + + IntegrationNamespaceUtils.setValueIfAttributeDefined(requestMappingDefBuilder, element, "path", "pathPatterns"); + + Element requestMappingElement = DomUtils.getChildElementByTagName(element, "request-mapping"); + + if (requestMappingElement != null) { + for (String requestMappingAttribute : new String[]{"params", "headers", "consumes", "produces"}) { + IntegrationNamespaceUtils.setValueIfAttributeDefined(requestMappingDefBuilder, requestMappingElement, + requestMappingAttribute); + } + } + + return requestMappingDefBuilder.getRawBeanDefinition(); } + + /** + * This method will auto-register an {@link IntegrationRequestMappingHandlerMapping} + * which could also be overridden by the user by simply registering + * a {@link IntegrationRequestMappingHandlerMapping} {@code } with 'id' + * {@link HttpContextUtils#HANDLER_MAPPING_BEAN_NAME}. + */ + private void registerRequestMappingHandlerMappingIfNecessary(ParserContext parserContext) { + if (!parserContext.getRegistry().containsBeanDefinition(HttpContextUtils.HANDLER_MAPPING_BEAN_NAME)) { + BeanDefinitionBuilder requestMappingBuilder = + BeanDefinitionBuilder.genericBeanDefinition(IntegrationRequestMappingHandlerMapping.class); + requestMappingBuilder.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); + requestMappingBuilder.addPropertyValue(IntegrationNamespaceUtils.ORDER, 0); + BeanComponentDefinition requestMappingComponent = + new BeanComponentDefinition(requestMappingBuilder.getBeanDefinition(), HttpContextUtils.HANDLER_MAPPING_BEAN_NAME); + parserContext.registerBeanComponent(requestMappingComponent); + } + } + } diff --git a/spring-integration-http/src/main/java/org/springframework/integration/http/config/HttpNamespaceHandler.java b/spring-integration-http/src/main/java/org/springframework/integration/http/config/HttpNamespaceHandler.java index 28931f891a..2bcdfc130c 100644 --- a/spring-integration-http/src/main/java/org/springframework/integration/http/config/HttpNamespaceHandler.java +++ b/spring-integration-http/src/main/java/org/springframework/integration/http/config/HttpNamespaceHandler.java @@ -20,7 +20,7 @@ import org.springframework.integration.config.xml.AbstractIntegrationNamespaceHa /** * Namespace handler for Spring Integration's http namespace. - * + * * @author Mark Fisher * @since 1.0.2 */ diff --git a/spring-integration-http/src/main/java/org/springframework/integration/http/inbound/HttpRequestHandlingEndpointSupport.java b/spring-integration-http/src/main/java/org/springframework/integration/http/inbound/HttpRequestHandlingEndpointSupport.java index 957e6fc879..9ebce05a6d 100644 --- a/spring-integration-http/src/main/java/org/springframework/integration/http/inbound/HttpRequestHandlingEndpointSupport.java +++ b/spring-integration-http/src/main/java/org/springframework/integration/http/inbound/HttpRequestHandlingEndpointSupport.java @@ -59,24 +59,22 @@ import org.springframework.integration.http.support.DefaultHttpHeaderMapper; import org.springframework.integration.mapping.HeaderMapper; import org.springframework.integration.support.MessageBuilder; import org.springframework.integration.support.json.JacksonJsonUtils; -import org.springframework.util.AntPathMatcher; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; import org.springframework.util.CollectionUtils; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; -import org.springframework.util.PathMatcher; -import org.springframework.util.StringUtils; +import org.springframework.util.ObjectUtils; import org.springframework.web.multipart.MultipartHttpServletRequest; import org.springframework.web.multipart.MultipartResolver; import org.springframework.web.servlet.DispatcherServlet; -import org.springframework.web.util.UrlPathHelper; +import org.springframework.web.servlet.HandlerMapping; /** * Base class for HTTP request handling endpoints. *

- * By default GET and POST requests are accepted, but the 'supportedMethods' property may be set to include others or - * limit the options (e.g. POST only). A GET request will generate a payload containing its 'parameterMap' while a POST + * By default GET and POST requests are accepted via a supplied default instance of {@link RequestMapping}. + * A GET request will generate a payload containing its 'parameterMap' while a POST * request will be converted to a Message payload according to the registered {@link HttpMessageConverter}s. Several are * registered by default, but the list can be explicitly set via {@link #setMessageConverters(List)}. *

@@ -84,14 +82,14 @@ import org.springframework.web.util.UrlPathHelper; * reference to a {@code HeaderMapper} implementation * to the {@link #setHeaderMapper(HeaderMapper)} method. *

- * The behavior is "request/reply" by default. Pass false to the constructor to force send-only as opposed + * The behavior is "request/reply" by default. Pass {@code false} to the constructor to force send-only as opposed * to sendAndReceive. Send-only means that as soon as the Message is created and passed to the * {@link #setRequestChannel(org.springframework.integration.MessageChannel) request channel}, a response will be * generated. Subclasses determine how that response is generated (e.g. simple status response or rendering a View). *

* In a request-reply scenario, the reply Message's payload will be extracted prior to generating a response by default. * To have the entire serialized Message available for the response, switch the {@link #extractReplyPayload} value to - * false. + * {@code false}. * * @author Mark Fisher * @author Oleg Zhurakousky @@ -101,20 +99,22 @@ import org.springframework.web.util.UrlPathHelper; * @since 2.0 */ public abstract class HttpRequestHandlingEndpointSupport extends MessagingGatewaySupport - implements OrderlyShutdownCapable { + implements OrderlyShutdownCapable { private static final boolean jaxb2Present = ClassUtils.isPresent("javax.xml.bind.Binder", HttpRequestHandlingEndpointSupport.class.getClassLoader()); - private static boolean romePresent = ClassUtils.isPresent("com.sun.syndication.feed.WireFeed", HttpRequestHandlingEndpointSupport.class.getClassLoader()); + private static final List nonReadableBodyHttpMethods = + Arrays.asList(HttpMethod.GET, HttpMethod.HEAD, HttpMethod.OPTIONS); + private final List> defaultMessageConverters = new ArrayList>(); private volatile List> messageConverters = new ArrayList>(); - private volatile List supportedMethods = Arrays.asList(HttpMethod.GET, HttpMethod.POST); + private volatile RequestMapping requestMapping = new RequestMapping(); private volatile Class requestPayloadType = null; @@ -126,12 +126,6 @@ public abstract class HttpRequestHandlingEndpointSupport extends MessagingGatewa private final boolean expectReply; - private volatile String path; - - private final UrlPathHelper urlPathHelper = new UrlPathHelper(); - - private final PathMatcher pathMatcher = new AntPathMatcher(); - private volatile boolean extractReplyPayload = true; private volatile MultipartResolver multipartResolver; @@ -162,27 +156,27 @@ public abstract class HttpRequestHandlingEndpointSupport extends MessagingGatewa if (jaxb2Present) { this.defaultMessageConverters.add(new Jaxb2RootElementHttpMessageConverter()); if (logger.isDebugEnabled()) { - logger.debug("'Jaxb2RootElementHttpMessageConverter' was added to the 'messageConverters'."); + logger.debug("'Jaxb2RootElementHttpMessageConverter' was added to the 'defaultMessageConverters'."); } } if (JacksonJsonUtils.isJackson2Present()) { this.defaultMessageConverters.add(new MappingJackson2HttpMessageConverter()); if (logger.isDebugEnabled()) { - logger.debug("'MappingJackson2HttpMessageConverter' was added to the 'messageConverters'."); + logger.debug("'MappingJackson2HttpMessageConverter' was added to the 'defaultMessageConverters'."); } } else if (JacksonJsonUtils.isJacksonPresent()) { this.defaultMessageConverters.add(new MappingJacksonHttpMessageConverter()); if (logger.isDebugEnabled()) { - logger.debug("'MappingJacksonHttpMessageConverter' was added to the 'messageConverters'."); + logger.debug("'MappingJacksonHttpMessageConverter' was added to the 'defaultMessageConverters'."); } } if (romePresent) { this.defaultMessageConverters.add(new AtomFeedHttpMessageConverter()); this.defaultMessageConverters.add(new RssChannelHttpMessageConverter()); if (logger.isDebugEnabled()) { - logger.debug("'AtomFeedHttpMessageConverter' was added to the 'messageConverters'."); - logger.debug("'RssChannelHttpMessageConverter' was added to the 'messageConverters'."); + logger.debug("'AtomFeedHttpMessageConverter' was added to the 'defaultMessageConverters'."); + logger.debug("'RssChannelHttpMessageConverter' was added to the 'defaultMessageConverters'."); } } } @@ -197,13 +191,20 @@ public abstract class HttpRequestHandlingEndpointSupport extends MessagingGatewa /** * Set the path template for which this endpoint expects requests. * May include path variable {keys} to match against. + * @deprecated since 3.0 in favor of {@linkplain #requestMapping} */ + @Deprecated public void setPath(String path) { - this.path = path; + this.requestMapping.setPathPatterns(path); } + /** + * @deprecated since 3.0 in favor of {@linkplain #requestMapping} + */ + @Deprecated String getPath() { - return path; + String[] pathPatterns = this.requestMapping.getPathPatterns(); + return !ObjectUtils.isEmpty(pathPatterns) ? pathPatterns[0] : null; } /** @@ -263,23 +264,25 @@ public abstract class HttpRequestHandlingEndpointSupport extends MessagingGatewa } /** - * Specify the supported request method names for this gateway. By default, only GET and POST are supported. + * Set the {@link RequestMapping} which allows you to specify a flexible RESTFul-mapping for this endpoint. */ - public void setSupportedMethodNames(String... supportedMethods) { - Assert.notEmpty(supportedMethods, "at least one supported method is required"); - HttpMethod[] methodArray = new HttpMethod[supportedMethods.length]; - for (int i = 0; i < methodArray.length; i++) { - methodArray[i] = HttpMethod.valueOf(supportedMethods[i].toUpperCase()); - } - this.supportedMethods = Arrays.asList(methodArray); + public void setRequestMapping(RequestMapping requestMapping) { + Assert.notNull(requestMapping, "requestMapping must not be null"); + this.requestMapping = requestMapping; + } + + public RequestMapping getRequestMapping() { + return requestMapping; } /** * Specify the supported request methods for this gateway. By default, only GET and POST are supported. + * @deprecated since 3.0 in favor to {@linkplain #requestMapping} */ + @Deprecated public void setSupportedMethods(HttpMethod... supportedMethods) { Assert.notEmpty(supportedMethods, "at least one supported method is required"); - this.supportedMethods = Arrays.asList(supportedMethods); + this.requestMapping.setMethods(supportedMethods); } /** @@ -308,10 +311,6 @@ public abstract class HttpRequestHandlingEndpointSupport extends MessagingGatewa this.multipartResolver = multipartResolver; } - protected boolean isShuttingDown() { - return this.shuttingDown; - } - @Override public String getComponentType() { return (this.expectReply) ? "http:inbound-gateway" : "http:inbound-channel-adapter"; @@ -329,7 +328,7 @@ public abstract class HttpRequestHandlingEndpointSupport extends MessagingGatewa BeanFactory beanFactory = this.getBeanFactory(); if (this.multipartResolver == null && beanFactory != null) { try { - MultipartResolver multipartResolver = this.getBeanFactory().getBean( + MultipartResolver multipartResolver = beanFactory.getBean( DispatcherServlet.MULTIPART_RESOLVER_BEAN_NAME, MultipartResolver.class); if (logger.isDebugEnabled()) { logger.debug("Using MultipartResolver [" + multipartResolver + "]"); @@ -350,15 +349,10 @@ public abstract class HttpRequestHandlingEndpointSupport extends MessagingGatewa this.validateSupportedMethods(); } - @Override - protected void doStart() { - this.shuttingDown = false; - super.doStart(); - } - /** * Handles the HTTP request by generating a Message and sending it to the request channel. If this gateway's * 'expectReply' property is true, it will also generate a response from the reply Message once received. + * * @return a the response Message */ protected final Message doHandleRequest(HttpServletRequest servletRequest, HttpServletResponse servletResponse) throws IOException { @@ -370,15 +364,11 @@ public abstract class HttpRequestHandlingEndpointSupport extends MessagingGatewa } } - @SuppressWarnings({ "rawtypes", "unchecked" }) + @SuppressWarnings({"rawtypes", "unchecked"}) private Message actualDoHandleRequest(HttpServletRequest servletRequest, HttpServletResponse servletResponse) throws IOException { this.activeCount.incrementAndGet(); try { ServletServerHttpRequest request = this.prepareRequest(servletRequest); - if (!this.supportedMethods.contains(request.getMethod())) { - servletResponse.setStatus(HttpServletResponse.SC_METHOD_NOT_ALLOWED); - return null; - } Object requestBody = null; if (this.isReadable(request)) { @@ -389,18 +379,17 @@ public abstract class HttpRequestHandlingEndpointSupport extends MessagingGatewa StandardEvaluationContext evaluationContext = this.createEvaluationContext(); evaluationContext.setRootObject(httpEntity); - LinkedMultiValueMap requestParams = this.convertParameterMap(servletRequest.getParameterMap()); + MultiValueMap requestParams = this.convertParameterMap(servletRequest.getParameterMap()); evaluationContext.setVariable("requestParams", requestParams); - if (StringUtils.hasText(this.path)) { - String lookupPath = this.urlPathHelper.getLookupPathForRequest(servletRequest); - Map pathVariables = this.pathMatcher.extractUriTemplateVariables(this.path, lookupPath); - if (!pathVariables.isEmpty()) { - if (logger.isDebugEnabled()) { - logger.debug("Mapped path variables: " + pathVariables); - } - evaluationContext.setVariable("pathVariables", pathVariables); + Map pathVariables = + (Map) servletRequest.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE); + + if (!CollectionUtils.isEmpty(pathVariables)) { + if (logger.isDebugEnabled()) { + logger.debug("Mapped path variables: " + pathVariables); } + evaluationContext.setVariable("pathVariables", pathVariables); } Map headers = this.headerMapper.toHeaders(request.getHeaders()); @@ -430,7 +419,7 @@ public abstract class HttpRequestHandlingEndpointSupport extends MessagingGatewa MessageBuilder messageBuilder = null; - if (payload instanceof Message){ + if (payload instanceof Message) { messageBuilder = MessageBuilder.fromMessage((Message) payload).copyHeadersIfAbsent(headers); } else { @@ -471,15 +460,14 @@ public abstract class HttpRequestHandlingEndpointSupport extends MessagingGatewa * Converts the reply message to the appropriate HTTP reply object and * sets up the {@link ServletServerHttpResponse}. * - * @param response The ServletServerHttpResponse. + * @param response The ServletServerHttpResponse. * @param replyMessage The reply message. - * @return The message payload (if {@link #extractReplyPayload}) otherwise the - * message. + * @return The message payload (if {@link #extractReplyPayload}) otherwise the message. */ protected final Object setupResponseAndConvertReply(ServletServerHttpResponse response, Message replyMessage) { this.headerMapper.fromHeaders(replyMessage.getHeaders(), response.getHeaders()); - HttpStatus httpStatus = this.resolveHttpStatusFromHeaders(((Message) replyMessage).getHeaders()); + HttpStatus httpStatus = this.resolveHttpStatusFromHeaders(replyMessage.getHeaders()); if (httpStatus != null) { response.setStatusCode(httpStatus); } @@ -492,17 +480,6 @@ public abstract class HttpRequestHandlingEndpointSupport extends MessagingGatewa } - /** - * @deprecated As of release 2.2, please use {@link #setupResponseAndConvertReply(ServletServerHttpResponse, Message)} instead. - */ - @Deprecated - protected final Object setupResponseAndConvertReply(HttpServletResponse servletResponse, Message replyMessage) { - ServletServerHttpResponse response = new ServletServerHttpResponse(servletResponse); - Object reply = setupResponseAndConvertReply(response, replyMessage); - response.close(); - return reply; - } - /** * Prepares an instance of {@link ServletServerHttpRequest} from the raw {@link HttpServletRequest}. Also converts * the request into a multipart request to make multiparts available if necessary. If no multipart resolver is set, @@ -525,15 +502,13 @@ public abstract class HttpRequestHandlingEndpointSupport extends MessagingGatewa * Checks if the request has a readable body (not a GET, HEAD, or OPTIONS request) and a Content-Type header. */ private boolean isReadable(ServletServerHttpRequest request) { - HttpMethod method = request.getMethod(); - if (HttpMethod.GET.equals(method) || HttpMethod.HEAD.equals(method) || HttpMethod.OPTIONS.equals(method)) { - return false; - } - return request.getHeaders().getContentType() != null; + return !(CollectionUtils.containsInstance(nonReadableBodyHttpMethods, request.getMethod())) + && request.getHeaders().getContentType() != null; } /** * Clean up any resources used by the given multipart request (if any). + * * @param request current HTTP request * @see MultipartResolver#cleanupMultipart */ @@ -547,12 +522,12 @@ public abstract class HttpRequestHandlingEndpointSupport extends MessagingGatewa * Converts a servlet request's parameterMap to a {@link MultiValueMap}. */ @SuppressWarnings("rawtypes") - private LinkedMultiValueMap convertParameterMap(Map parameterMap) { - LinkedMultiValueMap convertedMap = new LinkedMultiValueMap(); - for (Object key : parameterMap.keySet()) { - String[] values = (String[]) parameterMap.get(key); + private MultiValueMap convertParameterMap(Map parameterMap) { + MultiValueMap convertedMap = new LinkedMultiValueMap(parameterMap.size()); + for (Map.Entry entry : parameterMap.entrySet()) { + String[] values = entry.getValue(); for (String value : values) { - convertedMap.add((String) key, value); + convertedMap.add(entry.getKey(), value); } } return convertedMap; @@ -595,18 +570,28 @@ public abstract class HttpRequestHandlingEndpointSupport extends MessagingGatewa } private void validateSupportedMethods() { - if (this.requestPayloadType != null){ - for (HttpMethod httpMethod : this.supportedMethods) { - if (HttpMethod.GET.equals(httpMethod) || HttpMethod.HEAD.equals(httpMethod) || HttpMethod.OPTIONS.equals(httpMethod)){ - if (logger.isWarnEnabled()){ - logger.warn("The 'requestPayloadType' attribute will have no relevance for one of the specified HTTP methods '" + - httpMethod + "'"); - } - } + if (this.requestPayloadType != null + && CollectionUtils.containsAny(nonReadableBodyHttpMethods, Arrays.asList(this.requestMapping.getMethods()))) { + if (logger.isWarnEnabled()) { + logger.warn("The 'requestPayloadType' attribute will have no relevance for one of the specified HTTP methods '" + + nonReadableBodyHttpMethods + "'"); } } } + /** + * Lifecycle + */ + @Override + protected void doStart() { + this.shuttingDown = false; + super.doStart(); + } + + protected boolean isShuttingDown() { + return this.shuttingDown; + } + public int beforeShutdown() { this.shuttingDown = true; return this.activeCount.get(); @@ -615,4 +600,5 @@ public abstract class HttpRequestHandlingEndpointSupport extends MessagingGatewa public int afterShutdown() { return this.activeCount.get(); } + } diff --git a/spring-integration-http/src/main/java/org/springframework/integration/http/inbound/HttpRequestHandlingMessagingGateway.java b/spring-integration-http/src/main/java/org/springframework/integration/http/inbound/HttpRequestHandlingMessagingGateway.java index 2c0cd6e353..0bc7a24603 100644 --- a/spring-integration-http/src/main/java/org/springframework/integration/http/inbound/HttpRequestHandlingMessagingGateway.java +++ b/spring-integration-http/src/main/java/org/springframework/integration/http/inbound/HttpRequestHandlingMessagingGateway.java @@ -44,7 +44,7 @@ import org.springframework.web.HttpRequestHandler; * (e.g. 200 OK). *

* The default supported request methods are GET and POST, but the list of values can be configured with the - * {@link #supportedMethods} property. The payload generated from a GET request (or HEAD or OPTIONS if supported) will + * {@link RequestMapping#methods} property. The payload generated from a GET request (or HEAD or OPTIONS if supported) will * be a {@link MultiValueMap} containing the parameter values. For a request containing a body (e.g. a POST), the type * of the payload is determined by the {@link #setRequestPayloadType(Class) request payload type}. *

diff --git a/spring-integration-http/src/main/java/org/springframework/integration/http/inbound/IntegrationRequestMappingHandlerMapping.java b/spring-integration-http/src/main/java/org/springframework/integration/http/inbound/IntegrationRequestMappingHandlerMapping.java new file mode 100644 index 0000000000..e46f7e471c --- /dev/null +++ b/spring-integration-http/src/main/java/org/springframework/integration/http/inbound/IntegrationRequestMappingHandlerMapping.java @@ -0,0 +1,145 @@ +/* + * Copyright 2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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.inbound; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.springframework.util.ReflectionUtils; +import org.springframework.web.HttpRequestHandler; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.method.HandlerMethod; +import org.springframework.web.servlet.HandlerExecutionChain; +import org.springframework.web.servlet.mvc.condition.RequestCondition; +import org.springframework.web.servlet.mvc.method.RequestMappingInfo; +import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; + +/** + * The {@link org.springframework.web.servlet.HandlerMapping} implementation that + * detects and registers {@link RequestMappingInfo}s for {@link HttpRequestHandlingEndpointSupport} + * from a Spring Integration HTTP configuration of + * {@code } and {@code } elements. + *

+ * This class is automatically configured as bean in the application context on the parsing phase of + * the {@code } and {@code } elements, if there is none registered, yet. + * However it can be configured as a regular bean with appropriate configuration for + * {@link RequestMappingHandlerMapping}. It is recommended to have only one similar bean in the application context + * using the 'id' {@link org.springframework.integration.http.support.HttpContextUtils#HANDLER_MAPPING_BEAN_NAME}. + *

+ * In most cases Spring MVC offers to configure Request Mapping via {@link org.springframework.stereotype.Controller} + * and {@link org.springframework.web.bind.annotation.RequestMapping}. + * That's why Spring MVC's Handler Mapping infrastructure relies on {@link org.springframework.web.method.HandlerMethod}, + * as different methods at the same {@link org.springframework.stereotype.Controller} user-class may have their own + * {@link org.springframework.web.bind.annotation.RequestMapping}. On the other side, all Spring Integration HTTP Inbound + * Endpoints are configured on the basis of the same {@link HttpRequestHandlingEndpointSupport} class and there is no + * single {@link RequestMappingInfo} configuration without {@link org.springframework.web.method.HandlerMethod} in Spring MVC. + * Accordingly {@link IntegrationRequestMappingHandlerMapping} is a some {@link org.springframework.web.servlet.HandlerMapping} + * compromise implementation between method-level annotations and component-level (e.g. Spring Integration XML) configurations. + * + * @author Artem Bilan + * @see RequestMapping + * @see RequestMappingHandlerMapping + * + * @since 3.0 + */ +public final class IntegrationRequestMappingHandlerMapping extends RequestMappingHandlerMapping { + + private static final Method HANDLE_REQUEST_METHOD = ReflectionUtils.findMethod(HttpRequestHandler.class, + "handleRequest", HttpServletRequest.class, HttpServletResponse.class); + + private static final Method CREATE_REQUEST_MAPPING_INFO_METHOD; + + static { + /** + * Need for full reuse {@link RequestMappingHandlerMapping}'s logic + * and makes this class Spring MVC version independent. + */ + CREATE_REQUEST_MAPPING_INFO_METHOD = ReflectionUtils.findMethod(RequestMappingHandlerMapping.class, + "createRequestMappingInfo", org.springframework.web.bind.annotation.RequestMapping.class, RequestCondition.class); + ReflectionUtils.makeAccessible(CREATE_REQUEST_MAPPING_INFO_METHOD); + } + + @Override + protected final boolean isHandler(Class beanType) { + return HttpRequestHandlingEndpointSupport.class.isAssignableFrom(beanType); + } + + @Override + protected final HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) { + if (handler instanceof HandlerMethod) { + HandlerMethod handlerMethod = (HandlerMethod) handler; + handler = handlerMethod.getBean(); + } + return super.getHandlerExecutionChain(handler, request); + } + + @Override + protected final void detectHandlerMethods(Object handler) { + if (handler instanceof String) { + handler = this.getApplicationContext().getBean((String) handler); + } + RequestMappingInfo mapping = this.getMappingForEndpoint((HttpRequestHandlingEndpointSupport) handler); + this.registerHandlerMethod(handler, HANDLE_REQUEST_METHOD, mapping); + } + + /** + * Created a {@link RequestMappingInfo} from a 'Spring Integration HTTP Inbound Endpoint' {@link RequestMapping}. + * + * @see RequestMappingHandlerMapping#getMappingForMethod + */ + private RequestMappingInfo getMappingForEndpoint(HttpRequestHandlingEndpointSupport endpoint) { + final RequestMapping requestMapping = endpoint.getRequestMapping(); + + org.springframework.web.bind.annotation.RequestMapping requestMappingAnnotation = + new org.springframework.web.bind.annotation.RequestMapping() { + public String[] value() { + return requestMapping.getPathPatterns(); + } + + public RequestMethod[] method() { + return requestMapping.getRequestMethods(); + } + + public String[] params() { + return requestMapping.getParams(); + } + + public String[] headers() { + return requestMapping.getHeaders(); + } + + public String[] consumes() { + return requestMapping.getConsumes(); + } + + public String[] produces() { + return requestMapping.getProduces(); + } + + public Class annotationType() { + return org.springframework.web.bind.annotation.RequestMapping.class; + } + }; + + Object[] createRequestMappingInfoParams = new Object[]{requestMappingAnnotation, this.getCustomTypeCondition(endpoint.getClass())}; + return (RequestMappingInfo) ReflectionUtils.invokeMethod(CREATE_REQUEST_MAPPING_INFO_METHOD, this, createRequestMappingInfoParams); + } + +} diff --git a/spring-integration-http/src/main/java/org/springframework/integration/http/inbound/RequestMapping.java b/spring-integration-http/src/main/java/org/springframework/integration/http/inbound/RequestMapping.java new file mode 100644 index 0000000000..c343410c16 --- /dev/null +++ b/spring-integration-http/src/main/java/org/springframework/integration/http/inbound/RequestMapping.java @@ -0,0 +1,104 @@ +/* + * Copyright 2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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.inbound; + +import org.springframework.http.HttpMethod; +import org.springframework.util.Assert; +import org.springframework.web.bind.annotation.RequestMethod; + +/** + * Class for mapping web requests onto specific {@link HttpRequestHandlingEndpointSupport}. + * Provides direct mapping in terms of functionality compared to + * {@link org.springframework.web.bind.annotation.RequestMapping}. + * + * @author Artem Bilan + * @since 3.0 + * + * @see org.springframework.web.bind.annotation.RequestMapping + * @see IntegrationRequestMappingHandlerMapping + */ +public class RequestMapping { + + private String[] pathPatterns; + + private HttpMethod[] methods = new HttpMethod[]{HttpMethod.GET, HttpMethod.POST}; + + private String[] params; + + private String[] headers; + + private String[] consumes; + + private String[] produces; + + public void setPathPatterns(String... pathPatterns) { + this.pathPatterns = pathPatterns; + } + + public String[] getPathPatterns() { + return pathPatterns; + } + + public void setMethods(HttpMethod... supportedMethods) { + Assert.notEmpty(supportedMethods, "at least one supported methods is required"); + this.methods = supportedMethods; + } + + public HttpMethod[] getMethods() { + return methods; + } + + public void setParams(String... params) { + this.params = params; + } + + public String[] getParams() { + return params; + } + + public void setHeaders(String... headers) { + this.headers = headers; + } + + public String[] getHeaders() { + return headers; + } + + public void setConsumes(String... consumes) { + this.consumes = consumes; + } + + public String[] getConsumes() { + return consumes; + } + + public void setProduces(String... produces) { + this.produces = produces; + } + + public String[] getProduces() { + return produces; + } + + public RequestMethod[] getRequestMethods() { + RequestMethod[] requestMethods = new RequestMethod[this.methods.length]; + for (int i = 0; i < this.methods.length; i++) { + requestMethods[i] = RequestMethod.valueOf(this.methods[i].name()); + } + return requestMethods; + } + +} diff --git a/spring-integration-http/src/main/java/org/springframework/integration/http/inbound/UriPathHandlerMapping.java b/spring-integration-http/src/main/java/org/springframework/integration/http/inbound/UriPathHandlerMapping.java deleted file mode 100644 index 971f11abd9..0000000000 --- a/spring-integration-http/src/main/java/org/springframework/integration/http/inbound/UriPathHandlerMapping.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright 2002-2011 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.inbound; - -import org.springframework.web.servlet.handler.AbstractDetectingUrlHandlerMapping; - -/** - * A {@link org.springframework.web.servlet.HandlerMapping} implementation that matches - * against the value of the 'path' attribute, if present, on a Spring Integration HTTP - * <inbound-channel-adapter> or <inbound-gateway> element. - * - * @author Oleg Zhurakousky - * @author Mark Fisher - * @since 2.1 - */ -public class UriPathHandlerMapping extends AbstractDetectingUrlHandlerMapping { - - @Override - protected String[] determineUrlsForHandler(String beanName) { - String[] urls = null; - Class beanClass = getApplicationContext().getType(beanName); - if (HttpRequestHandlingEndpointSupport.class.isAssignableFrom(beanClass)) { - HttpRequestHandlingEndpointSupport endpoint = getApplicationContext().getBean(beanName, HttpRequestHandlingEndpointSupport.class); - String path = endpoint.getPath(); - if (path != null) { - urls = new String[]{path}; - } - } - return urls; - } - -} diff --git a/spring-integration-http/src/main/java/org/springframework/integration/http/support/HttpContextUtils.java b/spring-integration-http/src/main/java/org/springframework/integration/http/support/HttpContextUtils.java new file mode 100644 index 0000000000..de2eee1817 --- /dev/null +++ b/spring-integration-http/src/main/java/org/springframework/integration/http/support/HttpContextUtils.java @@ -0,0 +1,31 @@ +/* + * Copyright 2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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.support; + +/** + * Utility class for accessing HTTP integration components from the {@link org.springframework.beans.factory.BeanFactory}. + * + * @author Artem Bilan + * @since 3.0 + */ +public final class HttpContextUtils { + + /** + * @see org.springframework.integration.http.config.HttpInboundEndpointParser + */ + public static final String HANDLER_MAPPING_BEAN_NAME = "integrationRequestMappingHandlerMapping"; + +} diff --git a/spring-integration-http/src/main/resources/org/springframework/integration/http/config/spring-integration-http-3.0.xsd b/spring-integration-http/src/main/resources/org/springframework/integration/http/config/spring-integration-http-3.0.xsd index ed26457783..9768ebac03 100644 --- a/spring-integration-http/src/main/resources/org/springframework/integration/http/config/spring-integration-http-3.0.xsd +++ b/spring-integration-http/src/main/resources/org/springframework/integration/http/config/spring-integration-http-3.0.xsd @@ -1,13 +1,14 @@ + xmlns:tool="http://www.springframework.org/schema/tool" + xmlns:integration="http://www.springframework.org/schema/integration" + targetNamespace="http://www.springframework.org/schema/integration/http" elementFormDefault="qualified" + attributeFormDefault="unqualified"> - + + + + Defines an inbound HTTP-based Channel Adapter. + + - - - Defines an inbound HTTP-based Channel Adapter. - - - + + + + Defines configuration for org.springframework.integration.http.inbound.RequestMapping + as RESTFul attributes for Spring Integration HTTP Inbound Endpoints. + + + + + + + Specifies a Message header as a result of expression evaluation + against ServletRequest and URI Variables. + + + - + - [DEPRECATED since v2.1] Use the 'path' attribute if you want - to specify the path or the 'id' attribute if you simply want - to identify this component. - - When using the 'path' attribute, please ensure to also - declare a handler mapping bean of type - 'org.springframework.integration.http.inbound.UriPathHandlerMapping'. - - This bean is used by the Spring MVC DispatcherServlet - to evaluate which URL maps to which inbound endpoint. - For more information please see the chapter on - 'Handler mappings' in the Spring Framework Reference - Documentation. + Maximum amount of time in milliseconds to wait when sending + a message to the channel if such channel may block. + For example, a Queue Channel can block until space + is available if its maximum capacity has been reached. - - - - Target type for payload that is the conversion result of the request. - - - - - - - - - View name to be resolved when rendering a response. - This attribute is not allowed if there is a 'view-expression' attribute. - - - - - - - SpEL expression that resolves to a view to be resolved when rendering a response. - The expression can resolve to a view name or View object. - However, because there is no reply message, the - 'evaluationContext' for this expression is rather lightweight; it has a - 'BeanResolver' but no variables, - so the usage of this attribute is somewhat limited. - An example might be to resolve, at runtime, some scoped Bean that returns a - view name or View object. - This attribute is not allowed if there is a 'view-name' attribute. - - - - - - - In the case that a view-name is specified this attribute can be used to - override the default key of the Errors (if the request cannot be handled). - Defaults to "errors" (similar to normal MVC - usage). - - - - - - - Allows you to specify SpEL expression to construct a Message payload - - - - - - - Allows you to specify the URI path (e.g., /orderId/{order}) - - When using the 'path' attribute, please ensure to also - declare a handler mapping bean of type - 'org.springframework.integration.http.inbound.UriPathHandlerMapping'. - - This bean is used by the Spring MVC DispatcherServlet to - evaluate which URL maps to which inbound endpoint. For - more information please see the chapter on 'Handler mappings' - in the Spring Framework Reference Documentation. - - - - - - - In the case that a view-name is specified this attribute can be used to - override the default error code under which the handling exception is exposed. - Defaults to - "spring.integration.http.handler.error" and is supplied with 3 - parameters: the exception itself, its message and - its stack trace as a String. - - - - - - - List of HttpMessageConverters for this Channel Adapter. - - - - - - - Flag to indicate if the default converters should be registered after any - custom converters. This flag is used only if message-converters - are provided, otherwise all default converters will be registered. - - Defaults to "false" - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + Defines an inbound HTTP-based Messaging Gateway. + + - - - Defines an inbound HTTP-based Messaging Gateway. - - - - + + + + + Defines configuration for org.springframework.integration.http.inbound.RequestMapping + as RESTFul attributes for Spring Integration HTTP Inbound Endpoints. + + + + + + + + + The producible media types of the mapped request, narrowing the primary mapping. + The format is a sequence of media types ("text/plain", "application/*), + with a request only mapped if the Accept matches one of these media types. + Expressions can be negated by using the "!" operator, as in "!text/plain", which matches + all requests with a Accept other than "text/plain". + + + + + + + + + + + Specifies a Message header as a result of expression evaluation + against ServletRequest and URI Variables. + + + @@ -190,87 +107,18 @@ The String "HTTP_REQUEST_HEADERS" will match against any of the standard HTTP Re - - - - - [DEPRECATED since v2.1] Use the 'path' attribute if you want - to specify the path or the 'id' attribute if you simply want - to identify this component. - - When using the 'path' attribute, please ensure to also - declare a handler mapping bean of type - 'org.springframework.integration.http.inbound.UriPathHandlerMapping'. - - This bean is used by the Spring MVC DispatcherServlet - to evaluate which URL maps to which inbound endpoint. - For more information please see the chapter on - 'Handler mappings' in the Spring Framework Reference - Documentation. + The receiving Message Channel of this endpoint. - - - + + - View name to be resolved when rendering a response. - This attribute is not allowed if there is a 'view-expression' attribute. - - - - - - - SpEL expression that resolves to a view to be resolved when rendering a response. - The expression can resolve to a view name or View object. - The root object of the evaluation context is the reply message. - This attribute is not allowed if there is a 'view-name' attribute. - - - - - - - In the case that a view-name is specified this attribute can be used to - override the default key of the Errors (if the request cannot be handled). - Defaults to "errors" (similar to normal MVC usage). - - - - - - - Allows you to specify SpEL expression to construct a Message payload - - - - - - - Allows you to specify the URI path (e.g., /orderId/{order}) - - When using the 'path' attribute, please ensure to also - declare a handler mapping bean of type - 'org.springframework.integration.http.inbound.UriPathHandlerMapping'. - - This bean is used by the Spring MVC DispatcherServlet - to evaluate which URL maps to which inbound endpoint. - For more information please see the chapter on - 'Handler mappings' in the Spring Framework Reference - Documentation. - - - - - - - In the case that a view-name is specified this attribute can be used to - override the default error code under which the handling exception is exposed. - Defaults to "spring.integration.http.handler.error" and is supplied with 3 - parameters: the exception itself, its message and its stack trace as a String. + Specify whether only the reply Message's payload should be passed in the response. + If this is set to 'false', the entire Message will be used to generate the response. + The default is 'true'. @@ -284,59 +132,6 @@ The String "HTTP_REQUEST_HEADERS" will match against any of the standard HTTP Re - - - - Target type for payload that is the conversion result of the request. - - - - - - - List of HttpMessageConverters for this Gateway. - - - - - - - Flag to indicate if the default converters should be registered after any custom - converters. This flag is used only if message-converters - are provided, otherwise all default converters will be registered. - - Defaults to "false" - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + In the case that a view-name is specified this attribute can be used to + override the default key of the MVC Controller's ModelAndView attribute + to keep a reply from underlying message flow. + Default is 'reply'. + + + - + - - + + - Defines an outbound HTTP-based Channel Adapter. + Comma-separated URI paths (e.g., /orderId/{order}). + Ant-style path patterns are also supported (e.g. /myPath/*.do). + + + + + Comma-separated HTTP Method names. Determines which types of Request are + allowed with this Endpoint. + + + + + + + + + + View name to be resolved when rendering a response. + + + + + + + SpEL expression that resolves to a view to be resolved when rendering a response. + The expression can resolve to a view name or View object. + In the case of 'inbound-gateway' the root object of the evaluation context is the reply message. + In the case of 'inbound-channel-adapter' the 'evaluationContext' for this expression + is rather lightweight, because there is no reply message, ; it has a + 'BeanResolver' but no variables, + so the usage of this attribute is somewhat limited. + An example might be to resolve, at runtime, some scoped Bean that returns a + view name or View object. + This attribute is not allowed if there is a 'view-name' attribute. + + + + + + + In the case that a view-name is specified this attribute can be used to + override the default error code under which the handling exception is exposed. + Defaults to "spring.integration.http.handler.error" and is supplied with 3 + parameters: the exception itself, its message and its stack trace as a String. + + + + + + + In the case that a view-name is specified this attribute can be used to + override the default key of the Errors (if the request cannot be handled). + Defaults to "errors" (similar to normal MVC usage). + + + + + + + Target type for payload that is the conversion result of the request. + + + + + + + Allows you to specify SpEL expression to construct a Message payload + + + + + + + List of HttpMessageConverters for this Channel Adapter. + + + + + + + Flag to indicate if the default converters should be registered after any custom + converters. This flag is used only if message-converters + are provided, otherwise all default converters will be registered. + + Defaults to "false" + + + + + + + + + + + + Specifies a reference to org.springframework.integration.mapping.HeaderMapper + implementation bean. Only one of 'header-mapper' or 'mapped-request-headers' attributes + can be provided. + + + + + + + + + + + + + + + + + The MessagingGateway's 'error-channel' where to send an ErrorMessage in case + of Exception is caused from original message flow. + + + + + + + + + Defines an outbound HTTP-based Channel Adapter. + + + - + + + + Specify an expression for URI variable placeholder within 'url'. + + + @@ -438,10 +381,29 @@ The String "HTTP_REQUEST_HEADERS" will match against any of the standard HTTP Re + + The reference to org.springframework.web.client.RestTemplate bean to send the HTTP Request. + + + + + + + Specify the charset name to use for converting String-typed payloads to bytes. + The default is 'UTF-8' + + + + + + + Specify whether the outbound message's payload should be extracted + when preparing the request body. Otherwise the Message instance itself + will be serialized. + The default value is 'true'. + - - @@ -481,6 +443,11 @@ The String "HTTP_REQUEST_HEADERS" will match against any of the standard HTTP Re + + Specify a reference to org.springframework.integration.mapping.HeaderMapper + implementation bean. Only one of 'header-mapper' or 'mapped-request-headers' attributes + can be provided. + @@ -528,16 +495,22 @@ The String "HTTP_REQUEST_HEADERS" will match against any of the standard HTTP Re + + + Defines an outbound HTTP-based Messaging Gateway. + + - - - Defines an outbound HTTP-based Messaging Gateway. - - - + + + + Specify an expression for URI variable placeholder within 'url'. + + + @@ -547,6 +520,9 @@ The String "HTTP_REQUEST_HEADERS" will match against any of the standard HTTP Re + + The receiving Message Channel of this endpoint. + @@ -609,6 +585,12 @@ The String "HTTP_REQUEST_HEADERS" will match against any of the standard HTTP Re + + Specifies a reference to org.springframework.integration.mapping.HeaderMapper + implementation bean. Only one of 'header-mapper' or 'mapped-request-headers'('mapped-response-headers') + attributes + can be provided. + @@ -617,6 +599,9 @@ The String "HTTP_REQUEST_HEADERS" will match against any of the standard HTTP Re + + The reference to org.springframework.web.client.RestTemplate bean to send the HTTP Request. + @@ -640,7 +625,16 @@ The String "HTTP_REQUEST_HEADERS" will match against any of the standard HTTP Re ]]> - + + + + Specifies whether the outbound message's payload should be extracted + when preparing the request body. Otherwise the Message instance itself + will be serialized. + The default value is 'true'. + + + @@ -665,7 +659,14 @@ The String "HTTP_REQUEST_HEADERS" will match against any of the standard HTTP Re - + + + + Specify the charset name to use for converting String-typed payloads to bytes. + The default is 'UTF-8' + + + + + Identifies the channel to which this gateway will subscribe, to receive(send) reply Messages. + + + + + Defines configuration for org.springframework.integration.http.inbound.RequestMapping + as RESTFul attributes for Spring Integration HTTP Inbound Endpoints. + + + + + + The parameters of the mapped request, narrowing the primary mapping. + A sequence of "myParam=myValue" style + expressions, with a request only mapped if each such parameter is found + to have the given value. + Expressions can be negated by using the "!=" operator, + as in "myParam!=myValue". + "myParam" style expressions are also supported, + with such parameters having to be present in the request (allowed to have + any value). + "!myParam" style expressions indicate that the + specified parameter is not supposed to be present in the request. + + + + + + + The headers of the mapped request, narrowing the primary mapping. + A sequence of "My-Header=myValue" style + expressions, with a request only mapped if each such header is found + to have the given value. + Expressions can be negated by using the "!=" operator, + as in "My-Header!=myValue". + "My-Header" style expressions are also supported, + with such headers having to be present in the request (allowed to have + any value). + "!My-Header" style expressions indicate that the + specified header is not supposed to be present in the request. + Also supports media type wildcards (*), for headers such as Accept + and Content-Type. For instance, headers = "content-type=text/*" + will match requests with a Content-Type of "text/html", "text/plain", etc. + + + + + + + The consumable media types of the mapped request, narrowing the primary mapping. + The format is a sequence of media types ("text/plain", "application/*), + with a request only mapped if the Content-Type matches one of these media types. + Expressions can be negated by using the "!" operator, as in "!text/plain", which matches + all requests with a Content-Type other than "text/plain". + + + + + diff --git a/spring-integration-http/src/test/java/org/springframework/integration/http/HttpProxyScenarioTests-context.xml b/spring-integration-http/src/test/java/org/springframework/integration/http/HttpProxyScenarioTests-context.xml index a8e0bdc9a7..dcb766da82 100644 --- a/spring-integration-http/src/test/java/org/springframework/integration/http/HttpProxyScenarioTests-context.xml +++ b/spring-integration-http/src/test/java/org/springframework/integration/http/HttpProxyScenarioTests-context.xml @@ -7,8 +7,6 @@ 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"> - - diff --git a/spring-integration-http/src/test/java/org/springframework/integration/http/HttpProxyScenarioTests.java b/spring-integration-http/src/test/java/org/springframework/integration/http/HttpProxyScenarioTests.java index 6ad55a8638..903f958864 100644 --- a/spring-integration-http/src/test/java/org/springframework/integration/http/HttpProxyScenarioTests.java +++ b/spring-integration-http/src/test/java/org/springframework/integration/http/HttpProxyScenarioTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 the original author or authors. + * Copyright 2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -32,6 +32,7 @@ import org.junit.runner.RunWith; import org.mockito.Mockito; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; + import org.springframework.beans.DirectFieldAccessor; import org.springframework.beans.PropertyAccessor; import org.springframework.beans.factory.annotation.Autowired; diff --git a/spring-integration-http/src/test/java/org/springframework/integration/http/config/HttpInboundChannelAdapterParserTests-context.xml b/spring-integration-http/src/test/java/org/springframework/integration/http/config/HttpInboundChannelAdapterParserTests-context.xml index 044117155e..03487727c8 100644 --- a/spring-integration-http/src/test/java/org/springframework/integration/http/config/HttpInboundChannelAdapterParserTests-context.xml +++ b/spring-integration-http/src/test/java/org/springframework/integration/http/config/HttpInboundChannelAdapterParserTests-context.xml @@ -1,14 +1,18 @@ + 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/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.xsd + http://www.springframework.org/schema/util + http://www.springframework.org/schema/util/spring-util.xsd"> @@ -18,13 +22,13 @@ - + + channel="requests" supported-methods="DELETE" merge-with-default-converters="true"/> + channel="requests" supported-methods="HEAD" /> @@ -34,43 +38,41 @@ - + + + - + + + + mapped-request-headers="foo,bar"> + +

- -
- - - +
- + diff --git a/spring-integration-http/src/test/java/org/springframework/integration/http/config/HttpInboundChannelAdapterParserTests.java b/spring-integration-http/src/test/java/org/springframework/integration/http/config/HttpInboundChannelAdapterParserTests.java index 19fa1ddbed..d86d234170 100644 --- a/spring-integration-http/src/test/java/org/springframework/integration/http/config/HttpInboundChannelAdapterParserTests.java +++ b/spring-integration-http/src/test/java/org/springframework/integration/http/config/HttpInboundChannelAdapterParserTests.java @@ -20,9 +20,9 @@ import static org.hamcrest.CoreMatchers.instanceOf; import static org.hamcrest.MatcherAssert.assertThat; 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 static org.junit.Assert.assertSame; +import static org.junit.Assert.assertArrayEquals; import java.io.ByteArrayOutputStream; import java.io.ObjectOutputStream; @@ -58,7 +58,11 @@ import org.springframework.mock.web.MockHttpServletResponse; import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.util.AntPathMatcher; import org.springframework.util.MultiValueMap; +import org.springframework.web.HttpRequestMethodNotSupportedException; +import org.springframework.web.servlet.HandlerMapping; + /** * @author Mark Fisher @@ -66,7 +70,6 @@ import org.springframework.util.MultiValueMap; * @author Gary Russell * @author Gunnar Hillert * @author Artem Bilan - * @author Gary Russell * @author Biju Kunjummen */ @RunWith(SpringJUnit4ClassRunner.class) @@ -76,6 +79,9 @@ public class HttpInboundChannelAdapterParserTests { @Autowired private PollableChannel requests; + @Autowired + private HandlerMapping integrationRequestMappingHandlerMapping; + @Autowired private HttpRequestHandlingMessagingGateway defaultAdapter; @@ -95,14 +101,6 @@ public class HttpInboundChannelAdapterParserTests { @Autowired private HttpRequestHandlingMessagingGateway inboundAdapterWithExpressions; - @Autowired - @Qualifier("/fname/{blah}/lname/{boo}") - private HttpRequestHandlingMessagingGateway inboundAdapterWithNameAndExpressions; - - @Autowired - @Qualifier("/fname/{f}/lname/{l}") - private HttpRequestHandlingMessagingGateway inboundAdapterWithNameNoPath; - @Autowired @Qualifier("adapterWithCustomConverterNoDefaults") private HttpRequestHandlingMessagingGateway adapterWithCustomConverterNoDefaults; @@ -167,7 +165,15 @@ public class HttpInboundChannelAdapterParserTests { request.setContentType("text/plain"); request.setParameter("foo", "bar"); request.setContent("hello".getBytes()); - request.setRequestURI("/fname/bill/lname/clinton"); + + String requestURI = "/fname/bill/lname/clinton"; + + //See org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping#handleMatch + Map uriTemplateVariables = + new AntPathMatcher().extractUriTemplateVariables("/fname/{f}/lname/{l}", requestURI); + request.setAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, uriTemplateVariables); + + request.setRequestURI(requestURI); MockHttpServletResponse response = new MockHttpServletResponse(); inboundAdapterWithExpressions.handleRequest(request, response); @@ -180,60 +186,20 @@ public class HttpInboundChannelAdapterParserTests { assertEquals("clinton", message.getHeaders().get("lname")); } - @Test // ensure that 'path' takes priority over name - // INT-1677 - public void withNameAndExpressionsAndPath() throws Exception { - MockHttpServletRequest request = new MockHttpServletRequest(); - request.setMethod("POST"); - request.setContentType("text/plain"); - request.setParameter("foo", "bar"); - request.setContent("hello".getBytes()); - request.setRequestURI("/fname/bill/lname/clinton"); - - MockHttpServletResponse response = new MockHttpServletResponse(); - inboundAdapterWithNameAndExpressions.handleRequest(request, response); - assertEquals(HttpServletResponse.SC_OK, response.getStatus()); - Message message = requests.receive(0); - assertNotNull(message); - Object payload = message.getPayload(); - assertTrue(payload instanceof String); - assertEquals("bill", payload); - assertEquals("clinton", message.getHeaders().get("lname")); - } - - @Test(expected=SpelEvaluationException.class) - // INT-1677 - public void withNameAndExpressionsNoPath() throws Exception { - MockHttpServletRequest request = new MockHttpServletRequest(); - request.setMethod("POST"); - request.setContentType("text/plain"); - request.setParameter("foo", "bar"); - request.setContent("hello".getBytes()); - request.setRequestURI("/fname/bill/lname/clinton"); - - MockHttpServletResponse response = new MockHttpServletResponse(); - inboundAdapterWithNameNoPath.handleRequest(request, response); - assertEquals(HttpServletResponse.SC_OK, response.getStatus()); - Message message = requests.receive(0); - assertNotNull(message); - Object payload = message.getPayload(); - assertTrue(payload instanceof String); - assertEquals("hello", payload); // default payload - assertNull(message.getHeaders().get("lname")); - } - - - @Test public void getRequestNotAllowed() throws Exception { MockHttpServletRequest request = new MockHttpServletRequest(); request.setMethod("GET"); request.setParameter("foo", "bar"); - MockHttpServletResponse response = new MockHttpServletResponse(); - postOnlyAdapter.handleRequest(request, response); - assertEquals(HttpServletResponse.SC_METHOD_NOT_ALLOWED, response.getStatus()); - Message message = requests.receive(0); - assertNull(message); + request.setRequestURI("/postOnly"); + try { + this.integrationRequestMappingHandlerMapping.getHandler(request); + } + catch (HttpRequestMethodNotSupportedException e) { + assertEquals("GET", e.getMethod()); + assertArrayEquals(new String[] {"POST"}, e.getSupportedMethods()); + } + } @Test @@ -241,10 +207,7 @@ public class HttpInboundChannelAdapterParserTests { MockHttpServletRequest request = new MockHttpServletRequest(); request.setMethod("POST"); request.setContent("test".getBytes()); - - //request.setContentType("text/plain"); //Works in Spring 3.1.2.RELEASE but not in Spring 3.0.7.RELEASE - //Instead use: - request.addHeader("Content-Type", "text/plain"); + request.setContentType("text/plain"); MockHttpServletResponse response = new MockHttpServletResponse(); postOnlyAdapter.handleRequest(request, response); @@ -267,10 +230,7 @@ public class HttpInboundChannelAdapterParserTests { ByteArrayOutputStream byteStream = new ByteArrayOutputStream(); new ObjectOutputStream(byteStream).writeObject(obj); request.setContent(byteStream.toByteArray()); - -// //request.setContentType("application/x-java-serialized-object"); //Works in Spring 3.1.2.RELEASE but not in Spring 3.0.7.RELEASE -// //Instead use: - request.addHeader("Content-Type", "application/x-java-serialized-object"); + request.setContentType("application/x-java-serialized-object"); MockHttpServletResponse response = new MockHttpServletResponse(); @@ -284,13 +244,11 @@ public class HttpInboundChannelAdapterParserTests { @Test - @SuppressWarnings("unchecked") public void putOrDeleteMethodsSupported() throws Exception { - DirectFieldAccessor accessor = new DirectFieldAccessor(putOrDeleteAdapter); - List supportedMethods = (List) accessor.getPropertyValue("supportedMethods"); - assertEquals(2, supportedMethods.size()); - assertTrue(supportedMethods.contains(HttpMethod.PUT)); - assertTrue(supportedMethods.contains(HttpMethod.DELETE)); + HttpMethod[] supportedMethods = + TestUtils.getPropertyValue(putOrDeleteAdapter, "requestMapping.methods", HttpMethod[].class); + assertEquals(2, supportedMethods.length); + assertArrayEquals(new HttpMethod[]{HttpMethod.PUT, HttpMethod.DELETE}, supportedMethods); } @Test diff --git a/spring-integration-http/src/test/java/org/springframework/integration/http/config/HttpInboundGatewayParserTests-context.xml b/spring-integration-http/src/test/java/org/springframework/integration/http/config/HttpInboundGatewayParserTests-context.xml index 3060c83c3f..7fb98a49f4 100644 --- a/spring-integration-http/src/test/java/org/springframework/integration/http/config/HttpInboundGatewayParserTests-context.xml +++ b/spring-integration-http/src/test/java/org/springframework/integration/http/config/HttpInboundGatewayParserTests-context.xml @@ -1,11 +1,10 @@ - @@ -19,27 +18,35 @@ + request-channel="requests" + reply-channel="responses" + convert-exceptions="true" + request-timeout="1234" + reply-timeout="4567" + error-channel="errorChannel"/> + + + + @@ -47,26 +54,28 @@ - - - + request-channel="requests" + reply-channel="responses" + view-expression="'bar'" + error-code="oops"> + + - + mapped-response-headers="abc, xyz" + mapped-request-headers="foo,bar"> + + + mapped-response-headers="abc, xyz, person" + mapped-request-headers="foo,bar"> + + - - + diff --git a/spring-integration-http/src/test/java/org/springframework/integration/http/config/HttpInboundGatewayParserTests.java b/spring-integration-http/src/test/java/org/springframework/integration/http/config/HttpInboundGatewayParserTests.java index 502218da31..4454aedee2 100644 --- a/spring-integration-http/src/test/java/org/springframework/integration/http/config/HttpInboundGatewayParserTests.java +++ b/spring-integration-http/src/test/java/org/springframework/integration/http/config/HttpInboundGatewayParserTests.java @@ -68,6 +68,7 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; * @author Gary Russell * @author Gunnar Hillert * @author Biju Kunjummen + * @author Artem Bilan */ @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration @@ -119,8 +120,8 @@ public class HttpInboundGatewayParserTests { assertNotNull(TestUtils.getPropertyValue(gateway, "errorChannel")); MessagingTemplate messagingTemplate = TestUtils.getPropertyValue( gateway, "messagingTemplate", MessagingTemplate.class); - assertEquals(Long.valueOf(1234), TestUtils.getPropertyValue(messagingTemplate, "sendTimeout")); - assertEquals(Long.valueOf(4567), TestUtils.getPropertyValue(messagingTemplate, "receiveTimeout")); + assertEquals(1234L, TestUtils.getPropertyValue(messagingTemplate, "sendTimeout")); + assertEquals(4567L, TestUtils.getPropertyValue(messagingTemplate, "receiveTimeout")); boolean registerDefaultConverters = TestUtils.getPropertyValue(gateway,"mergeWithDefaultConverters", Boolean.class); assertFalse("By default the register-default-converters flag should be false", registerDefaultConverters); @@ -148,9 +149,7 @@ public class HttpInboundGatewayParserTests { gateway.handleRequest(request, response); assertThat(response.getStatus(), is(HttpServletResponse.SC_OK)); - //MockHttpServletResponse#getContentType() works in Spring 3.1.2.RELEASE but not in 3.0.7.RELEASE - //For 3.0.7.RELEASE we have to rely on MockHttpServletResponse#getHeader instead - assertEquals(response.getHeader("Content-Type"), "application/x-java-serialized-object"); + assertEquals(response.getContentType(), "application/x-java-serialized-object"); } @Test diff --git a/spring-integration-http/src/test/java/org/springframework/integration/http/inbound/HttpRequestHandlingMessagingGatewayWithPathMappingTests.java b/spring-integration-http/src/test/java/org/springframework/integration/http/inbound/HttpRequestHandlingMessagingGatewayWithPathMappingTests.java index c5ceeb25ab..37784a121c 100644 --- a/spring-integration-http/src/test/java/org/springframework/integration/http/inbound/HttpRequestHandlingMessagingGatewayWithPathMappingTests.java +++ b/spring-integration-http/src/test/java/org/springframework/integration/http/inbound/HttpRequestHandlingMessagingGatewayWithPathMappingTests.java @@ -34,12 +34,15 @@ import org.springframework.integration.channel.DirectChannel; import org.springframework.integration.core.MessageHandler; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.util.AntPathMatcher; +import org.springframework.web.servlet.HandlerMapping; /** * @author Oleg Zhurakousky * @author Gary Russell * @author Gunnar Hillert * @author Gary Russell + * @author Artem Bilan * @author Biju Kunjummen */ public class HttpRequestHandlingMessagingGatewayWithPathMappingTests { @@ -69,10 +72,14 @@ public class HttpRequestHandlingMessagingGatewayWithPathMappingTests { HttpRequestHandlingMessagingGateway gateway = new HttpRequestHandlingMessagingGateway(true); gateway.setBeanFactory(mock(BeanFactory.class)); - gateway.setPath("/fname/{f}/lname/{l}"); - gateway.setRequestChannel(echoChannel); + + RequestMapping requestMapping = new RequestMapping(); + requestMapping.setPathPatterns("/fname/{f}/lname/{l}"); + gateway.setRequestMapping(requestMapping); gateway.afterPropertiesSet(); + gateway.setRequestChannel(echoChannel); + MockHttpServletResponse response = new MockHttpServletResponse(); Object result = gateway.doHandleRequest(request, response); @@ -97,12 +104,24 @@ public class HttpRequestHandlingMessagingGatewayWithPathMappingTests { request.setContentType("text/plain"); request.setParameter("foo", "bar"); request.setContent("hello".getBytes()); - request.setRequestURI("/fname/bill/lname/clinton"); + + String requestURI = "/fname/bill/lname/clinton"; + + //See org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping#handleMatch + Map uriTemplateVariables = + new AntPathMatcher().extractUriTemplateVariables("/fname/{f}/lname/{l}", requestURI); + request.setAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, uriTemplateVariables); + + request.setRequestURI(requestURI); HttpRequestHandlingMessagingGateway gateway = new HttpRequestHandlingMessagingGateway(true); gateway.setBeanFactory(mock(BeanFactory.class)); - gateway.setPath("/fname/{f}/lname/{l}"); - gateway.setRequestChannel(echoChannel); + + RequestMapping requestMapping = new RequestMapping(); + requestMapping.setPathPatterns("/fname/{f}/lname/{l}"); + gateway.setRequestMapping(requestMapping); + + gateway.setRequestChannel(echoChannel); gateway.setPayloadExpression(PARSER.parseExpression("#pathVariables.f")); gateway.afterPropertiesSet(); @@ -130,12 +149,24 @@ public class HttpRequestHandlingMessagingGatewayWithPathMappingTests { request.setContentType("text/plain"); request.setParameter("foo", "bar"); request.setContent("hello".getBytes()); - request.setRequestURI("/fname/bill/lname/clinton"); + + String requestURI = "/fname/bill/lname/clinton"; + + //See org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping#handleMatch + Map uriTemplateVariables = + new AntPathMatcher().extractUriTemplateVariables("/fname/{f}/lname/{l}", requestURI); + request.setAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, uriTemplateVariables); + + request.setRequestURI(requestURI); HttpRequestHandlingMessagingGateway gateway = new HttpRequestHandlingMessagingGateway(true); gateway.setBeanFactory(mock(BeanFactory.class)); - gateway.setPath("/fname/{f}/lname/{l}"); - gateway.setRequestChannel(echoChannel); + + RequestMapping requestMapping = new RequestMapping(); + requestMapping.setPathPatterns("/fname/{f}/lname/{l}"); + gateway.setRequestMapping(requestMapping); + + gateway.setRequestChannel(echoChannel); gateway.setPayloadExpression(PARSER.parseExpression("#pathVariables")); gateway.afterPropertiesSet(); diff --git a/spring-integration-http/src/test/java/org/springframework/integration/http/inbound/Int2312RequestMappingIntegrationTests-context.xml b/spring-integration-http/src/test/java/org/springframework/integration/http/inbound/Int2312RequestMappingIntegrationTests-context.xml new file mode 100644 index 0000000000..b2516af13a --- /dev/null +++ b/spring-integration-http/src/test/java/org/springframework/integration/http/inbound/Int2312RequestMappingIntegrationTests-context.xml @@ -0,0 +1,86 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/spring-integration-http/src/test/java/org/springframework/integration/http/inbound/Int2312RequestMappingIntegrationTests.java b/spring-integration-http/src/test/java/org/springframework/integration/http/inbound/Int2312RequestMappingIntegrationTests.java new file mode 100644 index 0000000000..64a599d35c --- /dev/null +++ b/spring-integration-http/src/test/java/org/springframework/integration/http/inbound/Int2312RequestMappingIntegrationTests.java @@ -0,0 +1,190 @@ +/* + * Copyright 2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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.inbound; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import java.util.Collections; +import java.util.Map; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.MediaType; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.util.AntPathMatcher; +import org.springframework.web.bind.UnsatisfiedServletRequestParameterException; +import org.springframework.web.servlet.HandlerAdapter; +import org.springframework.web.servlet.HandlerMapping; +import org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter; + +/** + * @author Artem Bilan + * @since 3.0 + */ +//INT-2312 +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration +public class Int2312RequestMappingIntegrationTests { + + public static final String TEST_PATH = "/test/{value}"; + + public static final String TEST_STRING_MULTIPLE_PATHS = "Multiple Paths The Same Endpoint"; + + @Autowired + private HandlerMapping handlerMapping; + + private HandlerAdapter handlerAdapter = new HttpRequestHandlerAdapter(); + + @Test + public void testMultiplePathsTheSameEndpoint() throws Exception { + MockHttpServletRequest request = new MockHttpServletRequest(); + request.setMethod("GET"); + request.setRequestURI("/path1"); + MockHttpServletResponse response = new MockHttpServletResponse(); + Object handler = this.handlerMapping.getHandler(request).getHandler(); + this.handlerAdapter.handle(request, response, handler); + assertEquals(TEST_STRING_MULTIPLE_PATHS, response.getContentAsString()); + + request = new MockHttpServletRequest(); + request.setMethod("GET"); + request.setRequestURI("/path2"); + response = new MockHttpServletResponse(); + handler = this.handlerMapping.getHandler(request).getHandler(); + this.handlerAdapter.handle(request, response, handler); + assertEquals(TEST_STRING_MULTIPLE_PATHS, response.getContentAsString()); + } + + + + @Test + public void testURIVariablesAndHeaders() throws Exception { + MockHttpServletRequest request = new MockHttpServletRequest(); + request.setMethod("GET"); + String testRequest = "aBc"; + String requestURI = "/test/" + testRequest; + request.setRequestURI(requestURI); + + //See org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping#handleMatch + Map uriTemplateVariables = + new AntPathMatcher().extractUriTemplateVariables(TEST_PATH, requestURI); + request.setAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, uriTemplateVariables); + + MockHttpServletResponse response = new MockHttpServletResponse(); + + request.addHeader("toLowerCase", true); + + Object handler = this.handlerMapping.getHandler(request).getHandler(); + this.handlerAdapter.handle(request, response, handler); + final String testResponse = response.getContentAsString(); + assertEquals(testRequest.toLowerCase(), testResponse); + } + + @Test + public void testParams() throws Exception { + MockHttpServletRequest request = new MockHttpServletRequest("GET", "/params"); + Object handler = null; + try { + handler = this.handlerMapping.getHandler(request); + } + catch (Exception e) { + // There is no matching handlers and some default handler + //See org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping#handleNoMatch + assertTrue(e instanceof UnsatisfiedServletRequestParameterException); + } + + request = new MockHttpServletRequest("GET", "/params"); + request.addParameter("param1", "1"); + request.addParameter("param2", "1"); + + handler = this.handlerMapping.getHandler(request).getHandler(); + + MockHttpServletResponse response = new MockHttpServletResponse(); + + this.handlerAdapter.handle(request, response, handler); + String testResponse = response.getContentAsString(); + assertEquals("User=1;account=1", testResponse); + + request = new MockHttpServletRequest("GET", "/params"); + request.addParameter("param1", "1"); + handler = this.handlerMapping.getHandler(request).getHandler(); + + response = new MockHttpServletResponse(); + + this.handlerAdapter.handle(request, response, handler); + testResponse = response.getContentAsString(); + assertEquals("User=1", testResponse); + } + + @Test + public void testConsumes() throws Exception { + MockHttpServletRequest request = new MockHttpServletRequest("GET", "/consumes"); + request.setContentType("text/plain"); + Object handler = this.handlerMapping.getHandler(request).getHandler(); + MockHttpServletResponse response = new MockHttpServletResponse(); + + this.handlerAdapter.handle(request, response, handler); + String testResponse = response.getContentAsString(); + assertEquals("BAR", testResponse); + + request = new MockHttpServletRequest("GET", "/consumes"); + request.setContentType("text/xml"); + handler = this.handlerMapping.getHandler(request).getHandler(); + response = new MockHttpServletResponse(); + + this.handlerAdapter.handle(request, response, handler); + testResponse = response.getContentAsString(); + assertEquals("TEXT_XML", testResponse); + } + + @Test + public void testProduces() throws Exception { + MockHttpServletRequest request = new MockHttpServletRequest("GET", "/produces"); + request.addHeader("Accept", "application/xml"); + Object handler = this.handlerMapping.getHandler(request).getHandler(); + + //See org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping#handleMatch + assertEquals(Collections.singleton(MediaType.APPLICATION_XML), + request.getAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE)); + + MockHttpServletResponse response = new MockHttpServletResponse(); + + this.handlerAdapter.handle(request, response, handler); + String testResponse = response.getContentAsString(); + assertEquals("XML", testResponse); + + request = new MockHttpServletRequest("GET", "/produces"); + request.addHeader("Accept", "application/json"); + handler = this.handlerMapping.getHandler(request).getHandler(); + + //See org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping#handleMatch + assertNull("Negated expression should not be listed as a producible type", + request.getAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE)); + + response = new MockHttpServletResponse(); + + this.handlerAdapter.handle(request, response, handler); + testResponse = response.getContentAsString(); + assertEquals("{\"json\":\"body\"}", testResponse); + } + +} diff --git a/spring-integration-http/src/test/java/org/springframework/integration/http/support/DefaultHttpHeaderMapperFromMessageInboundTests.java b/spring-integration-http/src/test/java/org/springframework/integration/http/support/DefaultHttpHeaderMapperFromMessageInboundTests.java index bc86e05c82..6f48fdce0d 100644 --- a/spring-integration-http/src/test/java/org/springframework/integration/http/support/DefaultHttpHeaderMapperFromMessageInboundTests.java +++ b/spring-integration-http/src/test/java/org/springframework/integration/http/support/DefaultHttpHeaderMapperFromMessageInboundTests.java @@ -37,6 +37,7 @@ import org.springframework.beans.factory.support.DefaultListableBeanFactory; import org.springframework.core.convert.ConversionService; import org.springframework.core.convert.converter.Converter; import org.springframework.core.convert.support.ConversionServiceFactory; +import org.springframework.core.convert.support.DefaultConversionService; import org.springframework.core.convert.support.GenericConversionService; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; @@ -501,11 +502,10 @@ public class DefaultHttpHeaderMapperFromMessageInboundTests { @Test - @SuppressWarnings("deprecation") public void validateCustomHeadersWithNonStringValuesAndDefaultConverterOnly() throws Exception{ DefaultHttpHeaderMapper mapper = new DefaultHttpHeaderMapper(); mapper.setOutboundHeaderNames(new String[] {"customHeader*"}); - ConversionService cs = ConversionServiceFactory.createDefaultConversionService(); + ConversionService cs = new DefaultConversionService(); DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); beanFactory.registerSingleton("integrationConversionService", cs); mapper.setBeanFactory(beanFactory); @@ -523,11 +523,10 @@ public class DefaultHttpHeaderMapperFromMessageInboundTests { } @Test - @SuppressWarnings("deprecation") public void validateCustomHeadersWithNonStringValuesAndDefaultConverterWithCustomConverter() throws Exception{ DefaultHttpHeaderMapper mapper = new DefaultHttpHeaderMapper(); mapper.setOutboundHeaderNames(new String[] {"customHeader*"}); - GenericConversionService cs = ConversionServiceFactory.createDefaultConversionService(); + GenericConversionService cs = new DefaultConversionService(); cs.addConverter(new TestClassConverter()); DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); beanFactory.registerSingleton("integrationConversionService", cs); diff --git a/spring-integration-jpa/src/main/java/org/springframework/integration/jpa/config/xml/RetrievingJpaOutboundGatewayParser.java b/spring-integration-jpa/src/main/java/org/springframework/integration/jpa/config/xml/RetrievingJpaOutboundGatewayParser.java index 4e23346f45..420f803987 100644 --- a/spring-integration-jpa/src/main/java/org/springframework/integration/jpa/config/xml/RetrievingJpaOutboundGatewayParser.java +++ b/spring-integration-jpa/src/main/java/org/springframework/integration/jpa/config/xml/RetrievingJpaOutboundGatewayParser.java @@ -22,7 +22,6 @@ import org.w3c.dom.Element; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.parsing.BeanComponentDefinition; import org.springframework.beans.factory.support.BeanDefinitionBuilder; -import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.beans.factory.xml.ParserContext; import org.springframework.integration.config.xml.IntegrationNamespaceUtils; import org.springframework.integration.jpa.support.OutboundGatewayType; @@ -46,7 +45,7 @@ public class RetrievingJpaOutboundGatewayParser extends AbstractJpaOutboundGatew final BeanDefinitionBuilder jpaExecutorBuilder = JpaParserUtils.getOutboundGatewayJpaExecutorBuilder(gatewayElement, parserContext); - RootBeanDefinition firstResultExpression = createExpressionDefinitionFromValueOrExpression("first-result", + BeanDefinition firstResultExpression = createExpressionDefinitionFromValueOrExpression("first-result", "first-result-expression", parserContext, gatewayElement, false); if(firstResultExpression != null) { diff --git a/spring-integration-mongodb/src/main/java/org/springframework/integration/mongodb/config/MongoDbInboundChannelAdapterParser.java b/spring-integration-mongodb/src/main/java/org/springframework/integration/mongodb/config/MongoDbInboundChannelAdapterParser.java index 56b82a1d3e..216f642ec6 100644 --- a/spring-integration-mongodb/src/main/java/org/springframework/integration/mongodb/config/MongoDbInboundChannelAdapterParser.java +++ b/spring-integration-mongodb/src/main/java/org/springframework/integration/mongodb/config/MongoDbInboundChannelAdapterParser.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,13 +15,13 @@ */ package org.springframework.integration.mongodb.config; +import org.springframework.beans.factory.config.BeanDefinition; import org.w3c.dom.Element; import org.springframework.beans.BeanMetadataElement; import org.springframework.beans.factory.config.RuntimeBeanReference; import org.springframework.beans.factory.support.BeanDefinitionBuilder; import org.springframework.beans.factory.support.BeanDefinitionReaderUtils; -import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.beans.factory.xml.ParserContext; import org.springframework.integration.config.xml.AbstractPollingInboundChannelAdapterParser; import org.springframework.integration.config.xml.IntegrationNamespaceUtils; @@ -43,7 +43,7 @@ public class MongoDbInboundChannelAdapterParser extends AbstractPollingInboundCh // 'collection-name', 'collection-name-expression' and 'mongo-converter' MongoParserUtils.processCommonAttributes(element, parserContext, builder); - RootBeanDefinition queryExpressionDef = + BeanDefinition queryExpressionDef = IntegrationNamespaceUtils.createExpressionDefinitionFromValueOrExpression("query", "query-expression", parserContext, element, true); diff --git a/spring-integration-mongodb/src/main/java/org/springframework/integration/mongodb/config/MongoParserUtils.java b/spring-integration-mongodb/src/main/java/org/springframework/integration/mongodb/config/MongoParserUtils.java index 5f1bc8941e..806860ac17 100644 --- a/spring-integration-mongodb/src/main/java/org/springframework/integration/mongodb/config/MongoParserUtils.java +++ b/spring-integration-mongodb/src/main/java/org/springframework/integration/mongodb/config/MongoParserUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,8 +15,8 @@ */ package org.springframework.integration.mongodb.config; +import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.support.BeanDefinitionBuilder; -import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.beans.factory.xml.ParserContext; import org.springframework.integration.config.xml.IntegrationNamespaceUtils; import org.springframework.util.StringUtils; @@ -62,7 +62,7 @@ class MongoParserUtils { IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "mongo-converter"); } - RootBeanDefinition collectionNameExpressionDef = + BeanDefinition collectionNameExpressionDef = IntegrationNamespaceUtils.createExpressionDefinitionFromValueOrExpression("collection-name", "collection-name-expression", parserContext, element, false); diff --git a/spring-integration-redis/src/main/java/org/springframework/integration/redis/config/RedisStoreInboundChannelAdapterParser.java b/spring-integration-redis/src/main/java/org/springframework/integration/redis/config/RedisStoreInboundChannelAdapterParser.java index 461a9ef20b..1920f4ad97 100644 --- a/spring-integration-redis/src/main/java/org/springframework/integration/redis/config/RedisStoreInboundChannelAdapterParser.java +++ b/spring-integration-redis/src/main/java/org/springframework/integration/redis/config/RedisStoreInboundChannelAdapterParser.java @@ -19,10 +19,10 @@ package org.springframework.integration.redis.config; import org.w3c.dom.Element; import org.springframework.beans.BeanMetadataElement; +import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.RuntimeBeanReference; import org.springframework.beans.factory.support.BeanDefinitionBuilder; import org.springframework.beans.factory.support.BeanDefinitionReaderUtils; -import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.beans.factory.xml.ParserContext; import org.springframework.integration.config.xml.AbstractPollingInboundChannelAdapterParser; import org.springframework.integration.config.xml.IntegrationNamespaceUtils; @@ -57,7 +57,7 @@ public class RedisStoreInboundChannelAdapterParser extends AbstractPollingInboun builder.addConstructorArgReference(connectionFactory); } boolean atLeastOneRequired = true; - RootBeanDefinition expressionDef = + BeanDefinition expressionDef = IntegrationNamespaceUtils.createExpressionDefinitionFromValueOrExpression("key", "key-expression", parserContext, element, atLeastOneRequired); builder.addConstructorArgValue(expressionDef); diff --git a/src/reference/docbook/http.xml b/src/reference/docbook/http.xml index 5011079de3..2a720af4e7 100644 --- a/src/reference/docbook/http.xml +++ b/src/reference/docbook/http.xml @@ -75,7 +75,7 @@ "multipartResolver" (the same name expected by Spring's DispatcherServlet). If it does in fact locate that bean, then the support for MultipartFiles will be enabled on the inbound request mapper. Otherwise, it will fail when trying to map a multipart-file request to a Spring Integration Message. For more on Spring's - support for MultipartResolvers, refer to the Spring Reference Manual. + support for MultipartResolvers, refer to the Spring Reference Manual. @@ -139,11 +139,10 @@ By default the HTTP request will be generated using an instance of Si HttpURLConnection. Use of the Apache Commons HTTP Client is also supported through the provided CommonsClientHttpRequestFactory which can be injected as shown above. - - -In the case of the Outbound Gateway, the reply message produced by the gateway will contain all Message Headers present in the request message. - - + + In the case of the Outbound Gateway, the reply message produced by the gateway + will contain all Message Headers present in the request message. + Cookies Basic cookie support is provided by the transfer-cookies attribute on the outbound gateway. When @@ -158,8 +157,7 @@ In the case of the Outbound Gateway, the reply message produced by the gateway w If transfer-cookies is false, any Set-Cookie header received will remain as Set-Cookie in the reply message, and will be dropped on subsequent sends. - - + Note: Empty Repsonse Bodies HTTP is a request/response protocol. However the response may not have a body, just headers. In this case, the HttpRequestExecutingMessageHandler produces @@ -174,8 +172,7 @@ In the case of the Outbound Gateway, the reply message produced by the gateway w routing logic after the Http Outbound Gateway. You could also use a <payload-type-router/> to route messages with an HttpEntity to a different flow than that used for responses with a body. - - +
@@ -224,88 +221,140 @@ In the case of the Outbound Gateway, the reply message produced by the gateway w request-channel="requests" reply-channel="responses"/>]]> - - - Beginning with Spring Integration 2.1 the - HTTP Inbound Gateway and the HTTP - Inbound Channel Adapter should use the path - attribute instead of the name attribute for - specifying the request path. The name attribute - for those 2 components has been deprecated. - - - If you simply want to identify component itself within your application - context, please use the id attribute. - - + Request Mapping support + + Spring Integration 3.0 is improving the REST support by introducing the + IntegrationRequestMappingHandlerMapping. The implementation relies on the enhanced REST support provided by Spring Framework 3.1 or higher. + + + The parsing of the HTTP Inbound Gateway or the + HTTP Inbound Channel Adapter registers an integrationRequestMappingHandlerMapping + bean of type + IntegrationRequestMappingHandlerMapping, in case there is none registered, yet. This particular implementation of the + HandlerMapping delegates its logic to the + RequestMappingInfoHandlerMapping. The implementation provides similar functionality as the one provided by the + org.springframework.web.bind.annotation.RequestMapping annotation in Spring MVC. + + + For more information, please see + Mapping Requests With @RequestMapping. + + + For this purpose, Spring Integration 3.0 introduces the <request-mapping> sub-element. + This optional sub-element can be added to the <http:inbound-channel-adapter> and the <http:inbound-gateway>. + It works in conjunction with the path and supported-methods attributes: + + + +]]> + + Based on this configuration, the namespace parser creates an instance of the IntegrationRequestMappingHandlerMapping (if none exists, yet), + a HttpRequestHandlingController bean and associated with it an instance of + RequestMapping, which in turn, is converted to the Spring MVC + RequestMappingInfo. + + + The <request-mapping> sub-element provides the following attributes: + + + headers + params + consumes + produces + + + With the path and supported-methods attributes of the <http:inbound-channel-adapter> or + the <http:inbound-gateway>, <request-mapping> attributes translate directly into the respective options + provided by the org.springframework.web.bind.annotation.RequestMapping annotation in Spring MVC. + + + The <request-mapping> sub-element allows you to configure + several Spring Integration HTTP Inbound Endpoints to the + same path (or even the same supported-methods) + and to provide different downstream message flows based on incoming HTTP requests. + + + Alternatively, you can also declare just one HTTP Inbound Endpoint and + apply routing and filtering logic within the Spring Integration + flow to achieve the same result. This allows you to get the Message + into the flow as early as possibly, e.g.: + + - Defining the UriPathHandlerMapping + + + + - - In order to use the HTTP Inbound Gateway or the - HTTP Inbound Channel Adapter you must define a + - UriPathHandlerMapping. This particular implementation of the - HandlerMapping matches against - the value of the path attribute. - +]]> + + For more information regarding Handler Mappings, please see: + + + + + + - ]]> - - For more information regarding Handler Mappings, please - see: - + URI Template Variables and Expressions + + By Using the path attribute in conjunction with the + payload-expression attribute as well as the + header sub-element, you have a high degree of flexibility for + mapping inbound request data. + + + In the following example configuration, an Inbound Channel Adapter is + configured to accept requests using the following URI: + /first-name/{firstName}/last-name/{lastName} + + + Using the payload-expression attribute, the URI + template variable {firstName} is mapped to be the + Message payload, while the {lastName} URI template + variable will map to the lname Message header. + - - - - - - - URI Template Variables and Expressions - - - By Using the path attribute in conjunction with the - payload-expression attribute as well as the - header sub-element, you have a high degree of flexiblity for - mapping inbound request data. - - - - In the following example configuration, an Inbound Channel Adapter is - configured to accept requests using the following URI: - /first-name/{firstName}/last-name/{lastName} - - - - Using the payload-expression attribute, the URI - template variable {firstName} is mapped to be the - Message payload, while the {lastName} URI template - variable will map to the lname Message header. - - - ]]> - - For more information about URI template variables, - please see the Spring Reference Manual: - + + For more information about URI template variables, + please see the Spring Reference Manual: + - - - - - - - Outbound + + + + + + Outbound 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 null. With a null response type, the payload of the reply Message would diff --git a/src/reference/docbook/whats-new.xml b/src/reference/docbook/whats-new.xml index ae3898899b..32db3c7eed 100644 --- a/src/reference/docbook/whats-new.xml +++ b/src/reference/docbook/whats-new.xml @@ -119,6 +119,19 @@ For more information see .
+
+ HTTP Request Mapping + + The HTTP module now provides powerful Request Mapping support for Inbound Endpoints. Class UriPathHandlerMapping + was replaced by IntegrationRequestMappingHandlerMapping, which is registered under the bean name + integrationRequestMappingHandlerMapping in the application context. Upon parsing of the HTTP Inbound Endpoint, + a new IntegrationRequestMappingHandlerMapping bean is either registered or an existing bean is being reused. + To achieve flexible Request Mapping configuration, Spring Integration provides the <request-mapping/> + sub-element for <http:inbound-channel-adapter/> and <http:inbound-gateway/>. + Both HTTP Inbound Endpoints are now fully based on the Request Mapping infrastructure that was introduced with Spring MVC 3.1. + For more information see . + +
@@ -260,14 +273,14 @@ Outbound Endpoint 'encode-uri' - <http:outbound-gateway/> and <http:outbound-channel-adapter/> now - provides an encode-uri attribute to allow disabling the encoding of the URI object + provide an encode-uri attribute to allow disabling the encoding of the URI object before sending the request. Inbound Endpoint 'merge-with-default-converters' - <http:inbound-gateway/> and <http:inbound-channel-adapter/> now have a merge-with-default-converters attribute to include the list of default - HttpMessageConverter after the custom message converters. + HttpMessageConverters after the custom message converters. 'If-(Un)Modified-Since' HTTP headers - previously,