com.predic8.membrane.core.multipart.XOPReconstitutor.java Source code

Java tutorial

Introduction

Here is the source code for com.predic8.membrane.core.multipart.XOPReconstitutor.java

Source

/* Copyright 2012 predic8 GmbH, www.predic8.com
    
   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at
    
   http://www.apache.org/licenses/LICENSE-2.0
    
   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License. */

package com.predic8.membrane.core.multipart;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;

import javax.annotation.concurrent.ThreadSafe;
import javax.mail.internet.ContentType;
import javax.mail.internet.ParseException;
import javax.xml.namespace.QName;
import javax.xml.stream.FactoryConfigurationError;
import javax.xml.stream.XMLEventReader;
import javax.xml.stream.XMLEventWriter;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLOutputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.events.EndElement;
import javax.xml.stream.events.StartElement;
import javax.xml.stream.events.XMLEvent;

import org.apache.commons.fileupload.MultipartStream;
import org.apache.commons.fileupload.MultipartStream.MalformedStreamException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.predic8.membrane.core.Constants;
import com.predic8.membrane.core.http.Header;
import com.predic8.membrane.core.http.Message;
import com.predic8.membrane.core.util.EndOfStreamException;
import com.predic8.membrane.core.util.MessageUtil;

/**
 * Reassemble a multipart XOP message (see
 * http://en.wikipedia.org/wiki/XML-binary_Optimized_Packaging and
 * http://www.w3.org/TR/xop10/ ) into one stream (that can be used for schema
 * validation, for example).
 */
@ThreadSafe
public class XOPReconstitutor {
    private static Log log = LogFactory.getLog(XOPReconstitutor.class.getName());
    private static final String XOP_NAMESPACE_URI = "http://www.w3.org/2004/08/xop/include";

    private final XMLInputFactory xmlInputFactory;

    public XOPReconstitutor() {
        xmlInputFactory = XMLInputFactory.newInstance();
        xmlInputFactory.setProperty(XMLInputFactory.IS_REPLACING_ENTITY_REFERENCES, false);
        xmlInputFactory.setProperty(XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES, false);
    }

    public InputStream reconstituteIfNecessary(Message message) throws XMLStreamException, IOException {
        try {
            Message reconstitutedMessage = getReconstitutedMessage(message);
            if (reconstitutedMessage != null)
                return reconstitutedMessage.getBodyAsStream();
        } catch (Exception e) {
            log.warn("", e);
        }
        return MessageUtil.getContentAsStream(message);
    }

    private XMLEventReader createEventReaderFromStream(InputStream is) throws XMLStreamException {
        synchronized (xmlInputFactory) {
            return xmlInputFactory.createXMLEventReader(is);
        }
    }

    /**
     * @return reassembled SOAP message or null if message is not SOAP or not multipart
     */
    public Message getReconstitutedMessage(Message message) throws ParseException, MalformedStreamException,
            IOException, EndOfStreamException, XMLStreamException, FactoryConfigurationError {
        ContentType contentType = message.getHeader().getContentTypeObject();
        if (contentType == null || contentType.getPrimaryType() == null)
            return null;
        if (!contentType.getPrimaryType().equals("multipart") || !contentType.getSubType().equals("related"))
            return null;

        String type = contentType.getParameter("type");
        if (!"application/xop+xml".equals(type))
            return null;
        String start = contentType.getParameter("start");
        if (start == null)
            return null;
        String boundary = contentType.getParameter("boundary");
        if (boundary == null)
            return null;

        HashMap<String, Part> parts = split(message, boundary);
        Part startPart = parts.get(start);
        if (startPart == null)
            return null;

        ContentType innerContentType = new ContentType(startPart.getHeader().getContentType());
        if (!innerContentType.getPrimaryType().equals("application")
                || !innerContentType.getSubType().equals("xop+xml"))
            return null;

        byte[] body = fillInXOPParts(startPart.getInputStream(), parts);

        Message m = new Message() {
            @Override
            protected void parseStartLine(InputStream in) throws IOException, EndOfStreamException {
                throw new RuntimeException("not implemented.");
            }

            @Override
            public String getStartLine() {
                throw new RuntimeException("not implemented.");
            }
        };
        m.setBodyContent(body);

        String reconstitutedContentType = innerContentType.getParameter("type");
        if (reconstitutedContentType != null)
            m.getHeader().add(Header.CONTENT_TYPE, reconstitutedContentType);

        return m;
    }

    @SuppressWarnings("deprecation")
    private HashMap<String, Part> split(Message message, String boundary)
            throws IOException, EndOfStreamException, MalformedStreamException {
        HashMap<String, Part> parts = new HashMap<String, Part>();

        MultipartStream multipartStream = new MultipartStream(MessageUtil.getContentAsStream(message),
                boundary.getBytes(Constants.UTF_8_CHARSET));
        boolean nextPart = multipartStream.skipPreamble();
        while (nextPart) {
            Header header = new Header(multipartStream.readHeaders());
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            multipartStream.readBodyData(baos);

            // see http://www.iana.org/assignments/transfer-encodings/transfer-encodings.xml
            String cte = header.getFirstValue("Content-Transfer-Encoding");
            if (cte != null && !cte.equals("binary") && !cte.equals("8bit") && !cte.equals("7bit"))
                throw new RuntimeException("Content-Transfer-Encoding '" + cte + "' not implemented.");

            Part part = new Part(header, baos.toByteArray());
            String id = part.getContentID();
            if (id != null) {
                parts.put(id, part);
            }

            nextPart = multipartStream.readBoundary();
        }
        return parts;
    }

    private byte[] fillInXOPParts(InputStream inputStream, HashMap<String, Part> parts)
            throws XMLStreamException, FactoryConfigurationError {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        XMLEventWriter writer = XMLOutputFactory.newInstance().createXMLEventWriter(baos);

        try {
            XMLEventReader parser = createEventReaderFromStream(inputStream);

            boolean xopIncludeOpen = false;

            while (parser.hasNext()) {
                XMLEvent event = parser.nextEvent();

                if (event instanceof StartElement) {
                    StartElement start = (StartElement) event;
                    if (XOP_NAMESPACE_URI.equals(start.getName().getNamespaceURI())
                            && start.getName().getLocalPart().equals("Include")) {
                        String href = start.getAttributeByName(new QName("href")).getValue();

                        if (href.startsWith("cid:"))
                            href = href.substring(4);

                        Part p = parts.get("<" + href + ">");
                        if (p == null)
                            throw new RuntimeException("Did not find multipart with id " + href);

                        writer.add(p.asXMLEvent());
                        xopIncludeOpen = true;
                        continue;
                    }
                } else if (event instanceof EndElement) {
                    EndElement start = (EndElement) event;
                    if (XOP_NAMESPACE_URI.equals(start.getName().getNamespaceURI())
                            && start.getName().getLocalPart().equals("Include") && xopIncludeOpen) {
                        xopIncludeOpen = false;
                        continue;
                    }
                }

                writer.add(event);
            }
            writer.flush();
        } catch (XMLStreamException e) {
            log.warn("Received not-wellformed XML.");
            return null;
        }
        return baos.toByteArray();
    }

}