/*
* 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;
import com.rabbitmq.client.AMQP;
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.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 RabbitMQMessageSender {
private static final Log log = LogFactory.getLog(RabbitMQMessageSender.class);
private RMQChannel rmqChannel = null;
private String targetEPR = null;
private Hashtable<String, String> properties;
private RabbitMQConnectionFactory connectionFactory;
/**
* Create a RabbitMQSender using a ConnectionFactory and target EPR
*
* @param factory the ConnectionFactory
* @param targetEPR the targetAddress
* @param epProperties
*/
public RabbitMQMessageSender(RabbitMQConnectionFactory factory, String targetEPR, Hashtable<String, String> epProperties) {
this.targetEPR = targetEPR;
this.connectionFactory = factory;
try {
rmqChannel = connectionFactory.getRMQChannel();
} catch (InterruptedException e) {
handleException("Error while getting RPC channel", e);
}
if (!targetEPR.startsWith(RabbitMQConstants.RABBITMQ_PREFIX)) {
handleException("Invalid prefix for a AMQP EPR : " + targetEPR);
} else {
this.properties = epProperties;
}
}
public void send(RabbitMQMessage message, MessageContext msgContext) throws
AxisRabbitMQException, IOException {
try {
publish(message, msgContext);
} finally {
//release the rmq channel to the pool
try {
connectionFactory.pushRMQChannel(rmqChannel);
} catch (InterruptedException e) {
handleException(e.getMessage());
}
}
}
/**
* Perform the creation of exchange/queue and the Outputstream
*
* @param message the RabbitMQ AMQP message
* @param msgContext the Axis2 MessageContext
*/
public void publish(RabbitMQMessage message, MessageContext msgContext) throws
AxisRabbitMQException, IOException {
String exchangeName = null;
AMQP.BasicProperties basicProperties = null;
byte[] messageBody = null;
if (rmqChannel.isOpen()) {
String queueName = properties.get(RabbitMQConstants.QUEUE_NAME);
String routeKey = properties
.get(RabbitMQConstants.QUEUE_ROUTING_KEY);
exchangeName = properties.get(RabbitMQConstants.EXCHANGE_NAME);
String exchangeType = properties
.get(RabbitMQConstants.EXCHANGE_TYPE);
String replyTo = properties.get(RabbitMQConstants.REPLY_TO_NAME);
String correlationID = properties.get(RabbitMQConstants.CORRELATION_ID);
String queueAutoDeclareStr = properties.get(RabbitMQConstants.QUEUE_AUTODECLARE);
String exchangeAutoDeclareStr = properties.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(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.debug("No queue name is specified");
}
if (routeKey == null && !"x-consistent-hash".equals(exchangeType)) {
if (queueName == null || queueName.equals("")) {
log.debug("Routing key is not specified");
} else {
log.debug(
"Routing key is not specified. Using queue name as the routing key.");
routeKey = queueName;
}
}
//read publish properties corr id and route key from message context
Object prRouteKey = msgContext.getProperty(RabbitMQConstants.QUEUE_ROUTING_KEY);
Object prCorrId = msgContext.getProperty(RabbitMQConstants.CORRELATION_ID).toString();
if (prRouteKey != null) {
routeKey = prRouteKey.toString();
log.debug("Specifying routing key from axis2 properties");
}
if (prCorrId != null) {
message.setCorrelationId(prCorrId.toString());
log.debug("Specifying correlation id from axis2 properties");
}
//Declaring the queue
if (queueAutoDeclare && queueName != null && !queueName.equals("")) {
RabbitMQUtils.declareQueue(rmqChannel, queueName, properties);
}
//Declaring the exchange
if (exchangeAutoDeclare && exchangeName != null && !exchangeName.equals("")) {
RabbitMQUtils.declareExchange(rmqChannel, exchangeName, properties);
if (queueName != null && !"x-consistent-hash".equals(exchangeType)) {
// Create bind between the queue and exchange with the routeKey
try {
rmqChannel.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);
}
}
}
AMQP.BasicProperties.Builder builder = buildBasicProperties(message);
String deliveryModeString = properties
.get(RabbitMQConstants.QUEUE_DELIVERY_MODE);
int deliveryMode = RabbitMQConstants.DEFAULT_DELIVERY_MODE;
if (deliveryModeString != null) {
deliveryMode = Integer.parseInt(deliveryModeString);
}
builder.deliveryMode(deliveryMode);
if (!StringUtils.isEmpty(replyTo)) {
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 {
/**
* Check for parameter on confirmed delivery in RabbitMq to ensure reliable delivery.
* Can be enabled by setting the parameter "rabbitmq.confirm.delivery" to "true" in broker URL.
*/
boolean isConfirmedDeliveryEnabled = Boolean.parseBoolean(properties.get(RabbitMQConstants
.CONFIRMED_DELIVERY));
if (isConfirmedDeliveryEnabled) {
rmqChannel.getChannel().confirmSelect();
}
if (exchangeName != null && exchangeName != "") {
rmqChannel.getChannel().basicPublish(exchangeName, routeKey, basicProperties,
messageBody);
} else {
rmqChannel.getChannel().basicPublish("", routeKey, basicProperties,
messageBody);
}
if (isConfirmedDeliveryEnabled) {
rmqChannel.getChannel().waitForConfirmsOrDie();
}
} catch (IOException e) {
handleException("Error while publishing the message", e);
} catch (InterruptedException e) {
handleException("InterruptedException while waiting for AMQP message confirm :", e);
}
} else {
handleException("Channel cannot be created");
}
}
/**
* 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);
}
}