com.mirth.connect.connectors.ws.WebServiceMessageDispatcher.java Source code

Java tutorial

Introduction

Here is the source code for com.mirth.connect.connectors.ws.WebServiceMessageDispatcher.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.ws;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.StringReader;
import java.io.StringWriter;
import java.io.Writer;
import java.net.ConnectException;
import java.net.URI;
import java.net.URL;
import java.util.List;

import javax.xml.namespace.QName;
import javax.xml.soap.AttachmentPart;
import javax.xml.soap.SOAPMessage;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
import javax.xml.ws.BindingProvider;
import javax.xml.ws.Dispatch;
import javax.xml.ws.Service;
import javax.xml.ws.soap.SOAPBinding;

import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpMethod;
import org.apache.commons.httpclient.HttpStatus;
import org.apache.commons.httpclient.UsernamePasswordCredentials;
import org.apache.commons.httpclient.auth.AuthScope;
import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import org.mule.providers.AbstractMessageDispatcher;
import org.mule.providers.QueueEnabledMessageDispatcher;
import org.mule.providers.TemplateValueReplacer;
import org.mule.umo.UMOEvent;
import org.mule.umo.UMOException;
import org.mule.umo.UMOMessage;
import org.mule.umo.endpoint.UMOEndpointURI;

import com.mirth.connect.model.MessageObject;
import com.mirth.connect.model.QueuedMessage;
import com.mirth.connect.server.Constants;
import com.mirth.connect.server.controllers.AlertController;
import com.mirth.connect.server.controllers.ChannelController;
import com.mirth.connect.server.controllers.ControllerFactory;
import com.mirth.connect.server.controllers.MessageObjectController;
import com.mirth.connect.server.controllers.MonitoringController;
import com.mirth.connect.server.controllers.MonitoringController.ConnectorType;
import com.mirth.connect.server.controllers.MonitoringController.Event;
import com.mirth.connect.server.util.VMRouter;

