Add Validation to HTTP Inbound (#2978)

* Add Validation to HTTP Inbound

* Pull a validation functionality from `WebFluxInboundEndpoint` to its
super class `BaseHttpInboundEndpoint` making a validation available for
the `HttpRequestHandlingEndpointSupport` as well
* Do the same for the `validator()` option in DSL for the
`HttpInboundEndpointSupportSpec`
* Add `validator` XML attribute for both HTTP and WebFlux inbound
endpoint XSDs
* Test parsers for a new `validator` option
* Document validation in the `http.adoc`
* Apply some polishing in the `http.adoc`, as well as in the
`HttpRequestHandlingEndpointSupport` JavaDocs
* Clarify in `webflux.adoc` that validation is applied for the
`Publisher` items before the payload is finally built for the message to
send.
* Add WebFlux into the table of endpoints in the `endpoint-summary.adoc`

* * Remove unused imports
* Fix `SimpleMessageListenerContainerSpec` for deprecated `txSize` option
This commit is contained in:
Artem Bilan
2019-06-27 09:47:45 -04:00
committed by Gary Russell
parent c5ec2d94c6
commit c18c2e2141
19 changed files with 293 additions and 130 deletions

View File

@@ -107,12 +107,13 @@ public class HttpInboundEndpointParser extends AbstractSingleBeanDefinitionParse
List<Element> headerElements = DomUtils.getChildElementsByTagName(element, "header");
if (!CollectionUtils.isEmpty(headerElements)) {
ManagedMap<String, Object> headerElementsMap = new ManagedMap<String, Object>();
ManagedMap<String, Object> headerElementsMap = new ManagedMap<>();
for (Element headerElement : headerElements) {
String name = headerElement.getAttribute(NAME_ATTRIBUTE);
BeanDefinition headerExpressionDef =
IntegrationNamespaceUtils.createExpressionDefIfAttributeDefined(IntegrationNamespaceUtils.EXPRESSION_ATTRIBUTE,
headerElement);
IntegrationNamespaceUtils
.createExpressionDefIfAttributeDefined(IntegrationNamespaceUtils.EXPRESSION_ATTRIBUTE,
headerElement);
if (headerExpressionDef != null) {
headerElementsMap.put(name, headerExpressionDef);
}
@@ -133,8 +134,8 @@ public class HttpInboundEndpointParser extends AbstractSingleBeanDefinitionParse
}
BeanDefinition expressionDef =
IntegrationNamespaceUtils.createExpressionDefinitionFromValueOrExpression("view-name", "view-expression",
parserContext, element, false);
IntegrationNamespaceUtils.createExpressionDefinitionFromValueOrExpression("view-name",
"view-expression", parserContext, element, false);
if (expressionDef != null) {
builder.addPropertyValue("viewExpression", expressionDef);
}
@@ -154,13 +155,16 @@ public class HttpInboundEndpointParser extends AbstractSingleBeanDefinitionParse
if (StringUtils.hasText(headerMapper)) {
if (hasMappedRequestHeaders || hasMappedResponseHeaders) {
parserContext.getReaderContext().error("Neither 'mapped-request-headers' or 'mapped-response-headers' " +
"attributes are allowed when a 'header-mapper' has been specified.", parserContext.extractSource(element));
parserContext.getReaderContext()
.error("Neither 'mapped-request-headers' or 'mapped-response-headers' " +
"attributes are allowed when a 'header-mapper' has been specified.",
parserContext.extractSource(element));
}
builder.addPropertyReference("headerMapper", headerMapper);
}
else {
BeanDefinitionBuilder headerMapperBuilder = BeanDefinitionBuilder.genericBeanDefinition(DefaultHttpHeaderMapper.class);
BeanDefinitionBuilder headerMapperBuilder =
BeanDefinitionBuilder.genericBeanDefinition(DefaultHttpHeaderMapper.class);
headerMapperBuilder.setFactoryMethod("inboundMapper");
if (hasMappedRequestHeaders) {
@@ -181,7 +185,7 @@ public class HttpInboundEndpointParser extends AbstractSingleBeanDefinitionParse
if (crossOriginElement != null) {
BeanDefinitionBuilder crossOriginBuilder =
BeanDefinitionBuilder.genericBeanDefinition(CrossOrigin.class);
String[] attributes = {"origin", "allowed-headers", "exposed-headers", "max-age", "method"};
String[] attributes = { "origin", "allowed-headers", "exposed-headers", "max-age", "method" };
for (String crossOriginAttribute : attributes) {
IntegrationNamespaceUtils.setValueIfAttributeDefined(crossOriginBuilder, crossOriginElement,
crossOriginAttribute);
@@ -191,7 +195,8 @@ public class HttpInboundEndpointParser extends AbstractSingleBeanDefinitionParse
builder.addPropertyValue("crossOrigin", crossOriginBuilder.getBeanDefinition());
}
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "request-payload-type", "requestPayloadTypeClass");
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element,
"request-payload-type", "requestPayloadTypeClass");
BeanDefinition statusCodeExpressionDef =
IntegrationNamespaceUtils.createExpressionDefIfAttributeDefined("status-code-expression", element);
@@ -205,6 +210,7 @@ public class HttpInboundEndpointParser extends AbstractSingleBeanDefinitionParse
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, IntegrationNamespaceUtils.AUTO_STARTUP);
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, IntegrationNamespaceUtils.PHASE);
IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "validator");
}
private String getInputChannelAttributeName() {
@@ -212,7 +218,8 @@ public class HttpInboundEndpointParser extends AbstractSingleBeanDefinitionParse
}
private BeanDefinition createRequestMapping(Element element) {
BeanDefinitionBuilder requestMappingDefBuilder = BeanDefinitionBuilder.genericBeanDefinition(RequestMapping.class);
BeanDefinitionBuilder requestMappingDefBuilder =
BeanDefinitionBuilder.genericBeanDefinition(RequestMapping.class);
String methods = element.getAttribute("supported-methods");
if (StringUtils.hasText(methods)) {
@@ -224,7 +231,7 @@ public class HttpInboundEndpointParser extends AbstractSingleBeanDefinitionParse
Element requestMappingElement = DomUtils.getChildElementByTagName(element, "request-mapping");
if (requestMappingElement != null) {
for (String requestMappingAttribute : new String[]{"params", "headers", "consumes", "produces"}) {
for (String requestMappingAttribute : new String[] { "params", "headers", "consumes", "produces" }) {
IntegrationNamespaceUtils.setValueIfAttributeDefined(requestMappingDefBuilder, requestMappingElement,
requestMappingAttribute);
}

View File

@@ -37,6 +37,7 @@ 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.validation.Validator;
import org.springframework.web.bind.annotation.RequestMethod;
/**
@@ -45,7 +46,8 @@ import org.springframework.web.bind.annotation.RequestMethod;
*
* @since 5.0
*/
public abstract class HttpInboundEndpointSupportSpec<S extends HttpInboundEndpointSupportSpec<S, E>, E extends BaseHttpInboundEndpoint>
public abstract class HttpInboundEndpointSupportSpec<S extends HttpInboundEndpointSupportSpec<S, E>,
E extends BaseHttpInboundEndpoint>
extends MessagingGatewaySpec<S, E>
implements ComponentsRegistration {
@@ -93,7 +95,7 @@ public abstract class HttpInboundEndpointSupportSpec<S extends HttpInboundEndpoi
* Specify a SpEL expression to evaluate in order to generate the Message payload.
* @param payloadExpression The payload expression.
* @return the spec
* @see org.springframework.integration.http.inbound.HttpRequestHandlingEndpointSupport#setPayloadExpression(Expression)
* @see BaseHttpInboundEndpoint#setPayloadExpression(Expression)
*/
public S payloadExpression(String payloadExpression) {
return payloadExpression(PARSER.parseExpression(payloadExpression));
@@ -103,7 +105,7 @@ public abstract class HttpInboundEndpointSupportSpec<S extends HttpInboundEndpoi
* Specify a SpEL expression to evaluate in order to generate the Message payload.
* @param payloadExpression The payload expression.
* @return the spec
* @see org.springframework.integration.http.inbound.HttpRequestHandlingEndpointSupport#setPayloadExpression(Expression)
* @see BaseHttpInboundEndpoint#setPayloadExpression(Expression)
*/
public S payloadExpression(Expression payloadExpression) {
this.target.setPayloadExpression(payloadExpression);
@@ -115,7 +117,7 @@ public abstract class HttpInboundEndpointSupportSpec<S extends HttpInboundEndpoi
* @param payloadFunction The payload {@link Function}.
* @param <P> the expected HTTP request body type.
* @return the spec
* @see org.springframework.integration.http.inbound.HttpRequestHandlingEndpointSupport#setPayloadExpression(Expression)
* @see BaseHttpInboundEndpoint#setPayloadExpression(Expression)
*/
public <P> S payloadFunction(Function<HttpEntity<P>, ?> payloadFunction) {
return payloadExpression(new FunctionExpression<>(payloadFunction));
@@ -125,7 +127,7 @@ public abstract class HttpInboundEndpointSupportSpec<S extends HttpInboundEndpoi
* Specify a Map of SpEL expressions to evaluate in order to generate the Message headers.
* @param expressions The {@link Map} of SpEL expressions for headers.
* @return the spec
* @see org.springframework.integration.http.inbound.HttpRequestHandlingEndpointSupport#setHeaderExpressions(Map)
* @see BaseHttpInboundEndpoint#setHeaderExpressions(Map)
*/
public S headerExpressions(Map<String, Expression> expressions) {
Assert.notNull(expressions, "'headerExpressions' must not be null");
@@ -139,7 +141,7 @@ public abstract class HttpInboundEndpointSupportSpec<S extends HttpInboundEndpoi
* @param header the header name to populate.
* @param expression the SpEL expression for the header.
* @return the spec
* @see org.springframework.integration.http.inbound.HttpRequestHandlingEndpointSupport#setHeaderExpressions(Map)
* @see BaseHttpInboundEndpoint#setHeaderExpressions(Map)
*/
public S headerExpression(String header, String expression) {
return headerExpression(header, PARSER.parseExpression(expression));
@@ -150,7 +152,7 @@ public abstract class HttpInboundEndpointSupportSpec<S extends HttpInboundEndpoi
* @param header the header name to populate.
* @param expression the SpEL expression for the header.
* @return the spec
* @see org.springframework.integration.http.inbound.HttpRequestHandlingEndpointSupport#setHeaderExpressions(Map)
* @see BaseHttpInboundEndpoint#setHeaderExpressions(Map)
*/
public S headerExpression(String header, Expression expression) {
this.headerExpressions.put(header, expression);
@@ -163,7 +165,7 @@ public abstract class HttpInboundEndpointSupportSpec<S extends HttpInboundEndpoi
* @param headerFunction the function to evaluate the header value against {@link HttpEntity}.
* @param <P> the expected HTTP body type.
* @return the current Spec.
* @see org.springframework.integration.http.inbound.HttpRequestHandlingEndpointSupport#setHeaderExpressions(Map)
* @see BaseHttpInboundEndpoint#setHeaderExpressions(Map)
*/
public <P> S headerFunction(String header, Function<HttpEntity<P>, ?> headerFunction) {
return headerExpression(header, new FunctionExpression<>(headerFunction));
@@ -251,7 +253,7 @@ public abstract class HttpInboundEndpointSupportSpec<S extends HttpInboundEndpoi
* the default '200 OK' or '500 Internal Server Error' for a timeout.
* @param statusCodeExpression The status code Expression.
* @return the current Spec.
* @see org.springframework.integration.http.inbound.HttpRequestHandlingEndpointSupport#setStatusCodeExpression(Expression)
* @see BaseHttpInboundEndpoint#setStatusCodeExpression(Expression)
*/
public S statusCodeExpression(String statusCodeExpression) {
this.target.setStatusCodeExpressionString(statusCodeExpression);
@@ -263,7 +265,7 @@ public abstract class HttpInboundEndpointSupportSpec<S extends HttpInboundEndpoi
* the default '200 OK' or '500 Internal Server Error' for a timeout.
* @param statusCodeExpression The status code Expression.
* @return the current Spec.
* @see org.springframework.integration.http.inbound.HttpRequestHandlingEndpointSupport#setStatusCodeExpression(Expression)
* @see BaseHttpInboundEndpoint#setStatusCodeExpression(Expression)
*/
public S statusCodeExpression(Expression statusCodeExpression) {
this.target.setStatusCodeExpression(statusCodeExpression);
@@ -275,12 +277,23 @@ public abstract class HttpInboundEndpointSupportSpec<S extends HttpInboundEndpoi
* the default '200 OK' or '500 Internal Server Error' for a timeout.
* @param statusCodeFunction The status code {@link Function}.
* @return the current Spec.
* @see org.springframework.integration.http.inbound.HttpRequestHandlingEndpointSupport#setStatusCodeExpression(Expression)
* @see BaseHttpInboundEndpoint#setStatusCodeExpression(Expression)
*/
public S statusCodeFunction(Function<RequestEntity<?>, ?> statusCodeFunction) {
return statusCodeExpression(new FunctionExpression<>(statusCodeFunction));
}
/**
* Specify a {@link Validator} to validate a converted payload from request.
* @param validator the {@link Validator} to use.
* @return the spec
* @since 5.2
*/
public S validator(Validator validator) {
this.target.setValidator(validator);
return _this();
}
@Override
public Map<Object, String> getComponentsToRegister() {
HeaderMapper<HttpHeaders> headerMapperToRegister =

View File

@@ -33,11 +33,15 @@ import org.springframework.integration.context.OrderlyShutdownCapable;
import org.springframework.integration.expression.ExpressionUtils;
import org.springframework.integration.gateway.MessagingGatewaySupport;
import org.springframework.integration.http.support.DefaultHttpHeaderMapper;
import org.springframework.integration.http.support.IntegrationWebExchangeBindException;
import org.springframework.integration.mapping.HeaderMapper;
import org.springframework.messaging.MessageHeaders;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.CollectionUtils;
import org.springframework.validation.BeanPropertyBindingResult;
import org.springframework.validation.ValidationUtils;
import org.springframework.validation.Validator;
/**
* The {@link MessagingGatewaySupport} extension for HTTP Inbound endpoints
@@ -65,6 +69,8 @@ public class BaseHttpInboundEndpoint extends MessagingGatewaySupport implements
private final boolean expectReply;
private Validator validator;
private ResolvableType requestPayloadType = null;
private HeaderMapper<HttpHeaders> headerMapper = DefaultHttpHeaderMapper.inboundMapper();
@@ -253,6 +259,19 @@ public class BaseHttpInboundEndpoint extends MessagingGatewaySupport implements
return this.statusCodeExpression;
}
/**
* Specify a {@link Validator} to validate a converted payload from request.
* @param validator the {@link Validator} to use.
* @since 5.2
*/
public void setValidator(Validator validator) {
this.validator = validator;
}
protected Validator getValidator() {
return this.validator;
}
@Override
protected void onInit() {
super.onInit();
@@ -334,4 +353,12 @@ public class BaseHttpInboundEndpoint extends MessagingGatewaySupport implements
return !(CollectionUtils.containsInstance(NON_READABLE_BODY_HTTP_METHODS, httpMethod));
}
protected void validate(Object value) {
BeanPropertyBindingResult errors = new BeanPropertyBindingResult(value, "requestPayload");
ValidationUtils.invokeValidator(this.validator, value, errors);
if (errors.hasErrors()) {
throw new IntegrationWebExchangeBindException(getComponentName(), value, errors);
}
}
}

View File

@@ -319,6 +319,10 @@ public abstract class HttpRequestHandlingEndpointSupport extends BaseHttpInbound
AbstractIntegrationMessageBuilder<?> messageBuilder;
if (getValidator() != null) {
validate(payload);
}
if (payload instanceof Message<?>) {
messageBuilder =
getMessageBuilderFactory()

View File

@@ -362,6 +362,18 @@
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="validator" type="xsd:string">
<xsd:annotation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:expected-type type="org.springframework.validation.Validator" />
</tool:annotation>
</xsd:appinfo>
<xsd:documentation>
A 'Validator' bean reference to validate a payload converted from the HTTP request.
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
</xsd:attributeGroup>
<xsd:element name="outbound-channel-adapter">

View File

@@ -1,11 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans:beans
xmlns="http://www.springframework.org/schema/integration/http"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:beans="http://www.springframework.org/schema/beans"
xmlns:si="http://www.springframework.org/schema/integration"
xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/beans
xmlns="http://www.springframework.org/schema/integration/http"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:beans="http://www.springframework.org/schema/beans"
xmlns:si="http://www.springframework.org/schema/integration"
xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/integration
https://www.springframework.org/schema/integration/spring-integration.xsd
@@ -20,20 +20,25 @@
<si:queue capacity="1"/>
</si:channel>
<beans:bean id="validator" class="org.mockito.Mockito" factory-method="mock">
<beans:constructor-arg value="org.springframework.validation.Validator"/>
</beans:bean>
<inbound-channel-adapter id="defaultAdapter" channel="requests" error-channel="errorChannel"
auto-startup="false"
phase="1001"
status-code-expression="'101'"/>
auto-startup="false"
phase="1001"
status-code-expression="'101'"
validator="validator"/>
<inbound-channel-adapter id="postOnlyAdapter" path="/postOnly" channel="requests" supported-methods="POST"/>
<inbound-channel-adapter id="adapterWithCustomConverterWithDefaults" message-converters="customConverters"
channel="requests" supported-methods="DELETE" merge-with-default-converters="true"/>
channel="requests" supported-methods="DELETE" merge-with-default-converters="true"/>
<inbound-channel-adapter id="adapterWithCustomConverterNoDefaults" message-converters="customConverters"
channel="requests" supported-methods="HEAD" />
channel="requests" supported-methods="HEAD"/>
<inbound-channel-adapter id="adapterNoCustomConverterNoDefaults" channel="requests" supported-methods="POST" />
<inbound-channel-adapter id="adapterNoCustomConverterNoDefaults" channel="requests" supported-methods="POST"/>
<util:list id="customConverters">
<beans:bean class="org.springframework.integration.http.converter.SerializingHttpMessageConverter"/>
@@ -42,7 +47,7 @@
<inbound-channel-adapter id="putOrDeleteAdapter" channel="requests" supported-methods="PUT, delete"/>
<inbound-channel-adapter id="inboundController" channel="requests" view-name="foo" error-code="oops"
status-code-expression="T(org.springframework.http.HttpStatus).ACCEPTED">
status-code-expression="T(org.springframework.http.HttpStatus).ACCEPTED">
<request-mapping headers="BAR"/>
</inbound-channel-adapter>

View File

@@ -17,6 +17,9 @@
package org.springframework.integration.http.config;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.BDDMockito.willReturn;
import java.io.ByteArrayOutputStream;
import java.io.ObjectOutputStream;
@@ -49,10 +52,10 @@ import org.springframework.messaging.PollableChannel;
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.test.context.junit4.SpringRunner;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.MultiValueMap;
import org.springframework.validation.Validator;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.servlet.HandlerMapping;
@@ -65,8 +68,7 @@ import org.springframework.web.servlet.HandlerMapping;
* @author Artem Bilan
* @author Biju Kunjummen
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
@RunWith(SpringRunner.class)
@DirtiesContext
public class HttpInboundChannelAdapterParserTests extends AbstractHttpInboundTests {
@@ -112,9 +114,13 @@ public class HttpInboundChannelAdapterParserTests extends AbstractHttpInboundTes
@Autowired
private MessageChannel autoChannel;
@Autowired @Qualifier("autoChannel.adapter")
@Autowired
@Qualifier("autoChannel.adapter")
private HttpRequestHandlingMessagingGateway autoChannelAdapter;
@Autowired
private Validator validator;
@Test
@SuppressWarnings("unchecked")
public void getRequestOk() throws Exception {
@@ -128,6 +134,7 @@ public class HttpInboundChannelAdapterParserTests extends AbstractHttpInboundTes
assertThat(response.getStatus()).isEqualTo(HttpServletResponse.SC_SERVICE_UNAVAILABLE);
this.defaultAdapter.start();
response = new MockHttpServletResponse();
willReturn(true).given(this.validator).supports(any());
this.defaultAdapter.handleRequest(request, response);
assertThat(response.getStatus()).isEqualTo(HttpServletResponse.SC_SWITCHING_PROTOCOLS);
Message<?> message = requests.receive(0);
@@ -140,12 +147,13 @@ public class HttpInboundChannelAdapterParserTests extends AbstractHttpInboundTes
assertThat(map.get("foo").size()).isEqualTo(1);
assertThat(map.getFirst("foo")).isEqualTo("bar");
assertThat(TestUtils.getPropertyValue(this.defaultAdapter, "errorChannel")).isNotNull();
assertThat(TestUtils.getPropertyValue(this.defaultAdapter, "validator")).isSameAs(this.validator);
}
@Test
public void getRequestWithHeaders() throws Exception {
public void getRequestWithHeaders() {
DefaultHttpHeaderMapper headerMapper =
(DefaultHttpHeaderMapper) TestUtils.getPropertyValue(withMappedHeaders, "headerMapper");
TestUtils.getPropertyValue(withMappedHeaders, "headerMapper", DefaultHttpHeaderMapper.class);
HttpHeaders headers = new HttpHeaders();
headers.set("foo", "foo");
@@ -187,19 +195,18 @@ public class HttpInboundChannelAdapterParserTests extends AbstractHttpInboundTes
}
@Test
public void getRequestNotAllowed() throws Exception {
public void getRequestNotAllowed() {
MockHttpServletRequest request = new MockHttpServletRequest();
request.setMethod("GET");
request.setParameter("foo", "bar");
request.setRequestURI("/postOnly");
try {
this.integrationRequestMappingHandlerMapping.getHandler(request);
}
catch (HttpRequestMethodNotSupportedException e) {
assertThat(e.getMethod()).isEqualTo("GET");
assertThat(e.getSupportedMethods()).isEqualTo(new String[] { "POST" });
}
assertThatExceptionOfType(HttpRequestMethodNotSupportedException.class)
.isThrownBy(() -> this.integrationRequestMappingHandlerMapping.getHandler(request))
.satisfies((ex) -> {
assertThat(ex.getMethod()).isEqualTo("GET");
assertThat(ex.getSupportedMethods()).containsExactly("POST");
});
}
@Test
@@ -222,7 +229,8 @@ public class HttpInboundChannelAdapterParserTests extends AbstractHttpInboundTes
assertThat(message.getPayload()).isEqualTo("test");
}
@Test @DirtiesContext
@Test
@DirtiesContext
public void postRequestWithSerializedObjectContentOk() throws Exception {
MockHttpServletRequest request = new MockHttpServletRequest();
request.setMethod("POST");
@@ -244,15 +252,15 @@ public class HttpInboundChannelAdapterParserTests extends AbstractHttpInboundTes
@Test
public void putOrDeleteMethodsSupported() throws Exception {
public void putOrDeleteMethodsSupported() {
HttpMethod[] supportedMethods =
TestUtils.getPropertyValue(putOrDeleteAdapter, "requestMapping.methods", HttpMethod[].class);
assertThat(supportedMethods.length).isEqualTo(2);
assertThat(supportedMethods).isEqualTo(new HttpMethod[] { HttpMethod.PUT, HttpMethod.DELETE });
assertThat(supportedMethods).containsExactly(HttpMethod.PUT, HttpMethod.DELETE);
}
@Test
public void testController() throws Exception {
public void testController() {
String errorCode = TestUtils.getPropertyValue(inboundController, "errorCode", String.class);
assertThat(errorCode).isEqualTo("oops");
Expression viewExpression = TestUtils.getPropertyValue(inboundController, "viewExpression", Expression.class);
@@ -269,8 +277,9 @@ public class HttpInboundChannelAdapterParserTests extends AbstractHttpInboundTes
}
@Test
public void testInt2717ControllerWithViewExpression() throws Exception {
Expression viewExpression = TestUtils.getPropertyValue(inboundControllerViewExp, "viewExpression", Expression.class);
public void testInt2717ControllerWithViewExpression() {
Expression viewExpression = TestUtils
.getPropertyValue(inboundControllerViewExp, "viewExpression", Expression.class);
assertThat(viewExpression.getExpressionString()).isEqualTo("'foo'");
}
@@ -282,7 +291,8 @@ public class HttpInboundChannelAdapterParserTests extends AbstractHttpInboundTes
@Test
public void testInboundAdapterWithMessageConverterDefaults() {
@SuppressWarnings("unchecked")
List<HttpMessageConverter<?>> messageConverters = TestUtils.getPropertyValue(adapterWithCustomConverterWithDefaults, "messageConverters", List.class);
List<HttpMessageConverter<?>> messageConverters = TestUtils
.getPropertyValue(adapterWithCustomConverterWithDefaults, "messageConverters", List.class);
assertThat(messageConverters.size() > 1)
.as("There should be more than 1 message converter. The customized one and the defaults.").isTrue();
@@ -293,17 +303,19 @@ public class HttpInboundChannelAdapterParserTests extends AbstractHttpInboundTes
@Test
public void testInboundAdapterWithNoMessageConverterDefaults() {
@SuppressWarnings("unchecked")
List<HttpMessageConverter<?>> messageConverters = TestUtils.getPropertyValue(adapterWithCustomConverterNoDefaults, "messageConverters", List.class);
List<HttpMessageConverter<?>> messageConverters = TestUtils
.getPropertyValue(adapterWithCustomConverterNoDefaults, "messageConverters", List.class);
//First converter should be the customized one
assertThat(messageConverters.get(0)).isInstanceOf(SerializingHttpMessageConverter.class);
assertThat(messageConverters.size() == 1).as("There should be only the customized messageconverter registered.")
.isTrue();
assertThat(messageConverters).as("There should be only the customized MessageConverter registered.")
.hasSize(1);
}
@Test
public void testInboundAdapterWithNoMessageConverterNoDefaults() {
@SuppressWarnings("unchecked")
List<HttpMessageConverter<?>> messageConverters = TestUtils.getPropertyValue(adapterNoCustomConverterNoDefaults, "messageConverters", List.class);
List<HttpMessageConverter<?>> messageConverters = TestUtils
.getPropertyValue(adapterNoCustomConverterNoDefaults, "messageConverters", List.class);
assertThat(messageConverters.size() > 1).as("There should be more than 1 message converter. The defaults.")
.isTrue();
}
@@ -316,6 +328,7 @@ public class HttpInboundChannelAdapterParserTests extends AbstractHttpInboundTes
TestObject(String text) {
this.text = text;
}
}
}

View File

@@ -21,6 +21,7 @@ import static org.springframework.security.test.web.servlet.request.SecurityMock
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.request.MockMvcRequestBuilders.multipart;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@@ -38,6 +39,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.http.client.ClientHttpRequestFactory;
@@ -71,6 +73,9 @@ 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.util.StringUtils;
import org.springframework.validation.Errors;
import org.springframework.validation.Validator;
import org.springframework.web.client.DefaultResponseErrorHandler;
import org.springframework.web.client.HttpClientErrorException;
import org.springframework.web.context.WebApplicationContext;
@@ -199,6 +204,37 @@ public class HttpDslTests {
}));
}
@Autowired
private Validator validator;
@Test
public void testValidation() throws Exception {
IntegrationFlow flow =
IntegrationFlows.from(
Http.inboundChannelAdapter("/validation")
.requestMapping((mapping) -> mapping
.methods(HttpMethod.POST)
.consumes(MediaType.APPLICATION_JSON_VALUE))
.requestPayloadType(TestModel.class)
.validator(this.validator))
.bridge()
.get();
IntegrationFlowContext.IntegrationFlowRegistration flowRegistration =
this.integrationFlowContext.registration(flow).register();
this.mockMvc.perform(
post("/validation")
.with(httpBasic("user", "user"))
.contentType(MediaType.APPLICATION_JSON)
.content("{\"name\": \"\"}"))
.andExpect(status().isBadRequest())
.andExpect(status().reason("Validation failure"));
flowRegistration.destroy();
}
@Configuration
@EnableWebSecurity
@EnableIntegration
@@ -309,6 +345,11 @@ public class HttpDslTests {
return channelSecurityInterceptor;
}
@Bean
public Validator customValidator() {
return new TestModelValidator();
}
}
public static class HttpProxyResponseErrorHandler extends DefaultResponseErrorHandler {
@@ -322,4 +363,35 @@ public class HttpDslTests {
}
public static class TestModel {
private String name;
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
}
private static class TestModelValidator implements Validator {
@Override
public boolean supports(Class<?> clazz) {
return TestModel.class.isAssignableFrom(clazz);
}
@Override
public void validate(Object target, Errors errors) {
TestModel testModel = (TestModel) target;
if (!StringUtils.hasText(testModel.getName())) {
errors.rejectValue("name", "Must not be empty");
}
}
}
}