WSDL first MTOM enabled cxf 2.7 web-service client causes Java heap space error for large attachments

3.1k Views Asked by At

I am building a WSDL-first java web-service using CXF2.7.7 that uses MTOM. My intention is to be able to upload large ( multiple Gigabytes) files through this web-service Below is a fragment of my the WSDL

 <!-- WSDL for MTOM service -->
    <wsdl:types>
    <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" 
xmlns:tns="http://objectstoreservice.example.com/" attributeFormDefault="unqualified"  
elementFormDefault="unqualified" targetNamespace="http://objectstoreservice.example.com/">
        <xs:element name="objectReqParam" type="tns:objectReqParam"/>
        <xs:element name="objectRespParam" type="tns:objectRespParam"/>
        <xs:complexType name="objectReqParam">
            <xs:sequence>
                 <xs:element minOccurs="0" name="objName" type="xs:string"/> 
                <xs:element minOccurs="0" maxOccurs="1" name="ObjData" 
type="xs:base64Binary" xmime:expectedContentTypes="application/octet-stream"/>
            </xs:sequence>
        </xs:complexType>
        <xs:complexType name="objectRespParam">
            <xs:sequence>
                <xs:element minOccurs="0" name="respCode" type="xs:string"/>
            </xs:sequence>
        </xs:complexType>
    </xs:schema>
</wsdl:types>
<wsdl:message name="objectUploadRequest">
    <wsdl:part name="objectReqParam" element="tns:objectReqParam">
</wsdl:part>
</wsdl:message>
<wsdl:message name="objectUploadResponse">
    <wsdl:part name="objectRespParam" element="tns:objectRespParam">
</wsdl:part>
</wsdl:message>
<wsdl:portType name="ObjectStoreService">
    <wsdl:operation name="uploadObject">
        <wsdl:input name="objectUploadRequest" message="tns:objectUploadRequest"/>
        <wsdl:output name="objectUploadResponse" message="tns:objectUploadResponse"/>
    </wsdl:operation>
</wsdl:portType>
<wsdl:binding name="ObjectStoreServiceServiceSoapBinding" type="tns:ObjectStoreService">
    <soap:binding style="document" transport="http://schemas.xmlsoap.org/soap/http"/>
    <wsdl:operation name="uploadObject">
        <soap:operation soapAction="" style="document"/>
        <wsdl:input name="objectUploadRequest">
            <soap:body use="literal"/>
        </wsdl:input>
        <wsdl:output name="objectUploadResponse">
            <soap:body use="literal"/>
        </wsdl:output>
    </wsdl:operation>
</wsdl:binding>
<wsdl:service name="ObjectStoreServiceService">
    <wsdl:port name="ObjectStoreServicePort"         
              binding="tns:ObjectStoreServiceServiceSoapBinding">
        <soap:address location="http://localhost:9090/ObjectStoreServicePort"/>
    </wsdl:port>
</wsdl:service>
    </wsdl:definitions>

