net.instantcom.mm7.MM7Message.java Source code

Java tutorial

Introduction

Here is the source code for net.instantcom.mm7.MM7Message.java

Source

/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright (c) 2007-2014 InstantCom Ltd. All rights reserved.
 *
 * The contents of this file are subject to the terms of either the GNU
 * General Public License Version 2 only ("GPL") or the Common Development
 * and Distribution License("CDDL") (collectively, the "License").  You
 * may not use this file except in compliance with the License.  You can
 * obtain a copy of the License at
 * https://raw.github.com/vnesek/instantcom-mm7/master/LICENSE.txt
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * When distributing the software, include this License Header Notice in each
 * file and include the License file at appropriate location.
 */

package net.instantcom.mm7;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.StringWriter;
import java.io.Writer;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.UUID;

import org.jdom2.Document;
import org.jdom2.Element;
import org.jdom2.Namespace;
import org.jdom2.filter.ElementFilter;
import org.jdom2.output.Format;
import org.jdom2.output.XMLOutputter;
import org.jvnet.mimepull.MIMEConfig;
import org.jvnet.mimepull.MIMEMessage;
import org.jvnet.mimepull.MIMEPart;

public class MM7Message implements JDOMSupport {

    public static Namespace ENVELOPE = Namespace.getNamespace("env", "http://schemas.xmlsoap.org/soap/envelope/");

    /**
     * Loads a correct subclass of a MM7 message by looking at a SOAP request
     * and instantiating an object of related class. Handles SOAP with
     * attachments.
     *
     * @param in
     *            input stream to load message from
     * @param contentType
     *            of a request. Can be an multipart or text/xml
     * @param ctx
     *            configuration for loading of message
     *
     * @return an MM7Message instance
     * @throws IOException
     *             if can't deserialize SOAP message or some other IO problem
     *             occurs
     * @throws MM7Error
     *             if SOAP fault is received.
     */
    public static MM7Message load(InputStream in, String contentType, MM7Context ctx) throws IOException, MM7Error {
        ContentType ct = new ContentType(contentType);
        BasicContent content = fromStream(in, ct);
        SoapContent soap = null;
        if (content.getParts() != null) {
            String start = (String) ct.getParameter("start");
            if (start != null) {
                if (start.length() == 0) {
                    throw new MM7Error("invalid content type, start parameter is empty: " + contentType);
                }
                if (start.charAt(0) == '<') {
                    start = start.substring(1, start.length() - 1);
                }
                for (Content c : content.getParts()) {
                    if (start.equals(c.getContentId())) {
                        soap = (SoapContent) c;
                        break;
                    }
                }
            } else {
                for (Content c : content.getParts()) {
                    if (c instanceof SoapContent) {
                        soap = (SoapContent) c;
                        break;
                    }
                }
            }
        } else {
            soap = (SoapContent) content;
        }

        // Parse SOAP message to JDOM
        if (soap == null) {
            throw new MM7Error("can't find SOAP parts");
        }
        Document doc = soap.getDoc();

        Element body = doc.getRootElement().getChild("Body", ENVELOPE);
        Element e = (Element) body.getChildren().get(0);
        String message = e.getName();

        // Check if we've got a SOAP fault
        if ("Fault".equals(message)) {
            MM7Error mm7error = new MM7Error();
            mm7error.load(doc.getRootElement());
            throw mm7error;
        }

        // Instantiate a correct message class
        try {
            Class<?> clazz = Class.forName("net.instantcom.mm7." + message);
            MM7Message mm7 = (MM7Message) clazz.newInstance();

            // Load response
            mm7.load(doc.getRootElement());

            // Set content if any
            if (content.getParts() != null && mm7 instanceof HasContent) {
                Element contentElement = e.getChild("Content", e.getNamespace());
                String href = contentElement.getAttributeValue("href", contentElement.getNamespace());
                if (href == null) {
                    href = contentElement.getAttributeValue("href");
                }

                // Loop over content, try to match content-location or
                // content-id
                Content payload = null;
                if (href.startsWith("cid:")) {
                    // Match by content-id
                    String cid = href.substring(4).trim();
                    for (Content c : content.getParts()) {
                        if (cid.equals(c.getContentId())) {
                            payload = c;
                            break;
                        }
                    }
                } else {
                    // Match by content-location
                    for (Content c : content.getParts()) {
                        if (href.equals(c.getContentLocation())) {
                            payload = c;
                            break;
                        }
                    }
                }
                // We've got a junk message here... try to be a smart cookie and
                // use first non-SOAP part
                if (payload == null) {
                    for (Content c : content.getParts()) {
                        if (!(c instanceof SoapContent)) {
                            payload = c;
                            break;
                        }
                    }
                }
                if (payload != null) {
                    ((HasContent) mm7).setContent(payload);
                }
            }
            return mm7;
        } catch (Throwable t) {
            throw new MM7Error("failed to instantiate message " + message, t);
        }
    }

