From c0784bf6ef1c2f6d390f8facb16313b31a23804a Mon Sep 17 00:00:00 2001 From: Artem Bilan Date: Mon, 18 Jan 2016 16:09:11 -0500 Subject: [PATCH] INT-3933: HTTP & WS Inbound Lifecycle Handling JIRA: https://jira.spring.io/browse/INT-3933 Previously the HTTP and WS Inbound Endpoint haven't handled the `stopped` state properly. * Add `lifecycle` parsing to the `HttpInboundEndpointParser` * Handle `!isRunning()` state and throw an appropriate `503 Service Unavailable` HTTP response * Expose `lifecycle` options for the `` * Introduce `ServiceUnavailableException` `WebServiceException` to indicate `stopped` state for the WS Inbound Endpoint * Fix tests for the new logic INT-3933: PR comments: `OrderlyShutdownCapable` for `AbstractWebServiceInboundGateway` --- .../config/HttpInboundEndpointParser.java | 5 ++- .../HttpRequestHandlingEndpointSupport.java | 26 +++-------- .../config/spring-integration-http-4.3.xsd | 4 +- ...boundChannelAdapterParserTests-context.xml | 2 + .../HttpInboundChannelAdapterParserTests.java | 13 ++++-- .../HttpInboundGatewayParserTests-context.xml | 2 + .../config/HttpInboundGatewayParserTests.java | 34 +++++++++------ .../HttpRequestHandlingControllerTests.java | 21 ++++++++- ...pRequestHandlingMessagingGatewayTests.java | 21 +++++++++ ...gMessagingGatewayWithPathMappingTests.java | 5 ++- .../inbound/MultipartAsRawByteArrayTests.java | 3 +- .../ws/AbstractWebServiceInboundGateway.java | 29 ++++++++++++- .../ws/ServiceUnavailableException.java | 43 +++++++++++++++++++ .../ws/config/spring-integration-ws-4.3.xsd | 1 + .../SimpleWebServiceInboundGatewayTests.java | 8 +++- ...bServiceInboundGatewayJavaConfigTests.java | 18 ++++++++ ...rviceInboundGatewayParserTests-context.xml | 28 ++++++------ .../WebServiceInboundGatewayParserTests.java | 25 ++++++----- 18 files changed, 216 insertions(+), 72 deletions(-) create mode 100644 spring-integration-ws/src/main/java/org/springframework/integration/ws/ServiceUnavailableException.java diff --git a/spring-integration-http/src/main/java/org/springframework/integration/http/config/HttpInboundEndpointParser.java b/spring-integration-http/src/main/java/org/springframework/integration/http/config/HttpInboundEndpointParser.java index bba7fb3b9a..a30967e18e 100644 --- a/spring-integration-http/src/main/java/org/springframework/integration/http/config/HttpInboundEndpointParser.java +++ b/spring-integration-http/src/main/java/org/springframework/integration/http/config/HttpInboundEndpointParser.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-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. @@ -202,6 +202,9 @@ public class HttpInboundEndpointParser extends AbstractSingleBeanDefinitionParse if (statusCodeExpressionDef != null) { builder.addPropertyValue("statusCodeExpression", statusCodeExpressionDef); } + + IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, IntegrationNamespaceUtils.AUTO_STARTUP); + IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, IntegrationNamespaceUtils.PHASE); } private String getInputChannelAttributeName() { 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 aba5025ff3..a354f227b8 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 @@ -148,8 +148,6 @@ public abstract class HttpRequestHandlingEndpointSupport extends MessagingGatewa private volatile Map headerExpressions; - private volatile boolean shuttingDown; - private volatile Expression statusCodeExpression; private volatile EvaluationContext evaluationContext; @@ -397,11 +395,11 @@ public abstract class HttpRequestHandlingEndpointSupport extends MessagingGatewa */ protected final Message doHandleRequest(HttpServletRequest servletRequest, HttpServletResponse servletResponse) throws IOException { - if (this.isShuttingDown()) { - return createServiceUnavailableResponse(); + if (isRunning()) { + return actualDoHandleRequest(servletRequest, servletResponse); } else { - return actualDoHandleRequest(servletRequest, servletResponse); + return createServiceUnavailableResponse(); } } @@ -536,9 +534,9 @@ public abstract class HttpRequestHandlingEndpointSupport extends MessagingGatewa private Message createServiceUnavailableResponse() { if (logger.isDebugEnabled()) { - logger.debug("Endpoint is shutting down; returning status " + HttpStatus.SERVICE_UNAVAILABLE); + logger.debug("Endpoint is stopped; returning status " + HttpStatus.SERVICE_UNAVAILABLE); } - return this.getMessageBuilderFactory().withPayload("Endpoint is shutting down") + return this.getMessageBuilderFactory().withPayload("Endpoint is stopped") .setHeader(org.springframework.integration.http.HttpHeaders.STATUS_CODE, HttpStatus.SERVICE_UNAVAILABLE) .build(); } @@ -688,22 +686,10 @@ public abstract class HttpRequestHandlingEndpointSupport extends MessagingGatewa } } - /** - * Lifecycle - */ - @Override - protected void doStart() { - this.shuttingDown = false; - super.doStart(); - } - - protected boolean isShuttingDown() { - return this.shuttingDown; - } @Override public int beforeShutdown() { - this.shuttingDown = true; + stop(); return this.activeCount.get(); } diff --git a/spring-integration-http/src/main/resources/org/springframework/integration/http/config/spring-integration-http-4.3.xsd b/spring-integration-http/src/main/resources/org/springframework/integration/http/config/spring-integration-http-4.3.xsd index f103468725..38dc0c9e65 100644 --- a/spring-integration-http/src/main/resources/org/springframework/integration/http/config/spring-integration-http-4.3.xsd +++ b/spring-integration-http/src/main/resources/org/springframework/integration/http/config/spring-integration-http-4.3.xsd @@ -188,7 +188,7 @@ @@ -198,7 +198,7 @@ diff --git a/spring-integration-http/src/test/java/org/springframework/integration/http/config/HttpInboundChannelAdapterParserTests-context.xml b/spring-integration-http/src/test/java/org/springframework/integration/http/config/HttpInboundChannelAdapterParserTests-context.xml index 80b00e77c0..169267812c 100644 --- a/spring-integration-http/src/test/java/org/springframework/integration/http/config/HttpInboundChannelAdapterParserTests-context.xml +++ b/spring-integration-http/src/test/java/org/springframework/integration/http/config/HttpInboundChannelAdapterParserTests-context.xml @@ -21,6 +21,8 @@ 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 7b45adabae..c9db8b338a 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 @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-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. @@ -20,6 +20,7 @@ import static org.hamcrest.CoreMatchers.instanceOf; import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; @@ -123,11 +124,17 @@ public class HttpInboundChannelAdapterParserTests extends AbstractHttpInboundTes @Test @SuppressWarnings("unchecked") public void getRequestOk() throws Exception { + assertFalse(TestUtils.getPropertyValue(this.defaultAdapter, "autoStartup", Boolean.class)); + assertEquals(1001, TestUtils.getPropertyValue(this.defaultAdapter, "phase")); MockHttpServletRequest request = new MockHttpServletRequest(); request.setMethod("GET"); request.setParameter("foo", "bar"); MockHttpServletResponse response = new MockHttpServletResponse(); - defaultAdapter.handleRequest(request, response); + this.defaultAdapter.handleRequest(request, response); + assertEquals(HttpServletResponse.SC_SERVICE_UNAVAILABLE, response.getStatus()); + this.defaultAdapter.start(); + response = new MockHttpServletResponse(); + this.defaultAdapter.handleRequest(request, response); assertEquals(HttpServletResponse.SC_SWITCHING_PROTOCOLS, response.getStatus()); Message message = requests.receive(0); assertNotNull(message); @@ -138,7 +145,7 @@ public class HttpInboundChannelAdapterParserTests extends AbstractHttpInboundTes assertEquals("foo", map.keySet().iterator().next()); assertEquals(1, map.get("foo").size()); assertEquals("bar", map.getFirst("foo")); - assertNotNull(TestUtils.getPropertyValue(defaultAdapter, "errorChannel")); + assertNotNull(TestUtils.getPropertyValue(this.defaultAdapter, "errorChannel")); } @Test diff --git a/spring-integration-http/src/test/java/org/springframework/integration/http/config/HttpInboundGatewayParserTests-context.xml b/spring-integration-http/src/test/java/org/springframework/integration/http/config/HttpInboundGatewayParserTests-context.xml index 7fb98a49f4..c12691f722 100644 --- a/spring-integration-http/src/test/java/org/springframework/integration/http/config/HttpInboundGatewayParserTests-context.xml +++ b/spring-integration-http/src/test/java/org/springframework/integration/http/config/HttpInboundGatewayParserTests-context.xml @@ -23,6 +23,8 @@ convert-exceptions="true" request-timeout="1234" reply-timeout="4567" + auto-startup="false" + phase="1001" error-channel="errorChannel"/> > messageConverters = TestUtils.getPropertyValue(gateway,"messageConverters", List.class); + List> messageConverters = + TestUtils.getPropertyValue(this.gateway,"messageConverters", List.class); assertTrue("The default converters should have been registered, given there are no custom converters", messageConverters.size() > 0); + + assertFalse(TestUtils.getPropertyValue(this.gateway, "autoStartup", Boolean.class)); + assertEquals(1001, TestUtils.getPropertyValue(this.gateway, "phase")); } @Test @DirtiesContext public void checkFlow() throws Exception { - requests.subscribe(handlerExpecting(any(Message.class))); + this.requests.subscribe(handlerExpecting(any(Message.class))); MockHttpServletRequest request = new MockHttpServletRequest(); request.setMethod("GET"); request.addHeader("Accept", "application/x-java-serialized-object"); @@ -148,10 +153,11 @@ public class HttpInboundGatewayParserTests { MockHttpServletResponse response = new MockHttpServletResponse(); List> converters = new ArrayList>(); converters.add(new SerializingHttpMessageConverter()); - gateway.setMessageConverters(converters); - gateway.afterPropertiesSet(); + this.gateway.setMessageConverters(converters); + this.gateway.afterPropertiesSet(); + this.gateway.start(); - gateway.handleRequest(request, response); + this.gateway.handleRequest(request, response); assertThat(response.getStatus(), is(HttpServletResponse.SC_OK)); assertEquals(response.getContentType(), "application/x-java-serialized-object"); diff --git a/spring-integration-http/src/test/java/org/springframework/integration/http/inbound/HttpRequestHandlingControllerTests.java b/spring-integration-http/src/test/java/org/springframework/integration/http/inbound/HttpRequestHandlingControllerTests.java index 052e665489..49875d1ae9 100644 --- a/spring-integration-http/src/test/java/org/springframework/integration/http/inbound/HttpRequestHandlingControllerTests.java +++ b/spring-integration-http/src/test/java/org/springframework/integration/http/inbound/HttpRequestHandlingControllerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 the original author or authors. + * Copyright 2002-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. @@ -53,6 +53,7 @@ import org.springframework.web.servlet.View; * @author Gary Russell * @author Gunnar Hillert * @author Biju Kunjummen + * @author Artem Bilan * @since 2.0 */ public class HttpRequestHandlingControllerTests extends AbstractHttpInboundTests { @@ -65,6 +66,8 @@ public class HttpRequestHandlingControllerTests extends AbstractHttpInboundTests controller.setRequestChannel(requestChannel); controller.setViewName("foo"); controller.afterPropertiesSet(); + controller.start(); + MockHttpServletRequest request = new MockHttpServletRequest(); request.setMethod("POST"); request.setContent("hello".getBytes()); @@ -91,6 +94,8 @@ public class HttpRequestHandlingControllerTests extends AbstractHttpInboundTests Expression viewExpression = new SpelExpressionParser().parseExpression("'baz'"); controller.setViewExpression(viewExpression); controller.afterPropertiesSet(); + controller.start(); + MockHttpServletRequest request = new MockHttpServletRequest(); request.setMethod("POST"); request.setContent("hello".getBytes()); @@ -123,6 +128,8 @@ public class HttpRequestHandlingControllerTests extends AbstractHttpInboundTests controller.setRequestChannel(requestChannel); controller.setViewName("foo"); controller.afterPropertiesSet(); + controller.start(); + MockHttpServletRequest request = new MockHttpServletRequest(); request.setMethod("POST"); @@ -157,6 +164,8 @@ public class HttpRequestHandlingControllerTests extends AbstractHttpInboundTests Expression viewExpression = new SpelExpressionParser().parseExpression("headers['bar']"); controller.setViewExpression(viewExpression); controller.afterPropertiesSet(); + controller.start(); + MockHttpServletRequest request = new MockHttpServletRequest(); request.setMethod("POST"); request.setContent("hello".getBytes()); @@ -188,6 +197,8 @@ public class HttpRequestHandlingControllerTests extends AbstractHttpInboundTests Expression viewExpression = new SpelExpressionParser().parseExpression("headers['bar']"); controller.setViewExpression(viewExpression); controller.afterPropertiesSet(); + controller.start(); + MockHttpServletRequest request = new MockHttpServletRequest(); request.setMethod("POST"); request.setContent("hello".getBytes()); @@ -217,6 +228,8 @@ public class HttpRequestHandlingControllerTests extends AbstractHttpInboundTests controller.setViewName("foo"); controller.setReplyKey("myReply"); controller.afterPropertiesSet(); + controller.start(); + MockHttpServletRequest request = new MockHttpServletRequest(); request.setMethod("POST"); request.setContent("howdy".getBytes()); @@ -250,6 +263,8 @@ public class HttpRequestHandlingControllerTests extends AbstractHttpInboundTests controller.setViewName("foo"); controller.setExtractReplyPayload(false); controller.afterPropertiesSet(); + controller.start(); + MockHttpServletRequest request = new MockHttpServletRequest(); request.setMethod("POST"); request.setContent("abc".getBytes()); @@ -280,6 +295,8 @@ public class HttpRequestHandlingControllerTests extends AbstractHttpInboundTests controller.setBeanFactory(mock(BeanFactory.class)); controller.setRequestChannel(requestChannel); controller.afterPropertiesSet(); + controller.start(); + MockHttpServletRequest request = new MockHttpServletRequest(); request.setMethod("POST"); request.setContent("hello".getBytes()); @@ -319,6 +336,8 @@ public class HttpRequestHandlingControllerTests extends AbstractHttpInboundTests controller.setRequestChannel(requestChannel); controller.setViewName("foo"); controller.afterPropertiesSet(); + controller.start(); + final MockHttpServletRequest request = new MockHttpServletRequest(); request.setMethod("POST"); request.setContent("hello".getBytes()); diff --git a/spring-integration-http/src/test/java/org/springframework/integration/http/inbound/HttpRequestHandlingMessagingGatewayTests.java b/spring-integration-http/src/test/java/org/springframework/integration/http/inbound/HttpRequestHandlingMessagingGatewayTests.java index 19e8a26451..d9adc4e9ed 100644 --- a/spring-integration-http/src/test/java/org/springframework/integration/http/inbound/HttpRequestHandlingMessagingGatewayTests.java +++ b/spring-integration-http/src/test/java/org/springframework/integration/http/inbound/HttpRequestHandlingMessagingGatewayTests.java @@ -76,6 +76,8 @@ public class HttpRequestHandlingMessagingGatewayTests extends AbstractHttpInboun gateway.setBeanFactory(mock(BeanFactory.class)); gateway.setRequestChannel(requestChannel); gateway.afterPropertiesSet(); + gateway.start(); + MockHttpServletRequest request = new MockHttpServletRequest(); request.setMethod("GET"); request.setParameter("foo", "bar"); @@ -97,6 +99,8 @@ public class HttpRequestHandlingMessagingGatewayTests extends AbstractHttpInboun gateway.setRequestPayloadType(String.class); gateway.setRequestChannel(requestChannel); gateway.afterPropertiesSet(); + gateway.start(); + MockHttpServletRequest request = new MockHttpServletRequest(); request.setMethod("POST"); request.setContentType("text/plain"); @@ -133,6 +137,8 @@ public class HttpRequestHandlingMessagingGatewayTests extends AbstractHttpInboun gateway.setRequestPayloadType(String.class); gateway.setRequestChannel(requestChannel); gateway.afterPropertiesSet(); + gateway.start(); + MockHttpServletRequest request = new MockHttpServletRequest(); request.setMethod("POST"); request.addHeader("Accept", "x-application/octet-stream"); @@ -159,6 +165,8 @@ public class HttpRequestHandlingMessagingGatewayTests extends AbstractHttpInboun gateway.setRequestPayloadType(String.class); gateway.setRequestChannel(requestChannel); gateway.afterPropertiesSet(); + gateway.start(); + MockHttpServletRequest request = new MockHttpServletRequest(); request.setMethod("POST"); request.setContentType("text/plain"); @@ -184,6 +192,8 @@ public class HttpRequestHandlingMessagingGatewayTests extends AbstractHttpInboun gateway.setConvertExceptions(true); gateway.setMessageConverters(Arrays.>asList(new TestHttpMessageConverter())); gateway.afterPropertiesSet(); + gateway.start(); + MockHttpServletRequest request = new MockHttpServletRequest(); request.addHeader("Accept", "application/x-java-serialized-object"); request.setMethod("GET"); @@ -200,6 +210,8 @@ public class HttpRequestHandlingMessagingGatewayTests extends AbstractHttpInboun gateway.setBeanFactory(mock(BeanFactory.class)); gateway.setRequestChannel(channel); gateway.afterPropertiesSet(); + gateway.start(); + MockHttpServletRequest request = new MockHttpServletRequest("GET", "/test"); request.setParameter("foo", "123"); request.addParameter("bar", "456"); @@ -233,6 +245,7 @@ public class HttpRequestHandlingMessagingGatewayTests extends AbstractHttpInboun converters.add(new SerializingHttpMessageConverter()); gateway.setMessageConverters(converters); gateway.afterPropertiesSet(); + gateway.start(); MockHttpServletRequest request = new MockHttpServletRequest("POST", "/test"); @@ -281,6 +294,7 @@ public class HttpRequestHandlingMessagingGatewayTests extends AbstractHttpInboun gateway.setBeanFactory(mock(BeanFactory.class)); gateway.setMessageConverters(messageConverters); gateway.setRequestChannel(requestChannel); + gateway.start(); final MockHttpServletRequest request = new MockHttpServletRequest(); request.setMethod("GET"); @@ -306,6 +320,7 @@ public class HttpRequestHandlingMessagingGatewayTests extends AbstractHttpInboun gateway.setRequestChannel(requestChannel); gateway.setReplyTimeout(0); gateway.afterPropertiesSet(); + gateway.start(); MockHttpServletRequest request = new MockHttpServletRequest(); request.setMethod("GET"); MockHttpServletResponse response = new MockHttpServletResponse(); @@ -324,6 +339,8 @@ public class HttpRequestHandlingMessagingGatewayTests extends AbstractHttpInboun gateway.setReplyTimeout(0); gateway.setStatusCodeExpression(new LiteralExpression("501")); gateway.afterPropertiesSet(); + gateway.start(); + MockHttpServletRequest request = new MockHttpServletRequest(); request.setMethod("GET"); MockHttpServletResponse response = new MockHttpServletResponse(); @@ -352,6 +369,8 @@ public class HttpRequestHandlingMessagingGatewayTests extends AbstractHttpInboun }); gateway.setErrorChannel(errorChannel); gateway.afterPropertiesSet(); + gateway.start(); + MockHttpServletRequest request = new MockHttpServletRequest(); request.setMethod("GET"); MockHttpServletResponse response = new MockHttpServletResponse(); @@ -372,6 +391,8 @@ public class HttpRequestHandlingMessagingGatewayTests extends AbstractHttpInboun gateway.setErrorChannel(errorChannel); gateway.setStatusCodeExpression(new LiteralExpression("501")); gateway.afterPropertiesSet(); + gateway.start(); + MockHttpServletRequest request = new MockHttpServletRequest(); request.setMethod("GET"); MockHttpServletResponse response = new MockHttpServletResponse(); diff --git a/spring-integration-http/src/test/java/org/springframework/integration/http/inbound/HttpRequestHandlingMessagingGatewayWithPathMappingTests.java b/spring-integration-http/src/test/java/org/springframework/integration/http/inbound/HttpRequestHandlingMessagingGatewayWithPathMappingTests.java index 73c6c20aab..dde4b88023 100644 --- a/spring-integration-http/src/test/java/org/springframework/integration/http/inbound/HttpRequestHandlingMessagingGatewayWithPathMappingTests.java +++ b/spring-integration-http/src/test/java/org/springframework/integration/http/inbound/HttpRequestHandlingMessagingGatewayWithPathMappingTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 the original author or authors. + * Copyright 2002-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. @@ -78,6 +78,7 @@ public class HttpRequestHandlingMessagingGatewayWithPathMappingTests extends Abs requestMapping.setPathPatterns("/fname/{f}/lname/{l}"); gateway.setRequestMapping(requestMapping); gateway.afterPropertiesSet(); + gateway.start(); gateway.setRequestChannel(echoChannel); @@ -125,6 +126,7 @@ public class HttpRequestHandlingMessagingGatewayWithPathMappingTests extends Abs gateway.setRequestChannel(echoChannel); gateway.setPayloadExpression(PARSER.parseExpression("#pathVariables.f")); gateway.afterPropertiesSet(); + gateway.start(); Object result = gateway.doHandleRequest(request, response); assertTrue(result instanceof Message); @@ -170,6 +172,7 @@ public class HttpRequestHandlingMessagingGatewayWithPathMappingTests extends Abs gateway.setRequestChannel(echoChannel); gateway.setPayloadExpression(PARSER.parseExpression("#pathVariables")); gateway.afterPropertiesSet(); + gateway.start(); Object result = gateway.doHandleRequest(request, response); assertTrue(result instanceof Message); diff --git a/spring-integration-http/src/test/java/org/springframework/integration/http/inbound/MultipartAsRawByteArrayTests.java b/spring-integration-http/src/test/java/org/springframework/integration/http/inbound/MultipartAsRawByteArrayTests.java index a0cf07fe69..a00b171fd7 100644 --- a/spring-integration-http/src/test/java/org/springframework/integration/http/inbound/MultipartAsRawByteArrayTests.java +++ b/spring-integration-http/src/test/java/org/springframework/integration/http/inbound/MultipartAsRawByteArrayTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015 the original author or authors. + * Copyright 2015-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. @@ -63,6 +63,7 @@ public class MultipartAsRawByteArrayTests { gw.setBeanFactory(mock(BeanFactory.class)); gw.setRequestPayloadType(byte[].class); gw.afterPropertiesSet(); + gw.start(); HttpServletRequest request = mock(HttpServletRequest.class); ServletInputStream sis = mock(ServletInputStream.class); diff --git a/spring-integration-ws/src/main/java/org/springframework/integration/ws/AbstractWebServiceInboundGateway.java b/spring-integration-ws/src/main/java/org/springframework/integration/ws/AbstractWebServiceInboundGateway.java index 169b2642d5..8ba89fd7b8 100644 --- a/spring-integration-ws/src/main/java/org/springframework/integration/ws/AbstractWebServiceInboundGateway.java +++ b/spring-integration-ws/src/main/java/org/springframework/integration/ws/AbstractWebServiceInboundGateway.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2011 the original author or authors. + * Copyright 2002-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. @@ -16,8 +16,10 @@ package org.springframework.integration.ws; import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; import org.springframework.expression.ExpressionException; +import org.springframework.integration.context.OrderlyShutdownCapable; import org.springframework.integration.gateway.MessagingGatewaySupport; import org.springframework.integration.support.AbstractIntegrationMessageBuilder; import org.springframework.messaging.Message; @@ -31,9 +33,13 @@ import org.springframework.ws.soap.SoapMessage; /** * @author Oleg Zhurakousky + * @author Artem Bilan * @since 2.1 */ -abstract public class AbstractWebServiceInboundGateway extends MessagingGatewaySupport implements MessageEndpoint { +public abstract class AbstractWebServiceInboundGateway extends MessagingGatewaySupport + implements MessageEndpoint, OrderlyShutdownCapable { + + private final AtomicInteger activeCount = new AtomicInteger(); protected volatile SoapHeaderMapper headerMapper = new DefaultSoapHeaderMapper(); @@ -48,9 +54,13 @@ abstract public class AbstractWebServiceInboundGateway extends MessagingGatewayS } public void invoke(MessageContext messageContext) throws Exception { + if (!isRunning()) { + throw new ServiceUnavailableException("503 Service Unavailable"); + } Assert.notNull(messageContext,"'messageContext' is required; it must not be null."); try { + this.activeCount.incrementAndGet(); this.doInvoke(messageContext); } catch (Exception e) { @@ -60,6 +70,9 @@ abstract public class AbstractWebServiceInboundGateway extends MessagingGatewayS } throw e; } + finally { + this.activeCount.decrementAndGet(); + } } protected void fromSoapHeaders(MessageContext messageContext, AbstractIntegrationMessageBuilder builder){ @@ -86,5 +99,17 @@ abstract public class AbstractWebServiceInboundGateway extends MessagingGatewayS } } + @Override + public int beforeShutdown() { + stop(); + return this.activeCount.get(); + } + + @Override + public int afterShutdown() { + return this.activeCount.get(); + } + abstract protected void doInvoke(MessageContext messageContext) throws Exception; + } diff --git a/spring-integration-ws/src/main/java/org/springframework/integration/ws/ServiceUnavailableException.java b/spring-integration-ws/src/main/java/org/springframework/integration/ws/ServiceUnavailableException.java new file mode 100644 index 0000000000..2410649908 --- /dev/null +++ b/spring-integration-ws/src/main/java/org/springframework/integration/ws/ServiceUnavailableException.java @@ -0,0 +1,43 @@ +/* + * 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.ws; + +import org.springframework.ws.WebServiceException; +import org.springframework.ws.soap.server.endpoint.annotation.FaultCode; +import org.springframework.ws.soap.server.endpoint.annotation.SoapFault; + +/** + * The {@link WebServiceException} extension to indicate that the server endpoint is + * temporary unavailable. + * + * @author Artem Bilan + * @since 4.3 + */ +@SoapFault(faultCode = FaultCode.RECEIVER) +public class ServiceUnavailableException extends WebServiceException { + + private static final long serialVersionUID = 1L; + + public ServiceUnavailableException(String msg) { + super(msg); + } + + public ServiceUnavailableException(String msg, Throwable ex) { + super(msg, ex); + } + +} diff --git a/spring-integration-ws/src/main/resources/org/springframework/integration/ws/config/spring-integration-ws-4.3.xsd b/spring-integration-ws/src/main/resources/org/springframework/integration/ws/config/spring-integration-ws-4.3.xsd index 84063b62a0..4b31a64499 100644 --- a/spring-integration-ws/src/main/resources/org/springframework/integration/ws/config/spring-integration-ws-4.3.xsd +++ b/spring-integration-ws/src/main/resources/org/springframework/integration/ws/config/spring-integration-ws-4.3.xsd @@ -410,6 +410,7 @@ this list can also be simple patterns to be matched against the header names (e. ]]> + diff --git a/spring-integration-ws/src/test/java/org/springframework/integration/ws/SimpleWebServiceInboundGatewayTests.java b/spring-integration-ws/src/test/java/org/springframework/integration/ws/SimpleWebServiceInboundGatewayTests.java index 40ceceb8bd..59a483830b 100644 --- a/spring-integration-ws/src/test/java/org/springframework/integration/ws/SimpleWebServiceInboundGatewayTests.java +++ b/spring-integration-ws/src/test/java/org/springframework/integration/ws/SimpleWebServiceInboundGatewayTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2010 the original author or authors. + * Copyright 2002-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. @@ -86,6 +86,8 @@ public class SimpleWebServiceInboundGatewayTests { gateway.setRequestChannel(requestChannel); gateway.setReplyChannel(replyChannel); gateway.setBeanFactory(mock(BeanFactory.class)); + gateway.start(); + when(context.getResponse()).thenReturn(response); when(response.getPayloadResult()).thenReturn(payloadResult); when(context.getRequest()).thenReturn(request); @@ -124,16 +126,20 @@ public class SimpleWebServiceInboundGatewayTests { public void describeTo(Description description) { description.appendText("A message with payload: " + payload); } + }); } private Answer withReplyTo(final MessageChannel replyChannel) { return new Answer() { + @Override public Boolean answer(InvocationOnMock invocation) throws Throwable { replyChannel.send((Message) invocation.getArguments()[0]); return true; } + }; } + } diff --git a/spring-integration-ws/src/test/java/org/springframework/integration/ws/WebServiceInboundGatewayJavaConfigTests.java b/spring-integration-ws/src/test/java/org/springframework/integration/ws/WebServiceInboundGatewayJavaConfigTests.java index 07782dedf5..fc0bc4fd4c 100644 --- a/spring-integration-ws/src/test/java/org/springframework/integration/ws/WebServiceInboundGatewayJavaConfigTests.java +++ b/spring-integration-ws/src/test/java/org/springframework/integration/ws/WebServiceInboundGatewayJavaConfigTests.java @@ -21,10 +21,14 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.eq; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import java.io.StringWriter; +import java.util.Locale; import javax.xml.transform.Result; import javax.xml.transform.Source; import javax.xml.transform.stream.StreamResult; @@ -34,6 +38,7 @@ import org.junit.runner.RunWith; import org.w3c.dom.Element; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.Lifecycle; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.expression.spel.standard.SpelExpressionParser; @@ -58,6 +63,7 @@ import org.springframework.ws.server.endpoint.annotation.Endpoint; import org.springframework.ws.server.endpoint.annotation.PayloadRoot; import org.springframework.ws.server.endpoint.annotation.RequestPayload; import org.springframework.ws.server.endpoint.mapping.UriEndpointMapping; +import org.springframework.ws.soap.SoapBody; import org.springframework.ws.soap.SoapMessage; import org.springframework.ws.soap.server.SoapMessageDispatcher; import org.springframework.ws.transport.WebServiceMessageReceiver; @@ -78,11 +84,15 @@ public class WebServiceInboundGatewayJavaConfigTests { @Autowired private PollableChannel webserviceRequestsQueue; + @Autowired + private Lifecycle wsGateway; + @Test public void testWebServiceInboundGatewayJavaConfig() throws Exception { MessageContext context = mock(MessageContext.class); SoapMessage request = mock(SoapMessage.class); SoapMessage response = mock(SoapMessage.class); + SoapBody soapBody = mock(SoapBody.class); String input = ""; Source payloadSource = new StringSource(input); @@ -91,11 +101,18 @@ public class WebServiceInboundGatewayJavaConfigTests { when(context.getResponse()).thenReturn(response); when(response.getPayloadResult()).thenReturn(payloadResult); + when(response.getSoapBody()).thenReturn(soapBody); when(context.getRequest()).thenReturn(request); when(request.getPayloadSource()).thenReturn(payloadSource); this.messageReceiver.receive(context); + verify(soapBody).addServerOrReceiverFault(eq("503 Service Unavailable"), any(Locale.class)); + + this.wsGateway.start(); + + this.messageReceiver.receive(context); + assertTrue(output.toString().endsWith(input)); @@ -138,6 +155,7 @@ public class WebServiceInboundGatewayJavaConfigTests { public MessageEndpoint wsGateway() { SimpleWebServiceInboundGateway gateway = new SimpleWebServiceInboundGateway(); gateway.setRequestChannel(gatewayRequests()); + gateway.setAutoStartup(false); return gateway; } diff --git a/spring-integration-ws/src/test/java/org/springframework/integration/ws/config/WebServiceInboundGatewayParserTests-context.xml b/spring-integration-ws/src/test/java/org/springframework/integration/ws/config/WebServiceInboundGatewayParserTests-context.xml index 7288eeee5c..d570bb32f1 100644 --- a/spring-integration-ws/src/test/java/org/springframework/integration/ws/config/WebServiceInboundGatewayParserTests-context.xml +++ b/spring-integration-ws/src/test/java/org/springframework/integration/ws/config/WebServiceInboundGatewayParserTests-context.xml @@ -1,27 +1,29 @@ - + http://www.springframework.org/schema/integration/ws/spring-integration-ws.xsd"> + - - + + - - - + + + - + - + diff --git a/spring-integration-ws/src/test/java/org/springframework/integration/ws/config/WebServiceInboundGatewayParserTests.java b/spring-integration-ws/src/test/java/org/springframework/integration/ws/config/WebServiceInboundGatewayParserTests.java index bc51764e54..5331e35eb7 100644 --- a/spring-integration-ws/src/test/java/org/springframework/integration/ws/config/WebServiceInboundGatewayParserTests.java +++ b/spring-integration-ws/src/test/java/org/springframework/integration/ws/config/WebServiceInboundGatewayParserTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-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. @@ -20,6 +20,7 @@ import static org.hamcrest.CoreMatchers.is; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertSame; import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.mock; @@ -64,10 +65,11 @@ import org.springframework.ws.soap.SoapMessage; * @author Mark Fisher * @author Gunnar Hillert * @author Stephane Nicoll + * @author Artem Bilan */ @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration -@DirtiesContext(classMode=ClassMode.AFTER_EACH_TEST_METHOD) +@DirtiesContext(classMode = ClassMode.AFTER_EACH_TEST_METHOD) public class WebServiceInboundGatewayParserTests { @Autowired @@ -98,14 +100,10 @@ public class WebServiceInboundGatewayParserTests { @Test public void simpleGatewayProperties() throws Exception { - DirectFieldAccessor accessor = new DirectFieldAccessor(simpleGateway); - assertThat( - (MessageChannel) accessor.getPropertyValue("requestChannel"), - is(requestsVerySimple)); - - assertThat( - (MessageChannel) accessor.getPropertyValue("errorChannel"), - is(customErrorChannel)); + assertSame(this.requestsVerySimple, TestUtils.getPropertyValue(this.simpleGateway, "requestChannel")); + assertSame(this.customErrorChannel, TestUtils.getPropertyValue(this.simpleGateway, "errorChannel")); + assertFalse(TestUtils.getPropertyValue(this.simpleGateway, "autoStartup", Boolean.class)); + assertEquals(101, TestUtils.getPropertyValue(this.simpleGateway, "phase")); } //extractPayload = false @@ -160,7 +158,7 @@ public class WebServiceInboundGatewayParserTests { public void testMessageHistoryWithMarshallingGateway() throws Exception { MessageContext context = new DefaultMessageContext(new StubMessageFactory()); Unmarshaller unmarshaller = mock(Unmarshaller.class); - when(unmarshaller.unmarshal((Source)Mockito.any())).thenReturn("hello"); + when(unmarshaller.unmarshal((Source) Mockito.any())).thenReturn("hello"); marshallingGateway.setUnmarshaller(unmarshaller); marshallingGateway.invoke(context); Message message = requestsMarshalling.receive(100); @@ -196,7 +194,8 @@ public class WebServiceInboundGatewayParserTests { assertEquals(testHeaderMapper, headerMapper); } - @Autowired @Qualifier("replyTimeoutGateway") + @Autowired + @Qualifier("replyTimeoutGateway") private SimpleWebServiceInboundGateway replyTimeoutGateway; @Test @@ -212,7 +211,7 @@ public class WebServiceInboundGatewayParserTests { @Override public void fromHeadersToRequest(MessageHeaders headers, - SoapMessage target) { + SoapMessage target) { } @Override