My intention is to use MTOM ( as it would be evident from the element type="xs:base64Binary" xmime:expectedContentTypes="application/octet-stream) for uploading objects through this service.

I have also configured my jax WS end-point to use MTOM as seen from my Server side Spring configuration file fragment below

<beans xmlns="http://www.springframework.org/schema/beans" 
... />
<jaxws:endpoint xmlns:objectstore="http://objectstoreservice.example.com/"
    id="ObjectStoreServiceHTTP" address="http://localhost:9090/ObjectStoreServicePort"
    serviceName="objectstore:ObjectStoreServiceService"   
endpointName="objectstore:ObjectStoreServiceEndpoint"
    implementor="com.example.objectstoreservice.server.ObjectStoreServiceImpl">
    <jaxws:properties>
    <!-- MTOM properties -->
    <entry key="mtom-enabled" value="true"/>
    <entry key="attachment-directory" value="/tmp/mtomattachments"/>
            <entry key="attachment-memory-threshold" value="100000"/>
    </jaxws:properties>
</jaxws:endpoint>
</beans>

And similarly I have also configured my jax-ws client to use MTOM as seen from my client side spring configuration file below

<beans xmlns="http://www.springframework.org/schema/beans ..../>
<jaxws:client id="objectStoreService" 
    serviceName="objectstore:ObjectStoreServiceService" 
    endpointName="objectstore:ObjectStoreServiceEndpoint"
    address="http://localhost:9090/ObjectStoreServicePort" 
    serviceClass="com.example.objectstoreservice.ObjectStoreService">
    <jaxws:properties>
    <!-- MTOM properties -->
    <entry key="mtom-enabled" value="true"/>
    <entry key="attachment-memory-threshold" value="100000"/>
    <entry key="attachment-directory" value="/tmp/mtomattachments"/>
    <entry key="javax.xml.ws.client.connectionTimeout" 
                            value="10000000" />
            <entry key="javax.xml.ws.client.requestTimeout" 
                            value="10000000" />
    <jaxws:properties>
</jaxws:client>

And I am also including a relevant fragment of my Java code for client below

/* ---      Web-service client 
*************************************************************************** */

package com.example.objectstoreservice.client;

import java.util.List;
import java.awt.Image;
import java.io.ByteArrayInputStream;
import java.io.File;

import java.io.InputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import javax.activation.DataHandler;
import javax.activation.FileDataSource;
import javax.xml.namespace.QName;


import com.example.objectstoreservice.ObjectStoreService;
import com.example.objectstoreservice.ObjectReqParam;
import com.example.objectstoreservice.ObjectRespParam;
import javax.xml.ws.Binding;
import javax.xml.ws.BindingProvider;

public final class ObjectStoreServiceTester {

ObjectStoreService objectStoreService;
Binding binding ;

public ObjectStoreService getObjectStoreService() {
    return objectStoreService;
}

// I haven't shown the code that sets the port object - objectStoreService 
// Take my worrd for it - I have the right port object 
public void setObjectStoreService(ObjectStoreService objectStoreService) {
    this.objectStoreService = objectStoreService;
}

public void enableMTOM () {
 binding = ((BindingProvider)objectStoreService).getBinding();
 ((SOAPBinding)binding).setMTOMEnabled(true);

}


public void testObjectStoreService() 
{
            enableMTOM();

            System.out.println("Now uploading a data file to  service");

    ObjectReqParam objReqParam = new ObjectReqParam() ;
    objReqParam.setObjName("File");
            String fileName="C:/root/opt/files/ToSend.jpg";
            FileDataSource inFileDataSource=new FileDataSource(fileName);
    DataHandler dataHandler = new DataHandler(inFileDataSource);
    System.out.println("Check content-type from dataHandler : " + dataHandler.getContentType());
    System.out.println("Check obj signature from dataSource : " + dataHandler.getDataSource());
    System.out.println("Check file-name from dataSource : " + dataHandler.getDataSource().getName());

    objReqParam.setObjData(dataHandler);
    System.out.println("Now uploading file:" + fileName);
    objectStoreService.uploadObject(objReqParam);
    System.out.println("Object upload successful");


}

}

When run - this client runs fine for small attachments, but gives following error for large attachments ( based on my JVM heap space allocated)

** An exception occured while executing the Java class. null: InvocationTargetException: Java heap space **

Below is the full stack trace. *Please let me know if you see something amiss or if I should be doing something different * *- And BTW I have seen some other posts with similar problems and am wondering if anyone has done MTOM for large files.Is there a way I can explicitly force it to stream the file or force it to send in small chunks ?
*

MTOM: An exception occured while executing the Java class. null
at org.apache.maven.lifecycle.internal.MojoExecutor.execute(MojoExecutor
.java:217)
        at org.apache.maven.lifecycle.internal.MojoExecutor.execute(MojoExecutor
.java:153)
        at org.apache.maven.lifecycle.internal.MojoExecutor.execute(MojoExecutor
.java:145)
    at org.apache.maven.lifecycle.internal.LifecycleModuleBuilder.buildProje
ct(LifecycleModuleBuilder.java:84)
    at org.apache.maven.lifecycle.internal.LifecycleModuleBuilder.buildProje
ct(LifecycleModuleBuilder.java:59)
    at org.apache.maven.lifecycle.internal.LifecycleStarter.singleThreadedBu 
ild(LifecycleStarter.java:183)
    at org.apache.maven.lifecycle.internal.LifecycleStarter.execute(Lifecycl
eStarter.java:161)
    at org.apache.maven.DefaultMaven.doExecute(DefaultMaven.java:320)
    at org.apache.maven.DefaultMaven.execute(DefaultMaven.java:156)
    at org.apache.maven.cli.MavenCli.execute(MavenCli.java:537)
    at org.apache.maven.cli.MavenCli.doMain(MavenCli.java:196)
    at org.apache.maven.cli.MavenCli.main(MavenCli.java:141)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.
java:57)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAcces
sorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:606)
    at org.codehaus.plexus.classworlds.launcher.Launcher.launchEnhanced(Laun
cher.java:290)
    at org.codehaus.plexus.classworlds.launcher.Launcher.launch(Launcher.jav
a:230)
    at org.codehaus.plexus.classworlds.launcher.Launcher.mainWithExitCode(La
uncher.java:409)
    at org.codehaus.plexus.classworlds.launcher.Launcher.main(Launcher.java:
352)
Caused by: org.apache.maven.plugin.MojoExecutionException: An exception occured
while executing the Java class. null
    at org.codehaus.mojo.exec.ExecJavaMojo.execute(ExecJavaMojo.java:346)
    at org.apache.maven.plugin.DefaultBuildPluginManager.executeMojo(Default
BuildPluginManager.java:101)
    at org.apache.maven.lifecycle.internal.MojoExecutor.execute(MojoExecutor
.java:209)
    ... 19 more