    public static void save(MM7Message mm7, OutputStream out, MM7Context ctx) throws IOException {
        // Setup MM7 version and XML name space if not already set
        if (mm7.getMm7Version() == null) {
            mm7.setMm7Version(ctx.getMm7Version());
        }
        if (mm7.getNamespace() == null) {
            mm7.setNamespace(ctx.getMm7Namespace());
        }

        final XMLOutputter xo = new XMLOutputter();
        xo.setFormat(ctx.getJdomFormat());
        final Writer w = new OutputStreamWriter(out, "utf-8");

        if (mm7.isMultipart()) {
            final Content content = ((HasContent) mm7).getContent();

            final String boundary = mm7.getSoapBoundary();
            w.write("--");
            w.write(boundary);
            w.write("\r\nContent-Type: text/xml; charset=\"utf-8\"\r\nContent-Transfer-Encoding: binary\r\nContent-ID: <");
            w.write(mm7.getSoapContentId());
            w.write(">\r\n\r\n");
            w.write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n");

            xo.output(mm7.toSOAP(ctx), w);

            w.write("\r\n--");
            w.write(boundary);
            w.flush();

            content.writeTo(out, "mm7-content", ctx);
            w.write("\r\n--");
            w.write(boundary);
            w.write("--");
        } else {
            xo.output(mm7.toSOAP(ctx), w);
        }
        w.flush();
    }

    private static BasicContent fromPart(MIMEPart part, ContentType contentType) throws IOException {
        BasicContent result;
        if (contentType.getPrimaryType().equals("text") || contentType.getSubType().equals("smilx")) {
            if (contentType.getSubType().equals("xml")) {
                result = new SoapContent(part.readOnce());
            } else {
                TextContent text = new TextContent();
                String encoding = contentType.getParameter("charset");
                if (encoding == null) {
                    encoding = "iso-8859-1";
                }
                text.setText(new String(BinaryContent.toByteArray(part.readOnce(), 1024, 256 * 1024), encoding));
                result = text;
            }
        } else {
            result = new BinaryContent(part.getContentType(), part.readOnce());
        }
        result.setContentType(part.getContentType());
        result.setContentId(part.getContentId());
        result.setContentLocation(getContentLocation(part));
        return result;
    }

    private static BasicContent fromStream(InputStream in, ContentType contentType) throws IOException {
        if (in == null) {
            throw new NullPointerException("input stream is null");
        }
        BasicContent result;
        if (contentType.isMultipart()) {
            MIMEConfig mimeConfig = new MIMEConfig();
            MIMEMessage msg = new MIMEMessage(in, contentType.getParameter("boundary"), mimeConfig);
            List<Content> contents = new ArrayList<Content>();
            for (MIMEPart part : msg.getAttachments()) {
                BasicContent content;
                ContentType partType = new ContentType(part.getContentType());
                if (partType.isMultipart()) {
                    content = fromStream(part.readOnce(), partType);
                    part.close();
                } else {
                    content = fromPart(part, partType);
                    part.close();
                }
                content.setContentId(part.getContentId());
                content.setContentLocation(getContentLocation(part));
                content.setContentType(part.getContentType());
                contents.add(content);
            }
            result = new BasicContent(contents);
        } else if (contentType.getMimeTypeWithoutParams().equals("text/xml")) {
            result = new SoapContent(in);
        } else if (contentType.getPrimaryType().equals("text")) {
            TextContent text = new TextContent();
            String encoding = new ContentType(contentType).getParameter("charset");
            if (encoding == null) {
                encoding = "iso-8859-1";
            }
            text.setText(new String(BinaryContent.toByteArray(in, 1024, 256 * 1024), encoding));
            result = text;
        } else {
            result = new BinaryContent(contentType.getMimeType(), in);
        }
        result.setContentType(contentType.getMimeType());
        return result;
    }

