/*
* 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 <S> the target {@link BaseHttpInboundEndpointSpec} implementation type.
* @param <E> the target {@link HttpRequestHandlingEndpointSupport} implementation type.
*
* @author Artem Bilan
*
* @since 5.0
*/
public abstract class BaseHttpInboundEndpointSpec<S extends BaseHttpInboundEndpointSpec<S, E>,
E extends HttpRequestHandlingEndpointSupport>
extends MessagingGatewaySpec<S, E> implements ComponentsRegistration {
private final RequestMapping requestMapping = new RequestMapping();
private final Map<String, Expression> headerExpressions = new HashMap<>();
private final HeaderMapper<HttpHeaders> headerMapper = DefaultHttpHeaderMapper.inboundMapper();
private HeaderMapper<HttpHeaders> 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<RequestMappingSpec> 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<CrossOriginSpec> 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 <P> the expected HTTP request body type.
* @return the spec
* @see HttpRequestHandlingEndpointSupport#setPayloadExpression(Expression)
*/
public <P> S payloadFunction(Function<HttpEntity<P>, ?> 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<String, Expression> 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 <P> the expected HTTP body type.
* @return the current Spec.
* @see HttpRequestHandlingEndpointSupport#setHeaderExpressions(Map)
*/
public <P> S headerFunction(String header, Function<HttpEntity<P>, ?> 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<HttpHeaders> 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).setInboundHeaderNames(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).setOutboundHeaderNames(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 <code>byte[].class</code>.
* @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<Void, ?> statusCodeFunction) {
return statusCodeExpression(new FunctionExpression<>(statusCodeFunction));
}
@Override
public Collection<Object> getComponentsToRegister() {
HeaderMapper<HttpHeaders> 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"}.
* <p>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.
* <p>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.
* <p>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.
* <p>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}"}.
* <p>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.
* <p>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.
* <p>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;
}
}
}