Caused by: java.lang.reflect.InvocationTargetException
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.
java:57)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAcces
sorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:606)
    at org.codehaus.mojo.exec.ExecJavaMojo$1.run(ExecJavaMojo.java:291)
    at java.lang.Thread.run(Thread.java:744)
Caused by: java.lang.OutOfMemoryError: Java heap space
    at com.sun.xml.bind.v2.util.ByteArrayOutputStreamEx.readFrom(ByteArrayOu
tputStreamEx.java:75)
    at com.sun.xml.bind.v2.runtime.unmarshaller.Base64Data.get(Base64Data.ja
va:196)
    at com.sun.xml.bind.v2.runtime.unmarshaller.Base64Data.writeTo(Base64Dat
a.java:312)
    at com.sun.xml.bind.v2.runtime.output.UTF8XmlOutput.text(UTF8XmlOutput.j
ava:312)
    at com.sun.xml.bind.v2.runtime.XMLSerializer.leafElement(XMLSerializer.j
ava:356)
    at com.sun.xml.bind.v2.model.impl.RuntimeBuiltinLeafInfoImpl$PcdataImpl.
writeLeafElement(RuntimeBuiltinLeafInfoImpl.java:183)
    at com.sun.xml.bind.v2.runtime.MimeTypedTransducer.writeLeafElement(Mime
TypedTransducer.java:96)
    at com.sun.xml.bind.v2.runtime.reflect.TransducedAccessor$CompositeTrans
ducedAccessorImpl.writeLeafElement(TransducedAccessor.java:256)
    at com.sun.xml.bind.v2.runtime.property.SingleElementLeafProperty.serial
izeBody(SingleElementLeafProperty.java:130)
    at com.sun.xml.bind.v2.runtime.ClassBeanInfoImpl.serializeBody(ClassBean
InfoImpl.java:361)
    at com.sun.xml.bind.v2.runtime.XMLSerializer.childAsXsiType(XMLSerialize
r.java:696)
    at com.sun.xml.bind.v2.runtime.property.SingleElementNodeProperty.serial