public class WebServiceMessageDispatcher extends AbstractMessageDispatcher
        implements QueueEnabledMessageDispatcher {
    private Logger logger = Logger.getLogger(this.getClass());
    protected WebServiceConnector connector;
    private MessageObjectController messageObjectController = ControllerFactory.getFactory()
            .createMessageObjectController();
    private MonitoringController monitoringController = ControllerFactory.getFactory().createMonitoringController();
    private AlertController alertController = ControllerFactory.getFactory().createAlertController();
    private ChannelController channelController = ControllerFactory.getFactory().createChannelController();
    private TemplateValueReplacer replacer = new TemplateValueReplacer();
    private ConnectorType connectorType = ConnectorType.WRITER;

    /*
     * Dispatch object used for pooling the soap connection, and the current
     * properties used to create the dispatch object
     */
    private Dispatch<SOAPMessage> dispatch = null;
    private String currentWsdlUrl = null;
    private String currentUsername = null;
    private String currentPassword = null;
    private String currentServiceName = null;
    private String currentPortName = null;

    public WebServiceMessageDispatcher(WebServiceConnector connector) {
        super(connector);
        this.connector = connector;
    }

    public void doDispatch(UMOEvent event) throws Exception {
        monitoringController.updateStatus(connector, connectorType, Event.BUSY);
        MessageObject mo = messageObjectController.getMessageObjectFromEvent(event);

        if (mo == null) {
            return;
        }

        try {
            if (connector.isUsePersistentQueues()) {
                connector.putMessageInQueue(event.getEndpoint().getEndpointURI(), mo);
                return;
            } else {
                processMessage(mo);
            }
        } catch (Exception e) {
            alertController.sendAlerts(connector.getChannelId(), Constants.ERROR_410,
                    "Error connecting to web service.", e);
            messageObjectController.setError(mo, Constants.ERROR_410, "Error connecting to web service.", e, null);
            connector.handleException(new Exception(e));
        } finally {
            monitoringController.updateStatus(connector, connectorType, Event.DONE);
        }
    }

    private void processMessage(MessageObject mo) throws Exception {
        /*
         * Initialize the dispatch object if it hasn't been initialized yet, or
         * create a new one if the connector properties have changed due to
         * variables.
         */
        createDispatch(mo);

        SOAPBinding soapBinding = (SOAPBinding) dispatch.getBinding();

        if (connector.isDispatcherUseAuthentication()) {
            dispatch.getRequestContext().put(BindingProvider.USERNAME_PROPERTY, currentUsername);
            dispatch.getRequestContext().put(BindingProvider.PASSWORD_PROPERTY, currentPassword);
            logger.debug("Using authentication: username=" + currentUsername + ", password length="
                    + currentPassword.length());
        }

        // See: http://www.w3.org/TR/2000/NOTE-SOAP-20000508/#_Toc478383528
        String soapAction = replacer.replaceValues(connector.getDispatcherSoapAction(), mo);

        if (StringUtils.isNotEmpty(soapAction)) {
            dispatch.getRequestContext().put(BindingProvider.SOAPACTION_URI_PROPERTY, soapAction);
        }

        // build the message
        logger.debug("Creating SOAP envelope.");
        String content = replacer.replaceValues(connector.getDispatcherEnvelope(), mo);
        Source source = new StreamSource(new StringReader(content));
        SOAPMessage message = soapBinding.getMessageFactory().createMessage();
        message.getSOAPPart().setContent(source);

        if (connector.isDispatcherUseMtom()) {
            soapBinding.setMTOMEnabled(true);

            List<String> attachmentIds = connector.getDispatcherAttachmentNames();
            List<String> attachmentContents = connector.getDispatcherAttachmentContents();
            List<String> attachmentTypes = connector.getDispatcherAttachmentTypes();

            for (int i = 0; i < attachmentIds.size(); i++) {
                String attachmentContentId = replacer.replaceValues(attachmentIds.get(i), mo);
                String attachmentContentType = replacer.replaceValues(attachmentTypes.get(i), mo);
                String attachmentContent = replacer.replaceValues(attachmentContents.get(i), mo);

                AttachmentPart attachment = message.createAttachmentPart();
                attachment.setBase64Content(new ByteArrayInputStream(attachmentContent.getBytes("UTF-8")),
                        attachmentContentType);
                attachment.setContentId(attachmentContentId);
                message.addAttachmentPart(attachment);
            }
        }

        message.saveChanges();

        // make the call
        String response = null;
        if (connector.isDispatcherOneWay()) {
            logger.debug("Invoking one way service...");
            dispatch.invokeOneWay(message);
            response = "Invoked one way operation successfully.";
        } else {
            logger.debug("Invoking web service...");
            SOAPMessage result = dispatch.invoke(message);
            response = sourceToXmlString(result.getSOAPPart().getContent());
        }
        logger.debug("Finished invoking web service, got result.");

        // process the result
        messageObjectController.setSuccess(mo, response, null);

        // send to reply channel
        if (connector.getDispatcherReplyChannelId() != null
                && !connector.getDispatcherReplyChannelId().equals("sink")) {
            new VMRouter().routeMessageByChannelId(connector.getDispatcherReplyChannelId(), response, true);
        }
    }

    private String sourceToXmlString(Source source) throws TransformerConfigurationException, TransformerException {
        Transformer transformer = TransformerFactory.newInstance().newTransformer();
        transformer.setOutputProperty(OutputKeys.METHOD, "xml");
        transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
        Writer writer = new StringWriter();
        transformer.transform(source, new StreamResult(writer));
        return writer.toString();
    }

    public void doDispose() {

    }

    public UMOMessage doSend(UMOEvent event) throws Exception {
        doDispatch(event);
        return event.getMessage();
    }

    public Object getDelegateSession() throws UMOException {
        return null;
    }

    public UMOMessage receive(UMOEndpointURI endpointUri, long timeout) throws Exception {
        return null;
    }

    public boolean sendPayload(QueuedMessage thePayload) throws Exception {
        monitoringController.updateStatus(connector, connectorType, Event.BUSY);
        try {
            processMessage(thePayload.getMessageObject());
        } catch (Exception e) {
            /*
             * If there's an exception getting the dispatch WSDL from the
             * connector, ConnectException will be the root exception class. If
             * the dispatch has already been retrieved and then the destination
             * goes down, ConnectException will be inside of a
             * ClientTransportException.
             */
            if ((e.getClass() == ConnectException.class)
                    || ((e.getCause() != null) && (e.getCause().getClass() == ConnectException.class))) {
                logger.warn("Can't connect to the queued endpoint: "
                        + channelController.getDeployedChannelById(connector.getChannelId()).getName() + " - "
                        + channelController.getDeployedDestinationName(connector.getName()) + " \r\n'"
                        + e.getMessage());
                messageObjectController.setError(thePayload.getMessageObject(), Constants.ERROR_410,
                        "Connection refused", e, null);
                throw e;
            }
            messageObjectController.setError(thePayload.getMessageObject(), Constants.ERROR_410,
                    "Error connecting to web service.", e, null);
            alertController.sendAlerts(connector.getChannelId(), Constants.ERROR_410,
                    "Error connecting to web service.", e);
            connector.handleException(new Exception(e));
        } finally {
            monitoringController.updateStatus(connector, connectorType, Event.DONE);
        }

        return true;
    }

    private void createDispatch(MessageObject mo) throws Exception {
        String wsdlUrl = replacer.replaceValues(connector.getDispatcherWsdlUrl(), mo);
        String username = replacer.replaceValues(connector.getDispatcherUsername(), mo);
        String password = replacer.replaceValues(connector.getDispatcherPassword(), mo);
        String serviceName = replacer.replaceValues(connector.getDispatcherService(), mo);
        String portName = replacer.replaceValues(connector.getDispatcherPort(), mo);

        /*
         * The dispatch needs to be created if it hasn't been created yet
         * (null). It needs to be recreated if any of the above variables are
         * different than what were used to create the current dispatch object.
         * This could happen if variables are being used for these properties.
         */
        if (dispatch == null || !StringUtils.equals(wsdlUrl, currentWsdlUrl)
                || !StringUtils.equals(username, currentUsername) || !StringUtils.equals(password, currentPassword)
                || !StringUtils.equals(serviceName, currentServiceName)
                || !StringUtils.equals(portName, currentPortName)) {
            currentWsdlUrl = wsdlUrl;
            currentUsername = username;
            currentPassword = password;
            currentServiceName = serviceName;
            currentPortName = portName;

            URL endpointUrl = getWsdlUrl(wsdlUrl, username, password);
            QName serviceQName = QName.valueOf(serviceName);
            QName portQName = QName.valueOf(portName);

            // create the service and dispatch
            logger.debug("Creating web service: url=" + endpointUrl.toString() + ", service=" + serviceQName
                    + ", port=" + portQName);
            Service service = Service.create(endpointUrl, serviceQName);

            dispatch = service.createDispatch(portQName, SOAPMessage.class, Service.Mode.MESSAGE);
        }
    }

    /**
     * Returns the URL for the passed in String. If the URL requires
     * authentication, then the WSDL is saved as a temp file and the URL for
     * that file is returned.
     * 
     * @param wsdlUrl
     * @param username
     * @param password
     * @return
     * @throws Exception
     */
    private URL getWsdlUrl(String wsdlUrl, String username, String password) throws Exception {
        URI uri = new URI(wsdlUrl);

        // If the URL points to file, just return it
        if (!uri.getScheme().equalsIgnoreCase("file")) {
            HttpClient client = new HttpClient();
            HttpMethod method = new GetMethod(wsdlUrl);

            int status = client.executeMethod(method);
            if ((status == HttpStatus.SC_UNAUTHORIZED) && (username != null) && (password != null)) {
                client.getState().setCredentials(AuthScope.ANY,
                        new UsernamePasswordCredentials(username, password));
                status = client.executeMethod(method);

                if (status == HttpStatus.SC_OK) {
                    String wsdl = method.getResponseBodyAsString();
                    File tempFile = File.createTempFile("WebServiceSender", ".wsdl");
                    tempFile.deleteOnExit();

                    FileUtils.writeStringToFile(tempFile, wsdl);

                    return tempFile.toURI().toURL();
                }
            }
        }

        return uri.toURL();
    }
}