    private static String getContentLocation(MIMEPart part) {
        String result;
        List<String> contentLocation = part.getHeader("Content-Location");
        if (contentLocation != null) {
            result = contentLocation.get(0);
        } else {
            result = null;
        }
        return result;

    }

    public String getMm7Version() {
        return mm7Version;
    }

    public String getNamespace() {
        return namespace != null ? namespace.getURI() : null;
    }

    public String getSoapBoundary() {
        if (soapBoundary == null) {
            this.soapBoundary = "==MM7-SOAP==" + UUID.randomUUID().toString();
        }
        return soapBoundary;
    }

    public String getSoapContentId() {
        return soapContentId;
    }

    public String getSoapContentType() {
        String contentType;
        if (isMultipart()) {
            contentType = "multipart/related; boundary=\"" + getSoapBoundary() + //
                    "\"; type=\"text/xml\"; start=\"<" + getSoapContentId() + ">\"";
        } else {
            contentType = "text/xml; charset=\"utf-8\"";
        }
        return contentType;
    }

    public String getTransactionId() {
        return transactionId;
    }

    /**
     * Returns true if this message has content.
     *
     * @return true if SOAP message needs multipart encoding
     */
    public boolean isMultipart() {
        if (this instanceof HasContent) {
            return ((HasContent) this).getContent() != null;
        }
        return false;
    }

    @Override
    public void load(Element element) {
        Element body = element.getChild("Body", element.getNamespace());

        // Extract MM7 namespace from SOAP body
        Iterator<?> i = body.getDescendants(new ElementFilter());
        while (i.hasNext()) {
            Element e = (Element) i.next();
            Namespace ns = e.getNamespace();
            if (ns != null && ns.getURI().contains("MM7")) {
                this.namespace = ns;
                break;
            }
        }

        if (this.namespace == null) {
            throw new IllegalStateException("can't autodetect MM7 namespace: " + body.toString());
        }

        Element header = element.getChild("Header", element.getNamespace());
        setTransactionId(header.getChildTextTrim("TransactionID", namespace));
    }

    @Override
    public Element save(Element parent) {
        Element e = new Element(getClass().getSimpleName(), namespace);
        final String mm7Version = getMm7Version();
        if (mm7Version != null) {
            e.addContent(new Element("MM7Version", e.getNamespace()).setText(mm7Version));
        }
        return e;
    }

    public void setMm7Version(String mm7Version) {
        this.mm7Version = mm7Version;
    }

    public void setNamespace(String namespace) {
        this.namespace = namespace != null ? Namespace.getNamespace(mm7NamespacePrefix, namespace) : null;
    }

    public void setSoapBoundary(String mimeBoundary) {
        this.soapBoundary = mimeBoundary;
    }

    public void setSoapContentId(String soapContentId) {
        this.soapContentId = soapContentId;
    }

    public void setTransactionId(String transactionId) {
        this.transactionId = transactionId;
    }

    @Override
    public String toString() {
        XMLOutputter out = new XMLOutputter(Format.getPrettyFormat());
        Document doc = new Document(toSOAP(new MM7Context()));
        StringWriter w = new StringWriter();
        try {
            out.output(doc, w);
            return w.toString();
        } catch (IOException e) {
            return super.toString();
        }
    }

    private Element toSOAP(MM7Context ctx) {
        Element env = new Element("Envelope", ENVELOPE);
        if (namespace != null) {
            env.addNamespaceDeclaration(namespace);
        }
        Element header = new Element("Header", ENVELOPE);
        if (transactionId != null) {
            header.addContent(new Element("TransactionID", namespace) //
                    .setText(transactionId) //
                    .setAttribute("mustUnderstand", "1", ENVELOPE));
        }
        env.addContent(header);

        Element body = new Element("Body", ENVELOPE);
        body.addContent(save(body));
        env.addContent(body);
        return env;
    }

    public void setMm7NamespacePrefix(String mm7NamespacePrefix) {
        this.mm7NamespacePrefix = mm7NamespacePrefix;
        setNamespace(getNamespace());
    }

    public String getMm7NamespacePrefix() {
        return mm7NamespacePrefix;
    }

    Namespace namespace;
    private String mm7Version;
    private String mm7NamespacePrefix;
    private String soapBoundary;
    private String soapContentId = "mm7-soap";
    private String transactionId;
}