org.apache.synapse.transport.jms.JMSSender.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.synapse.transport.jms.JMSSender.java

Source

/*
* Copyright 2004,2005 The Apache Software Foundation.
*
* 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 org.apache.synapse.transport.jms;

import org.apache.axiom.om.OMOutputFormat;
import org.apache.axiom.om.OMElement;
import org.apache.axiom.om.OMText;
import org.apache.axiom.om.OMNode;
import org.apache.axiom.om.util.UUIDGenerator;
import org.apache.axis2.AxisFault;
import org.apache.axis2.context.MessageContext;
import org.apache.axis2.context.ConfigurationContext;
import org.apache.axis2.description.TransportOutDescription;
import org.apache.axis2.description.Parameter;
import org.apache.axis2.transport.TransportUtils;
import org.apache.axis2.transport.MessageFormatter;
import org.apache.axis2.transport.OutTransportInfo;
import org.apache.synapse.transport.base.AbstractTransportSender;
import org.apache.synapse.transport.base.BaseUtils;
import org.apache.synapse.transport.base.BaseConstants;
import org.apache.axis2.transport.http.HTTPConstants;
import org.apache.commons.logging.LogFactory;

import javax.jms.*;
import javax.jms.Queue;
import javax.activation.DataHandler;
import javax.naming.Context;
import javax.naming.NamingException;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.*;

/**
 * The TransportSender for JMS
 */
public class JMSSender extends AbstractTransportSender {

    public static final String TRANSPORT_NAME = "jms";

    /** A Map containing the JMS connection factories managed by this, keyed by name */
    private Map connectionFactories = new HashMap();

    public JMSSender() {
        log = LogFactory.getLog(JMSSender.class);
    }

    /**
     * Initialize the transport sender by reading pre-defined connection factories for
     * outgoing messages. These will create sessions (one per each destination dealth with)
     * to be used when messages are being sent.
     * @param cfgCtx the configuration context
     * @param transportOut the transport sender definition from axis2.xml
     * @throws AxisFault on error
     */
    public void init(ConfigurationContext cfgCtx, TransportOutDescription transportOut) throws AxisFault {
        setTransportName(TRANSPORT_NAME);
        super.init(cfgCtx, transportOut);
        // read the connection factory definitions and create them
        loadConnectionFactoryDefinitions(transportOut);
    }

    /**
     * Get corresponding JMS connection factory defined within the transport sender for the
     * transport-out information - usually constructed from a targetEPR
     * 
     * @param trpInfo the transport-out information
     * @return the corresponding JMS connection factory, if any
     */
    private JMSConnectionFactory getJMSConnectionFactory(JMSOutTransportInfo trpInfo) {
        if (trpInfo.getProperties() != null) {
            String jmsConnectionFactoryName = (String) trpInfo.getProperties().get(JMSConstants.CONFAC_PARAM);
            if (jmsConnectionFactoryName != null) {
                return (JMSConnectionFactory) connectionFactories.get(jmsConnectionFactoryName);
            }
        }

        Iterator cfNames = connectionFactories.keySet().iterator();
        while (cfNames.hasNext()) {
            String cfName = (String) cfNames.next();
            JMSConnectionFactory cf = (JMSConnectionFactory) connectionFactories.get(cfName);
            if (cf.equals(trpInfo)) {
                return cf;
            }
        }
        return null;
    }

