diff --git a/build.gradle b/build.gradle index d78cfb9ff4..8d071c786d 100644 --- a/build.gradle +++ b/build.gradle @@ -688,6 +688,16 @@ project('spring-integration-xml') { exclude group: 'org.springframework', module: 'spring-context' exclude group: 'org.springframework', module: 'spring-core' } + compile ("org.springframework.ws:spring-ws-core:$springWsVersion") { + optional it + exclude group: 'org.springframework', module: 'spring-aop' + exclude group: 'org.springframework', module: 'spring-beans' + exclude group: 'org.springframework', module: 'spring-context' + exclude group: 'org.springframework', module: 'spring-core' + exclude group: 'org.springframework', module: 'spring-oxm' + exclude group: 'org.springframework', module: 'spring-web' + exclude group: 'org.springframework', module: 'spring-webmvc' + } testCompile "xmlunit:xmlunit:$xmlUnitVersion" } } diff --git a/spring-integration-ws/src/main/java/org/springframework/integration/ws/SimpleWebServiceInboundGateway.java b/spring-integration-ws/src/main/java/org/springframework/integration/ws/SimpleWebServiceInboundGateway.java index 4187a8d175..7f9bd5ae92 100644 --- a/spring-integration-ws/src/main/java/org/springframework/integration/ws/SimpleWebServiceInboundGateway.java +++ b/spring-integration-ws/src/main/java/org/springframework/integration/ws/SimpleWebServiceInboundGateway.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2011 the original author or authors. + * Copyright 2002-2017 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. @@ -34,6 +34,8 @@ import org.springframework.xml.transform.TransformerObjectSupport; /** * @author Mark Fisher * @author Oleg Zhurakousky + * @author Artem Bilan + * * @since 1.0.2 */ public class SimpleWebServiceInboundGateway extends AbstractWebServiceInboundGateway { @@ -62,26 +64,31 @@ public class SimpleWebServiceInboundGateway extends AbstractWebServiceInboundGat if (replyMessage != null) { Object replyPayload = replyMessage.getPayload(); Source responseSource = null; - if (replyPayload instanceof Source) { - responseSource = (Source) replyPayload; - } - else if (replyPayload instanceof Document) { - responseSource = new DOMSource((Document) replyPayload); - } - else if (replyPayload instanceof String) { - responseSource = new StringSource((String) replyPayload); + + if (replyPayload instanceof WebServiceMessage) { + messageContext.setResponse((WebServiceMessage) replyPayload); } else { - throw new IllegalArgumentException("The reply Message payload must be a [" - + Source.class.getName() + "], [" + Document.class.getName() - + "], or [java.lang.String]. The actual type was [" - + replyPayload.getClass().getName() + "]"); + if (replyPayload instanceof Source) { + responseSource = (Source) replyPayload; + } + else if (replyPayload instanceof Document) { + responseSource = new DOMSource((Document) replyPayload); + } + else if (replyPayload instanceof String) { + responseSource = new StringSource((String) replyPayload); + } + else { + throw new IllegalArgumentException("The reply Message payload must be a [" + + Source.class.getName() + "], [" + Document.class.getName() + + "], [java.lang.String] or [" + WebServiceMessage.class.getName() + "]. " + + "The actual type was [" + replyPayload.getClass().getName() + "]"); + } + WebServiceMessage response = messageContext.getResponse(); + this.transformerSupportDelegate.transformSourceToResult(responseSource, response.getPayloadResult()); + + toSoapHeaders(response, replyMessage); } - WebServiceMessage response = messageContext.getResponse(); - this.transformerSupportDelegate.transformSourceToResult(responseSource, response.getPayloadResult()); - - this.toSoapHeaders(response, replyMessage); - } } @@ -95,6 +102,7 @@ public class SimpleWebServiceInboundGateway extends AbstractWebServiceInboundGat void transformSourceToResult(Source source, Result result) throws TransformerException { this.transform(source, result); } + } } diff --git a/spring-integration-ws/src/main/java/org/springframework/integration/ws/SimpleWebServiceOutboundGateway.java b/spring-integration-ws/src/main/java/org/springframework/integration/ws/SimpleWebServiceOutboundGateway.java index 1678e7dff6..a3825c12b0 100644 --- a/spring-integration-ws/src/main/java/org/springframework/integration/ws/SimpleWebServiceOutboundGateway.java +++ b/spring-integration-ws/src/main/java/org/springframework/integration/ws/SimpleWebServiceOutboundGateway.java @@ -17,6 +17,7 @@ package org.springframework.integration.ws; import java.io.IOException; +import java.util.Iterator; import javax.xml.transform.Result; import javax.xml.transform.Source; @@ -34,6 +35,8 @@ import org.springframework.ws.WebServiceMessageFactory; import org.springframework.ws.client.core.SourceExtractor; import org.springframework.ws.client.core.WebServiceMessageCallback; import org.springframework.ws.client.support.destination.DestinationProvider; +import org.springframework.ws.mime.Attachment; +import org.springframework.ws.mime.MimeMessage; import org.springframework.xml.transform.StringResult; import org.springframework.xml.transform.StringSource; import org.springframework.xml.transform.TransformerObjectSupport; @@ -50,6 +53,8 @@ public class SimpleWebServiceOutboundGateway extends AbstractWebServiceOutboundG private final SourceExtractor sourceExtractor; + private volatile boolean extractPayload = true; + public SimpleWebServiceOutboundGateway(DestinationProvider destinationProvider) { this(destinationProvider, null, null); } @@ -79,6 +84,15 @@ public class SimpleWebServiceOutboundGateway extends AbstractWebServiceOutboundG this.sourceExtractor = (sourceExtractor != null) ? sourceExtractor : new DefaultSourceExtractor(); } + /** + * + * @param extractPayload + * @since 5.0 + */ + public void setExtractPayload(boolean extractPayload) { + this.extractPayload = extractPayload; + } + @Override public String getComponentType() { return "ws:outbound-gateway(simple)"; @@ -95,9 +109,10 @@ public class SimpleWebServiceOutboundGateway extends AbstractWebServiceOutboundG else if (requestPayload instanceof Document) { responseResultInstance = new DOMResult(); } - return this.getWebServiceTemplate().sendAndReceive(uri, - new SimpleRequestMessageCallback(requestCallback, requestMessage), - new SimpleResponseMessageExtractor(responseResultInstance)); + return getWebServiceTemplate() + .sendAndReceive(uri, + new SimpleRequestMessageCallback(requestCallback, requestMessage), + new SimpleResponseMessageExtractor(responseResultInstance)); } private final class SimpleRequestMessageCallback extends RequestMessageCallback { @@ -110,7 +125,10 @@ public class SimpleWebServiceOutboundGateway extends AbstractWebServiceOutboundG public void doWithMessageInternal(WebServiceMessage message, Object payload) throws IOException, TransformerException { Source source = this.extractSource(payload); - this.transform(source, message.getPayloadResult()); + transform(source, message.getPayloadResult()); + if (message instanceof MimeMessage && payload instanceof MimeMessage) { + copyAttachments((MimeMessage) payload, (MimeMessage) message); + } } private Source extractSource(Object requestPayload) throws IOException, TransformerException { @@ -128,17 +146,28 @@ public class SimpleWebServiceOutboundGateway extends AbstractWebServiceOutboundG else if (requestPayload instanceof Document) { source = new DOMSource((Document) requestPayload); } + else if (requestPayload instanceof WebServiceMessage) { + source = ((WebServiceMessage) requestPayload).getPayloadSource(); + } else { throw new MessagingException("Unsupported payload type '" + requestPayload.getClass() + "'. " + this.getClass().getName() + " only supports 'java.lang.String', '" + Source.class.getName() + - "', and '" + Document.class.getName() + "'. Consider either using the '" + "', '" + Document.class.getName() + "' and '" + WebServiceMessage.class.getName() + "'. " + + "Consider either using the '" + MarshallingWebServiceOutboundGateway.class.getName() + "' or a Message Transformer."); } return source; } + private void copyAttachments(MimeMessage source, MimeMessage target) { + for (Iterator attachments = source.getAttachments(); attachments.hasNext(); ) { + Attachment attachment = attachments.next(); + target.addAttachment(attachment.getContentId(), attachment.getDataHandler()); + } + } + } private final class SimpleResponseMessageExtractor extends ResponseMessageExtractor { @@ -152,22 +181,27 @@ public class SimpleWebServiceOutboundGateway extends AbstractWebServiceOutboundG @Override public Object doExtractData(WebServiceMessage message) throws IOException, TransformerException { - Source payloadSource = message.getPayloadSource(); - - if (payloadSource != null && this.result != null) { - this.transform(payloadSource, this.result); - if (this.result instanceof StringResult) { - return this.result.toString(); - } - else if (this.result instanceof DOMResult) { - return ((DOMResult) this.result).getNode(); - } - else { - return this.result; - } + if (!SimpleWebServiceOutboundGateway.this.extractPayload) { + return message; } + else { + Source payloadSource = message.getPayloadSource(); - return payloadSource; + if (payloadSource != null && this.result != null) { + this.transform(payloadSource, this.result); + if (this.result instanceof StringResult) { + return this.result.toString(); + } + else if (this.result instanceof DOMResult) { + return ((DOMResult) this.result).getNode(); + } + else { + return this.result; + } + } + + return payloadSource; + } } } diff --git a/spring-integration-ws/src/main/java/org/springframework/integration/ws/config/WebServiceOutboundGatewayParser.java b/spring-integration-ws/src/main/java/org/springframework/integration/ws/config/WebServiceOutboundGatewayParser.java index 5069fd560e..aecb222cdc 100644 --- a/spring-integration-ws/src/main/java/org/springframework/integration/ws/config/WebServiceOutboundGatewayParser.java +++ b/spring-integration-ws/src/main/java/org/springframework/integration/ws/config/WebServiceOutboundGatewayParser.java @@ -97,9 +97,9 @@ public class WebServiceOutboundGatewayParser extends AbstractOutboundGatewayPars @Override protected void postProcessGateway(BeanDefinitionBuilder builder, Element element, ParserContext parserContext) { String marshallerRef = element.getAttribute("marshaller"); + String unmarshallerRef = element.getAttribute("unmarshaller"); if (StringUtils.hasText(marshallerRef)) { builder.addConstructorArgReference(marshallerRef); - String unmarshallerRef = element.getAttribute("unmarshaller"); if (StringUtils.hasText(unmarshallerRef)) { builder.addConstructorArgReference(unmarshallerRef); } @@ -154,6 +154,19 @@ public class WebServiceOutboundGatewayParser extends AbstractOutboundGatewayPars if (StringUtils.hasText(interceptorListRef)) { builder.addPropertyReference("interceptors", interceptorListRef); } + + if (StringUtils.hasText(marshallerRef) || StringUtils.hasText(unmarshallerRef)) { + String extractPayload = element.getAttribute("extract-payload"); + if (StringUtils.hasText(extractPayload)) { + parserContext.getReaderContext() + .warning("Setting 'extract-payload' attribute has no effect when used with " + + "a marshalling Web Service Outbound Gateway.", element); + } + } + else { + IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "extract-payload"); + } + } } diff --git a/spring-integration-ws/src/main/resources/org/springframework/integration/ws/config/spring-integration-ws-5.0.xsd b/spring-integration-ws/src/main/resources/org/springframework/integration/ws/config/spring-integration-ws-5.0.xsd index 21eafbddaf..9ddd87901c 100644 --- a/spring-integration-ws/src/main/resources/org/springframework/integration/ws/config/spring-integration-ws-5.0.xsd +++ b/spring-integration-ws/src/main/resources/org/springframework/integration/ws/config/spring-integration-ws-5.0.xsd @@ -300,6 +300,18 @@ this list can also be simple patterns to be matched against the header names (e. ]]> + + + + Set to 'true' to extract the WebServiceMessage payload. + Otherwise the whole WebServiceMessage is used as the integration message payload. + This option is only applied for the simple gateway. + + + + + + @@ -396,7 +408,18 @@ this list can also be simple patterns to be matched against the header names (e. - + + + + Set to 'true' to extract the WebServiceMessage payload. + Otherwise the whole WebServiceMessage is used as the integration message payload. + This option is only applied for the simple gateway. + + + + + + diff --git a/spring-integration-ws/src/test/java/org/springframework/integration/ws/SimpleWebServiceOutboundGatewayTests.java b/spring-integration-ws/src/test/java/org/springframework/integration/ws/SimpleWebServiceOutboundGatewayTests.java index c64f9db887..0766e17389 100644 --- a/spring-integration-ws/src/test/java/org/springframework/integration/ws/SimpleWebServiceOutboundGatewayTests.java +++ b/spring-integration-ws/src/test/java/org/springframework/integration/ws/SimpleWebServiceOutboundGatewayTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2016 the original author or authors. + * Copyright 2002-2017 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,32 +16,57 @@ package org.springframework.integration.ws; +import static org.hamcrest.Matchers.instanceOf; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertThat; import static org.mockito.Mockito.mock; import java.io.ByteArrayInputStream; +import java.io.IOException; import java.net.URI; +import java.nio.charset.Charset; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicReference; +import javax.xml.soap.MessageFactory; +import javax.xml.soap.SOAPException; +import javax.xml.transform.OutputKeys; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerException; +import javax.xml.transform.TransformerFactory; + import org.hamcrest.Matchers; import org.junit.Test; import org.mockito.Mockito; import org.springframework.beans.factory.BeanFactory; import org.springframework.context.support.ClassPathXmlApplicationContext; +import org.springframework.core.io.ByteArrayResource; import org.springframework.integration.handler.ReplyRequiredException; import org.springframework.integration.support.MessageBuilder; import org.springframework.messaging.Message; import org.springframework.messaging.MessageChannel; import org.springframework.messaging.PollableChannel; import org.springframework.messaging.support.GenericMessage; +import org.springframework.util.StreamUtils; +import org.springframework.util.concurrent.SettableListenableFuture; +import org.springframework.ws.WebServiceMessage; import org.springframework.ws.WebServiceMessageFactory; +import org.springframework.ws.client.WebServiceClientException; import org.springframework.ws.client.support.destination.DestinationProvider; +import org.springframework.ws.client.support.interceptor.ClientInterceptorAdapter; +import org.springframework.ws.context.MessageContext; +import org.springframework.ws.mime.Attachment; +import org.springframework.ws.mime.MimeMessage; import org.springframework.ws.soap.SoapMessage; +import org.springframework.ws.soap.saaj.SaajSoapMessageFactory; import org.springframework.ws.transport.WebServiceConnection; import org.springframework.ws.transport.WebServiceMessageSender; +import org.springframework.xml.transform.StringResult; +import org.springframework.xml.transform.StringSource; /** * @author Mark Fisher @@ -111,6 +136,65 @@ public class SimpleWebServiceOutboundGatewayTests { gateway.handleMessage(new GenericMessage("foo")); } + @Test + public void testAttachments() throws TransformerException, SOAPException, InterruptedException, ExecutionException, TimeoutException, IOException { + String uri = "http://www.example.org"; + SimpleWebServiceOutboundGateway gateway = new SimpleWebServiceOutboundGateway(uri); + gateway.setBeanFactory(mock(BeanFactory.class)); + + final SettableListenableFuture requestFuture = new SettableListenableFuture<>(); + + ClientInterceptorAdapter interceptorAdapter = new ClientInterceptorAdapter() { + + @Override + public boolean handleRequest(MessageContext messageContext) throws WebServiceClientException { + requestFuture.set(messageContext.getRequest()); + return super.handleRequest(messageContext); + } + + }; + gateway.setInterceptors(interceptorAdapter); + + gateway.afterPropertiesSet(); + + WebServiceMessageFactory messageFactory = new SaajSoapMessageFactory(MessageFactory.newInstance()); + MimeMessage webServiceMessage = (MimeMessage) messageFactory.createWebServiceMessage(); + + String request = "foo"; + + TransformerFactory transformerFactory = TransformerFactory.newInstance(); + Transformer transformer = transformerFactory.newTransformer(); + transformer.transform(new StringSource(request), webServiceMessage.getPayloadResult()); + + webServiceMessage.addAttachment("myAttachment", new ByteArrayResource("my_data".getBytes()), "text/plain"); + + try { + gateway.handleMessage(new GenericMessage<>(webServiceMessage)); + } + catch (Exception e) { + // expected + } + + WebServiceMessage requestMessage = requestFuture.get(10, TimeUnit.SECONDS); + + assertNotNull(requestMessage); + assertThat(requestMessage, instanceOf(MimeMessage.class)); + + transformer = transformerFactory.newTransformer(); + transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes"); + StringResult stringResult = new StringResult(); + transformer.transform(requestMessage.getPayloadSource(), stringResult); + + assertEquals(request, stringResult.toString()); + + Attachment myAttachment = ((MimeMessage) requestMessage).getAttachment("myAttachment"); + + assertNotNull(myAttachment); + assertEquals("text/plain", myAttachment.getContentType()); + assertEquals("my_data", StreamUtils.copyToString(myAttachment.getInputStream(), Charset.forName("UTF-8"))); + } + + public static WebServiceMessageSender createMockMessageSender(final String mockResponseMessage) throws Exception { WebServiceMessageSender messageSender = Mockito.mock(WebServiceMessageSender.class); WebServiceConnection wsConnection = Mockito.mock(WebServiceConnection.class); diff --git a/spring-integration-ws/src/test/java/org/springframework/integration/ws/config/WebServiceOutboundGatewayParserTests-context.xml b/spring-integration-ws/src/test/java/org/springframework/integration/ws/config/WebServiceOutboundGatewayParserTests-context.xml index 9483bb6a2e..64d3f40ea2 100644 --- a/spring-integration-ws/src/test/java/org/springframework/integration/ws/config/WebServiceOutboundGatewayParserTests-context.xml +++ b/spring-integration-ws/src/test/java/org/springframework/integration/ws/config/WebServiceOutboundGatewayParserTests-context.xml @@ -38,6 +38,7 @@ request-channel="inputChannel" uri="http://example.org" ignore-empty-responses="false" + extract-payload="false" requires-reply="true"/> " + + private static String responseSoapMessage = " " + " " + - "" + - " " + - "jane" + - " " + + " " + + " " + + " jane" + + " " + ""; - String responseNonSoapMessage = " " + + private static String responseNonSoapMessage = " " + "oleg"; + @Autowired + private ApplicationContext context; + @Test public void headerMapperParserTest() throws Exception { - ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext( - "ws-outbound-gateway-with-headermappers.xml", this.getClass()); - SimpleWebServiceOutboundGateway gateway = TestUtils.getPropertyValue(context.getBean("withHeaderMapper"), + SimpleWebServiceOutboundGateway gateway = + TestUtils.getPropertyValue(this.context.getBean("withHeaderMapper"), "handler", SimpleWebServiceOutboundGateway.class); DefaultSoapHeaderMapper headerMapper = TestUtils.getPropertyValue(gateway, "headerMapper", DefaultSoapHeaderMapper.class); @@ -108,22 +118,34 @@ public class WebServiceOutboundGatewayWithHeaderMapperTests { assertFalse(replyHeaderMatcher.matchHeader("123baz123")); assertTrue(replyHeaderMatcher.matchHeader("bar")); assertTrue(replyHeaderMatcher.matchHeader("bar123")); - context.close(); } @Test public void withHeaderMapperString() throws Exception { String payload = "bill"; - Message replyMessage = this.process(payload, "withHeaderMapper", "inputChannel", true); + Message replyMessage = process(payload, "withHeaderMapper", "inputChannel", true); assertTrue(replyMessage.getPayload() instanceof String); assertEquals("bar", replyMessage.getHeaders().get("bar")); assertNull(replyMessage.getHeaders().get("baz")); } + @Test + public void withHeaderMapperAndExtractPayloadFalse() throws Exception { + SimpleWebServiceOutboundGateway gateway = + this.context.getBean("withHeaderMapper.handler", SimpleWebServiceOutboundGateway.class); + gateway.setExtractPayload(false); + + String payload = "bill"; + Message replyMessage = process(payload, "withHeaderMapper", "inputChannel", true); + assertThat(replyMessage.getPayload(), instanceOf(WebServiceMessage.class)); + assertEquals("bar", replyMessage.getHeaders().get("bar")); + assertNull(replyMessage.getHeaders().get("baz")); + } + @Test public void withHeaderMapperStringPOX() throws Exception { String payload = "bill"; - Message replyMessage = this.process(payload, "withHeaderMapper", "inputChannel", false); + Message replyMessage = process(payload, "withHeaderMapper", "inputChannel", false); assertTrue(replyMessage.getPayload() instanceof String); assertTrue(((String) replyMessage.getPayload()).contains("oleg")); } @@ -134,7 +156,7 @@ public class WebServiceOutboundGatewayWithHeaderMapperTests { DocumentBuilder docBuilder = dbfac.newDocumentBuilder(); Document document = docBuilder.parse(new ByteArrayInputStream("bill".getBytes())); DOMSource payload = new DOMSource(document); - Message replyMessage = this.process(payload, "withHeaderMapper", "inputChannel", true); + Message replyMessage = process(payload, "withHeaderMapper", "inputChannel", true); assertTrue(replyMessage.getPayload() instanceof DOMSource); assertEquals("bar", replyMessage.getHeaders().get("bar")); assertNull(replyMessage.getHeaders().get("baz")); @@ -146,7 +168,7 @@ public class WebServiceOutboundGatewayWithHeaderMapperTests { DocumentBuilder docBuilder = dbfac.newDocumentBuilder(); Document document = docBuilder.parse(new ByteArrayInputStream("bill".getBytes())); DOMSource payload = new DOMSource(document); - Message replyMessage = this.process(payload, "withHeaderMapper", "inputChannel", false); + Message replyMessage = process(payload, "withHeaderMapper", "inputChannel", false); assertTrue(replyMessage.getPayload() instanceof DOMSource); assertTrue(this.extractStringResult(replyMessage).contains("oleg")); } @@ -156,7 +178,7 @@ public class WebServiceOutboundGatewayWithHeaderMapperTests { DocumentBuilderFactory dbfac = DocumentBuilderFactory.newInstance(); DocumentBuilder docBuilder = dbfac.newDocumentBuilder(); Document payload = docBuilder.parse(new ByteArrayInputStream("bill".getBytes())); - Message replyMessage = this.process(payload, "withHeaderMapper", "inputChannel", true); + Message replyMessage = process(payload, "withHeaderMapper", "inputChannel", true); assertTrue(replyMessage.getPayload() instanceof Document); assertEquals("bar", replyMessage.getHeaders().get("bar")); assertNull(replyMessage.getHeaders().get("baz")); @@ -167,7 +189,7 @@ public class WebServiceOutboundGatewayWithHeaderMapperTests { DocumentBuilderFactory dbfac = DocumentBuilderFactory.newInstance(); DocumentBuilder docBuilder = dbfac.newDocumentBuilder(); Document payload = docBuilder.parse(new ByteArrayInputStream("bill".getBytes())); - Message replyMessage = this.process(payload, "withHeaderMapper", "inputChannel", false); + Message replyMessage = process(payload, "withHeaderMapper", "inputChannel", false); assertTrue(replyMessage.getPayload() instanceof Document); assertTrue(this.extractStringResult(replyMessage).contains("oleg")); } @@ -176,16 +198,14 @@ public class WebServiceOutboundGatewayWithHeaderMapperTests { public void withHeaderMapperAndMarshaller() throws Exception { Person person = new Person(); person.setName("Bill Clinton"); - Message replyMessage = this.process(person, "marshallingWithHeaderMapper", "inputMarshallingChannel", true); + Message replyMessage = process(person, "marshallingWithHeaderMapper", "inputMarshallingChannel", true); assertEquals("bar", replyMessage.getHeaders().get("bar")); assertNull(replyMessage.getHeaders().get("baz")); } - @SuppressWarnings({ "resource" }) - public Message process(Object payload, String gatewayName, String channelName, final boolean soap) throws Exception { - ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext( - "ws-outbound-gateway-with-headermappers.xml", this.getClass()); - AbstractWebServiceOutboundGateway gateway = TestUtils.getPropertyValue(context.getBean(gatewayName), "handler", + private Message process(Object payload, String gatewayName, String channelName, final boolean soap) throws Exception { + AbstractWebServiceOutboundGateway gateway = + TestUtils.getPropertyValue(this.context.getBean(gatewayName), "handler", AbstractWebServiceOutboundGateway.class); if (!soap) { @@ -199,6 +219,7 @@ public class WebServiceOutboundGatewayWithHeaderMapperTests { Mockito.when(messageSender.supports(Mockito.any(URI.class))).thenReturn(true); Mockito.doAnswer(invocation -> { + Object[] args = invocation.getArguments(); WebServiceMessage wsMessage = (WebServiceMessage) args[0]; // try { // uncomment if you want to see a pretty-print of SOAP message @@ -217,7 +238,9 @@ public class WebServiceOutboundGatewayWithHeaderMapperTests { assertNull(soapHeader.getAttributeValue(QNameUtils.parseQNameString("bar"))); } return null; - }).when(wsConnection).send(Mockito.any(WebServiceMessage.class)); + + }).when(wsConnection) + .send(Mockito.any(WebServiceMessage.class)); Mockito.doAnswer(invocation -> { Object[] args = invocation.getArguments(); @@ -252,9 +275,23 @@ public class WebServiceOutboundGatewayWithHeaderMapperTests { setHeader(WebServiceHeaders.SOAP_ACTION, "someAction").build(); inputChannel.send(message); QueueChannel outputChannel = context.getBean("outputChannel", QueueChannel.class); - Message replyMessage = outputChannel.receive(0); - context.close(); - return replyMessage; + return outputChannel.receive(0); + } + + private String extractStringResult(Message replyMessage) throws Exception { + Transformer transformer = TransformerFactory.newInstance().newTransformer(); + StringResult result = new StringResult(); + Object payload = replyMessage.getPayload(); + if (payload instanceof DOMSource) { + transformer.transform(((DOMSource) replyMessage.getPayload()), result); + } + else if (payload instanceof Document) { + transformer.transform(new DOMSource((Document) replyMessage.getPayload()), result); + } + else { + throw new IllegalArgumentException("Unsupported payload type: " + payload.getClass().getName()); + } + return result.toString(); } public static class Person { @@ -268,6 +305,7 @@ public class WebServiceOutboundGatewayWithHeaderMapperTests { public void setName(String name) { this.name = name; } + } public static class SampleUnmarshaller implements Unmarshaller { @@ -286,21 +324,7 @@ public class WebServiceOutboundGatewayWithHeaderMapperTests { person.setName(name); return person; } + } - private String extractStringResult(Message replyMessage) throws Exception { - Transformer transformer = TransformerFactory.newInstance().newTransformer(); - StringResult result = new StringResult(); - Object payload = replyMessage.getPayload(); - if (payload instanceof DOMSource) { - transformer.transform(((DOMSource) replyMessage.getPayload()), result); - } - else if (payload instanceof Document) { - transformer.transform(new DOMSource((Document) replyMessage.getPayload()), result); - } - else { - throw new IllegalArgumentException("Unsupported payload type: " + payload.getClass().getName()); - } - return result.toString(); - } } diff --git a/spring-integration-xml/src/main/java/org/springframework/integration/xml/transformer/UnmarshallingTransformer.java b/spring-integration-xml/src/main/java/org/springframework/integration/xml/transformer/UnmarshallingTransformer.java index e078a950e5..f4bc8fdc53 100644 --- a/spring-integration-xml/src/main/java/org/springframework/integration/xml/transformer/UnmarshallingTransformer.java +++ b/spring-integration-xml/src/main/java/org/springframework/integration/xml/transformer/UnmarshallingTransformer.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2014 the original author or authors. + * Copyright 2002-2017 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. @@ -32,6 +32,7 @@ import org.springframework.integration.xml.source.SourceFactory; import org.springframework.messaging.MessagingException; import org.springframework.oxm.Unmarshaller; import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; import org.springframework.xml.transform.StringSource; /** @@ -39,14 +40,18 @@ import org.springframework.xml.transform.StringSource; * {@link Unmarshaller}. Expects the payload to be of type {@link Document}, * {@link String}, {@link File}, {@link Source} or to have an instance of * {@link SourceFactory} that can convert to a {@link Source}. If - * alwaysUseSourceFactory is set to true, then the {@link SourceFactory} + * {@link #alwaysUseSourceFactory} is set to true, then the {@link SourceFactory} * will be used to create the {@link Source} regardless of payload type. *

+ * The {@link #alwaysUseSourceFactory} is ignored if payload is + * {@link org.springframework.ws.mime.MimeMessage}. + *

* The Unmarshaller may return a Message, but if the return value is not * already a Message instance, a new Message will be created with that * return value as its payload. * * @author Jonas Partner + * @author Artem Bilan */ public class UnmarshallingTransformer extends AbstractPayloadTransformer { @@ -56,15 +61,17 @@ public class UnmarshallingTransformer extends AbstractPayloadTransformer> for more information. - The `DefaultSoapHeaderMapper` can now map a `javax.xml.transform.Source` user-defined header to a SOAP header element. +- Simple WebService Inbound and Outbound gateways can now deal with the complete `WebServiceMessage` as a `payload`, allowing the manipulation of MTOM attachments. + See <> for more information. diff --git a/src/reference/asciidoc/ws.adoc b/src/reference/asciidoc/ws.adoc index 305981876d..94d46fa03f 100644 --- a/src/reference/asciidoc/ws.adoc +++ b/src/reference/asciidoc/ws.adoc @@ -271,3 +271,25 @@ And in the end we have SOAP envelope as: ---- + +[[mtom-support]] +=== MTOM Support + +The Marshalling Inbound and Outbound WebService Gateways support attachments directly via built-in functionality of the marshaller, e.g. `Jaxb2Marshaller` provides the `mtomEnabled` option. +Starting with _version 5.0_, the Simple WebService Gateways can operate with inbound and outbound `MimeMessage` s directly, which have an API to manipulate attachments. +When you need to send WebService message with attachments (either a reply from a server, or a client request) you should use the `WebServiceMessageFactory` directly and send a `WebServiceMessage` with attachments as a `payload` to the request or reply channel of the gateway: +[source, java] +---- +WebServiceMessageFactory messageFactory = new SaajSoapMessageFactory(MessageFactory.newInstance()); +MimeMessage webServiceMessage = (MimeMessage) messageFactory.createWebServiceMessage(); + +String request = "foo"; + +TransformerFactory transformerFactory = TransformerFactory.newInstance(); +Transformer transformer = transformerFactory.newTransformer(); +transformer.transform(new StringSource(request), webServiceMessage.getPayloadResult()); + +webServiceMessage.addAttachment("myAttachment", new ByteArrayResource("my_data".getBytes()), "plain/text"); + +this.webServiceChannel.send(new GenericMessage<>(webServiceMessage)); +---- diff --git a/src/reference/asciidoc/xml.adoc b/src/reference/asciidoc/xml.adoc index b296037863..e71b7d366b 100644 --- a/src/reference/asciidoc/xml.adoc +++ b/src/reference/asciidoc/xml.adoc @@ -275,6 +275,10 @@ Custom conversion to a `Source` is also supported by injecting an implementation NOTE: If a `SourceFactory` is not set explicitly, the property on the `UnmarshallingTransformer` will by default be set to a http://docs.spring.io/spring-integration/api/org/springframework/integration/xml/source/DomSourceFactory.html[DomSourceFactory]. +Starting with _version 5.0_, the `UnmarshallingTransformer` also supports an `org.springframework.ws.mime.MimeMessage` as the incoming payload. +This can be useful in scenarios when we receive a raw `WebServiceMessage` via SOAP with MTOM attachments. +See <> for more information. + [source,xml] ----