diff --git a/spring-integration-core/src/main/java/org/springframework/integration/mapping/HeaderMapper.java b/spring-integration-core/src/main/java/org/springframework/integration/mapping/HeaderMapper.java index 70b010d150..ba3fdeba1f 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/mapping/HeaderMapper.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/mapping/HeaderMapper.java @@ -33,6 +33,6 @@ public interface HeaderMapper { void fromHeaders(MessageHeaders headers, T target); - Map toHeaders(T source); + Map toHeaders(T source); } 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 09b385e8b4..90b7f8b7a2 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 @@ -57,15 +57,18 @@ import org.springframework.integration.http.multipart.MultipartHttpInputMessage; import org.springframework.integration.http.support.DefaultHttpHeaderMapper; import org.springframework.integration.mapping.HeaderMapper; import org.springframework.integration.support.MessageBuilder; +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.web.multipart.MultipartHttpServletRequest; import org.springframework.web.multipart.MultipartResolver; import org.springframework.web.servlet.DispatcherServlet; -import org.springframework.web.util.UriTemplate; +import org.springframework.web.util.UrlPathHelper; /** * Base class for HTTP request handling endpoints. @@ -116,6 +119,10 @@ abstract class HttpRequestHandlingEndpointSupport extends MessagingGatewaySuppor 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; @@ -156,7 +163,11 @@ abstract class HttpRequestHandlingEndpointSupport extends MessagingGatewaySuppor protected boolean isExpectReply() { return expectReply; } - + + /** + * Set the path template for which this endpoint expects requests. + * May include path variable {keys} to match against. + */ public void setPath(String path) { this.path = path; } @@ -165,13 +176,27 @@ abstract class HttpRequestHandlingEndpointSupport extends MessagingGatewaySuppor return path; } + /** + * 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. + */ public void setPayloadExpression(Expression payloadExpression) { this.payloadExpression = payloadExpression; } + /** + * 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. + */ public void setHeaderExpressions(Map headerExpressions) { this.headerExpressions = headerExpressions; } + /** * Set the message body converters to use. These converters are used to convert from and to HTTP requests and * responses. @@ -276,87 +301,70 @@ abstract class HttpRequestHandlingEndpointSupport extends MessagingGatewaySuppor * 'expectReply' property is true, it will also generate a response from the reply Message once received. */ @SuppressWarnings({ "rawtypes", "unchecked" }) - protected final Object doHandleRequest(HttpServletRequest servletRequest, HttpServletResponse servletResponse) - throws IOException { + protected final Object doHandleRequest(HttpServletRequest servletRequest, HttpServletResponse servletResponse) throws IOException { try { ServletServerHttpRequest request = this.prepareRequest(servletRequest); if (!this.supportedMethods.contains(request.getMethod())) { servletResponse.setStatus(HttpServletResponse.SC_METHOD_NOT_ALLOWED); return null; } - Map uriVariableMappings = null; - // - StandardEvaluationContext evaluationContext = this.createEvaluationContext(); - + Object requestBody = null; if (this.isReadable(request)) { requestBody = this.extractRequestBody(request); - evaluationContext.setVariable("requestBody", requestBody); } - HttpEntity httpEntity = new HttpEntity(requestBody, request.getHeaders()); + + StandardEvaluationContext evaluationContext = this.createEvaluationContext(); evaluationContext.setRootObject(httpEntity); - - LinkedMultiValueMap requestParameterMap = this.convertParameterMap(servletRequest.getParameterMap()); - evaluationContext.setVariable("requestParameterMap", uriVariableMappings);// bind the whole map - - for (String parameterName : requestParameterMap.keySet()) { // bind individual parameters - evaluationContext.setVariable(parameterName, requestParameterMap.get(parameterName)); - } - - if (StringUtils.hasText(this.path)){ - UriTemplate template = new UriTemplate(this.path); - uriVariableMappings = template.match(request.getURI().getPath()); - if (!uriVariableMappings.isEmpty()){ - if (logger.isDebugEnabled()){ - logger.debug("Mapped URI variables: " + uriVariableMappings); - } - - // set the whole map - evaluationContext.setVariable("pathVariables", uriVariableMappings); - for (Object key : uriVariableMappings.keySet()) { - // add individual elements - evaluationContext.setVariable((String) key, uriVariableMappings.get(key)); + + LinkedMultiValueMap 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); } - } - else { - logger.warn("Was not able to match UriVariables to path: " + this.path); - } - } - // - - Map headers = this.headerMapper.toHeaders(request.getHeaders()); - - Object payload = null; - - if (this.payloadExpression != null){ - // create payload based on SpEL - payload = this.payloadExpression.getValue(evaluationContext, request); - } - if (this.headerExpressions != null){ - for (String headerName : this.headerExpressions.keySet()) { - Expression headerExpression = this.headerExpressions.get(headerName); - Object headerValue = headerExpression.getValue(evaluationContext, request); - ((Map)headers).put(headerName, headerValue); + evaluationContext.setVariable("pathVariables", pathVariables); } - } - - if (payload == null){ - if (this.isReadable(request)) { - //payload = this.extractRequestBody(request); + } + + Map headers = this.headerMapper.toHeaders(request.getHeaders()); + Object payload = null; + if (this.payloadExpression != null) { + // create payload based on SpEL + payload = this.payloadExpression.getValue(evaluationContext); + } + if (!CollectionUtils.isEmpty(this.headerExpressions)) { + for (String headerName : this.headerExpressions.keySet()) { + Expression headerExpression = this.headerExpressions.get(headerName); + Object headerValue = headerExpression.getValue(evaluationContext); + if (headerValue != null) { + headers.put(headerName, headerValue); + } + } + } + + if (payload == null) { + if (requestBody != null) { payload = requestBody; } else { - payload = requestParameterMap; + payload = requestParams; } } - - Message message = MessageBuilder.withPayload(payload).copyHeaders(headers).setHeader( - org.springframework.integration.http.HttpHeaders.REQUEST_URL, request.getURI().toString()) + + Message message = MessageBuilder.withPayload(payload).copyHeaders(headers) + .setHeader(org.springframework.integration.http.HttpHeaders.REQUEST_URL, + request.getURI().toString()) .setHeader(org.springframework.integration.http.HttpHeaders.REQUEST_METHOD, - request.getMethod().toString()).setHeader( - org.springframework.integration.http.HttpHeaders.USER_PRINCIPAL, + request.getMethod().toString()) + .setHeader(org.springframework.integration.http.HttpHeaders.USER_PRINCIPAL, servletRequest.getUserPrincipal()).build(); + Object reply = null; if (this.expectReply) { reply = this.sendAndReceiveMessage(message); @@ -364,7 +372,7 @@ abstract class HttpRequestHandlingEndpointSupport extends MessagingGatewaySuppor ServletServerHttpResponse response = new ServletServerHttpResponse(servletResponse); this.headerMapper.fromHeaders(((Message) reply).getHeaders(), response.getHeaders()); HttpStatus httpStatus = this.resolveHttpStatusFromHeaders(((Message) reply).getHeaders()); - if (httpStatus != null){ + if (httpStatus != null) { response.setStatusCode(httpStatus); } response.close(); 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 index 844e786fa9..971f11abd9 100644 --- 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 @@ -13,52 +13,34 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package org.springframework.integration.http.inbound; -import java.util.Collections; - -import org.springframework.beans.BeansException; -import org.springframework.beans.factory.BeanFactoryUtils; -import org.springframework.context.ApplicationContext; -import org.springframework.util.ObjectUtils; 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 { - - public UriPathHandlerMapping(){ - this.setOrder(Integer.MIN_VALUE); - } - - protected void detectHandlers() throws BeansException { - if (logger.isDebugEnabled()) { - logger.debug("Looking for URL mappings in application context: " + getApplicationContext()); - } - - String[] beanNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(getApplicationContext(), HttpRequestHandlingEndpointSupport.class); - - for (String beanName : beanNames) { - String[] urls = determineUrlsForHandler(beanName); - if (!ObjectUtils.isEmpty(urls)) { - // URL paths found: Let's consider it a handler. - registerHandler(urls, beanName); - } - else { - if (logger.isDebugEnabled()) { - logger.debug("Rejected bean name '" + beanName + "': no URL paths identified"); - } - } - } - } @Override protected String[] determineUrlsForHandler(String beanName) { - ApplicationContext context = this.getApplicationContext(); - HttpRequestHandlingEndpointSupport handler = context.getBean(beanName, HttpRequestHandlingEndpointSupport.class); - String path = handler.getPath(); - return Collections.singletonList(path).toArray(new String[]{}); + 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/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 e00725864f..dff2dee5a0 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 @@ -31,23 +31,23 @@ path="/fname/{f}/lname/{l}" channel="requests" mapped-request-headers="foo,bar" - payload-expression="#f"> -
+ payload-expression="#pathVariables.f"> +
-
+ payload-expression="#pathVariables.f"> +
-
+ payload-expression="#pathVariables.f"> +
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 12035744ac..47b6b4eb47 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 @@ -36,6 +36,7 @@ import org.junit.runner.RunWith; import org.springframework.beans.DirectFieldAccessor; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.expression.spel.SpelEvaluationException; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.integration.Message; @@ -47,6 +48,7 @@ import org.springframework.integration.http.inbound.HttpRequestHandlingMessaging import org.springframework.integration.http.support.DefaultHttpHeaderMapper; import org.springframework.integration.test.util.TestUtils; import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.test.annotation.ExpectedException; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.util.MultiValueMap; @@ -146,6 +148,7 @@ public class HttpInboundChannelAdapterParserTests { assertEquals("bill", payload); assertEquals("clinton", message.getHeaders().get("lname")); } + @Test // ensure that 'path' takes priority over name // INT-1677 public void withNameAndExpressionsAndPath() throws Exception { @@ -167,8 +170,9 @@ public class HttpInboundChannelAdapterParserTests { assertEquals("clinton", message.getHeaders().get("lname")); } - @Test + @Test // INT-1677 + @ExpectedException(SpelEvaluationException.class) public void withNameAndExpressionsNoPath() throws Exception { MockHttpServletRequest request = new MockHttpServletRequest(); request.setMethod("POST"); 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 1071a98c9b..dd9648cf51 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 @@ -68,11 +68,9 @@ public class HttpRequestHandlingMessagingGatewayWithPathMappingTests { } @Test - public void withoutPayloadExpressionPointingToUriVariable() throws Exception { - + public void withPayloadExpressionPointingToPathVariable() throws Exception { DirectChannel echoChannel = new DirectChannel(); echoChannel.subscribe(new MessageHandler() { - public void handleMessage(Message message) throws MessagingException { MessageChannel replyChannel = (MessageChannel) message.getHeaders().getReplyChannel(); replyChannel.send(message); @@ -80,21 +78,20 @@ public class HttpRequestHandlingMessagingGatewayWithPathMappingTests { }); MockHttpServletRequest request = new MockHttpServletRequest(); MockHttpServletResponse response = new MockHttpServletResponse(); - + request.setMethod("POST"); request.setContentType("text/plain"); request.setParameter("foo", "bar"); request.setContent("hello".getBytes()); request.setRequestURI("/fname/bill/lname/clinton"); - + HttpRequestHandlingMessagingGateway gateway = new HttpRequestHandlingMessagingGateway(true); gateway.setPath("/fname/{f}/lname/{l}"); gateway.setRequestChannel(echoChannel); - gateway.setPayloadExpression(PARSER.parseExpression("#f")); - + gateway.setPayloadExpression(PARSER.parseExpression("#pathVariables.f")); + Object result = gateway.doHandleRequest(request, response); assertEquals("bill", result); - } @SuppressWarnings("unchecked")