INT-1800: Add MTOM Support for WS
JIRA: https://jira.spring.io/browse/INT-1800 The Simple WebService Inbound and Outbound Gateways can operate with `WebServiceMessage`s directly. This allows to create those messages manually and add attachments to them. Or, on the other hand, process incoming messages with attachments manually. For this purpose the `SimpleWebServiceOutboundGateway` is supplied with new `extractPayload` property. Also the `UnmarshallingTransformer` can now process `MimeMessage` as payload to unmarshal it into object graph with attachments if that
This commit is contained in:
committed by
Gary Russell
parent
695b168b0b
commit
1e07551294
10
build.gradle
10
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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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<Attachment> 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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -300,6 +300,18 @@ this list can also be simple patterns to be matched against the header names (e.
|
||||
]]></xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="extract-payload">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
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.
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
<xsd:simpleType>
|
||||
<xsd:union memberTypes="xsd:boolean xsd:string"/>
|
||||
</xsd:simpleType>
|
||||
</xsd:attribute>
|
||||
<xsd:attributeGroup ref="integration:smartLifeCycleAttributeGroup"/>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
@@ -396,7 +408,18 @@ this list can also be simple patterns to be matched against the header names (e.
|
||||
</xsd:appinfo>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="extract-payload" type="xsd:string"/>
|
||||
<xsd:attribute name="extract-payload">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
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.
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
<xsd:simpleType>
|
||||
<xsd:union memberTypes="xsd:boolean xsd:string"/>
|
||||
</xsd:simpleType>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="header-mapper">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
|
||||
@@ -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<String>("<test>foo</test>"));
|
||||
}
|
||||
|
||||
@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<WebServiceMessage> 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 = "<test>foo</test>";
|
||||
|
||||
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);
|
||||
|
||||
@@ -38,6 +38,7 @@
|
||||
request-channel="inputChannel"
|
||||
uri="http://example.org"
|
||||
ignore-empty-responses="false"
|
||||
extract-payload="false"
|
||||
requires-reply="true"/>
|
||||
|
||||
<ws:outbound-gateway id="gatewayWithDefaultSourceExtractor"
|
||||
|
||||
@@ -127,7 +127,8 @@ public class WebServiceOutboundGatewayParserTests {
|
||||
assertEquals(SimpleWebServiceOutboundGateway.class, gateway.getClass());
|
||||
DirectFieldAccessor accessor = new DirectFieldAccessor(gateway);
|
||||
assertEquals(Boolean.FALSE, accessor.getPropertyValue("ignoreEmptyResponses"));
|
||||
Assert.assertEquals(Boolean.TRUE, accessor.getPropertyValue("requiresReply"));
|
||||
assertEquals(Boolean.TRUE, accessor.getPropertyValue("requiresReply"));
|
||||
assertEquals(Boolean.FALSE, accessor.getPropertyValue("extractPayload"));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -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,10 +16,12 @@
|
||||
|
||||
package org.springframework.integration.ws.config;
|
||||
|
||||
import static org.hamcrest.Matchers.instanceOf;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
@@ -34,11 +36,13 @@ import javax.xml.transform.TransformerFactory;
|
||||
import javax.xml.transform.dom.DOMSource;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mockito;
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Element;
|
||||
|
||||
import org.springframework.context.support.ClassPathXmlApplicationContext;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.integration.channel.QueueChannel;
|
||||
import org.springframework.integration.mapping.AbstractHeaderMapper;
|
||||
import org.springframework.integration.support.MessageBuilder;
|
||||
@@ -51,6 +55,8 @@ import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.MessageChannel;
|
||||
import org.springframework.oxm.Unmarshaller;
|
||||
import org.springframework.oxm.XmlMappingException;
|
||||
import org.springframework.test.annotation.DirtiesContext;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
import org.springframework.util.xml.DomUtils;
|
||||
import org.springframework.ws.WebServiceMessage;
|
||||
import org.springframework.ws.WebServiceMessageFactory;
|
||||
@@ -66,26 +72,30 @@ import org.springframework.xml.transform.StringResult;
|
||||
/**
|
||||
* @author Oleg Zhurakousky
|
||||
* @author Gunnar Hillert
|
||||
*
|
||||
* @author Artem Bilan
|
||||
*/
|
||||
@RunWith(SpringRunner.class)
|
||||
@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD)
|
||||
public class WebServiceOutboundGatewayWithHeaderMapperTests {
|
||||
|
||||
String responseSoapMessage = "<?xml version=\"1.0\" encoding=\"UTF-8\" ?> " +
|
||||
private static String responseSoapMessage = "<?xml version=\"1.0\" encoding=\"UTF-8\" ?> " +
|
||||
"<SOAP-ENV:Envelope xmlns:SOAP-ENV=\"http://schemas.xmlsoap.org/soap/envelope/\"> " +
|
||||
"<SOAP-ENV:Header/>" +
|
||||
"<SOAP-ENV:Body> " +
|
||||
"<root><name>jane</name></root>" +
|
||||
"</SOAP-ENV:Body> " +
|
||||
" <SOAP-ENV:Header/>" +
|
||||
" <SOAP-ENV:Body> " +
|
||||
" <root><name>jane</name></root>" +
|
||||
" </SOAP-ENV:Body> " +
|
||||
"</SOAP-ENV:Envelope>";
|
||||
|
||||
String responseNonSoapMessage = "<?xml version=\"1.0\" encoding=\"UTF-8\" ?> " +
|
||||
private static String responseNonSoapMessage = "<?xml version=\"1.0\" encoding=\"UTF-8\" ?> " +
|
||||
"<person><name>oleg</name></person>";
|
||||
|
||||
@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 = "<root><name>bill</name></root>";
|
||||
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 = "<root><name>bill</name></root>";
|
||||
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 = "<root><name>bill</name></root>";
|
||||
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("<person><name>oleg</name></person>"));
|
||||
}
|
||||
@@ -134,7 +156,7 @@ public class WebServiceOutboundGatewayWithHeaderMapperTests {
|
||||
DocumentBuilder docBuilder = dbfac.newDocumentBuilder();
|
||||
Document document = docBuilder.parse(new ByteArrayInputStream("<root><name>bill</name></root>".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("<root><name>bill</name></root>".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("<person><name>oleg</name></person>"));
|
||||
}
|
||||
@@ -156,7 +178,7 @@ public class WebServiceOutboundGatewayWithHeaderMapperTests {
|
||||
DocumentBuilderFactory dbfac = DocumentBuilderFactory.newInstance();
|
||||
DocumentBuilder docBuilder = dbfac.newDocumentBuilder();
|
||||
Document payload = docBuilder.parse(new ByteArrayInputStream("<root><name>bill</name></root>".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("<root><name>bill</name></root>".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("<person><name>oleg</name></person>"));
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
* <p>
|
||||
* The {@link #alwaysUseSourceFactory} is ignored if payload is
|
||||
* {@link org.springframework.ws.mime.MimeMessage}.
|
||||
* <p>
|
||||
* 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<Object, Object> {
|
||||
|
||||
@@ -56,15 +61,17 @@ public class UnmarshallingTransformer extends AbstractPayloadTransformer<Object,
|
||||
|
||||
private volatile boolean alwaysUseSourceFactory = false;
|
||||
|
||||
private MimeMessageUnmarshallerHelper mimeMessageUnmarshallerHelper;
|
||||
|
||||
public UnmarshallingTransformer(Unmarshaller unmarshaller) {
|
||||
this.unmarshaller = unmarshaller;
|
||||
if (ClassUtils.isPresent("org.springframework.ws.mime.MimeMessage", ClassUtils.getDefaultClassLoader())) {
|
||||
this.mimeMessageUnmarshallerHelper = new MimeMessageUnmarshallerHelper(unmarshaller);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Provide the SourceFactory to be used. Must not be null.
|
||||
*
|
||||
* @param sourceFactory The source factory.
|
||||
*/
|
||||
public void setSourceFactory(SourceFactory sourceFactory) {
|
||||
@@ -74,7 +81,6 @@ public class UnmarshallingTransformer extends AbstractPayloadTransformer<Object,
|
||||
|
||||
/**
|
||||
* If true always delegate to the {@link SourceFactory}.
|
||||
*
|
||||
* @param alwaysUseSourceFactory true to always use the source factory.
|
||||
*/
|
||||
public void setAlwaysUseSourceFactory(boolean alwaysUseSourceFactory) {
|
||||
@@ -89,29 +95,38 @@ public class UnmarshallingTransformer extends AbstractPayloadTransformer<Object,
|
||||
@Override
|
||||
public Object transformPayload(Object payload) {
|
||||
Source source = null;
|
||||
if (this.alwaysUseSourceFactory) {
|
||||
source = this.sourceFactory.createSource(payload);
|
||||
}
|
||||
else if (payload instanceof String) {
|
||||
source = new StringSource((String) payload);
|
||||
}
|
||||
else if (payload instanceof File) {
|
||||
source = new StreamSource((File) payload);
|
||||
}
|
||||
else if (payload instanceof Document) {
|
||||
source = new DOMSource((Document) payload);
|
||||
}
|
||||
else if (payload instanceof Source) {
|
||||
source = (Source) payload;
|
||||
}
|
||||
else {
|
||||
source = this.sourceFactory.createSource(payload);
|
||||
}
|
||||
if (source == null) {
|
||||
throw new MessagingException(
|
||||
"failed to transform message, payload not assignable from javax.xml.transform.Source and no conversion possible");
|
||||
}
|
||||
|
||||
try {
|
||||
if (this.mimeMessageUnmarshallerHelper != null) {
|
||||
Object result = this.mimeMessageUnmarshallerHelper.maybeUnmarshalMimeMessage(payload);
|
||||
if (result != null) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.alwaysUseSourceFactory) {
|
||||
source = this.sourceFactory.createSource(payload);
|
||||
}
|
||||
else if (payload instanceof String) {
|
||||
source = new StringSource((String) payload);
|
||||
}
|
||||
else if (payload instanceof File) {
|
||||
source = new StreamSource((File) payload);
|
||||
}
|
||||
else if (payload instanceof Document) {
|
||||
source = new DOMSource((Document) payload);
|
||||
}
|
||||
else if (payload instanceof Source) {
|
||||
source = (Source) payload;
|
||||
}
|
||||
else {
|
||||
source = this.sourceFactory.createSource(payload);
|
||||
}
|
||||
if (source == null) {
|
||||
throw new MessagingException(
|
||||
"failed to transform message, payload not assignable from " + Source.class.getName()
|
||||
+ "and no conversion possible");
|
||||
}
|
||||
return this.unmarshaller.unmarshal(source);
|
||||
}
|
||||
catch (IOException e) {
|
||||
@@ -119,4 +134,24 @@ public class UnmarshallingTransformer extends AbstractPayloadTransformer<Object,
|
||||
}
|
||||
}
|
||||
|
||||
private static class MimeMessageUnmarshallerHelper {
|
||||
|
||||
private Unmarshaller delegate;
|
||||
|
||||
MimeMessageUnmarshallerHelper(Unmarshaller unmarshaller) {
|
||||
this.delegate = unmarshaller;
|
||||
}
|
||||
|
||||
public Object maybeUnmarshalMimeMessage(Object payload) throws IOException {
|
||||
if (payload instanceof org.springframework.ws.mime.MimeMessage) {
|
||||
return org.springframework.ws.support.MarshallingUtils.unmarshal(this.delegate,
|
||||
(org.springframework.ws.mime.MimeMessage) payload);
|
||||
}
|
||||
else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -148,4 +148,6 @@ See <<stomp>> 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 <<ws>> for more information.
|
||||
|
||||
@@ -271,3 +271,25 @@ And in the end we have SOAP envelope as:
|
||||
</soapenv:Body>
|
||||
</soapenv:Envelope>
|
||||
----
|
||||
|
||||
[[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 = "<test>foo</test>";
|
||||
|
||||
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));
|
||||
----
|
||||
|
||||
@@ -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 <<mtom-support>> for more information.
|
||||
|
||||
[source,xml]
|
||||
----
|
||||
<bean id="unmarshallingTransformer" class="o.s.i.xml.transformer.UnmarshallingTransformer">
|
||||
|
||||
Reference in New Issue
Block a user