    /**
     * Performs the actual sending of the JMS message
     */
    public void sendMessage(MessageContext msgCtx, String targetAddress, OutTransportInfo outTransportInfo)
            throws AxisFault {

        JMSConnectionFactory jmsConnectionFactory = null;
        Connection connection = null; // holds a one time connection if used
        JMSOutTransportInfo jmsOut = null;
        Session session = null;
        Destination destination = null;
        Destination replyDestination = null;

        try {
            if (targetAddress != null) {

                jmsOut = new JMSOutTransportInfo(targetAddress);
                // do we have a definition for a connection factory to use for this address?
                jmsConnectionFactory = getJMSConnectionFactory(jmsOut);

                if (jmsConnectionFactory != null) {
                    // create new or get existing session to send to the destination from the CF
                    session = jmsConnectionFactory.getSessionForDestination(JMSUtils.getDestination(targetAddress));

                } else {
                    // digest the targetAddress and locate CF from the EPR
                    jmsOut.loadConnectionFactoryFromProperies();
                    try {
                        // create a one time connection and session to be used
                        Hashtable jndiProps = jmsOut.getProperties();
                        String user = (String) jndiProps.get(Context.SECURITY_PRINCIPAL);
                        String pass = (String) jndiProps.get(Context.SECURITY_CREDENTIALS);

                        QueueConnectionFactory qConFac = null;
                        TopicConnectionFactory tConFac = null;
                        ConnectionFactory conFac = null;

                        if (JMSConstants.DESTINATION_TYPE_QUEUE.equals(jmsOut.getDestinationType())) {
                            qConFac = (QueueConnectionFactory) jmsOut.getConnectionFactory();
                        } else if (JMSConstants.DESTINATION_TYPE_TOPIC.equals(jmsOut.getDestinationType())) {
                            tConFac = (TopicConnectionFactory) jmsOut.getConnectionFactory();
                        } else {
                            handleException(
                                    "Unable to determine type of JMS " + "Connection Factory - i.e Queue/Topic");
                        }

                        if (user != null && pass != null) {
                            if (qConFac != null) {
                                connection = qConFac.createQueueConnection(user, pass);
                            } else if (tConFac != null) {
                                connection = tConFac.createTopicConnection(user, pass);
                            }
                        } else {
                            if (qConFac != null) {
                                connection = qConFac.createQueueConnection();
                            } else if (tConFac != null) {
                                connection = tConFac.createTopicConnection();
                            }
                        }

                        if (JMSConstants.DESTINATION_TYPE_QUEUE.equals(jmsOut.getDestinationType())) {
                            session = ((QueueConnection) connection).createQueueSession(false,
                                    Session.AUTO_ACKNOWLEDGE);
                        } else if (JMSConstants.DESTINATION_TYPE_TOPIC.equals(jmsOut.getDestinationType())) {
                            session = ((TopicConnection) connection).createTopicSession(false,
                                    Session.AUTO_ACKNOWLEDGE);
                        }

                    } catch (JMSException e) {
                        handleException("Error creating a connection/session for : " + targetAddress);
                    }
                }
                destination = jmsOut.getDestination();

            } else if (outTransportInfo != null && outTransportInfo instanceof JMSOutTransportInfo) {

                jmsOut = (JMSOutTransportInfo) outTransportInfo;
                jmsConnectionFactory = jmsOut.getJmsConnectionFactory();

                session = jmsConnectionFactory.getSessionForDestination(jmsOut.getDestination().toString());
                destination = jmsOut.getDestination();
            }

            String replyDestName = (String) msgCtx.getProperty(JMSConstants.JMS_REPLY_TO);
            if (replyDestName != null) {
                if (jmsConnectionFactory != null) {
                    replyDestination = jmsConnectionFactory.getDestination(replyDestName);
                } else {
                    replyDestination = jmsOut.getReplyDestination(replyDestName);
                }
            }

            // now we are going to use the JMS session, but if this was a session from a
            // defined JMS connection factory, we need to synchronize as sessions are not
            // thread safe
            synchronized (session) {

                // convert the axis message context into a JMS Message that we can send over JMS
                Message message = null;
                String correlationId = null;
                try {
                    message = createJMSMessage(msgCtx, session);
                } catch (JMSException e) {
                    handleException("Error creating a JMS message from the axis message context", e);
                }

                String destinationType = jmsOut.getDestinationType();

                // if the destination does not exist, see if we can create it
                destination = JMSUtils.createDestinationIfRequired(destination, destinationType, targetAddress,
                        session);

                // should we wait for a synchronous response on this same thread?
                boolean waitForResponse = waitForSynchronousResponse(msgCtx);

                // if this is a synchronous out-in, prepare to listen on the response destination
                if (waitForResponse) {
                    replyDestination = JMSUtils.setReplyDestination(replyDestination, session, message);
                }

                // send the outgoing message over JMS to the destination selected
                JMSUtils.sendMessageToJMSDestination(session, destination, message);

                // if we are expecting a synchronous response back for the message sent out
                if (waitForResponse) {
                    try {
                        connection.start();
                    } catch (JMSException ignore) {
                    }
                    try {
                        correlationId = message.getJMSMessageID();
                    } catch (JMSException ignore) {
                    }
                    waitForResponseAndProcess(session, replyDestination, msgCtx, correlationId);
                }
            }

        } finally {
            if (connection != null) {
                try {
                    connection.close();
                } catch (JMSException ignore) {
                }
            }
        }
    }

