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 0c15ed6248..150a3dc054 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,12 +20,14 @@ 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; import org.springframework.beans.factory.config.TypedStringValue; import org.springframework.beans.factory.parsing.BeanComponentDefinition; import org.springframework.beans.factory.support.BeanDefinitionBuilder; +import org.springframework.beans.factory.support.BeanDefinitionReaderUtils; import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.beans.factory.support.ManagedList; import org.springframework.beans.factory.support.RootBeanDefinition; @@ -33,14 +35,13 @@ import org.springframework.beans.factory.xml.BeanDefinitionParserDelegate; import org.springframework.beans.factory.xml.ParserContext; import org.springframework.core.Conventions; import org.springframework.expression.common.LiteralExpression; +import org.springframework.integration.channel.DirectChannel; import org.springframework.integration.config.ExpressionFactoryBean; import org.springframework.integration.config.SpelFunctionFactoryBean; 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; @@ -462,6 +463,7 @@ public abstract class IntegrationNamespaceUtils { } return expressionDef; } + public static void registerSpelFunctionBean(BeanDefinitionRegistry registry, String functionId, String className, String methodSignature) { BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(SpelFunctionFactoryBean.class) @@ -469,6 +471,7 @@ 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"); diff --git a/spring-integration-ftp/test/A.TEST.a b/spring-integration-ftp/test/A.TEST.a new file mode 100644 index 0000000000..e69de29bb2 diff --git a/spring-integration-ftp/test/B.TEST.a b/spring-integration-ftp/test/B.TEST.a new file mode 100644 index 0000000000..e69de29bb2 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 7645811022..599449f59b 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 @@ -19,10 +19,12 @@ package org.springframework.integration.http.inbound; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.atomic.AtomicInteger; +import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @@ -65,6 +67,7 @@ import org.springframework.util.CollectionUtils; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; import org.springframework.util.ObjectUtils; +import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.multipart.MultipartHttpServletRequest; import org.springframework.web.multipart.MultipartResolver; import org.springframework.web.servlet.DispatcherServlet; @@ -210,8 +213,15 @@ public abstract class HttpRequestHandlingEndpointSupport extends MessagingGatewa /** * Specifies a SpEL expression to evaluate in order to generate the Message payload. * The EvaluationContext will be populated with an HttpEntity instance as the root object, - * and it may contain one or both of the #pathVariables and - * #queryParameters variables if present. Those variables' values are Maps. + * and it may contain variables: + * */ public void setPayloadExpression(Expression payloadExpression) { this.payloadExpression = payloadExpression; @@ -221,8 +231,15 @@ public abstract class HttpRequestHandlingEndpointSupport extends MessagingGatewa * Specifies a Map of SpEL expressions to evaluate in order to generate the Message headers. * The keys in the map will be used as the header names. When evaluating the expression, * the EvaluationContext will be populated with an HttpEntity instance as the root object, - * and it may contain one or both of the #pathVariables and - * #queryParameters variables if present. Those variables' values are Maps. + * and it may contain variables: + * */ public void setHeaderExpressions(Map headerExpressions) { this.headerExpressions = headerExpressions; @@ -379,9 +396,22 @@ public abstract class HttpRequestHandlingEndpointSupport extends MessagingGatewa StandardEvaluationContext evaluationContext = this.createEvaluationContext(); evaluationContext.setRootObject(httpEntity); + evaluationContext.setVariable("requestAttributes", RequestContextHolder.currentRequestAttributes()); + MultiValueMap requestParams = this.convertParameterMap(servletRequest.getParameterMap()); evaluationContext.setVariable("requestParams", requestParams); + evaluationContext.setVariable("requestHeaders", new ServletServerHttpRequest(servletRequest).getHeaders()); + + Cookie[] requestCookies = servletRequest.getCookies(); + if (!ObjectUtils.isEmpty(requestCookies)) { + Map cookies = new HashMap(requestCookies.length); + for (Cookie requestCookie : requestCookies) { + cookies.put(requestCookie.getName(), requestCookie); + } + evaluationContext.setVariable("cookies", cookies); + } + Map pathVariables = (Map) servletRequest.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE); @@ -392,6 +422,17 @@ public abstract class HttpRequestHandlingEndpointSupport extends MessagingGatewa evaluationContext.setVariable("pathVariables", pathVariables); } + //TODO change it to HandlerMapping.MATRIX_VARIABLES_ATTRIBUTE after upgrade to Spring 4.0 + Map> matrixVariables = + (Map>) servletRequest.getAttribute(HandlerMapping.class.getName() + ".matrixVariables"); + + if (!CollectionUtils.isEmpty(matrixVariables)) { + if (logger.isDebugEnabled()) { + logger.debug("Mapped matrix variables: " + matrixVariables); + } + evaluationContext.setVariable("matrixVariables", matrixVariables); + } + Map headers = this.headerMapper.toHeaders(request.getHeaders()); Object payload = null; if (this.payloadExpression != null) { 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 index b2516af13a..2ff049b4e7 100644 --- 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 @@ -26,8 +26,16 @@ request-channel="toLowerCaseChannel" payload-expression="#pathVariables.value"> + + + + + + + + params = new HashMap(); + params.put("foo", "bar"); + request.setParameters(params); + request.setContent("hello".getBytes()); + final Cookie cookie = new Cookie("foo", "bar"); + request.setCookies(cookie); + request.addHeader("toLowerCase", true); - //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); + //See org.springframework.web.servlet.FrameworkServlet#initContextHolders + final RequestAttributes attributes = new ServletRequestAttributes(request); + RequestContextHolder.setRequestAttributes(attributes); + + this.toLowerCaseChannel.subscribe(new MessageHandler() { + @Override + public void handleMessage(Message message) throws MessagingException { + MessageHeaders headers = message.getHeaders(); + + assertEquals(attributes, headers.get("requestAttributes")); + + Object requestParams = headers.get("requestParams"); + assertNotNull(requestParams); + assertEquals(params, ((MultiValueMap) requestParams).toSingleValueMap()); + + // TODO test it after upgrade to Spring 4.0 + // assertEquals(matrixVariables, headers.get("matrixVariables")); + + Object requestHeaders = headers.get("requestHeaders"); + assertNotNull(requestParams); + assertEquals(MediaType.TEXT_PLAIN, ((HttpHeaders) requestHeaders).getContentType()); + + Map cookies = (Map) headers.get("cookies"); + assertEquals(1, cookies.size()); + Cookie foo = cookies.get("foo"); + assertNotNull(foo); + assertEquals(cookie, foo); + } + }); 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(); diff --git a/src/reference/docbook/http.xml b/src/reference/docbook/http.xml index 2a720af4e7..21a162a18a 100644 --- a/src/reference/docbook/http.xml +++ b/src/reference/docbook/http.xml @@ -354,9 +354,57 @@ By default the HTTP request will be generated using an instance of Si + + + Since Spring Integration 3.0, in addition to the existing + #pathVariables and #requestParams variables being available in payload and header + expressions, other useful variables have been added. + + + The entire list of available expression variables: + + + + #requestParams - the MultiValueMap from the + ServletRequest parameterMap. + + + #pathVariables - the Map from URI Template placeholders and their values; + + + #matrixVariables - the Map of MultiValueMap + according to + Spring MVC Specification. Note, #matrixVariables require Spring MVC 3.2 or higher; + + + #requestAttributes - the org.springframework.web.context.request.RequestAttributes + associated with the current Request; + + + #requestHeaders - the org.springframework.http.HttpHeaders object from the current Request; + + + #cookies - the Map<String, Cookie> + of javax.servlet.http.Cookies from the current Request. + + + + Note, all these values (and others) can be accessed within expressions in the downstream message + flow via the ThreadLocal org.springframework.web.context.request.RequestAttributes + variable, if that message flow is single-threaded and lives within the request thread: + + + ]]> + + 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 + 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 contain the ResponseEntity as long as it's http status is a success (non-successful status codes will throw Exceptions). If you are expecting a different type, such as a String, then provide that fully-qualified class name as shown below. diff --git a/src/reference/docbook/whats-new.xml b/src/reference/docbook/whats-new.xml index bb3b820e95..289b0d090a 100644 --- a/src/reference/docbook/whats-new.xml +++ b/src/reference/docbook/whats-new.xml @@ -333,12 +333,20 @@ HttpMessageConverters after the custom message converters. - 'If-(Un)Modified-Since' HTTP headers - previously, + 'If-(Un)Modified-Since' HTTP Headers - previously, 'If-Modified-Since' and 'If-Unmodified-Since' HTTP headers were incorrectly processed within from/to HTTP headers mapping in the DefaultHttpHeaderMapper. Now, in addition correcting that issue, DefaultHttpHeaderMapper provides date parsing from formatted strings for any HTTP headers that accept date-time values. + + Inbound Endpoint Expression Variables - + In addition to the existing #requestParams and #pathVariables, + the <http:inbound-gateway/> and <http:inbound-channel-adapter/> + now support additional useful variables: #matrixVariables, #requestAttributes, + #requestHeaders and #cookies. These variables are available in + both payload and header expressions. +