/**
* Copyright (c) 2015, 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.synapse.message.store.impl.rabbitmq;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.synapse.MessageContext;
import org.apache.synapse.SynapseException;
import org.apache.synapse.config.SynapseConfiguration;
import org.apache.synapse.core.SynapseEnvironment;
import org.apache.synapse.core.axis2.Axis2MessageContext;
import org.apache.synapse.core.axis2.Axis2SynapseEnvironment;
import org.apache.synapse.message.MessageConsumer;
import org.apache.synapse.message.MessageProducer;
import org.apache.synapse.message.store.AbstractMessageStore;
import org.apache.synapse.message.store.Constants;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManagerFactory;
import java.io.FileInputStream;
import java.io.IOException;
import java.security.KeyStore;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Properties;
import java.util.Set;
import java.util.List;
public class RabbitMQStore extends AbstractMessageStore {
/**
* RabbitMQ Broker username
*/
public static final String USERNAME = "store.rabbitmq.username";
/**
* RabbitMQ Broker password
*/
public static final String PASSWORD = "store.rabbitmq.password";
/**
* RabbitMQ Server Host name
*/
public static final String HOST_NAME = "store.rabbitmq.host.name";
/**
* RabbitMQ Server Port
*/
public static final String HOST_PORT = "store.rabbitmq.host.port";
/**
* RabbitMQ Server default port
*/
public static final int DEFAULT_PORT = 5672;
/**
* RabbitMQ Server Virtual Host
*/
public static final String VIRTUAL_HOST = "store.rabbitmq.virtual.host";
/**
* RabbitMQ queue name that this message store must store the messages to.
*/
public static final String QUEUE_NAME = "store.rabbitmq.queue.name";
/**
* RabbitMQ route key which queue is binded
*/
public static final String ROUTE_KEY = "store.rabbitmq.route.key";
/**
* RabbitMQ exchange.
*/
public static final String EXCHANGE_NAME = "store.rabbitmq.exchange.name";
//SSL related properties
public static final String SSL_ENABLED = "rabbitmq.connection.ssl.enabled";
public static final String SSL_KEYSTORE_LOCATION = "rabbitmq.connection.ssl.keystore.location";
public static final String SSL_KEYSTORE_TYPE = "rabbitmq.connection.ssl.keystore.type";
public static final String SSL_KEYSTORE_PASSWORD = "rabbitmq.connection.ssl.keystore.password";
public static final String SSL_TRUSTSTORE_LOCATION = "rabbitmq.connection.ssl.truststore.location";
public static final String SSL_TRUSTSTORE_TYPE = "rabbitmq.connection.ssl.truststore.type";
public static final String SSL_TRUSTSTORE_PASSWORD = "rabbitmq.connection.ssl.truststore.password";
public static final String SSL_VERSION = "rabbitmq.connection.ssl.version";
/**
* RabbitMQ connection properties
*/
private final Properties properties = new Properties();
/**
* RabbitMQ username
*/
private String userName;
/**
* RabbitMQ password
*/
private String password;
/**
* RabbitMQ queue name
*/
private String queueName;
/**
* RabbitMQ routing key
*/
private String routeKey;
/**
* RabbitMQ exchange name
*/
private String exchangeName;
/**
* RabbitMQ host name
*/
private String hostName;
/**
* RabbitMQ host port
*/
private String hostPort;
/**
* RabbitMQ virtual host
*/
private String virtualHost;
private static final Log logger = LogFactory.getLog(RabbitMQStore.class.getName());
/**
* Rabbitmq Connection factory
*/
private ConnectionFactory connectionFactory;
/**
* RabbitMQ Connection used to send messages to the queue
*/
private Connection producerConnection;
/**
* lock protecting the producer connection
*/
private final Object producerLock = new Object();
/**
* records the last retried time between the broker and ESB
*/
private long retryTime = -1;
public void init(SynapseEnvironment se) {
if (se == null) {
logger.error("Cannot initialize store.");
return;
}
boolean initOk = initme();
super.init(se);
if (initOk) {
logger.info(nameString() + ". Initialized... ");
} else {
logger.info(nameString() + ". Initialization failed...");
return;
}
}
private boolean initme() {
Set<Map.Entry<String, Object>> mapSet = parameters.entrySet();
for (Map.Entry<String, Object> e : mapSet) {
if (e.getValue() instanceof String) {
properties.put(e.getKey(), e.getValue());
}
}
userName = (String) parameters.get(USERNAME);
password = (String) parameters.get(PASSWORD);
hostName = (String) parameters.get(HOST_NAME);
hostPort = (String) parameters.get(HOST_PORT);
virtualHost = (String) parameters.get(VIRTUAL_HOST);
//Possible timeouts that can be added in future if requested, should be added to the
//setConnectionTimeout, ShutdownTimeout, RequestedHeartbeat
connectionFactory = new ConnectionFactory();
if (hostName != null && !hostName.equals("")) {
connectionFactory.setHost(hostName);
} else {
throw new SynapseException(nameString() + " host name is not correctly defined");
}
int port = 0;
try {
port = Integer.parseInt(hostPort);
} catch (NumberFormatException nfe) {
logger.error("Port value for " + nameString() + " is not correctly defined" + nfe);
}
if (port > 0) {
connectionFactory.setPort(port);
} else {
connectionFactory.setPort(DEFAULT_PORT);
logger.info(nameString() + " port is set to default value (5672");
}
if (userName != null && !userName.equals("")) {
connectionFactory.setUsername(userName);
}
if (password != null && !password.equals("")) {
connectionFactory.setPassword(password);
}
if (virtualHost != null && !virtualHost.equals("")) {
connectionFactory.setVirtualHost(virtualHost);
}
String sslEnabledS = parameters.get(SSL_ENABLED) != null ? parameters.get(SSL_ENABLED).toString() : "";
if (!StringUtils.isEmpty(sslEnabledS)) {
try {
boolean sslEnabled = Boolean.parseBoolean(sslEnabledS);
if (sslEnabled) {
String keyStoreLocation = parameters.get(SSL_KEYSTORE_LOCATION) != null ? parameters.get(SSL_KEYSTORE_LOCATION).toString() : "";
String keyStoreType = parameters.get(SSL_KEYSTORE_TYPE) != null ? parameters.get(SSL_KEYSTORE_TYPE).toString() : "";
String keyStorePassword = parameters.get(SSL_KEYSTORE_PASSWORD) != null ? parameters.get(SSL_KEYSTORE_PASSWORD).toString() : "";
String trustStoreLocation = parameters.get(SSL_TRUSTSTORE_LOCATION) != null ? parameters.get(SSL_TRUSTSTORE_LOCATION).toString() : "";
String trustStoreType = parameters.get(SSL_TRUSTSTORE_TYPE) != null ? parameters.get(SSL_TRUSTSTORE_TYPE).toString() : "";
String trustStorePassword = parameters.get(SSL_TRUSTSTORE_PASSWORD) != null ? parameters.get(SSL_TRUSTSTORE_PASSWORD).toString() : "";
String sslVersion = parameters.get(SSL_VERSION) != null ? parameters.get(SSL_VERSION).toString() : "";
if (StringUtils.isEmpty(keyStoreLocation) || StringUtils.isEmpty(keyStoreType) || StringUtils.isEmpty(keyStorePassword) ||
StringUtils.isEmpty(trustStoreLocation) || StringUtils.isEmpty(trustStoreType) || StringUtils.isEmpty(trustStorePassword)) {
logger.warn("Trustore and keystore information is not provided correctly. Proceeding with default SSL configuration");
connectionFactory.useSslProtocol();
} else {
char[] keyPassphrase = keyStorePassword.toCharArray();
KeyStore ks = KeyStore.getInstance(keyStoreType);
ks.load(new FileInputStream(keyStoreLocation), keyPassphrase);
KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
kmf.init(ks, keyPassphrase);
char[] trustPassphrase = trustStorePassword.toCharArray();
KeyStore tks = KeyStore.getInstance(trustStoreType);
tks.load(new FileInputStream(trustStoreLocation), trustPassphrase);
TrustManagerFactory tmf = TrustManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
tmf.init(tks);
SSLContext c = SSLContext.getInstance(sslVersion);
c.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
connectionFactory.useSslProtocol(c);
}
}
} catch (Exception e) {
logger.warn("Format error in SSL enabled value. Proceeding without enabling SSL", e);
}
}
//declaring queue
String queueName = (String) parameters.get(QUEUE_NAME);
if (queueName != null) {
this.queueName = queueName;
} else {
String name = getName();
String defaultQueue;
if (name != null && !name.isEmpty()) {
defaultQueue = name + "_Queue";
} else {
defaultQueue = "RabiitmqStore_" + System.currentTimeMillis() + "_Queue";
}
logger.warn(nameString() + ". Destination not provided. " +
"Setting default destination to [" + defaultQueue + "].");
this.queueName = defaultQueue;
}
exchangeName = (String) properties.get(EXCHANGE_NAME);
routeKey = (String) properties.get(ROUTE_KEY);
if (routeKey == null) {
logger.warn(nameString() + ". Routing key is not provided. " +
"Setting queue name " + this.queueName + " as routing key.");
routeKey = this.queueName;
}
if (!newProducerConnection()) {
logger.warn(nameString() + ". Starting with a faulty connection to the broker.");
return false;
}
try {
setQueue();
} catch (IOException e) {
logger.error(nameString() + " error in storage declaring queue " + queueName);
return false;
}
return true;
}
/**
* Create a Queue to store messages, this will used queueName parameter
*
* @throws IOException : if its enable to create the queue or exchange
*/
private void setQueue() throws IOException {
Channel channel = null;
try {
channel = producerConnection.createChannel();
try {
channel.queueDeclarePassive(queueName);
} catch (java.io.IOException e) {
logger.info("Queue :" + queueName + " not found.Declaring queue.");
if (!channel.isOpen()) {
channel = producerConnection.createChannel();
}
//Hashmap with names and parameters can be used in the place of null argument
//Eg: adding dead letter exchange to the queue
//params: (queueName, durable, exclusive, autoDelete, paramMap)
channel.queueDeclare(queueName, true, false, false, null);
}
//declaring exchange
if (exchangeName != null) {
try {
channel.exchangeDeclarePassive(exchangeName);
} catch (java.io.IOException e) {
logger.info("Exchange :" + exchangeName + " not found. Declaring exchange.");
if (!channel.isOpen()) {
channel = producerConnection.createChannel();
}
//params : ( exchangeName, exchangeType, durable, autoDelete, paramMap )
channel.exchangeDeclare(exchangeName, "direct", true, false, null);
}
channel.queueBind(queueName, exchangeName, routeKey);
}
} finally {
channel.close();
}
}
/**
* Create a new message producer connection, if there is an existing connection close it and
* create a new connection
*
* @return true if a new connection is created successfully
*/
public boolean newProducerConnection() {
synchronized (producerLock) {
if (producerConnection != null) {
//if the exiting producer connection not closed successfully, this will return false
if (!closeConnection(producerConnection)) {
return false;
}
}
try {
producerConnection = connectionFactory.newConnection();
} catch (IOException e) {
logger.error(nameString() + " cannot create connection to the broker." + e);
producerConnection = null;
}
}
return producerConnection != null;
}
public void destroy() {
// do whatever...
if (logger.isDebugEnabled()) {
logger.debug("Destroying " + nameString() + "...");
}
closeProducerConnection();
super.destroy();
}
/**
* Close any given connection
*
* @param connection
* @return true if the connection closed successfully false otherwise
*/
public boolean closeConnection(Connection connection) {
try {
if (connection.isOpen()) {
connection.close();
}
if (logger.isDebugEnabled()) {
logger.debug(nameString() + " closed connection to RabbitMQ broker.");
}
} catch (IOException e) {
return false;
}
return true;
}
/**
* Closes the existing RabbitMQ message producer connection.
*
* @return true if the producer connection was closed without any error, <br/>
* false otherwise.
*/
public boolean closeProducerConnection() {
synchronized (producerLock) {
if (producerConnection != null) {
if (!closeConnection(producerConnection)) {
return false;
}
}
}
return true;
}
/**
* @return new MessageProducer
*/
public MessageProducer getProducer() {
RabbitMQProducer producer = new RabbitMQProducer(this);
producer.setId(nextProducerId());
if (exchangeName != null) {
producer.setQueueName(routeKey);
producer.setExchangeName(exchangeName);
} else {
producer.setQueueName(queueName);
producer.setExchangeName(null);
if (logger.isDebugEnabled()) {
logger.debug(
nameString() + " exchange is not defined, using default exchange and " +
"queue name for routing messages");
}
}
Throwable throwable = null;
boolean error = false;
try {
synchronized (producerLock) {
if (producerConnection == null) {
boolean ok = newProducerConnection();
//if its not possible to create a producer connection, return messagePeoducer
//without a connection
if (!ok) {
return producer;
}
}
if (!producerConnection.isOpen()) {
producerConnection = connectionFactory.newConnection();
}
}
producer.setConnection(producerConnection());
} catch (Throwable t) {
error = true;
throwable = t;
}
if (error) {
String errorMsg = "Could not create a Message Producer for "
+ nameString() + ". Error:" + throwable.getLocalizedMessage();
logger.error(errorMsg, throwable);
synchronized (producerLock) {
cleanup(producerConnection, true);
producerConnection = null;
}
return producer;
}
if (logger.isDebugEnabled()) {
logger.debug(nameString() + " created message producer " + producer.getId());
}
return producer;
}
public MessageConsumer getConsumer() {
RabbitMQConsumer consumer = new RabbitMQConsumer(this);
consumer.setId(nextConsumerId());
consumer.setQueueName(queueName);
Connection connection = null;
try {
// To avoid piling up log files with the error message and throttle retries.
if ((System.currentTimeMillis() - retryTime) >= 3000) {
connection = connectionFactory.newConnection();
retryTime = -1;
}
} catch (IOException e) {
retryTime = System.currentTimeMillis();
if (logger.isDebugEnabled()) {
logger.error("Could not create a Message Consumer for "
+ nameString() + ". Could not create connection.");
}
return consumer;
}
if (connection == null) {
return consumer;
}
consumer.setConnection(connection);
if (logger.isDebugEnabled()) {
logger.debug(nameString() + " created message consumer " + consumer.getId());
}
return consumer;
}
public org.apache.axis2.context.MessageContext newAxis2Mc() {
return ((Axis2SynapseEnvironment) synapseEnvironment)
.getAxis2ConfigurationContext().createMessageContext();
}
public org.apache.synapse.MessageContext newSynapseMc(
org.apache.axis2.context.MessageContext msgCtx) {
SynapseConfiguration configuration = synapseEnvironment.getSynapseConfiguration();
return new Axis2MessageContext(msgCtx, configuration, synapseEnvironment);
}
public Connection producerConnection() {
return producerConnection;
}
public MessageContext remove() throws NoSuchElementException {
return null;
}
public void clear() {
}
public int getType() {
return Constants.RABBIT_MS;
}
public MessageContext remove(String messageID) {
return null;
}
public MessageContext get(int index) {
return null;
}
public List<MessageContext> getAll() {
return null;
}
public MessageContext get(String messageId) {
return null;
}
private String nameString() {
return "Store [" + getName() + "]";
}
/**
* Cleans up the RabbitMQ Connection
*
* @param connection RabbitMQ Connection
* @param error is this method called upon an error
* @return {@code true} if the cleanup is successful. {@code false} otherwise.
*/
public boolean cleanup(Connection connection, boolean error) {
if (connection == null && error) {
return true;
}
try {
if (connection != null && error && connection.isOpen()) {
connection.close();
}
} catch (IOException e) {
return false;
}
return true;
}
}