    /**
     * Create a Consumer for the reply destination and wait for the response JMS message
     * synchronously. If a message arrives within the specified time interval, process it
     * through Axis2
     * @param session the session to use to listen for the response
     * @param replyDestination the JMS reply Destination
     * @param msgCtx the outgoing message for which we are expecting the response
     * @throws AxisFault on error
     */
    private void waitForResponseAndProcess(Session session, Destination replyDestination, MessageContext msgCtx,
            String correlationId) throws AxisFault {

        try {
            MessageConsumer consumer = null;
            if (replyDestination instanceof Queue) {
                if (correlationId != null) {
                    consumer = ((QueueSession) session).createReceiver((Queue) replyDestination,
                            "JMSCorrelationID = '" + correlationId + "'");
                } else {
                    consumer = ((QueueSession) session).createReceiver((Queue) replyDestination);
                }
            } else {
                if (correlationId != null) {
                    consumer = ((TopicSession) session).createSubscriber((Topic) replyDestination, correlationId,
                            false);
                } else {
                    consumer = ((TopicSession) session).createSubscriber((Topic) replyDestination);
                }
            }

            // how long are we willing to wait for the sync response
            long timeout = JMSConstants.DEFAULT_JMS_TIMEOUT;
            String waitReply = (String) msgCtx.getProperty(JMSConstants.JMS_WAIT_REPLY);
            if (waitReply != null) {
                timeout = Long.valueOf(waitReply).longValue();
            }

            if (log.isDebugEnabled()) {
                log.debug("Waiting for a maximum of " + timeout + "ms for a response message to destination : "
                        + replyDestination + " with JMS correlation ID : " + correlationId);
            }

            Message reply = consumer.receive(timeout);
            if (reply != null) {
                processSyncResponse(msgCtx, reply);

            } else {
                log.warn("Did not receive a JMS response within " + timeout + " ms to destination : "
                        + replyDestination + " with JMS correlation ID : " + correlationId);
            }

        } catch (JMSException e) {
            handleException("Error creating consumer or receiving reply to : " + replyDestination, e);
        }
    }

    /**
     * Create a JMS Message from the given MessageContext and using the given
     * session
     *
     * @param msgContext the MessageContext
     * @param session    the JMS session
     * @return a JMS message from the context and session
     * @throws JMSException on exception
     * @throws AxisFault on exception
     */
    private Message createJMSMessage(MessageContext msgContext, Session session) throws JMSException, AxisFault {

        Message message = null;
        String msgType = getProperty(msgContext, JMSConstants.JMS_MESSAGE_TYPE);

        // check the first element of the SOAP body, do we have content wrapped using the
        // default wrapper elements for binary (BaseConstants.DEFAULT_BINARY_WRAPPER) or
        // text (BaseConstants.DEFAULT_TEXT_WRAPPER) ? If so, do not create SOAP messages
        // for JMS but just get the payload in its native format
        String jmsPayloadType = guessMessageType(msgContext);

        if (jmsPayloadType == null) {

            OMOutputFormat format = BaseUtils.getOMOutputFormat(msgContext);
            MessageFormatter messageFormatter = null;
            try {
                messageFormatter = TransportUtils.getMessageFormatter(msgContext);
            } catch (AxisFault axisFault) {
                throw new JMSException("Unable to get the message formatter to use");
            }

            String contentType = messageFormatter.getContentType(msgContext, format, msgContext.getSoapAction());

            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            try {
                messageFormatter.writeTo(msgContext, format, baos, true);
                baos.flush();
            } catch (IOException e) {
                handleException("IO Error while creating BytesMessage", e);
            }

            if (msgType != null && JMSConstants.JMS_BYTE_MESSAGE.equals(msgType)
                    || contentType.indexOf(HTTPConstants.HEADER_ACCEPT_MULTIPART_RELATED) > -1) {
                message = session.createBytesMessage();
                BytesMessage bytesMsg = (BytesMessage) message;
                bytesMsg.writeBytes(baos.toByteArray());
            } else {
                message = session.createTextMessage(); // default
                TextMessage txtMsg = (TextMessage) message;
                txtMsg.setText(new String(baos.toByteArray()));
            }
            message.setStringProperty(BaseConstants.CONTENT_TYPE, contentType);

        } else if (JMSConstants.JMS_BYTE_MESSAGE.equals(jmsPayloadType)) {
            message = session.createBytesMessage();
            BytesMessage bytesMsg = (BytesMessage) message;
            OMElement wrapper = msgContext.getEnvelope().getBody()
                    .getFirstChildWithName(BaseConstants.DEFAULT_BINARY_WRAPPER);
            OMNode omNode = wrapper.getFirstOMChild();
            if (omNode != null && omNode instanceof OMText) {
                Object dh = ((OMText) omNode).getDataHandler();
                if (dh != null && dh instanceof DataHandler) {
                    ByteArrayOutputStream baos = new ByteArrayOutputStream();
                    try {
                        ((DataHandler) dh).writeTo(baos);
                    } catch (IOException e) {
                        handleException("Error serializing binary content of element : "
                                + BaseConstants.DEFAULT_BINARY_WRAPPER, e);
                    }
                    bytesMsg.writeBytes(baos.toByteArray());
                }
            }

        } else if (JMSConstants.JMS_TEXT_MESSAGE.equals(jmsPayloadType)) {
            message = session.createTextMessage();
            TextMessage txtMsg = (TextMessage) message;
            txtMsg.setText(msgContext.getEnvelope().getBody()
                    .getFirstChildWithName(BaseConstants.DEFAULT_TEXT_WRAPPER).getText());
        }

        // set the JMS correlation ID if specified
        String correlationId = getProperty(msgContext, JMSConstants.JMS_COORELATION_ID);
        if (correlationId == null && msgContext.getRelatesTo() != null) {
            correlationId = msgContext.getRelatesTo().getValue();
        }

        if (correlationId != null) {
            message.setJMSCorrelationID(correlationId);
        }

        if (msgContext.isServerSide()) {
            // set SOAP Action as a property on the JMS message
            setProperty(message, msgContext, BaseConstants.SOAPACTION);
        } else {
            String action = msgContext.getOptions().getAction();
            if (action != null) {
                message.setStringProperty(BaseConstants.SOAPACTION, action);
            }
        }

        JMSUtils.setTransportHeaders(msgContext, message);
        return message;
    }

