org.apache.axis2.transport.rabbitmq.rpc.RabbitMQRPCMessageSender.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.axis2.transport.rabbitmq.rpc.RabbitMQRPCMessageSender.java

Source

/*
 * Copyright (c) 2005-2014, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
 *
 * WSO2 Inc. licenses this file to you 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.axis2.transport.rabbitmq.rpc;

import com.rabbitmq.client.AMQP;
import com.rabbitmq.client.ConsumerCancelledException;
import com.rabbitmq.client.QueueingConsumer;
import com.rabbitmq.client.ShutdownSignalException;
import org.apache.axiom.om.OMOutputFormat;
import org.apache.axis2.AxisFault;
import org.apache.axis2.context.MessageContext;
import org.apache.axis2.transport.MessageFormatter;
import org.apache.axis2.transport.base.BaseUtils;
import org.apache.axis2.transport.rabbitmq.RabbitMQConnectionFactory;
import org.apache.axis2.transport.rabbitmq.RabbitMQMessage;
import org.apache.axis2.transport.rabbitmq.utils.AxisRabbitMQException;
import org.apache.axis2.transport.rabbitmq.utils.RabbitMQConstants;
import org.apache.axis2.transport.rabbitmq.utils.RabbitMQUtils;
import org.apache.axis2.util.MessageProcessorSelector;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.charset.UnsupportedCharsetException;
import java.util.Hashtable;
import java.util.Map;
import java.util.UUID;

/**
 * Class that performs the actual sending of a RabbitMQ AMQP message,
 */

public class RabbitMQRPCMessageSender {
    private static final Log log = LogFactory.getLog(RabbitMQRPCMessageSender.class);

    private DualChannel dualChannel = null;
    private String targetEPR = null;
    private Hashtable<String, String> epProperties;
    private RabbitMQConnectionFactory connectionFactory;

    /**
     * Create a RabbitMQSender using a ConnectionFactory and target EPR
     *
     * @param connectionFactory the RabbitMQ connection factory
     * @param targetEPR         the targetAddress
     * @param epProperties
     */
    //TODO : cache connection factories with targetEPR string. should include queue autodeclare properties etc..
    public RabbitMQRPCMessageSender(RabbitMQConnectionFactory connectionFactory, String targetEPR,
            Hashtable<String, String> epProperties) {

        this.targetEPR = targetEPR;
        this.connectionFactory = connectionFactory;

        try {
            dualChannel = connectionFactory.getRPCChannel();
        } catch (InterruptedException e) {
            handleException("Error while getting RPC channel", e);
        }

        if (!this.targetEPR.startsWith(RabbitMQConstants.RABBITMQ_PREFIX)) {
            handleException("Invalid prefix for a AMQP EPR : " + targetEPR);
        } else {
            this.epProperties = epProperties;
        }
    }

    public RabbitMQMessage send(RabbitMQMessage message, MessageContext msgContext)
            throws AxisRabbitMQException, IOException {

        publish(message, msgContext);
        RabbitMQMessage responseMessage = processResponse(message.getCorrelationId());

        //release the dual channel to the pool
        try {
            connectionFactory.pushRPCChannel(dualChannel);
        } catch (InterruptedException e) {
            handleException(e.getMessage());
        }

        return responseMessage;
    }

