com.mirth.connect.connectors.doc.DocumentDispatcher.java Source code

Java tutorial

Introduction

Here is the source code for com.mirth.connect.connectors.doc.DocumentDispatcher.java

Source

/*
 * Copyright (c) Mirth Corporation. All rights reserved.
 * 
 * http://www.mirthcorp.com
 * 
 * The software in this package is published under the terms of the MPL license a copy of which has
 * been included with this distribution in the LICENSE.txt file.
 */

package com.mirth.connect.connectors.doc;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Reader;
import java.io.StringReader;
import java.util.regex.Pattern;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;

import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.log4j.Logger;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.encryption.AccessPermission;
import org.apache.pdfbox.pdmodel.encryption.StandardProtectionPolicy;
import org.xhtmlrenderer.pdf.ITextRenderer;
import org.xhtmlrenderer.resource.FSEntityResolver;
import org.xml.sax.InputSource;

import com.lowagie.text.Rectangle;
import com.lowagie.text.html.HtmlParser;
import com.lowagie.text.rtf.RtfBasicElement;
import com.lowagie.text.rtf.RtfWriter2;
import com.mirth.connect.donkey.model.channel.ConnectorProperties;
import com.mirth.connect.donkey.model.event.ConnectionStatusEventType;
import com.mirth.connect.donkey.model.event.ErrorEventType;
import com.mirth.connect.donkey.model.message.ConnectorMessage;
import com.mirth.connect.donkey.model.message.Response;
import com.mirth.connect.donkey.model.message.Status;
import com.mirth.connect.donkey.model.message.attachment.Attachment;
import com.mirth.connect.donkey.server.ConnectorTaskException;
import com.mirth.connect.donkey.server.channel.DestinationConnector;
import com.mirth.connect.donkey.server.controllers.MessageController;
import com.mirth.connect.donkey.server.event.ConnectionStatusEvent;
import com.mirth.connect.donkey.server.event.ErrorEvent;
import com.mirth.connect.donkey.util.Base64Util;
import com.mirth.connect.donkey.util.DonkeyElement;
import com.mirth.connect.server.controllers.ControllerFactory;
import com.mirth.connect.server.controllers.EventController;
import com.mirth.connect.server.util.TemplateValueReplacer;
import com.mirth.connect.util.ErrorMessageBuilder;

public class DocumentDispatcher extends DestinationConnector {

    private static final Pattern PAGE_SIZE_PATTERN = Pattern
            .compile("@page\\s*\\{[\\s\\S]*?size\\s*:[\\s\\S]*?\\}");

    private Logger logger = Logger.getLogger(this.getClass());
    private DocumentDispatcherProperties connectorProperties;
    private EventController eventController = ControllerFactory.getFactory().createEventController();
    private TemplateValueReplacer replacer = new TemplateValueReplacer();

    private static long ownerPasswordSeq = System.currentTimeMillis();

    @Override
    public void onDeploy() throws ConnectorTaskException {
        this.connectorProperties = (DocumentDispatcherProperties) getConnectorProperties();

        eventController.dispatchEvent(new ConnectionStatusEvent(getChannelId(), getMetaDataId(),
                getDestinationName(), ConnectionStatusEventType.IDLE));
    }

    @Override
    public void onUndeploy() throws ConnectorTaskException {
    }

    @Override
    public void onStart() throws ConnectorTaskException {
    }

    @Override
    public void onStop() throws ConnectorTaskException {
    }

    @Override
    public void onHalt() throws ConnectorTaskException {
    }

    @Override
    public void replaceConnectorProperties(ConnectorProperties connectorProperties,
            ConnectorMessage connectorMessage) {
        DocumentDispatcherProperties props = (DocumentDispatcherProperties) connectorProperties;

        props.setHost(replacer.replaceValues(props.getHost(), connectorMessage));
        props.setOutputPattern(replacer.replaceValues(props.getOutputPattern(), connectorMessage));
        props.setPassword(replacer.replaceValues(props.getPassword(), connectorMessage));
        props.setTemplate(replacer.replaceValues(props.getTemplate(), connectorMessage));
        props.setPageWidth(replacer.replaceValues(props.getPageWidth(), connectorMessage));
        props.setPageHeight(replacer.replaceValues(props.getPageHeight(), connectorMessage));
    }

