I have the following issue: I need to talk to an old SOAP service, and that one requires me to send a request object where a large amount of data is directly in the SOAP message body, like so:
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<SOAP-ENV:Body>
<MyRequest xmlns="http://my.company.com/xsd/portals/v4_0">
<documentList xmlns="">
<binaryData>
<blob>
VeryLongDataBlobInHere
</blob>
<extension>pdf</extension>
</binaryData>
</documentList>
</MyRequest>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
The problem is, Spring automatically turns that into an attachment like this if MTOM is enabled:
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<SOAP-ENV:Body>
<MyRequest xmlns="http://my.company.com/xsd/portals/v4_0">
<documentList xmlns="">
<binaryData>
<blob>
<xop:Include xmlns:xop="http://www.w3.org/2004/08/xop/include" href="cid:3be5f4d8-50ed-4f88-8e50-778f6cc70c74%40null"/>
</blob>
<extension>pdf</extension>
</binaryData>
</documentList>
</MyRequest>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
By contrast, if MTOM is disabled, the blob is empty like this:
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<SOAP-ENV:Body>
<MyRequest xmlns="http://my.company.com/xsd/portals/v4_0">
<documentList xmlns="">
<binaryData>
<blob/>
<extension>pdf</extension>
</binaryData>
</documentList>
</MyRequest>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
I have tried various approaches to solve this, including messing with the data types, and trying to adjust the properties of the marshaller in order to increase the MTOM threshold, but nothing I tried worked. Here's my marshaller configuration:
@Configuration
public class Jaxb2MarshallerConfig {
@Bean
public Jaxb2Marshaller myMarshaller() {
Jaxb2Marshaller marshaller = new Jaxb2Marshaller();
marshaller.setContextPath("com.company.project.xsd.some_portal.v4_0");
marshaller.setMtomEnabled(true);
return marshaller;
}
}
And here's where the binary data is built and assigned:
private BinaryData buildBinaryData(byte[] documentData) {
BinaryData binaryData = new BinaryData();
byte[] encodedData = Base64.getEncoder().encode(documentData);
DataHandler dataHandler = new DataHandler(encodedData, "application/pdf");
binaryData.setBlob(dataHandler);
binaryData.setExtension("pdf");
return binaryData;
}
BinaryData meanwhile is a generated class built from an WSDL, so I can't change anything in there. But here's how it looks:
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "BinaryData", propOrder = {
"blob",
"extension"
})
public class BinaryData {
@XmlElement(required = true)
@XmlMimeType("application/octet-stream")
protected DataHandler blob;
@XmlElement(required = true)
protected String extension;
[...]
}
Finally, here's how I sent this whole mess:
@Component
@Log4j2
public class MySoapClient extends WebServiceGatewaySupport {
private final WebServiceTemplate template;
public MySoapClient (
MyServiceProperties properties,
Jaxb2Marshaller marshaller
) {
setMarshaller(marshaller);
setUnmarshaller(marshaller);
setDefaultUri(properties.getTargetUrl());
template = getWebServiceTemplate();
}
@Override
public void sendDocuments(MyRequest request) {
try {
template.marshalSendAndReceive(request);
} catch (Exception e) {
log.error(e, e.getCause());
throw new RuntimeException(e);
}
}
}
My best guess is that I somehow need to increase the MTOM threshold, but I have no idea how. I tried messing around with marshaller.setMarshallerProperties()
, but nothing there worked.
Does anyone have any idea of how I can get the marshaller to write the blob inline? Or is the problem somewhere else?
I now created a github repository with the minimum required code, as well as a test to reproduce the issue and check for the desired behavior:
https://github.com/KiraResari/jaxb2-marshalling
If you like, you can check it out and try to get the test to pass somehow.
afflato provided a working solution for this on the linked GitHub repository which I hereby share:
The key is the DataHandler
which is assigned in buildBinaryData
. That one determines how the request is marshalled. By writing a custom DataSource
and using that here, the marshaller will write the blob into the body of the request.
First, a custom DataSource
has to be implemented like this:
import jakarta.activation.DataSource;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
public class ByteArrayDataSource implements DataSource {
private byte[] data;
private String type;
public ByteArrayDataSource(byte[] data, String type) {
this.data = data; this.type = type;
}
@Override
public InputStream getInputStream() {
return new ByteArrayInputStream(data);
}
@Override
public OutputStream getOutputStream() {
return new ByteArrayOutputStream(data.length);
}
@Override
public String getContentType() {
return type;
}
@Override
public String getName() {
return "ByteArrayDataSource";
}
}
After that, this DataSource
has to be used in the DataHandler
in the buildBinaryData
function like this:
private BinaryData buildBinaryData(byte[] documentData) {
BinaryData binaryData = new BinaryData();
DataSource ds = new ByteArrayDataSource(documentData, "application/pdf");
DataHandler dataHandler = new DataHandler(ds);
binaryData.setBlob(dataHandler);
binaryData.setExtension("pdf");
return binaryData;
}
That is all that is needed. The end result will be that the blob is always written into the body of the SOAP request and never attached.