    /**
     * Perform the creation of exchange/queue and the Outputstream
     *
     * @param message    the RabbitMQ AMQP message
     * @param msgContext the Axis2 MessageContext
     */
    private void publish(RabbitMQMessage message, MessageContext msgContext)
            throws AxisRabbitMQException, IOException {

        String exchangeName = null;
        AMQP.BasicProperties basicProperties = null;
        byte[] messageBody = null;

        if (dualChannel.isOpen()) {
            String queueName = epProperties.get(RabbitMQConstants.QUEUE_NAME);
            String routeKey = epProperties.get(RabbitMQConstants.QUEUE_ROUTING_KEY);
            exchangeName = epProperties.get(RabbitMQConstants.EXCHANGE_NAME);
            String exchangeType = epProperties.get(RabbitMQConstants.EXCHANGE_TYPE);
            String correlationID = epProperties.get(RabbitMQConstants.CORRELATION_ID);
            String replyTo = dualChannel.getReplyToQueue();

            String queueAutoDeclareStr = epProperties.get(RabbitMQConstants.QUEUE_AUTODECLARE);
            String exchangeAutoDeclareStr = epProperties.get(RabbitMQConstants.EXCHANGE_AUTODECLARE);
            boolean queueAutoDeclare = true;
            boolean exchangeAutoDeclare = true;

            if (!StringUtils.isEmpty(queueAutoDeclareStr)) {
                queueAutoDeclare = Boolean.parseBoolean(queueAutoDeclareStr);
            }

            if (!StringUtils.isEmpty(exchangeAutoDeclareStr)) {
                exchangeAutoDeclare = Boolean.parseBoolean(exchangeAutoDeclareStr);
            }

            message.setReplyTo(replyTo);

            if ((!StringUtils.isEmpty(replyTo)) && (StringUtils.isEmpty(correlationID))) {
                //if reply-to is enabled a correlationID must be available. If not specified, use messageID
                correlationID = message.getMessageId();
            }

            if (!StringUtils.isEmpty(correlationID)) {
                message.setCorrelationId(correlationID);
            }

            if (queueName == null || queueName.equals("")) {
                log.info("No queue name is specified");
            }

            if (routeKey == null && !"x-consistent-hash".equals(exchangeType)) {
                if (queueName == null || queueName.equals("")) {
                    log.info("Routing key is not specified");
                } else {
                    log.info("Routing key is not specified. Using queue name as the routing key.");
                    routeKey = queueName;
                }
            }

            //Declaring the queue
            if (queueAutoDeclare && queueName != null && !queueName.equals("")) {
                //get channel with dualChannel.getChannel() since it will create a new channel if channel is closed
                RabbitMQUtils.declareQueue(dualChannel, queueName, epProperties);
            }

            //Declaring the exchange
            if (exchangeAutoDeclare && exchangeName != null && !exchangeName.equals("")) {
                RabbitMQUtils.declareExchange(dualChannel, exchangeName, epProperties);

                if (queueName != null && !"x-consistent-hash".equals(exchangeType)) {
                    // Create bind between the queue and exchange with the routeKey
                    try {
                        dualChannel.getChannel().queueBind(queueName, exchangeName, routeKey);
                    } catch (IOException e) {
                        handleException("Error occurred while creating the bind between the queue: " + queueName
                                + " & exchange: " + exchangeName + " with route-key " + routeKey, e);
                    }
                }
            }

            //build basic properties from message
            AMQP.BasicProperties.Builder builder = buildBasicProperties(message);

            String deliveryModeString = epProperties.get(RabbitMQConstants.QUEUE_DELIVERY_MODE);
            int deliveryMode = RabbitMQConstants.DEFAULT_DELIVERY_MODE;
            if (deliveryModeString != null) {
                deliveryMode = Integer.parseInt(deliveryModeString);
            }

            //TODO : override properties from message with ones from transport properties
            //set builder properties from transport properties (overrides current properties)
            builder.deliveryMode(deliveryMode);
            builder.replyTo(replyTo);

            basicProperties = builder.build();
            OMOutputFormat format = BaseUtils.getOMOutputFormat(msgContext);
            MessageFormatter messageFormatter = null;
            ByteArrayOutputStream out = new ByteArrayOutputStream();
            try {
                messageFormatter = MessageProcessorSelector.getMessageFormatter(msgContext);
            } catch (AxisFault axisFault) {
                throw new AxisRabbitMQException("Unable to get the message formatter to use", axisFault);
            }

            //server plugging should be enabled before using x-consistent hashing
            //for x-consistent-hashing only exchangeName, exchangeType and routingKey should be
            // given. Queue/exchange creation, bindings should be done at the broker
            try {
                // generate random value as routeKey if the exchangeType is
                // x-consistent-hash type
                if (exchangeType != null && exchangeType.equals("x-consistent-hash")) {
                    routeKey = UUID.randomUUID().toString();
                }

            } catch (UnsupportedCharsetException ex) {
                handleException("Unsupported encoding " + format.getCharSetEncoding(), ex);
            }
            try {
                messageFormatter.writeTo(msgContext, format, out, false);
                messageBody = out.toByteArray();
            } catch (IOException e) {
                handleException("IO Error while creating BytesMessage", e);
            } finally {
                if (out != null) {
                    out.close();
                }
            }

            try {
                if (exchangeName != null && exchangeName != "") {
                    if (log.isDebugEnabled()) {
                        log.debug("Publishing message to exchange " + exchangeName + " with route key " + routeKey);
                    }
                    dualChannel.getChannel().basicPublish(exchangeName, routeKey, basicProperties, messageBody);
                } else {
                    if (log.isDebugEnabled()) {
                        log.debug("Publishing message with route key " + routeKey);
                    }
                    dualChannel.getChannel().basicPublish("", routeKey, basicProperties, messageBody);
                }
            } catch (IOException e) {
                handleException("Error while publishing the message", e);
            }
        } else {
            handleException("Channel cannot be created");
        }
    }

