From 6c4d9b2111221250e9d8e30f1be7ff794fe60636 Mon Sep 17 00:00:00 2001 From: Artem Bilan Date: Thu, 10 Nov 2016 14:10:01 -0500 Subject: [PATCH] INT-4156: Merge HTTP Java DSL and Some Refinements JIRA: https://jira.spring.io/browse/INT-4156 * Copy `Http` Java DSL from with some fixes to the `HttpMessageHandlerSpec` and Java 8 style * Port `HttpDslTests` and "de-Boot" it with the Mock MVC * Upgrade to Spring Security 4.2.0 * Make `spring-integration-security` as a dependency for HTTP module for better test coverage for Security from SI Web perspective No need in `AuthenticationManager` bean JavaDocs for HTTP DSL components and some JavaDocs improvements for `HttpRequestHandlingEndpointSupport` and `HttpRequestExecutingMessageHandler` Restore optional ROME dependency for proper HTTP module compilation Add missed JavaDoc and mark all HTTP tests with `@DirtiesContext` Add JavaDocs for `Http` factory methods --- build.gradle | 12 +- .../http/dsl/BaseHttpInboundEndpointSpec.java | 468 ++++++++++++++++++ .../integration/http/dsl/Http.java | 290 +++++++++++ .../http/dsl/HttpControllerEndpointSpec.java | 75 +++ .../http/dsl/HttpMessageHandlerSpec.java | 368 ++++++++++++++ .../dsl/HttpRequestHandlerEndpointSpec.java | 47 ++ .../integration/http/dsl/package-info.java | 4 + .../HttpRequestHandlingEndpointSupport.java | 10 + .../HttpRequestExecutingMessageHandler.java | 27 +- .../HttpInboundChannelAdapterParserTests.java | 1 + .../config/HttpInboundGatewayParserTests.java | 1 + ...HttpOutboundChannelAdapterParserTests.java | 2 + .../HttpOutboundGatewayParserTests.java | 2 + .../config/OutboundResponseTypeTests.java | 2 + .../integration/http/dsl/HttpDslTests.java | 178 +++++++ ...Int2312RequestMappingIntegrationTests.java | 2 + .../IntegrationGraphControllerTests.java | 8 +- .../http/outbound/CookieTests.java | 2 + 18 files changed, 1476 insertions(+), 23 deletions(-) create mode 100644 spring-integration-http/src/main/java/org/springframework/integration/http/dsl/BaseHttpInboundEndpointSpec.java create mode 100644 spring-integration-http/src/main/java/org/springframework/integration/http/dsl/Http.java create mode 100644 spring-integration-http/src/main/java/org/springframework/integration/http/dsl/HttpControllerEndpointSpec.java create mode 100644 spring-integration-http/src/main/java/org/springframework/integration/http/dsl/HttpMessageHandlerSpec.java create mode 100644 spring-integration-http/src/main/java/org/springframework/integration/http/dsl/HttpRequestHandlerEndpointSpec.java create mode 100644 spring-integration-http/src/main/java/org/springframework/integration/http/dsl/package-info.java create mode 100644 spring-integration-http/src/test/java/org/springframework/integration/http/dsl/HttpDslTests.java diff --git a/build.gradle b/build.gradle index 6cb9e6af48..53fa3e04cd 100644 --- a/build.gradle +++ b/build.gradle @@ -10,7 +10,7 @@ buildscript { } plugins { - id 'org.sonarqube' version '1.2' + id 'org.sonarqube' version '2.1' } description = 'Spring Integration' @@ -127,7 +127,7 @@ subprojects { subproject -> springDataMongoVersion = '1.10.0.BUILD-SNAPSHOT' springDataRedisVersion = '1.8.0.BUILD-SNAPSHOT' springGemfireVersion = '1.9.0.BUILD-SNAPSHOT' - springSecurityVersion = '4.1.2.RELEASE' + springSecurityVersion = '4.2.0.RELEASE' springSocialTwitterVersion = '1.1.2.RELEASE' springRetryVersion = '1.2.0.RC1' springVersion = project.hasProperty('springVersion') ? project.springVersion : '5.0.0.BUILD-SNAPSHOT' @@ -380,8 +380,12 @@ project('spring-integration-http') { dependencies { compile project(":spring-integration-core") compile "org.springframework:spring-webmvc:$springVersion" - compile("com.rometools:rome:$romeToolsVersion", optional) - compile("javax.servlet:javax.servlet-api:$servletApiVersion", provided) + compile ("javax.servlet:javax.servlet-api:$servletApiVersion", provided) + compile ("com.rometools:rome:$romeToolsVersion", optional) + + testCompile project(":spring-integration-security") + testCompile "org.springframework.security:spring-security-config:$springSecurityVersion" + testCompile "org.springframework.security:spring-security-test:$springSecurityVersion" } } diff --git a/spring-integration-http/src/main/java/org/springframework/integration/http/dsl/BaseHttpInboundEndpointSpec.java b/spring-integration-http/src/main/java/org/springframework/integration/http/dsl/BaseHttpInboundEndpointSpec.java new file mode 100644 index 0000000000..20ed6ae85a --- /dev/null +++ b/spring-integration-http/src/main/java/org/springframework/integration/http/dsl/BaseHttpInboundEndpointSpec.java @@ -0,0 +1,468 @@ +/* + * Copyright 2016 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.dsl; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.function.Consumer; +import java.util.function.Function; + +import org.springframework.expression.Expression; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.converter.HttpMessageConverter; +import org.springframework.integration.dsl.ComponentsRegistration; +import org.springframework.integration.dsl.MessagingGatewaySpec; +import org.springframework.integration.expression.FunctionExpression; +import org.springframework.integration.http.inbound.CrossOrigin; +import org.springframework.integration.http.inbound.HttpRequestHandlingEndpointSupport; +import org.springframework.integration.http.inbound.RequestMapping; +import org.springframework.integration.http.support.DefaultHttpHeaderMapper; +import org.springframework.integration.mapping.HeaderMapper; +import org.springframework.util.Assert; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.multipart.MultipartResolver; + +/** + * A base {@link MessagingGatewaySpec} for the {@link HttpRequestHandlingEndpointSupport} implementations. + * + * @param the target {@link BaseHttpInboundEndpointSpec} implementation type. + * @param the target {@link HttpRequestHandlingEndpointSupport} implementation type. + * + * @author Artem Bilan + * + * @since 5.0 + */ +public abstract class BaseHttpInboundEndpointSpec, + E extends HttpRequestHandlingEndpointSupport> + extends MessagingGatewaySpec implements ComponentsRegistration { + + private final RequestMapping requestMapping = new RequestMapping(); + + private final Map headerExpressions = new HashMap<>(); + + private final HeaderMapper headerMapper = DefaultHttpHeaderMapper.inboundMapper(); + + private HeaderMapper explicitHeaderMapper; + + BaseHttpInboundEndpointSpec(E endpoint, String... path) { + super(endpoint); + this.requestMapping.setPathPatterns(path); + this.target.setRequestMapping(this.requestMapping); + this.target.setHeaderExpressions(this.headerExpressions); + this.target.setHeaderMapper(this.headerMapper); + } + + /** + * Provide a {@link Consumer} for configuring {@link RequestMapping} via {@link RequestMappingSpec} + * @param requestMapping the {@link Consumer} to configure {@link RequestMappingSpec}. + * @return the spec + * @see RequestMapping + */ + public S requestMapping(Consumer requestMapping) { + requestMapping.accept(new RequestMappingSpec(this.requestMapping)); + return _this(); + } + + /** + * Provide a {@link Consumer} for configuring {@link CrossOrigin} via {@link CrossOriginSpec} + * @param crossOrigin the {@link Consumer} to configure {@link CrossOriginSpec}. + * @return the spec + * @see CrossOrigin + */ + public S crossOrigin(Consumer crossOrigin) { + CrossOriginSpec originSpec = new CrossOriginSpec(); + crossOrigin.accept(originSpec); + this.target.setCrossOrigin(originSpec.crossOrigin); + return _this(); + } + + /** + * Specify a SpEL expression to evaluate in order to generate the Message payload. + * @param payloadExpression The payload expression. + * @return the spec + * @see HttpRequestHandlingEndpointSupport#setPayloadExpression(Expression) + */ + public S payloadExpression(String payloadExpression) { + return payloadExpression(PARSER.parseExpression(payloadExpression)); + } + + /** + * Specify a SpEL expression to evaluate in order to generate the Message payload. + * @param payloadExpression The payload expression. + * @return the spec + * @see HttpRequestHandlingEndpointSupport#setPayloadExpression(Expression) + */ + public S payloadExpression(Expression payloadExpression) { + this.target.setPayloadExpression(payloadExpression); + return _this(); + } + + /** + * Specify a {@link Function} to evaluate in order to generate the Message payload. + * @param payloadFunction The payload {@link Function}. + * @param

