[SWS-905] Improve the experience to enable MTOM with JAXB Created: 08/Jul/15  Updated: 05/Oct/17

Status: Reopened
Project: Spring Web Services
Component/s: Core, Documentation, Samples
Affects Version/s: 2.2.1
Fix Version/s: 3.1

Type: Improvement Priority: Minor
Reporter: Mauro Molinari Assignee: Greg Turnquist
Resolution: Unresolved Votes: 0
Labels: None
Remaining Estimate: Not Specified
Time Spent: Not Specified
Original Estimate: Not Specified

Issue Links:
Supersede
supersedes SWS-904 Improve the experience to enable MTOM... Closed

 Description   

I had a very poor experience trying to enable MTOM attachment handling on a receiving endpoint expecting JAXB input parameters. Please let me write the whole story.

I have to receive web service requests as defined by the following contract: http://www.fatturapa.gov.it/export/fatturazione/sdi/ws/trasmissione/v1.0/TrasmissioneFatture_v1.1.wsdl
and the imported schema: http://www.fatturapa.gov.it/export/fatturazione/sdi/ws/trasmissione/v1.0/TrasmissioneTypes_v1.1.xsd

With the following configuration I could make it almost work:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:web-services="http://www.springframework.org/schema/web-services"
	xmlns:context="http://www.springframework.org/schema/context"
	xsi:schemaLocation="http://www.springframework.org/schema/web-services http://www.springframework.org/schema/web-services/web-services-2.0.xsd
		http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
		http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd"
	default-lazy-init="true">
 
	<context:component-scan base-package="mypackage" />
 
	<web-services:annotation-driven />
 
	<web-services:static-wsdl id="TrasmissioneFatture"
		location="classpath:/wsdl/TrasmissioneFatture_v1.1.wsdl" />
 
	<bean id="TrasmissioneTypes_v1.1" class="org.springframework.xml.xsd.SimpleXsdSchema">
		<property name="xsd" value="classpath:/wsdl/TrasmissioneTypes_v1.1.xsd" />
	</bean>
</beans>

@Endpoint
public class TrasmissioneFattureEndpoint {
 
  @SoapAction("http://www.fatturapa.it/TrasmissioneFatture/RicevutaConsegna")
  public void ricevutaConsegna(@RequestPayload FileSdIType input) {
    // processing
  }

where FileSdiType is a Java class generated by running the JAXB compiler XJC against the above XSD file. Please note that FileSdiType is annotated with @XmlType and not with @XmlRootElement (actually, no generated class has the @XmlRootElement annotation...).
Contrary to what the reference documentation says, unmarshalling for FileSdType works even if it is not annotated with @XmlRootElement, but that's fine for me: the code and configuration are very brief and neat!

This almost worked perfectly. I say "almost" because one of the elements of FileSdType schema type is a binary attachment. By trying to retrieve its content in the endpoing implementation using the InputStream on the DataHandler exposed by the corresponding Java type, all works fine as long as the client sends the attachment as an inline base64 binary string.
But if the client uses MTOM, the input stream reads nothing.

The first reaction was: well, I would have expected it to work out of the box, since MTOM is said to be the de-facto standard for attachments.
The second thought was: let's look at the reference documentation better... but nothing relevant is said for this use case.
The third was: let's search with Google.

The actual solution was given by this StackOverflow answer: http://stackoverflow.com/questions/11316023/spring-ws-webservice-with-mtom-attachement-hello-world-test/11576245#11576245
That is: define a Jaxb2Marshaller with enabled MTOM, define a MarshallingPayloadMethodProcessor that uses it, define a DefaultMethodEndpointAdapter that uses that processor, define a message receiver of type SoapMessageDispatcher that uses that method endpoint adapter and make the MessageDispatcherServlet use that message receiver.

With all of this lengthy configuration, I could make it work, but:

  • there's a lot of (manual) configuration to set up and there's no indication in the reference guide on how to do this
  • after founding the StackOverflow question, I discovered there's a MTOM example project in the spring-ws-samples GitHub project, but:
    • I couldn't find any link to this project in the Spring Web Services website on spring.io (I found it by chance using Google...)
    • I couldn't find any mention to that samples project in the reference guide
    • the sample uses Java configuration (not XML), so a bit of translation is required in my case; nevertheless, I couldn't find where the message receiver is configured, which is an essential part of the configuration (I debugged my configuration and, without it, the default method endpoint adapter created by the Spring WS namespace tags is still preferred and used, so MTOM attachment handling doesn't work)
  • while org.springframework.ws.server.endpoint.adapter.method.jaxb.XmlRootElementPayloadMethodProcessor.supportsRequestPayloadParameter(MethodParameter) checks for both the presence of @XmlRootElement and @XmlType, org.springframework.oxm.jaxb.Jaxb2Marshaller.supportsInternal(Class<?>, boolean) only checks for the former annotation and not for the latter, so I had to change my endpoint method signature to use JaxbElement<FileSdIType> (which IMHO introduces useless noise in the endpoint implementation)
  • another (smaller) annoyance is that if I define my Jaxb2Marshaller I have to explicitly set its context path (or packages/classes to scan/support), while with the default non-MTOM enabled configuration this is not required, because the appropriate Jaxb2 marshaller is found automatically (I think it's thanks to org.springframework.ws.server.endpoint.adapter.method.jaxb.AbstractJaxb2PayloadMethodProcessor.createUnmarshaller(Class<?>) and org.springframework.ws.server.endpoint.adapter.method.jaxb.AbstractJaxb2PayloadMethodProcessor.getJaxbContext(Class<?>))

I think all of this should be improved, or at least documented. Also, if the requirement to explicitely enable MTOM on the marshaller is desirable, why this shouldn't just work?

<web-services:annotation-driven marshaller="marshaller" unmarshaller="marshaller" />

where marshaller is my MTOM-enabled Jaxb2Marshaller? In my tests, this does not work (i.e.: specifying the marshaller in the <web-services:annotation-driven> tag is unexpectedly totally useless for this use case).

Probably this is related to SWS-825, which was closed as deferred probably because of a lack of resources, However, in the short-mid term, if this could not be improved, at least please document it in the reference guide: I don't think the need for a MTOM+JAXB working solution is so uncommon.



 Comments   
Comment by Mauro Molinari [ 08/Jul/15 ]

Another info that may be useful to know. I tried to extend the Spring WS classes to support the unwrapping of XmlType annotated types in endpoint methods with the above configuration that supports MTOM, to get rid of the requirement to have methods with JAXBElement arguments.
In addition to extending org.springframework.oxm.jaxb.Jaxb2Marshaller to add the XmlType annotated case in supportsInternal(Class, boolean (which is made harder by the fact that this method is private), there's an issue in org.springframework.ws.server.endpoint.adapter.method.MarshallingPayloadMethodProcessor.resolveArgument(MessageContext, MethodParameter), which simply ignores the parameter argument and delegates the resolution to the marshaller. This can't know which kind of parameters should be returned and therefore always returns a JAXBElement result (in the Jaxb2Marshaller case). So, it could be a MarshallingPayloadMethodProcessor responsibility to unwrap the JAXBElement in case the MethodParameter is not referring to a JAXBElement parameter. However this introduces a dependency between the MarshallingPayloadMethodProcessor and a specific type of marshaller, which is bad.

In the non-MTOM enabled default configuration, this is resolved by registering two method processors (a JaxbElementPayloadMethodProcessor and a XmlRootElementPayloadMethodProcessor) which take care of the different cases, however this would require me to register two method MarshallingPayloadMethodProcessor}}s with two different JAXB2 marshaller implementations, one that does the unwrapping when unmarshalling and one that does not. Probably a bit better, but even more complex, because the {{Jaxb2Marshaller should be configurable to do this...

Comment by Mauro Molinari [ 10/Jul/15 ]

Hi Greg,
nice to see you marked this as "resolved", but what did you do then?

Comment by Greg Turnquist [ 10/Jul/15 ]

See https://github.com/spring-projects/spring-ws/commit/15207405442ec225507a43228b4c260d943f7847

Comment by Mauro Molinari [ 13/Jul/15 ]

So... just a section in the manual to point to the sample project as-is?

Comment by Greg Turnquist [ 14/Jul/15 ]

Reopen issue to evaluate possibly improving the experience

Generated at Thu Dec 14 02:06:16 UTC 2017 using JIRA 6.4.14#64029-sha1:ae256fe0fbb912241490ff1cecfb323ea0905ca5.