cancel
Showing results for 
Search instead for 
Did you mean: 

MTOM support in CMIS setContentStream()

gregory
Champ in-the-making
Champ in-the-making
Hi, I'm trying to use SOAP Message Transmission Optimization Mechanism (MTOM) with CMIS services to upload large files (over 500Mb) to Alfresco 3.3g Community.

File downloading works fine with MTOM, that is

ObjectServicePort port = Service.create(wsdlUrl, serviceName).getPort(ObjectServicePort.class, new MTOMFeature());
CmisContentStreamType content = port.getContentStream(repoId, largeObjectId, null, null, null, null);

DataHandler stream = content.getStream();
OutputStream os = new FileOutputStream(DOWNLOAD_PATH);
stream.writeTo(os);
os.close();
uses single small buffer multiple times to transfer the file from Alfresco. It doesn't allocate a buffer of the size of the file, so it doesn't need much memory to run.

However, while uploading with MTOM

ObjectServicePort port = Service.create(wsdlUrl, serviceName).getPort(ObjectServicePort.class, new MTOMFeature());
CmisContentStreamType content = port.getContentStream(repoId, largeObjectId, null, null, null, null);

// These are required to turn MTOM on in jax-ws client
Map<String, Object> ctxt = ((BindingProvider)port).getRequestContext();
ctxt.put(JAXWSProperties.HTTP_CLIENT_STREAMING_CHUNK_SIZE, 8192);

final FileInputStream is = new FileInputStream(FILE_TO_UPLOAD);
DataHandler stream = new DataHandler(new DataSource() {
         
   @Override
   public OutputStream getOutputStream() throws IOException {
      return null;
   }
            
   @Override
   public String getName() {
      return "";
   }
            
   @Override
   public InputStream getInputStream() throws IOException {
      return is;
   }
            
   @Override
   public String getContentType() {
      return "application/octet-stream";
   }
});
         
CmisContentStreamType contentStream = new CmisContentStreamType();
contentStream.setMimeType("application/octet-stream");
contentStream.setStream(stream);
port.setContentStream(repoId, new Holder<String>(largeObjectId), true, null, contentStream, null);
it tries to allocate a buffer to hold the whole file read from FileInputStream and ends up with "java.lang.OutOfMemoryError: Java heap space"

This method does work with a test MTOM-enabled web service, but for some reason it fails to work with CmisObjectService.
What am I doing wrong? Is there any way to get upload streaming to work with CMIS?
1 REPLY 1

gregory
Champ in-the-making
Champ in-the-making
The problem was caused by SOAPHandler that was appending a security header for authentication in Alfresco:

class SecurityHandler implements SOAPHandler<SOAPMessageContext>{
   @Override
   public boolean handleMessage(SOAPMessageContext messageContext) {
       if ((Boolean) messageContext.get(MessageContext.MESSAGE_OUTBOUND_PROPERTY)) {
            try {
                SOAPMessage msg = messageContext.getMessage();
                SOAPEnvelope envelope = msg.getSOAPPart().getEnvelope();
                SOAPHeader soapHeader;
                if (envelope.getHeader() != null) {
                    soapHeader = envelope.getHeader();
                } else {
                    soapHeader = envelope.addHeader();
                }
                SOAPHeaderElement headerElement = soapHeader.addHeaderElement(
                        new QName(WS_SECEXT, "Security", "wsse"));
                headerElement.setMustUnderstand(true);
                SOAPElement timestamp = headerElement.addChildElement(
                        new QName(WS_SECUTILITY, "Timestamp", "wsu"));
      …
The statement
SOAPMessage msg = messageContext.getMessage();
forces preparation of the whole message though we need only the headers.

To solve the problem we have to set soap headers in some other way, for example

import org.w3c.dom.Document;
import org.w3c.dom.Element;

DatatypeFactory factory = DatatypeFactory.newInstance();
GregorianCalendar now = new GregorianCalendar();
GregorianCalendar fiveMinutesLater = new GregorianCalendar();
fiveMinutesLater.add(GregorianCalendar.MINUTE, 5);
XMLGregorianCalendar xmlNow = factory.newXMLGregorianCalendar(now);
XMLGregorianCalendar xmlFiveMinutesLater = factory.newXMLGregorianCalendar(fiveMinutesLater);

String xmlNowDate = xmlNow.toXMLFormat().substring(0, xmlNow.toXMLFormat().length() - 6) + "Z";
String xmlFiveMinutesLaterDate = xmlFiveMinutesLater.toXMLFormat()
   .substring(0, xmlFiveMinutesLater.toXMLFormat().length() - 6) + "Z";
   
Document doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();
Element header = doc.createElementNS(WS_SECEXT, "wsse:Security");
   Element timestamp = doc.createElementNS(WS_SECUTILITY, "wsu:Timestamp");
   timestamp.setAttributeNS(WS_SECUTILITY, "wsu:Id", "Timestamp");
      Element created = doc.createElementNS(WS_SECUTILITY, "wsu:Created");
      created.appendChild(doc.createTextNode(xmlNowDate));
   timestamp.appendChild(created);
      Element expired = doc.createElementNS(WS_SECUTILITY, "wsu:Expired");
      expired.appendChild(doc.createTextNode(xmlFiveMinutesLaterDate));
   timestamp.appendChild(expired);
header.appendChild(timestamp);
   Element usernameToken = doc.createElementNS(WS_SECEXT, "wsse:UsernameToken");
      Element username = doc.createElementNS(WS_SECEXT, "wsse:Username");
      username.appendChild(doc.createTextNode(getUsername()));
   usernameToken.appendChild(username);
      Element password = doc.createElementNS(WS_SECEXT, "wsse:Password");
      password.setAttribute("Type", WS_USER_TOKEN_PROFILE);
      password.appendChild(doc.createTextNode(getPassword()));
   usernameToken.appendChild(password);
header.appendChild(usernameToken);

ObjectServicePort port = service.getObjectServicePort();      
((WSBindingProvider)port).setOutboundHeaders(Headers.create(header));