the expected HTTP request body type. + * @return the spec + * @see HttpRequestHandlingEndpointSupport#setPayloadExpression(Expression) + */ + public

S payloadFunction(Function, ?> payloadFunction) { + return payloadExpression(new FunctionExpression<>(payloadFunction)); + } + + /** + * Specify a Map of SpEL expressions to evaluate in order to generate the Message headers. + * @param headerExpressions The {@link Map} of SpEL expressions for headers. + * @return the spec + * @see HttpRequestHandlingEndpointSupport#setHeaderExpressions(Map) + */ + public S headerExpressions(Map headerExpressions) { + Assert.notNull(headerExpressions, "'headerExpressions' must not be null"); + this.headerExpressions.clear(); + this.headerExpressions.putAll(headerExpressions); + return _this(); + } + + /** + * Specify SpEL expression for provided header to populate. + * @param header the header name to populate. + * @param expression the SpEL expression for the header. + * @return the spec + * @see HttpRequestHandlingEndpointSupport#setHeaderExpressions(Map) + */ + public S headerExpression(String header, String expression) { + return headerExpression(header, PARSER.parseExpression(expression)); + } + + /** + * Specify SpEL expression for provided header to populate. + * @param header the header name to populate. + * @param expression the SpEL expression for the header. + * @return the spec + * @see HttpRequestHandlingEndpointSupport#setHeaderExpressions(Map) + */ + public S headerExpression(String header, Expression expression) { + this.headerExpressions.put(header, expression); + return _this(); + } + + /** + * Specify a {@link Function} for provided header to populate. + * @param header the header name to add. + * @param headerFunction the function to evaluate the header value against {@link HttpEntity}. + * @param

the expected HTTP body type. + * @return the current Spec. + * @see HttpRequestHandlingEndpointSupport#setHeaderExpressions(Map) + */ + public

