3

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?


Update

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.

2
  • Is there an option to set com.sun.xml.ws.encoding.mtom.threshold propterties in Jaxb2MarshallerConfig available marshaller.setMarshallerProperties(new Properties()); marshaller.getMarshallerProperties().put("com.sun.xml.ws.encoding.mtom.threshold", "1000000"); Commented Oct 31, 2024 at 15:23
  • Yeah, I tried that. setMarshallerProperties takes a map, so here's what I tried: marshaller.setMarshallerProperties(Map.of("com.sun.xml.ws.encoding.mtom.threshold", 1000000)); (also, tried writing the value as a String, like "1000000". Unfortunately, both of these cause a jakarta.xml.bind.PropertyException: name: com.sun.xml.ws.encoding.mtom.threshold value: 1000000. I looked into the marshaller properties, and from what I could find that one takes only a very limited set of properties special to JAXB and not the underlying marshaller (at least as far as I can tell). Commented Nov 2, 2024 at 13:56

1 Answer 1

1

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.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.