    /**
     * Guess the message type to use for JMS looking at the message contexts' envelope
     * @param msgContext the message context
     * @return JMSConstants.JMS_BYTE_MESSAGE or JMSConstants.JMS_TEXT_MESSAGE or null
     */
    private String guessMessageType(MessageContext msgContext) {
        OMElement firstChild = msgContext.getEnvelope().getBody().getFirstElement();
        if (firstChild != null) {
            if (BaseConstants.DEFAULT_BINARY_WRAPPER.equals(firstChild.getQName())) {
                return JMSConstants.JMS_BYTE_MESSAGE;
            } else if (BaseConstants.DEFAULT_TEXT_WRAPPER.equals(firstChild.getQName())) {
                return JMSConstants.JMS_TEXT_MESSAGE;
            }
        }
        return null;
    }

    /**
     * Creates an Axis MessageContext for the received JMS message and
     * sets up the transports and various properties
     *
     * @param outMsgCtx the outgoing message for which we are expecting the response
     * @param message the JMS response message received
     * @throws AxisFault on error
     */
    private void processSyncResponse(MessageContext outMsgCtx, Message message) throws AxisFault {

        MessageContext responseMsgCtx = createResponseMessageContext(outMsgCtx);

        // load any transport headers from received message
        JMSUtils.loadTransportHeaders(message, responseMsgCtx);

        // workaround for Axis2 TransportUtils.createSOAPMessage() issue, where a response
        // of content type "text/xml" is thought to be REST if !MC.isServerSide(). This
        // question is still under debate and due to the timelines, I am commiting this
        // workaround as Axis2 1.2 is about to be released and Synapse 1.0
        responseMsgCtx.setServerSide(false);

        String contentType = JMSUtils.getInstace().getProperty(message, BaseConstants.CONTENT_TYPE);

        JMSUtils.getInstace().setSOAPEnvelope(message, responseMsgCtx, contentType);
        responseMsgCtx.setServerSide(true);

        handleIncomingMessage(responseMsgCtx, JMSUtils.getTransportHeaders(message),
                JMSUtils.getInstace().getProperty(message, BaseConstants.SOAPACTION), contentType);
    }

    private void setProperty(Message message, MessageContext msgCtx, String key) {

        String value = getProperty(msgCtx, key);
        if (value != null) {
            try {
                message.setStringProperty(key, value);
            } catch (JMSException e) {
                log.warn("Couldn't set message property : " + key + " = " + value, e);
            }
        }
    }

    private String getProperty(MessageContext mc, String key) {
        return (String) mc.getProperty(key);
    }

    /**
     * Create JMSConnectionFactory instances for the definitions in the transport sender,
     * and add these into our collection of connectionFactories map keyed by name
     *
     * @param transportOut the transport-in description for JMS
     */
    private void loadConnectionFactoryDefinitions(TransportOutDescription transportOut) {

        // iterate through all defined connection factories
        Iterator conFacIter = transportOut.getParameters().iterator();

        while (conFacIter.hasNext()) {
            Parameter conFacParams = (Parameter) conFacIter.next();

            JMSConnectionFactory jmsConFactory = new JMSConnectionFactory(conFacParams.getName(), cfgCtx);
            JMSUtils.setConnectionFactoryParameters(conFacParams, jmsConFactory);

            try {
                jmsConFactory.connectAndListen();
            } catch (NamingException e) {
                log.warn("Error looking up JMS connection factory : " + jmsConFactory.getName(), e);
            } catch (JMSException e) {
                log.warn("Error connecting to JMS connection factory : " + jmsConFactory.getName(), e);
            }

            connectionFactories.put(jmsConFactory.getName(), jmsConFactory);
        }
    }

}