S headerFunction(String header, Function, ?> headerFunction) { + return headerExpression(header, new FunctionExpression<>(headerFunction)); + } + + /** + * Set the message body converters to use. + * These converters are used to convert from and to HTTP requests and responses. + * @param messageConverters The message converters. + * @return the current Spec. + */ + public S messageConverters(HttpMessageConverter... messageConverters) { + this.target.setMessageConverters(Arrays.asList(messageConverters)); + return _this(); + } + + /** + * Flag which determines if the default converters should be available after custom converters. + * @param mergeWithDefaultConverters true to merge, false to replace. + * @return the current Spec. + */ + public S mergeWithDefaultConverters(boolean mergeWithDefaultConverters) { + this.target.setMergeWithDefaultConverters(mergeWithDefaultConverters); + return _this(); + } + + /** + * Set the {@link HeaderMapper} to use when mapping between HTTP headers and MessageHeaders. + * @param headerMapper The header mapper. + * @return the current Spec. + */ + public S headerMapper(HeaderMapper headerMapper) { + this.target.setHeaderMapper(headerMapper); + this.explicitHeaderMapper = headerMapper; + return _this(); + } + + /** + * Provide the pattern array for request headers to map. + * @param patterns the patterns for request headers to map. + * @return the current Spec. + * @see DefaultHttpHeaderMapper#setOutboundHeaderNames(String[]) + */ + public S mappedRequestHeaders(String... patterns) { + Assert.isNull(this.explicitHeaderMapper, + "The 'mappedRequestHeaders' must be specified on the provided 'headerMapper': " + + this.explicitHeaderMapper); + ((DefaultHttpHeaderMapper) this.headerMapper).setOutboundHeaderNames(patterns); + return _this(); + } + + /** + * Provide the pattern array for response headers to map. + * @param patterns the patterns for response headers to map. + * @return the current Spec. + * @see DefaultHttpHeaderMapper#setInboundHeaderNames(String[]) + */ + public S mappedResponseHeaders(String... patterns) { + Assert.isNull(this.explicitHeaderMapper, + "The 'mappedRequestHeaders' must be specified on the provided 'headerMapper': " + + this.explicitHeaderMapper); + ((DefaultHttpHeaderMapper) this.headerMapper).setInboundHeaderNames(patterns); + return _this(); + } + + /** + * Specify the type of payload to be generated when the inbound HTTP request content is read by the + * {@link HttpMessageConverter}s. + * By default this value is null which means at runtime any "text" Content-Type will + * result in String while all others default to byte[].class. + * @param requestPayloadType The payload type. + * @return the current Spec. + */ + public S requestPayloadType(Class requestPayloadType) { + this.target.setRequestPayloadType(requestPayloadType); + return _this(); + } + + /** + * Specify whether only the reply Message's payload should be passed in the response. + * If this is set to {@code false}, the entire Message will be used to generate the response. + * The default is {@code true}. + * @param extractReplyPayload true to extract the reply payload. + * @return the current Spec. + */ + public S extractReplyPayload(boolean extractReplyPayload) { + this.target.setExtractReplyPayload(extractReplyPayload); + return _this(); + } + + /** + * Specify the {@link MultipartResolver} to use when checking requests. + * @param multipartResolver The multipart resolver. + * @return the current Spec. + */ + public S multipartResolver(MultipartResolver multipartResolver) { + this.target.setMultipartResolver(multipartResolver); + return _this(); + } + + /** + * Specify the {@link Expression} to resolve a status code for Response to override + * the default '200 OK' or '500 Internal Server Error' for a timeout. + * @param statusCodeExpression The status code Expression. + * @return the current Spec. + * @see HttpRequestHandlingEndpointSupport#setStatusCodeExpression(Expression) + */ + public S statusCodeExpression(String statusCodeExpression) { + this.target.setStatusCodeExpressionString(statusCodeExpression); + return _this(); + } + + /** + * Specify the {@link Expression} to resolve a status code for Response to override + * the default '200 OK' or '500 Internal Server Error' for a timeout. + * @param statusCodeExpression The status code Expression. + * @return the current Spec. + * @see HttpRequestHandlingEndpointSupport#setStatusCodeExpression(Expression) + */ + public S statusCodeExpression(Expression statusCodeExpression) { + this.target.setStatusCodeExpression(statusCodeExpression); + return _this(); + } + + /** + * Specify the {@link Function} to resolve a status code for Response to override + * the default '200 OK' or '500 Internal Server Error' for a timeout. + * @param statusCodeFunction The status code {@link Function}. + * @return the current Spec. + * @see HttpRequestHandlingEndpointSupport#setStatusCodeExpression(Expression) + */ + public S statusCodeFunction(Function statusCodeFunction) { + return statusCodeExpression(new FunctionExpression<>(statusCodeFunction)); + } + + @Override + public Collection getComponentsToRegister() { + HeaderMapper headerMapperToRegister = + (this.explicitHeaderMapper != null ? this.explicitHeaderMapper : this.headerMapper); + return Collections.singletonList(headerMapperToRegister); + } + + /** + * A fluent API for the {@link RequestMapping}. + */ + public static final class RequestMappingSpec { + + private final RequestMapping requestMapping; + + RequestMappingSpec(RequestMapping requestMapping) { + this.requestMapping = requestMapping; + } + + /** + * The HTTP request methods to map to, narrowing the primary mapping: + * GET, POST, HEAD, OPTIONS, PUT, PATCH, DELETE, TRACE. + * @param supportedMethods the {@link HttpMethod}s to use. + * @return the spec + */ + public RequestMappingSpec methods(HttpMethod... supportedMethods) { + this.requestMapping.setMethods(supportedMethods); + return this; + } + + /** + * The parameters of the mapped request, narrowing the primary mapping. + * @param params the request params to map to. + * @return the spec + */ + public RequestMappingSpec params(String... params) { + this.requestMapping.setParams(params); + return this; + } + + /** + * The headers of the mapped request, narrowing the primary mapping. + * @param headers the request headers to map to. + * @return the spec + */ + public RequestMappingSpec headers(String... headers) { + this.requestMapping.setHeaders(headers); + return this; + } + + /** + * The consumable media types of the mapped request, narrowing the primary mapping. + * @param consumes the the media types for {@code Content-Type} header. + * @return the spec + */ + public RequestMappingSpec consumes(String... consumes) { + this.requestMapping.setConsumes(consumes); + return this; + } + + /** + * The producible media types of the mapped request, narrowing the primary mapping. + * @param produces the the media types for {@code Accept} header. + * @return the spec + */ + public RequestMappingSpec produces(String... produces) { + this.requestMapping.setProduces(produces); + return this; + } + + } + + /** + * A fluent API for the {@link CrossOrigin}. + */ + public static final class CrossOriginSpec { + + private final CrossOrigin crossOrigin = new CrossOrigin(); + + CrossOriginSpec() { + super(); + } + + /** + * List of allowed origins, e.g. {@code "http://domain1.com"}. + *

These values are placed in the {@code Access-Control-Allow-Origin} + * header of both the pre-flight response and the actual response. + * {@code "*"} means that all origins are allowed. + *

If undefined, all origins are allowed. + * @param origin the list of allowed origins. + * @return the spec + */ + public CrossOriginSpec origin(String... origin) { + this.crossOrigin.setOrigin(origin); + return this; + } + + /** + * List of request headers that can be used during the actual request. + *

This property controls the value of the pre-flight response's + * {@code Access-Control-Allow-Headers} header. + * {@code "*"} means that all headers requested by the client are allowed. + * @param allowedHeaders the list of request headers. + * @return the spec + */ + public CrossOriginSpec allowedHeaders(String... allowedHeaders) { + this.crossOrigin.setAllowedHeaders(allowedHeaders); + return this; + } + + /** + * List of response headers that the user-agent will allow the client to access. + *

This property controls the value of actual response's + * {@code Access-Control-Expose-Headers} header. + * @param exposedHeaders the list of response headers. + * @return the spec + */ + public CrossOriginSpec exposedHeaders(String... exposedHeaders) { + this.crossOrigin.setExposedHeaders(exposedHeaders); + return this; + } + + /** + * List of supported HTTP request methods, e.g. + * {@code "{RequestMethod.GET, RequestMethod.POST}"}. + *

Methods specified here override those specified via {@code RequestMapping}. + * @param method the list of supported HTTP request methods + * @return the spec + */ + public CrossOriginSpec method(RequestMethod... method) { + this.crossOrigin.setMethod(method); + return this; + } + + /** + * Whether the browser should include any cookies associated with the + * domain of the request being annotated. + *

Set to {@code "false"} if such cookies should not included. + * @param allowCredentials the {@code boolean} flag to include + * {@code Access-Control-Allow-Credentials=true} in pre-flight response or not + * @return the spec + */ + public CrossOriginSpec allowCredentials(Boolean allowCredentials) { + this.crossOrigin.setAllowCredentials(allowCredentials); + return this; + } + + /** + * The maximum age (in seconds) of the cache duration for pre-flight responses. + *

This property controls the value of the {@code Access-Control-Max-Age} + * header in the pre-flight response. + * @param maxAge the maximum age (in seconds) of the cache duration for pre-flight responses. + * @return the spec + */ + public CrossOriginSpec maxAge(long maxAge) { + this.crossOrigin.setMaxAge(maxAge); + return this; + } + + } + +} diff --git a/spring-integration-http/src/main/java/org/springframework/integration/http/dsl/Http.java b/spring-integration-http/src/main/java/org/springframework/integration/http/dsl/Http.java new file mode 100644 index 0000000000..721184a4f9 --- /dev/null +++ b/spring-integration-http/src/main/java/org/springframework/integration/http/dsl/Http.java @@ -0,0 +1,290 @@ +/* + * Copyright 2016 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.dsl; + +import java.net.URI; +import java.util.function.Function; + +import org.springframework.expression.Expression; +import org.springframework.expression.common.LiteralExpression; +import org.springframework.integration.expression.FunctionExpression; +import org.springframework.integration.http.inbound.HttpRequestHandlingController; +import org.springframework.integration.http.inbound.HttpRequestHandlingMessagingGateway; +import org.springframework.messaging.Message; +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; +import org.springframework.web.client.RestTemplate; + +/** + * The HTTP components Factory. + * + * @author Artem Bilan + * + * @since 5.0 + */ +public final class Http { + + /** + * Create an {@link HttpMessageHandlerSpec} builder for one-way adapter based on provided {@link URI}. + * @param uri the {@link URI} to send requests. + * @return the HttpMessageHandlerSpec instance + */ + public static HttpMessageHandlerSpec outboundChannelAdapter(URI uri) { + return outboundChannelAdapter(uri, null); + } + + /** + * Create an {@link HttpMessageHandlerSpec} builder for one-way adapter based on provided {@code uri}. + * @param uri the {@code uri} to send requests. + * @return the HttpMessageHandlerSpec instance + */ + public static HttpMessageHandlerSpec outboundChannelAdapter(String uri) { + return outboundChannelAdapter(uri, null); + } + + /** + * Create an {@link HttpMessageHandlerSpec} builder for one-way adapter based on provided {@code Function} + * to evaluate target {@code uri} against request message. + * @param uriFunction the {@code Function} to evaluate {@code uri} at runtime. + * @param

the expected payload type. + * @return the HttpMessageHandlerSpec instance + */ + public static

HttpMessageHandlerSpec outboundChannelAdapter(Function, ?> uriFunction) { + return outboundChannelAdapter(new FunctionExpression<>(uriFunction)); + } + + /** + * Create an {@link HttpMessageHandlerSpec} builder for one-way adapter based on provided SpEL {@link Expression} + * to evaluate target {@code uri} against request message. + * @param uriExpression the SpEL {@link Expression} to evaluate {@code uri} at runtime. + * @return the HttpMessageHandlerSpec instance + */ + public static HttpMessageHandlerSpec outboundChannelAdapter(Expression uriExpression) { + return outboundChannelAdapter(uriExpression, null); + } + + /** + * Create an {@link HttpMessageHandlerSpec} builder for one-way adapter + * based on provided {@link URI} and {@link RestTemplate}. + * @param uri the {@link URI} to send requests. + * @param restTemplate {@link RestTemplate} to use. + * @return the HttpMessageHandlerSpec instance + */ + public static HttpMessageHandlerSpec outboundChannelAdapter(URI uri, RestTemplate restTemplate) { + return new HttpMessageHandlerSpec(uri, restTemplate).expectReply(false); + } + + /** + * Create an {@link HttpMessageHandlerSpec} builder for one-way adapter + * based on provided {@code uri} and {@link RestTemplate}. + * @param uri the {@code uri} to send requests. + * @param restTemplate {@link RestTemplate} to use. + * @return the HttpMessageHandlerSpec instance + */ + public static HttpMessageHandlerSpec outboundChannelAdapter(String uri, RestTemplate restTemplate) { + return new HttpMessageHandlerSpec(uri, restTemplate).expectReply(false); + } + + /** + * Create an {@link HttpMessageHandlerSpec} builder for one-way adapter + * based on provided {@code Function} to evaluate target {@code uri} against request message + * and {@link RestTemplate} for HTTP exchanges. + * @param uriFunction the {@code Function} to evaluate {@code uri} at runtime. + * @param restTemplate {@link RestTemplate} to use. + * @param

the expected payload type. + * @return the HttpMessageHandlerSpec instance + */ + public static

HttpMessageHandlerSpec outboundChannelAdapter(Function, ?> uriFunction, + RestTemplate restTemplate) { + return outboundChannelAdapter(new FunctionExpression<>(uriFunction), restTemplate); + } + + /** + * Create an {@link HttpMessageHandlerSpec} builder for one-way adapter + * based on provided SpEL {@link Expression} to evaluate target {@code uri} + * against request message and {@link RestTemplate} for HTTP exchanges. + * @param uriExpression the SpEL {@link Expression} to evaluate {@code uri} at runtime. + * @param restTemplate {@link RestTemplate} to use. + * @return the HttpMessageHandlerSpec instance + */ + public static HttpMessageHandlerSpec outboundChannelAdapter(Expression uriExpression, RestTemplate restTemplate) { + return new HttpMessageHandlerSpec(uriExpression, restTemplate).expectReply(false); + } + + /** + * Create an {@link HttpMessageHandlerSpec} builder for request-reply gateway based on provided {@link URI}. + * @param uri the {@link URI} to send requests. + * @return the HttpMessageHandlerSpec instance + */ + public static HttpMessageHandlerSpec outboundGateway(URI uri) { + return outboundGateway(uri, null); + } + + /** + * Create an {@link HttpMessageHandlerSpec} builder for request-reply gateway based on provided {@code uri}. + * @param uri the {@code uri} to send requests. + * @return the HttpMessageHandlerSpec instance + */ + public static HttpMessageHandlerSpec outboundGateway(String uri) { + return outboundGateway(uri, null); + } + + /** + * Create an {@link HttpMessageHandlerSpec} builder for request-reply gateway + * based on provided {@code Function} to evaluate target {@code uri} against request message. + * @param uriFunction the {@code Function} to evaluate {@code uri} at runtime. + * @param

the expected payload type. + * @return the HttpMessageHandlerSpec instance + */ + public static

HttpMessageHandlerSpec outboundGateway(Function, ?> uriFunction) { + return outboundGateway(new FunctionExpression<>(uriFunction)); + } + + /** + * Create an {@link HttpMessageHandlerSpec} builder for request-reply gateway + * based on provided SpEL {@link Expression} to evaluate target {@code uri} against request message. + * @param uriExpression the SpEL {@link Expression} to evaluate {@code uri} at runtime. + * @return the HttpMessageHandlerSpec instance + */ + public static HttpMessageHandlerSpec outboundGateway(Expression uriExpression) { + return outboundGateway(uriExpression, null); + } + + /** + * Create an {@link HttpMessageHandlerSpec} builder for request-reply gateway + * based on provided {@link URI} and {@link RestTemplate}. + * @param uri the {@link URI} to send requests. + * @param restTemplate {@link RestTemplate} to use. + * @return the HttpMessageHandlerSpec instance + */ + public static HttpMessageHandlerSpec outboundGateway(URI uri, RestTemplate restTemplate) { + return new HttpMessageHandlerSpec(uri, restTemplate); + } + + /** + * Create an {@link HttpMessageHandlerSpec} builder for request-reply gateway + * based on provided {@code uri} and {@link RestTemplate}. + * @param uri the {@code uri} to send requests. + * @param restTemplate {@link RestTemplate} to use. + * @return the HttpMessageHandlerSpec instance + */ + public static HttpMessageHandlerSpec outboundGateway(String uri, RestTemplate restTemplate) { + return new HttpMessageHandlerSpec(uri, restTemplate); + } + + /** + * Create an {@link HttpMessageHandlerSpec} builder for request-reply gateway + * based on provided {@code Function} to evaluate target {@code uri} against request message + * and {@link RestTemplate} for HTTP exchanges. + * @param uriFunction the {@code Function} to evaluate {@code uri} at runtime. + * @param restTemplate {@link RestTemplate} to use. + * @param

the expected payload type. + * @return the HttpMessageHandlerSpec instance + */ + public static

HttpMessageHandlerSpec outboundGateway(Function, ?> uriFunction, + RestTemplate restTemplate) { + return outboundGateway(new FunctionExpression<>(uriFunction), restTemplate); + } + + /** + * Create an {@link HttpMessageHandlerSpec} builder for request-reply gateway + * based on provided SpEL {@link Expression} to evaluate target {@code uri} + * against request message and {@link RestTemplate} for HTTP exchanges. + * @param uriExpression the SpEL {@link Expression} to evaluate {@code uri} at runtime. + * @param restTemplate {@link RestTemplate} to use. + * @return the HttpMessageHandlerSpec instance + */ + public static HttpMessageHandlerSpec outboundGateway(Expression uriExpression, RestTemplate restTemplate) { + return new HttpMessageHandlerSpec(uriExpression, restTemplate); + } + + /** + * Create an {@link HttpControllerEndpointSpec} builder for one-way adapter + * based on the provided MVC {@code viewName} and {@code path} array for mapping. + * @param viewName the MVC view name to build in the end of request. + * @param path the path mapping URIs (e.g. "/myPath.do"). + * @return the HttpControllerEndpointSpec instance + */ + public static HttpControllerEndpointSpec inboundControllerAdapter(String viewName, String... path) { + Assert.isTrue(StringUtils.hasText(viewName), "View name must not be empty"); + return inboundControllerAdapter(new LiteralExpression(viewName), path); + } + + /** + * Create an {@link HttpControllerEndpointSpec} builder for one-way adapter + * based on the provided SpEL expression and {@code path} array for mapping. + * @param viewExpression the SpEL expression to evaluate MVC view name to build in the end of request. + * @param path the path mapping URIs (e.g. "/myPath.do"). + * @return the HttpControllerEndpointSpec instance + */ + public static HttpControllerEndpointSpec inboundControllerAdapter(Expression viewExpression, String... path) { + HttpRequestHandlingController controller = new HttpRequestHandlingController(false); + controller.setViewExpression(viewExpression); + return new HttpControllerEndpointSpec(controller, path); + } + + /** + * Create an {@link HttpControllerEndpointSpec} builder for request-reply gateway + * based on the provided MVC {@code viewName} and {@code path} array for mapping. + * @param viewName the MVC view name to build in the end of request. + * @param path the path mapping URIs (e.g. "/myPath.do"). + * @return the HttpControllerEndpointSpec instance + */ + public static HttpControllerEndpointSpec inboundControllerGateway(String viewName, String... path) { + Assert.isTrue(StringUtils.hasText(viewName), "View name must not be empty"); + return inboundControllerGateway(new LiteralExpression(viewName), path); + } + + /** + * Create an {@link HttpControllerEndpointSpec} builder for request-reply gateway + * based on the provided SpEL expression and {@code path} array for mapping. + * @param viewExpression the SpEL expression to evaluate MVC view name to build in the end of request. + * @param path the path mapping URIs (e.g. "/myPath.do"). + * @return the HttpControllerEndpointSpec instance + */ + public static HttpControllerEndpointSpec inboundControllerGateway(Expression viewExpression, String... path) { + HttpRequestHandlingController controller = new HttpRequestHandlingController(); + controller.setViewExpression(viewExpression); + return new HttpControllerEndpointSpec(controller, path); + } + + /** + * Create an {@link HttpRequestHandlerEndpointSpec} builder for one-way adapter + * based on the provided {@code path} array for mapping. + * @param path the path mapping URIs (e.g. "/myPath.do"). + * @return the HttpRequestHandlerEndpointSpec instance + */ + public static HttpRequestHandlerEndpointSpec inboundChannelAdapter(String... path) { + HttpRequestHandlingMessagingGateway httpInboundChannelAdapter = new HttpRequestHandlingMessagingGateway(false); + return new HttpRequestHandlerEndpointSpec(httpInboundChannelAdapter, path); + } + + /** + * Create an {@link HttpRequestHandlerEndpointSpec} builder for request-reply gateway + * based on the provided {@code path} array for mapping. + * @param path the path mapping URIs (e.g. "/myPath.do"). + * @return the HttpRequestHandlerEndpointSpec instance + */ + public static HttpRequestHandlerEndpointSpec inboundGateway(String... path) { + return new HttpRequestHandlerEndpointSpec(new HttpRequestHandlingMessagingGateway(), path); + } + + private Http() { + super(); + } + +} diff --git a/spring-integration-http/src/main/java/org/springframework/integration/http/dsl/HttpControllerEndpointSpec.java b/spring-integration-http/src/main/java/org/springframework/integration/http/dsl/HttpControllerEndpointSpec.java new file mode 100644 index 0000000000..df088dfa9f --- /dev/null +++ b/spring-integration-http/src/main/java/org/springframework/integration/http/dsl/HttpControllerEndpointSpec.java @@ -0,0 +1,75 @@ +/* + * Copyright 2016 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.dsl; + +import org.springframework.integration.http.inbound.HttpRequestHandlingController; + +/** + * The {@link BaseHttpInboundEndpointSpec} implementation for the {@link HttpRequestHandlingController}. + * + * @author Artem Bilan + * + * @since 5.0 + * + * @see HttpRequestHandlingController + */ +public class HttpControllerEndpointSpec + extends BaseHttpInboundEndpointSpec { + + HttpControllerEndpointSpec(HttpRequestHandlingController controller, String... path) { + super(controller, path); + } + + /** + * Specify the key to be used when adding the reply Message or payload to the core map + * (will be payload only unless the value + * of {@link HttpRequestHandlingController#setExtractReplyPayload(boolean)} is false). + * The default key is {@code reply}. + * @param replyKey The reply key. + * @return the spec + * @see HttpRequestHandlingController#setReplyKey(String) + */ + public HttpControllerEndpointSpec replyKey(String replyKey) { + this.target.setReplyKey(replyKey); + return this; + } + + /** + * The key used to expose {@link org.springframework.validation.Errors} in the core, + * in the case that message handling fails. + * Defaults to {@code errors}. + * @param errorsKey The key value to set. + * @return the spec + * @see HttpRequestHandlingController#setErrorsKey(String) + */ + public HttpControllerEndpointSpec errorsKey(String errorsKey) { + this.target.setErrorsKey(errorsKey); + return this; + } + + /** + * The error code to use to signal an error in the message handling. + * @param errorCode The error code to set. + * @return the spec + * @see HttpRequestHandlingController#setErrorCode(String) + */ + public HttpControllerEndpointSpec errorCode(String errorCode) { + this.target.setErrorCode(errorCode); + return this; + } + +} diff --git a/spring-integration-http/src/main/java/org/springframework/integration/http/dsl/HttpMessageHandlerSpec.java b/spring-integration-http/src/main/java/org/springframework/integration/http/dsl/HttpMessageHandlerSpec.java new file mode 100644 index 0000000000..8b82b5cf32 --- /dev/null +++ b/spring-integration-http/src/main/java/org/springframework/integration/http/dsl/HttpMessageHandlerSpec.java @@ -0,0 +1,368 @@ +/* + * Copyright 2016 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.dsl; + +import java.net.URI; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.function.Function; + +import org.springframework.core.ParameterizedTypeReference; +import org.springframework.expression.Expression; +import org.springframework.expression.common.LiteralExpression; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.client.ClientHttpRequestFactory; +import org.springframework.http.converter.HttpMessageConverter; +import org.springframework.integration.dsl.ComponentsRegistration; +import org.springframework.integration.dsl.MessageHandlerSpec; +import org.springframework.integration.expression.FunctionExpression; +import org.springframework.integration.expression.ValueExpression; +import org.springframework.integration.http.outbound.HttpRequestExecutingMessageHandler; +import org.springframework.integration.http.support.DefaultHttpHeaderMapper; +import org.springframework.integration.mapping.HeaderMapper; +import org.springframework.messaging.Message; +import org.springframework.util.Assert; +import org.springframework.web.client.ResponseErrorHandler; +import org.springframework.web.client.RestTemplate; + +/** + * The {@link MessageHandlerSpec} implementation for the {@link HttpRequestExecutingMessageHandler}. + * + * @author Artem Bilan + * + * @since 5.0 + * + * @see HttpRequestExecutingMessageHandler + */ +public class HttpMessageHandlerSpec + extends MessageHandlerSpec + implements ComponentsRegistration { + + private final RestTemplate restTemplate; + + private final Map uriVariableExpressions = new HashMap<>(); + + private HeaderMapper headerMapper = DefaultHttpHeaderMapper.outboundMapper(); + + private boolean headerMapperExplicitlySet; + + HttpMessageHandlerSpec(URI uri, RestTemplate restTemplate) { + this(new ValueExpression<>(uri), restTemplate); + } + + HttpMessageHandlerSpec(String uri, RestTemplate restTemplate) { + this(new LiteralExpression(uri), restTemplate); + } + + HttpMessageHandlerSpec(Expression uriExpression, RestTemplate restTemplate) { + this.target = new HttpRequestExecutingMessageHandler(uriExpression, restTemplate); + this.target.setUriVariableExpressions(this.uriVariableExpressions); + this.target.setHeaderMapper(this.headerMapper); + this.restTemplate = restTemplate; + } + + HttpMessageHandlerSpec expectReply(boolean expectReply) { + this.target.setExpectReply(expectReply); + return this; + } + + /** + * Specify whether the real URI should be encoded after uriVariables + * expanding and before send request via {@link RestTemplate}. The default value is true. + * @param encodeUri true if the URI should be encoded. + * @return the spec + */ + public HttpMessageHandlerSpec encodeUri(boolean encodeUri) { + this.target.setEncodeUri(encodeUri); + return this; + } + + /** + * Specify the SpEL {@link Expression} to determine {@link HttpMethod} at runtime. + * @param httpMethodExpression The method expression. + * @return the spec + */ + public HttpMessageHandlerSpec httpMethodExpression(Expression httpMethodExpression) { + this.target.setHttpMethodExpression(httpMethodExpression); + return this; + } + + /** + * Specify a {@link Function} to determine {@link HttpMethod} at runtime. + * @param httpMethodFunction The HTTP method {@link Function}. + * @param

the payload type. + * @return the spec + */ + public

HttpMessageHandlerSpec httpMethodFunction(Function, ?> httpMethodFunction) { + return httpMethodExpression(new FunctionExpression<>(httpMethodFunction)); + } + + /** + * Specify the {@link HttpMethod} for requests. + * The default method is {@code POST}. + * @param httpMethod the {@link HttpMethod} to use. + * @return the spec + */ + public HttpMessageHandlerSpec httpMethod(HttpMethod httpMethod) { + this.target.setHttpMethod(httpMethod); + return this; + } + + /** + * Specify whether the outbound message's payload should be extracted + * when preparing the request body. + * Otherwise the Message instance itself is serialized. + * The default value is {@code true}. + * @param extractPayload true if the payload should be extracted. + * @return the spec + */ + public HttpMessageHandlerSpec extractPayload(boolean extractPayload) { + this.target.setExtractPayload(extractPayload); + return this; + } + + /** + * Specify the charset name to use for converting String-typed payloads to bytes. + * The default is {@code UTF-8}. + * @param charset The charset. + * @return the spec + */ + public HttpMessageHandlerSpec charset(String charset) { + this.target.setCharset(charset); + return this; + } + + /** + * Specify the expected response type for the REST request. + * @param expectedResponseType The expected type. + * @return the spec + */ + public HttpMessageHandlerSpec expectedResponseType(Class expectedResponseType) { + this.target.setExpectedResponseType(expectedResponseType); + return this; + } + + /** + * Specify a {@link ParameterizedTypeReference} for the expected response type for the REST request. + * @param expectedResponseType The {@link ParameterizedTypeReference} for expected type. + * @return the spec + */ + public HttpMessageHandlerSpec expectedResponseType(ParameterizedTypeReference expectedResponseType) { + return expectedResponseTypeExpression(new ValueExpression>(expectedResponseType)); + } + + /** + * Specify a SpEL {@link Expression} to determine the type for the expected response + * The returned value of the expression could be an instance of {@link Class} or + * {@link String} representing a fully qualified class name. + * @param expectedResponseTypeExpression The expected response type expression. + * @return the spec + */ + public HttpMessageHandlerSpec expectedResponseTypeExpression(Expression expectedResponseTypeExpression) { + this.target.setExpectedResponseTypeExpression(expectedResponseTypeExpression); + return this; + } + + /** + * Specify a {@link Function} to determine the type for the expected response + * The returned value of the expression could be an instance of {@link Class} or + * {@link String} representing a fully qualified class name. + * @param expectedResponseTypeFunction The expected response type {@link Function}. + * @param

the payload type. + * @return the spec + */ + public

HttpMessageHandlerSpec expectedResponseTypeFunction( + Function, ?> expectedResponseTypeFunction) { + return expectedResponseTypeExpression(new FunctionExpression<>(expectedResponseTypeFunction)); + } + + /** + * Set the {@link ResponseErrorHandler} for the underlying {@link RestTemplate}. + * @param errorHandler The error handler. + * @return the spec + */ + public HttpMessageHandlerSpec errorHandler(ResponseErrorHandler errorHandler) { + Assert.isNull(this.restTemplate, + "the 'errorHandler' must be specified on the provided 'restTemplate': " + this.restTemplate); + this.target.setErrorHandler(errorHandler); + return this; + } + + /** + * Set a list of {@link HttpMessageConverter}s to be used by the underlying {@link RestTemplate}. + * Converters configured via this method will override the default converters. + * @param messageConverters The message converters. + * @return the spec + */ + public HttpMessageHandlerSpec messageConverters(HttpMessageConverter... messageConverters) { + Assert.isNull(this.restTemplate, + "the 'messageConverters' must be specified on the provided 'restTemplate': " + this.restTemplate); + this.target.setMessageConverters(Arrays.asList(messageConverters)); + return this; + } + + /** + * Set the {@link ClientHttpRequestFactory} for the underlying {@link RestTemplate}. + * @param requestFactory The request factory. + * @return the spec + */ + public HttpMessageHandlerSpec requestFactory(ClientHttpRequestFactory requestFactory) { + Assert.isNull(this.restTemplate, + "the 'requestFactory' must be specified on the provided 'restTemplate': " + this.restTemplate); + this.target.setRequestFactory(requestFactory); + return this; + } + + /** + * Set the {@link HeaderMapper} to use when mapping between HTTP headers and {@code MessageHeaders}. + * @param headerMapper The header mapper. + * @return the spec + */ + public HttpMessageHandlerSpec headerMapper(HeaderMapper headerMapper) { + this.headerMapper = headerMapper; + this.target.setHeaderMapper(this.headerMapper); + this.headerMapperExplicitlySet = true; + return this; + } + + /** + * Provide the pattern array for request headers to map. + * @param patterns the patterns for request headers to map. + * @return the spec + * @see DefaultHttpHeaderMapper#setOutboundHeaderNames(String[]) + */ + public HttpMessageHandlerSpec mappedRequestHeaders(String... patterns) { + Assert.isTrue(!this.headerMapperExplicitlySet, + "The 'mappedRequestHeaders' must be specified on the provided 'headerMapper': " + this.headerMapper); + ((DefaultHttpHeaderMapper) this.headerMapper).setOutboundHeaderNames(patterns); + return this; + } + + /** + * Provide the pattern array for response headers to map. + * @param patterns the patterns for response headers to map. + * @return the current Spec. + * @see DefaultHttpHeaderMapper#setInboundHeaderNames(String[]) + */ + public HttpMessageHandlerSpec mappedResponseHeaders(String... patterns) { + Assert.isTrue(!this.headerMapperExplicitlySet, + "The 'mappedResponseHeaders' must be specified on the provided 'headerMapper': " + this.headerMapper); + ((DefaultHttpHeaderMapper) this.headerMapper).setInboundHeaderNames(patterns); + return this; + } + + /** + * Set the Map of URI variable expressions to evaluate against the outbound message + * when replacing the variable placeholders in a URI template. + * @param uriVariableExpressions The URI variable expressions. + * @return the current Spec. + * @see HttpRequestExecutingMessageHandler#setUriVariableExpressions(Map) + */ + public HttpMessageHandlerSpec uriVariableExpressions(Map uriVariableExpressions) { + this.uriVariableExpressions.clear(); + this.uriVariableExpressions.putAll(uriVariableExpressions); + return this; + } + + /** + * Specify a SpEL expression to evaluate a value for the uri template variable. + * @param variable the uri template variable. + * @param value the expression to evaluate value for te uri template variable. + * @return the current Spec. + * @see HttpRequestExecutingMessageHandler#setUriVariableExpressions(Map) + */ + public HttpMessageHandlerSpec uriVariable(String variable, Expression value) { + this.uriVariableExpressions.put(variable, value); + return this; + } + + /** + * Specify a value for the uri template variable. + * @param variable the uri template variable. + * @param value the expression to evaluate value for te uri template variable. + * @return the current Spec. + * @see HttpRequestExecutingMessageHandler#setUriVariableExpressions(Map) + */ + public HttpMessageHandlerSpec uriVariable(String variable, String value) { + return uriVariable(variable, PARSER.parseExpression(value)); + } + + /** + * Specify a {@link Function} to evaluate a value for the uri template variable. + * @param variable the uri template variable. + * @param valueFunction the {@link Function} to evaluate a value for the uri template variable. + * @param

the payload type. + * @return the current Spec. + * @see HttpRequestExecutingMessageHandler#setUriVariableExpressions(Map) + */ + public

HttpMessageHandlerSpec uriVariable(String variable, Function, ?> valueFunction) { + return uriVariable(variable, new FunctionExpression<>(valueFunction)); + } + + /** + * Specify a SpEL expression to evaluate a {@link Map} of URI variables at runtime against request message. + * @param uriVariablesExpression to use. + * @return the current Spec. + * @see HttpRequestExecutingMessageHandler#setUriVariablesExpression(Expression) + */ + public HttpMessageHandlerSpec uriVariablesExpression(String uriVariablesExpression) { + return uriVariablesExpression(PARSER.parseExpression(uriVariablesExpression)); + } + + /** + * Specify a SpEL expression to evaluate a {@link Map} of URI variables at runtime against request message. + * @param uriVariablesExpression to use. + * @return the current Spec. + * @see HttpRequestExecutingMessageHandler#setUriVariablesExpression(Expression) + */ + public HttpMessageHandlerSpec uriVariablesExpression(Expression uriVariablesExpression) { + this.target.setUriVariablesExpression(uriVariablesExpression); + return this; + } + + /** + * Specify a {@link Function} to evaluate a {@link Map} of URI variables at runtime against request message. + * @param uriVariablesFunction the {@link Function} to use. + * @param

the payload type. + * @return the current Spec. + * @see HttpRequestExecutingMessageHandler#setUriVariablesExpression(Expression) + */ + public

HttpMessageHandlerSpec uriVariablesFunction(Function, Map> uriVariablesFunction) { + return uriVariablesExpression(new FunctionExpression<>(uriVariablesFunction)); + } + + /** + * Set to {@code true} if you wish {@code Set-Cookie} header in response to be + * transferred as {@code Cookie} header in subsequent interaction for a message. + * @param transferCookies the transferCookies to set. + * @return the current Spec. + */ + public HttpMessageHandlerSpec transferCookies(boolean transferCookies) { + this.target.setTransferCookies(transferCookies); + return this; + } + + @Override + public Collection getComponentsToRegister() { + return Collections.singletonList(this.headerMapper); + } + +} diff --git a/spring-integration-http/src/main/java/org/springframework/integration/http/dsl/HttpRequestHandlerEndpointSpec.java b/spring-integration-http/src/main/java/org/springframework/integration/http/dsl/HttpRequestHandlerEndpointSpec.java new file mode 100644 index 0000000000..d6e3b9b176 --- /dev/null +++ b/spring-integration-http/src/main/java/org/springframework/integration/http/dsl/HttpRequestHandlerEndpointSpec.java @@ -0,0 +1,47 @@ +/* + * Copyright 2016 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.dsl; + +import org.springframework.integration.http.inbound.HttpRequestHandlingMessagingGateway; + +/** + * The {@link BaseHttpInboundEndpointSpec} implementation for the {@link HttpRequestHandlingMessagingGateway}. + * + * @author Artem Bilan + * + * @since 5.0 + * + * @see HttpRequestHandlingMessagingGateway + */ +public class HttpRequestHandlerEndpointSpec + extends BaseHttpInboundEndpointSpec { + + HttpRequestHandlerEndpointSpec(HttpRequestHandlingMessagingGateway endpoint, String... path) { + super(endpoint, path); + } + + /** + * Flag to determine if conversion and writing out of message handling exceptions should be attempted. + * @param convertExceptions the flag to set + * @return the spec + */ + public HttpRequestHandlerEndpointSpec convertExceptions(boolean convertExceptions) { + this.target.setConvertExceptions(convertExceptions); + return this; + } + +} diff --git a/spring-integration-http/src/main/java/org/springframework/integration/http/dsl/package-info.java b/spring-integration-http/src/main/java/org/springframework/integration/http/dsl/package-info.java new file mode 100644 index 0000000000..d650fcd421 --- /dev/null +++ b/spring-integration-http/src/main/java/org/springframework/integration/http/dsl/package-info.java @@ -0,0 +1,4 @@ +/** + * Provides HTTP Components support for Spring Integration Java DSL. + */ +package org.springframework.integration.http.dsl; 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 054170efaf..755a66a570 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 @@ -347,6 +347,16 @@ public abstract class HttpRequestHandlingEndpointSupport extends MessagingGatewa public void setMultipartResolver(MultipartResolver multipartResolver) { this.multipartResolver = multipartResolver; } + /** + * Specify the {@link Expression} to resolve a status code for Response to override + * the default '200 OK' or '500 Internal Server Error' for a timeout. + * @param statusCodeExpression The status code Expression. + * @since 5.0 + * @see #setStatusCodeExpression(Expression) + */ + public void setStatusCodeExpressionString(String statusCodeExpression) { + setStatusCodeExpression(EXPRESSION_PARSER.parseExpression(statusCodeExpression)); + } /** * Specify the {@link Expression} to resolve a status code for Response to override diff --git a/spring-integration-http/src/main/java/org/springframework/integration/http/outbound/HttpRequestExecutingMessageHandler.java b/spring-integration-http/src/main/java/org/springframework/integration/http/outbound/HttpRequestExecutingMessageHandler.java index d06c08e4ed..1cc9f430d9 100755 --- a/spring-integration-http/src/main/java/org/springframework/integration/http/outbound/HttpRequestExecutingMessageHandler.java +++ b/spring-integration-http/src/main/java/org/springframework/integration/http/outbound/HttpRequestExecutingMessageHandler.java @@ -83,7 +83,7 @@ import org.springframework.web.util.UriComponentsBuilder; */ public class HttpRequestExecutingMessageHandler extends AbstractReplyProducingMessageHandler { - private final Map uriVariableExpressions = new HashMap(); + private final Map uriVariableExpressions = new HashMap<>(); private final RestTemplate restTemplate; @@ -93,7 +93,7 @@ public class HttpRequestExecutingMessageHandler extends AbstractReplyProducingMe private volatile boolean encodeUri = true; - private volatile Expression httpMethodExpression = new ValueExpression(HttpMethod.POST); + private volatile Expression httpMethodExpression = new ValueExpression<>(HttpMethod.POST); private volatile boolean expectReply = true; @@ -180,8 +180,7 @@ public class HttpRequestExecutingMessageHandler extends AbstractReplyProducingMe } /** - * Specify the SpEL {@link Expression} to determine {@link HttpMethod} dynamically - * + * Specify the SpEL {@link Expression} to determine {@link HttpMethod} at runtime. * @param httpMethodExpression The method expression. */ public void setHttpMethodExpression(Expression httpMethodExpression) { @@ -190,8 +189,8 @@ public class HttpRequestExecutingMessageHandler extends AbstractReplyProducingMe } /** - * Specify the {@link HttpMethod} for requests. The default method will be POST. - * + * Specify the {@link HttpMethod} for requests. + * The default method is {@code POST}. * @param httpMethod The method. */ public void setHttpMethod(HttpMethod httpMethod) { @@ -201,9 +200,9 @@ public class HttpRequestExecutingMessageHandler extends AbstractReplyProducingMe /** * 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. - * + * when preparing the request body. + * Otherwise the Message instance itself is serialized. + * The default value is {@code true}. * @param extractPayload true if the payload should be extracted. */ public void setExtractPayload(boolean extractPayload) { @@ -212,9 +211,8 @@ public class HttpRequestExecutingMessageHandler extends AbstractReplyProducingMe } /** - * Specify the charset name to use for converting String-typed payloads to - * bytes. The default is 'UTF-8'. - * + * Specify the charset name to use for converting String-typed payloads to bytes. + * The default is {@code UTF-8}. * @param charset The charset. */ public void setCharset(String charset) { @@ -251,7 +249,7 @@ public class HttpRequestExecutingMessageHandler extends AbstractReplyProducingMe /** * Specify the {@link Expression} to determine the type for the expected response * The returned value of the expression could be an instance of {@link Class} or - * {@link String} representing a fully qualified class name + * {@link String} representing a fully qualified class name. * @param expectedResponseTypeExpression The expected response type expression. * Also see {@link #setExpectedResponseType} */ @@ -283,9 +281,8 @@ public class HttpRequestExecutingMessageHandler extends AbstractReplyProducingMe } /** - * @param headerMapper The header mapper. - * * Set the {@link HeaderMapper} to use when mapping between HTTP headers and MessageHeaders. + * @param headerMapper The header mapper. */ public void setHeaderMapper(HeaderMapper headerMapper) { Assert.notNull(headerMapper, "headerMapper must not be null"); 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 c9db8b338a..9a924c31ff 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 @@ -74,6 +74,7 @@ import org.springframework.web.servlet.HandlerMapping; */ @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration +@DirtiesContext public class HttpInboundChannelAdapterParserTests extends AbstractHttpInboundTests { @Autowired 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 5bf273d024..8501a453e5 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 @@ -74,6 +74,7 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; */ @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration +@DirtiesContext public class HttpInboundGatewayParserTests { @Autowired diff --git a/spring-integration-http/src/test/java/org/springframework/integration/http/config/HttpOutboundChannelAdapterParserTests.java b/spring-integration-http/src/test/java/org/springframework/integration/http/config/HttpOutboundChannelAdapterParserTests.java index ea3300e3ba..b2c123c31f 100644 --- a/spring-integration-http/src/test/java/org/springframework/integration/http/config/HttpOutboundChannelAdapterParserTests.java +++ b/spring-integration-http/src/test/java/org/springframework/integration/http/config/HttpOutboundChannelAdapterParserTests.java @@ -52,6 +52,7 @@ import org.springframework.integration.test.util.TestUtils; import org.springframework.messaging.Message; import org.springframework.messaging.MessageHandler; import org.springframework.messaging.support.GenericMessage; +import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.util.ObjectUtils; @@ -67,6 +68,7 @@ import org.springframework.web.client.RestTemplate; */ @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration +@DirtiesContext public class HttpOutboundChannelAdapterParserTests { @Autowired @Qualifier("minimalConfig") diff --git a/spring-integration-http/src/test/java/org/springframework/integration/http/config/HttpOutboundGatewayParserTests.java b/spring-integration-http/src/test/java/org/springframework/integration/http/config/HttpOutboundGatewayParserTests.java index 355152cb6d..23cbb0623d 100644 --- a/spring-integration-http/src/test/java/org/springframework/integration/http/config/HttpOutboundGatewayParserTests.java +++ b/spring-integration-http/src/test/java/org/springframework/integration/http/config/HttpOutboundGatewayParserTests.java @@ -52,6 +52,7 @@ import org.springframework.integration.test.util.TestUtils; import org.springframework.messaging.Message; import org.springframework.messaging.MessageChannel; import org.springframework.messaging.support.GenericMessage; +import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.util.ObjectUtils; @@ -65,6 +66,7 @@ import org.springframework.web.client.ResponseErrorHandler; */ @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration +@DirtiesContext public class HttpOutboundGatewayParserTests { @Autowired @Qualifier("minimalConfig") diff --git a/spring-integration-http/src/test/java/org/springframework/integration/http/config/OutboundResponseTypeTests.java b/spring-integration-http/src/test/java/org/springframework/integration/http/config/OutboundResponseTypeTests.java index 8e57222739..402cd20b34 100644 --- a/spring-integration-http/src/test/java/org/springframework/integration/http/config/OutboundResponseTypeTests.java +++ b/spring-integration-http/src/test/java/org/springframework/integration/http/config/OutboundResponseTypeTests.java @@ -46,6 +46,7 @@ import org.springframework.messaging.Message; import org.springframework.messaging.MessageChannel; import org.springframework.messaging.MessageHandlingException; import org.springframework.messaging.support.GenericMessage; +import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.test.web.client.MockRestServiceServer; @@ -62,6 +63,7 @@ import org.springframework.web.client.RestTemplate; */ @ContextConfiguration @RunWith(SpringJUnit4ClassRunner.class) +@DirtiesContext public class OutboundResponseTypeTests { @Autowired diff --git a/spring-integration-http/src/test/java/org/springframework/integration/http/dsl/HttpDslTests.java b/spring-integration-http/src/test/java/org/springframework/integration/http/dsl/HttpDslTests.java new file mode 100644 index 0000000000..d2fd857bcc --- /dev/null +++ b/spring-integration-http/src/test/java/org/springframework/integration/http/dsl/HttpDslTests.java @@ -0,0 +1,178 @@ +/* + * Copyright 2016 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.dsl; + +import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.httpBasic; +import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; + +import java.util.Collections; +import java.util.List; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.springframework.beans.DirectFieldAccessor; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.integration.channel.DirectChannel; +import org.springframework.integration.config.EnableIntegration; +import org.springframework.integration.dsl.IntegrationFlow; +import org.springframework.integration.dsl.IntegrationFlows; +import org.springframework.integration.http.outbound.HttpRequestExecutingMessageHandler; +import org.springframework.integration.security.channel.ChannelSecurityInterceptor; +import org.springframework.integration.security.channel.SecuredChannel; +import org.springframework.messaging.MessageChannel; +import org.springframework.security.access.AccessDecisionManager; +import org.springframework.security.access.vote.AffirmativeBased; +import org.springframework.security.access.vote.RoleVoter; +import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.test.context.web.WebAppConfiguration; +import org.springframework.test.web.client.MockMvcClientHttpRequestFactory; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.web.client.RestTemplate; +import org.springframework.web.context.WebApplicationContext; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; +import org.springframework.web.servlet.config.annotation.EnableWebMvc; + +/** + * @author Artem Bilan + * + * @since 5.0 + */ +@RunWith(SpringRunner.class) +@WebAppConfiguration +@DirtiesContext +public class HttpDslTests { + + @Autowired + private WebApplicationContext wac; + + @Autowired + private HttpRequestExecutingMessageHandler serviceInternalGatewayHandler; + + private MockMvc mockMvc; + + @Before + public void setup() { + this.mockMvc = + MockMvcBuilders.webAppContextSetup(this.wac) + .apply(springSecurity()) + .build(); + } + + + @Test + public void testHttpProxyFlow() throws Exception { + RestTemplate mockMvcRestTemplate = new RestTemplate(new MockMvcClientHttpRequestFactory(this.mockMvc)); + new DirectFieldAccessor(this.serviceInternalGatewayHandler) + .setPropertyValue("restTemplate", mockMvcRestTemplate); + + this.mockMvc.perform( + get("/service") + .with(httpBasic("guest", "guest")) + .param("name", "foo")) + .andExpect( + content() + .string("FOO")); + } + + + @Configuration + @EnableWebMvc + @EnableWebSecurity + @EnableIntegration + public static class ContextConfiguration extends WebSecurityConfigurerAdapter { + + @Override + protected void configure(AuthenticationManagerBuilder auth) throws Exception { + auth.inMemoryAuthentication() + .withUser("guest") + .password("guest") + .roles("ADMIN"); + } + + @Override + protected void configure(HttpSecurity http) throws Exception { + http.authorizeRequests() + .anyRequest().hasRole("ADMIN") + .and() + .httpBasic() + .and() + .csrf().disable() + .anonymous().disable(); + } + + @Bean + @SecuredChannel(interceptor = "channelSecurityInterceptor", sendAccess = "ROLE_ADMIN") + public MessageChannel transformSecuredChannel() { + return new DirectChannel(); + } + + @Bean + public IntegrationFlow httpInternalServiceFlow() { + return IntegrationFlows + .from(Http.inboundGateway("/service/internal") + .requestMapping(r -> r.params("name")) + .payloadExpression("#requestParams.name")) + .channel(transformSecuredChannel()) + ., String>transform(p -> p.get(0).toUpperCase()) + .get(); + } + + @Bean + public IntegrationFlow httpProxyFlow() { + return IntegrationFlows + .from(Http.inboundGateway("/service") + .requestMapping(r -> r.params("name")) + .payloadFunction(httpEntity -> + ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()) + .getRequest() + .getQueryString())) + .handle(Http.outboundGateway(m -> "/service/internal?" + m.getPayload()) + .expectedResponseType(String.class), + e -> e.id("serviceInternalGateway")) + .get(); + } + + @Bean + public AccessDecisionManager accessDecisionManager() { + return new AffirmativeBased(Collections.singletonList(new RoleVoter())); + } + + @Bean + public ChannelSecurityInterceptor channelSecurityInterceptor(AccessDecisionManager accessDecisionManager) + throws Exception { + ChannelSecurityInterceptor channelSecurityInterceptor = new ChannelSecurityInterceptor(); + channelSecurityInterceptor.setAuthenticationManager(authenticationManager()); + channelSecurityInterceptor.setAccessDecisionManager(accessDecisionManager); + return channelSecurityInterceptor; + } + + } + +} 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 index f558daa273..d3b0f935ea 100644 --- 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 @@ -40,6 +40,7 @@ import org.springframework.messaging.MessageHeaders; import org.springframework.messaging.SubscribableChannel; import org.springframework.mock.web.MockHttpServletRequest; 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.MultiValueMap; @@ -59,6 +60,7 @@ import org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter; //INT-2312 @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration +@DirtiesContext public class Int2312RequestMappingIntegrationTests extends AbstractHttpInboundTests { public static final String TEST_PATH = "/test/{value}"; diff --git a/spring-integration-http/src/test/java/org/springframework/integration/http/management/IntegrationGraphControllerTests.java b/spring-integration-http/src/test/java/org/springframework/integration/http/management/IntegrationGraphControllerTests.java index 50b5cade3f..3740aeaa94 100644 --- a/spring-integration-http/src/test/java/org/springframework/integration/http/management/IntegrationGraphControllerTests.java +++ b/spring-integration-http/src/test/java/org/springframework/integration/http/management/IntegrationGraphControllerTests.java @@ -46,9 +46,9 @@ import org.springframework.integration.config.EnableIntegrationManagement; import org.springframework.integration.http.config.EnableIntegrationGraphController; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletResponse; -import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.context.TestPropertySource; -import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.junit4.SpringRunner; import org.springframework.test.context.web.WebAppConfiguration; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.setup.MockMvcBuilders; @@ -66,10 +66,10 @@ import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandl * @author Gary Russell * @since 4.3 */ -@RunWith(SpringJUnit4ClassRunner.class) -@ContextConfiguration +@RunWith(SpringRunner.class) @WebAppConfiguration @TestPropertySource(properties = "spring.application.name:testApplication") +@DirtiesContext public class IntegrationGraphControllerTests { @Autowired diff --git a/spring-integration-http/src/test/java/org/springframework/integration/http/outbound/CookieTests.java b/spring-integration-http/src/test/java/org/springframework/integration/http/outbound/CookieTests.java index c53eb042e8..02085ddf90 100644 --- a/spring-integration-http/src/test/java/org/springframework/integration/http/outbound/CookieTests.java +++ b/spring-integration-http/src/test/java/org/springframework/integration/http/outbound/CookieTests.java @@ -46,6 +46,7 @@ import org.springframework.http.client.ClientHttpResponse; import org.springframework.messaging.MessageChannel; import org.springframework.integration.channel.QueueChannel; import org.springframework.messaging.support.GenericMessage; +import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; @@ -58,6 +59,7 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; */ @ContextConfiguration @RunWith(SpringJUnit4ClassRunner.class) +@DirtiesContext public class CookieTests { @Autowired