    private RabbitMQMessage processResponse(String correlationID) throws IOException {

        QueueingConsumer consumer = dualChannel.getConsumer();
        QueueingConsumer.Delivery delivery = null;
        RabbitMQMessage message = new RabbitMQMessage();
        String replyToQueue = dualChannel.getReplyToQueue();

        String queueAutoDeclareStr = epProperties.get(RabbitMQConstants.QUEUE_AUTODECLARE);
        boolean queueAutoDeclare = true;

        if (!StringUtils.isEmpty(queueAutoDeclareStr)) {
            queueAutoDeclare = Boolean.parseBoolean(queueAutoDeclareStr);
        }

        if (queueAutoDeclare && !RabbitMQUtils.isQueueAvailable(dualChannel.getChannel(), replyToQueue)) {
            log.info("Reply-to queue : " + replyToQueue + " not available, hence creating a new one");
            RabbitMQUtils.declareQueue(dualChannel, replyToQueue, epProperties);
        }

        int timeout = RabbitMQConstants.DEFAULT_REPLY_TO_TIMEOUT;
        String timeoutStr = epProperties.get(RabbitMQConstants.REPLY_TO_TIMEOUT);
        if (!StringUtils.isEmpty(timeoutStr)) {
            try {
                timeout = Integer.parseInt(timeoutStr);
            } catch (NumberFormatException e) {
                log.warn(
                        "Number format error in reading replyto timeout value. Proceeding with default value (30000ms)",
                        e);
            }
        }

        try {
            if (log.isDebugEnabled()) {
                log.debug(
                        "Waiting for delivery from reply to queue " + replyToQueue + " corr id : " + correlationID);
            }
            delivery = consumer.nextDelivery(timeout);
            if (delivery != null) {
                if (!StringUtils.isEmpty(delivery.getProperties().getCorrelationId())) {
                    if (delivery.getProperties().getCorrelationId().equals(correlationID)) {
                        if (log.isDebugEnabled()) {
                            log.debug("Found matching response with correlation ID : " + correlationID + ".");
                        }
                    } else {
                        log.error("Response not queued in " + replyToQueue + " for correlation ID : "
                                + correlationID);
                        return null;
                    }
                }
            } else {
                log.error("Response not queued in " + replyToQueue);
            }
        } catch (ShutdownSignalException e) {
            log.error("Error receiving message from RabbitMQ broker " + e.getLocalizedMessage());
        } catch (InterruptedException e) {
            log.error("Error receiving message from RabbitMQ broker " + e.getLocalizedMessage());
        } catch (ConsumerCancelledException e) {
            log.error("Error receiving message from RabbitMQ broker" + e.getLocalizedMessage());
        }

        if (delivery != null) {
            log.debug("Processing response from reply-to queue");
            AMQP.BasicProperties properties = delivery.getProperties();
            Map<String, Object> headers = properties.getHeaders();
            message.setBody(delivery.getBody());
            message.setDeliveryTag(delivery.getEnvelope().getDeliveryTag());
            message.setReplyTo(properties.getReplyTo());
            message.setMessageId(properties.getMessageId());

            //get content type from message
            String contentType = properties.getContentType();
            if (contentType == null) {
                //if not get content type from transport parameter
                contentType = epProperties.get(RabbitMQConstants.REPLY_TO_CONTENT_TYPE);
                if (contentType == null) {
                    //if none is given, set to default content type
                    log.warn("Setting default content type " + RabbitMQConstants.DEFAULT_CONTENT_TYPE);
                    contentType = RabbitMQConstants.DEFAULT_CONTENT_TYPE;
                }
            }
            message.setContentType(contentType);
            message.setContentEncoding(properties.getContentEncoding());
            message.setCorrelationId(properties.getCorrelationId());

            if (headers != null) {
                message.setHeaders(headers);
                if (headers.get(RabbitMQConstants.SOAP_ACTION) != null) {
                    message.setSoapAction(headers.get(RabbitMQConstants.SOAP_ACTION).toString());
                }
            }
        }
        return message;
    }

    /**
     * Build and populate the AMQP.BasicProperties using the RabbitMQMessage
     *
     * @param message the RabbitMQMessage to be used to get the properties
     * @return AMQP.BasicProperties object
     */
    private AMQP.BasicProperties.Builder buildBasicProperties(RabbitMQMessage message) {
        AMQP.BasicProperties.Builder builder = new AMQP.BasicProperties().builder();
        builder.messageId(message.getMessageId());
        builder.contentType(message.getContentType());
        builder.replyTo(message.getReplyTo());
        builder.correlationId(message.getCorrelationId());
        builder.contentEncoding(message.getContentEncoding());
        Map<String, Object> headers = message.getHeaders();
        headers.put(RabbitMQConstants.SOAP_ACTION, message.getSoapAction());
        builder.headers(headers);
        return builder;
    }

    private void handleException(String s) {
        log.error(s);
        throw new AxisRabbitMQException(s);
    }

    private void handleException(String message, Exception e) {
        log.error(message, e);
        throw new AxisRabbitMQException(message, e);
    }

}