    @Override
    public Response send(ConnectorProperties connectorProperties, ConnectorMessage connectorMessage) {
        DocumentDispatcherProperties documentDispatcherProperties = (DocumentDispatcherProperties) connectorProperties;
        String responseData = null;
        String responseError = null;
        String responseStatusMessage = null;
        Status responseStatus = Status.QUEUED;

        String info = "";
        if (documentDispatcherProperties.isEncrypt()) {
            info = "Encrypted ";
        }
        info += documentDispatcherProperties.getDocumentType() + " Document Type Result Written To: "
                + documentDispatcherProperties.getHost() + "/" + documentDispatcherProperties.getOutputPattern();

        eventController.dispatchEvent(new ConnectionStatusEvent(getChannelId(), getMetaDataId(),
                getDestinationName(), ConnectionStatusEventType.WRITING, info));

        try {
            responseData = writeDocument(documentDispatcherProperties.getTemplate(), documentDispatcherProperties,
                    connectorMessage);

            StringBuilder builder = new StringBuilder();
            builder.append("Document successfully written to ");

            if (StringUtils.isNotBlank(documentDispatcherProperties.getOutput())) {
                if (documentDispatcherProperties.getOutput().equalsIgnoreCase("file")) {
                    builder.append("file: ");
                    builder.append(documentDispatcherProperties.toURIString());
                } else if (documentDispatcherProperties.getOutput().equalsIgnoreCase("attachment")) {
                    builder.append("attachment");
                } else if (documentDispatcherProperties.getOutput().equalsIgnoreCase("both")) {
                    builder.append("attachment and file: ");
                    builder.append(documentDispatcherProperties.toURIString());
                }
            } else {
                builder.append("file: ");
                builder.append(documentDispatcherProperties.toURIString());
            }
            responseStatusMessage = builder.toString();

            responseStatus = Status.SENT;
        } catch (Exception e) {
            eventController.dispatchEvent(new ErrorEvent(getChannelId(), getMetaDataId(),
                    connectorMessage.getMessageId(), ErrorEventType.DESTINATION_CONNECTOR, getDestinationName(),
                    connectorProperties.getName(), "Error writing document", e));
            responseStatusMessage = ErrorMessageBuilder.buildErrorResponse("Error writing document", e);
            responseError = ErrorMessageBuilder.buildErrorMessage(connectorProperties.getName(),
                    "Error writing document", e);

            // TODO: Handle exception
            //            connector.handleException(e);
        } finally {
            eventController.dispatchEvent(new ConnectionStatusEvent(getChannelId(), getMetaDataId(),
                    getDestinationName(), ConnectionStatusEventType.IDLE));
        }

        return new Response(responseStatus, responseData, responseStatusMessage, responseError);
    }

    private String writeDocument(String template, DocumentDispatcherProperties documentDispatcherProperties,
            ConnectorMessage connectorMessage) throws Exception {
        // add tags to the template to create a valid HTML document
        StringBuilder contents = new StringBuilder();
        if (template.lastIndexOf("<html") < 0) {
            contents.append("<html>");
            if (template.lastIndexOf("<body") < 0) {
                contents.append("<body>");
                contents.append(template);
                contents.append("</body>");
            } else {
                contents.append(template);
            }
            contents.append("</html>");
        } else {
            contents.append(template);
        }

        String stringContents = getAttachmentHandlerProvider().reAttachMessage(contents.toString(),
                connectorMessage);

        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        if (documentDispatcherProperties.getDocumentType().toLowerCase().equals("pdf")) {
            createPDF(new StringReader(stringContents), outputStream, documentDispatcherProperties);

            boolean encrypt = documentDispatcherProperties.isEncrypt();
            String password = documentDispatcherProperties.getPassword();

            if (encrypt && password != null) {
                ByteArrayInputStream inputStream = new ByteArrayInputStream(outputStream.toByteArray());

                outputStream = new ByteArrayOutputStream();
                encryptPDF(inputStream, outputStream, password);
            }
        } else if (documentDispatcherProperties.getDocumentType().toLowerCase().equals("rtf")) {
            createRTF(new ByteArrayInputStream(stringContents.getBytes()), outputStream,
                    documentDispatcherProperties);
        }

        if (StringUtils.isBlank(documentDispatcherProperties.getOutput())
                || !documentDispatcherProperties.getOutput().equalsIgnoreCase("attachment")) {
            FileOutputStream fileOutputStream = null;
            try {
                File file = createFile(documentDispatcherProperties.getHost() + "/"
                        + documentDispatcherProperties.getOutputPattern());
                logger.info("Writing document to: " + file.getAbsolutePath());

                fileOutputStream = new FileOutputStream(file);
                outputStream.writeTo(fileOutputStream);
            } catch (Exception e) {
                throw e;
            } finally {
                IOUtils.closeQuietly(fileOutputStream);
            }
        }

        if (StringUtils.isNotBlank(documentDispatcherProperties.getOutput())
                && !documentDispatcherProperties.getOutput().equalsIgnoreCase("file")) {
            Attachment attachment = MessageController.getInstance().createAttachment(
                    new String(Base64Util.encodeBase64(outputStream.toByteArray(), false), "US-ASCII"),
                    documentDispatcherProperties.getDocumentType().contains("pdf") ? "application/pdf"
                            : "application/rtf");
            MessageController.getInstance().insertAttachment(attachment, connectorMessage.getChannelId(),
                    connectorMessage.getMessageId());

            return attachment.getAttachmentId();
        }
        return null;
    }