izeBody(SingleElementNodeProperty.java:158)
    at com.sun.xml.bind.v2.runtime.ElementBeanInfoImpl$1.serializeBody(Eleme
ntBeanInfoImpl.java:161)
    at com.sun.xml.bind.v2.runtime.ElementBeanInfoImpl$1.serializeBody(Eleme
ntBeanInfoImpl.java:131)
    at com.sun.xml.bind.v2.runtime.ElementBeanInfoImpl.serializeBody(Element
BeanInfoImpl.java:333)
    at com.sun.xml.bind.v2.runtime.ElementBeanInfoImpl.serializeRoot(Element
BeanInfoImpl.java:340)
    at com.sun.xml.bind.v2.runtime.ElementBeanInfoImpl.serializeRoot(Element
BeanInfoImpl.java:76)
    at com.sun.xml.bind.v2.runtime.XMLSerializer.childAsRoot(XMLSerializer.j
ava:494)
    at com.sun.xml.bind.v2.runtime.MarshallerImpl.write(MarshallerImpl.java:
323)
    at com.sun.xml.bind.v2.runtime.MarshallerImpl.marshal(MarshallerImpl.jav
a:251)
    at javax.xml.bind.helpers.AbstractMarshallerImpl.marshal(AbstractMarshal
lerImpl.java:95)
    at org.apache.cxf.jaxb.JAXBEncoderDecoder.writeObject(JAXBEncoderDecoder
.java:612)
    at org.apache.cxf.jaxb.JAXBEncoderDecoder.marshall(JAXBEncoderDecoder.ja
va:240)
    at org.apache.cxf.jaxb.io.DataWriterImpl.write(DataWriterImpl.java:169)
    at org.apache.cxf.interceptor.AbstractOutDatabindingInterceptor.writePar
ts(AbstractOutDatabindingInterceptor.java:114)
    at org.apache.cxf.interceptor.BareOutInterceptor.handleMessage(BareOutIn
terceptor.java:68)
    at org.apache.cxf.phase.PhaseInterceptorChain.doIntercept(PhaseIntercept
orChain.java:272)
    at org.apache.cxf.endpoint.ClientImpl.doInvoke(ClientImpl.java:565)
    at org.apache.cxf.endpoint.ClientImpl.invoke(ClientImpl.java:474)
    at org.apache.cxf.endpoint.ClientImpl.invoke(ClientImpl.java:377)
    at org.apache.cxf.endpoint.ClientImpl.invoke(ClientImpl.java:330)
    at org.apache.cxf.frontend.ClientProxy.invokeSync(ClientProxy.java:96)

And another peculiar thing If I check the SOAP message I still see the attachment going "inline" as base64 encoded and not as a separate part ... atleast the CXF logging shows that to me ...

1

There are 1 best solutions below

0
On

This problem was result of the spring configuration relating to MTOM NOT getting picked up due to some strange reason - while I have not figured out why - I could alleviate this by programatically adding setting the MTOM related properties to my binding class - and then MTOM worked fine. One caveat though - I did away with my https transport for now ( am using plain http transport )

// In My Web-service client code .... 

import javax.xml.ws.Binding;
import javax.xml.ws.BindingProvider;
import javax.xml.ws.soap.SOAPBinding;
import com.sun.xml.ws.developer.JAXWSProperties; 

// ... REST of my code 

// After I create my Port - - did following for Enabling MTOM");
        Binding binding = ((BindingProvider)port).getBinding();
        if (binding == null) {
            System.out.println("port .getBinding  failed!!");
            System.exit(-1);
        }
        ((SOAPBinding) binding).setMTOMEnabled(true);

        if (debugFlag)
        System.out.println(" Port - setting chunking and other properties");
        //Map contextMap=((BindingProvider)port).getRequestContext();

               ((BindingProvider)port).getRequestContext().put(JAXWSProperties.HTTP_CLIENT_STREAMING_CHUNK_SIZE,32768);
            ((BindingProvider)port).getRequestContext().put(JAXWSProperties.MTOM_THRESHOLOD_VALUE,16000);
        ((BindingProvider)port).getRequestContext().put(JAXWSProperties.REQUEST_TIMEOUT,0);
        ((BindingProvider)port).getRequestContext().put(JAXWSProperties.CONNECT_TIMEOUT ,0);