    private void createPDF(Reader reader, OutputStream outputStream, DocumentDispatcherProperties props)
            throws Exception {
        DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
        builder.setEntityResolver(FSEntityResolver.instance());
        org.w3c.dom.Document doc = builder.parse(new InputSource(reader));

        try {
            try {
                DonkeyElement element = new DonkeyElement(doc.getDocumentElement());
                DonkeyElement head = element.addChildElementIfNotExists("head");
                DonkeyElement style = head.addChildElementIfNotExists("style");

                double width = Double.parseDouble(props.getPageWidth());
                double height = Double.parseDouble(props.getPageHeight());
                Unit unit = props.getPageUnit();

                if (!PAGE_SIZE_PATTERN.matcher(style.getTextContent()).find()) {
                    // This uses a CSS3 selector, so we can't use twips as a unit.
                    if (unit == Unit.TWIPS) {
                        width = unit.convertTo(width, Unit.MM);
                        height = unit.convertTo(height, Unit.MM);
                        unit = Unit.MM;
                    }

                    /*
                     * Flying Saucer has problems rendering sizes less than 26mm, so we just make
                     * that the minimum. That's the size of ISO-216 A10 anyway and I doubt anyone is
                     * going to want sizes smaller than that.
                     */
                    double min = Unit.MM.convertTo(26, unit);
                    width = Math.max(width, min);
                    height = Math.max(height, min);

                    StringBuilder pageSelector = new StringBuilder("@page { size: ");
                    pageSelector.append(String.format("%f", width)).append(unit).append(' ');
                    pageSelector.append(String.format("%f", height)).append(unit).append("; }\n");
                    pageSelector.append(style.getTextContent());
                    style.setTextContent(pageSelector.toString());
                }
            } catch (Exception e) {
            }

            ITextRenderer renderer = new ITextRenderer();
            renderer.setDocument(doc, null);

            renderer.layout();
            renderer.createPDF(outputStream, true);
        } catch (Throwable e) {
            throw new Exception(e);
        }
    }

    private void encryptPDF(InputStream inputStream, OutputStream outputStream, String password) throws Exception {
        PDDocument document = null;

        try {
            document = PDDocument.load(inputStream);

            AccessPermission accessPermission = new AccessPermission();
            accessPermission.setCanAssembleDocument(false);
            accessPermission.setCanExtractContent(true);
            accessPermission.setCanExtractForAccessibility(false);
            accessPermission.setCanFillInForm(false);
            accessPermission.setCanModify(false);
            accessPermission.setCanModifyAnnotations(false);
            accessPermission.setCanPrint(true);
            accessPermission.setCanPrintDegraded(true);

            String ownerPassword = System.currentTimeMillis() + "+" + Runtime.getRuntime().freeMemory() + "+"
                    + (ownerPasswordSeq++);
            StandardProtectionPolicy policy = new StandardProtectionPolicy(ownerPassword, password,
                    accessPermission);
            policy.setEncryptionKeyLength(128);
            document.protect(policy);

            document.save(outputStream);
        } catch (Exception e) {
            throw e;
        } finally {
            if (document != null) {
                document.close();
            }
        }
    }

    private void createRTF(InputStream inputStream, OutputStream outputStream, DocumentDispatcherProperties props)
            throws Exception {
        com.lowagie.text.Document document = null;

        try {
            document = new com.lowagie.text.Document();
            //TODO verify the character encoding

            RtfWriter2.getInstance(document, outputStream);

            document.open();

            try {
                double width = Double.parseDouble(props.getPageWidth());
                double height = Double.parseDouble(props.getPageHeight());
                Unit unit = props.getPageUnit();

                /*
                 * The version of iText being used only accepts points, so we need to convert to
                 * twips first and then convert to points (1 point = 20 twips).
                 */
                if (unit != Unit.TWIPS) {
                    width = unit.convertTo(width, Unit.TWIPS);
                    height = unit.convertTo(height, Unit.TWIPS);
                    unit = Unit.TWIPS;
                }
                width = Math.max(width, 1);
                height = Math.max(height, 1);
                document.setPageSize(new Rectangle((float) (Math.round(width) / RtfBasicElement.TWIPS_FACTOR),
                        (float) (Math.round(height) / RtfBasicElement.TWIPS_FACTOR)));
            } catch (Exception e) {
            }

            HtmlParser parser = new HtmlParser();
            parser.go(document, inputStream);
        } finally {
            if (document != null) {
                document.close();
            }
        }
    }

    private File createFile(String filename) throws IOException {
        File file = new File(filename);
        if (!file.canWrite()) {
            String dirName = file.getPath();
            int i = dirName.lastIndexOf(File.separator);
            if (i > -1) {
                dirName = dirName.substring(0, i);
                File dir = new File(dirName);
                dir.mkdirs();
            }
            file.createNewFile();
        }
        return file;
    }

}