/**
* Copyright (c) 2005-2010, 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.jms;
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.jms.Connection;
import javax.jms.Destination;
import javax.jms.JMSException;
import javax.jms.Queue;
import javax.jms.QueueConnection;
import javax.jms.QueueConnectionFactory;
import javax.jms.QueueSession;
import javax.jms.Session;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Properties;
import java.util.Set;
public class JmsStore extends AbstractMessageStore {
/** JMS Broker username */
public static final String USERNAME = "store.jms.username";
/** JMS Broker password */
public static final String PASSWORD = "store.jms.password";
/** Whether to cache the connection or not */
public static final String CACHE = "store.jms.cache.connection";
/** JMS destination (ie. Queue) name that this message store must store the messages to. */
public static final String DESTINATION = "store.jms.destination";
/** JMS Specification version */
public static final String JMS_VERSION = "store.jms.JMSSpecVersion";
/** */
public static final String CONSUMER_TIMEOUT = "store.jms.ConsumerReceiveTimeOut";
/** */
public static final String CONN_FACTORY = "store.jms.connection.factory";
/** */
public static final String NAMING_FACTORY_INITIAL = "java.naming.factory.initial";
/** */
public static final String CONNECTION_STRING = "connectionfactory.QueueConnectionFactory";
/** */
public static final String PROVIDER_URL = "java.naming.provider.url";
/** JNDI Queue Prefix */
public static final String QUEUE_PREFIX = "queue.";
/** Guaranteed delivery status*/
public static final String GUARANTEED_DELIVERY_ENABLE = "store.producer.guaranteed.delivery.enable";
/** JMS connection properties */
private final Properties connectionProperties = new Properties();
/** JMS username */
private String userName;
/** JMS password */
private String password;
/** JMS queue name */
private String destination;
/** type of the JMS destination. we support queue */
private String destinationType = "queue";
/** */
private static final Log logger = LogFactory.getLog(JmsStore.class.getName());
/** */
private int cacheLevel = 0;
/** */
public static final String JMS_SPEC_11 = "1.1";
/** Is JMS Version 1.1? */
private boolean isVersion11 = true;
/** Look up context */
private Context context;
/** JMS cachedConnection factory */
private javax.jms.ConnectionFactory connectionFactory;
/** JMS destination */
private Destination queue;
/** */
private final Object queueLock = new Object();
/** JMS 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;
/** Guaranteed delivery enable or disable flag */
private boolean isGuaranteedDeliveryEnable = false;
/** Preserve session for caching */
private MessageProducer cachedProducer;
public MessageProducer getProducer() {
if (cacheLevel == 1 && cachedProducer != null) {
return cachedProducer;
}
JmsProducer producer = new JmsProducer(this);
producer.setId(nextProducerId());
Throwable throwable = null;
Session session = null;
javax.jms.MessageProducer messageProducer;
boolean error = false;
try {
synchronized (producerLock) {
if (producerConnection == null) {
boolean ok = newWriteConnection();
if (!ok) {
return producer;
}
}
}
try {
session = newSession(producerConnection(), Session.AUTO_ACKNOWLEDGE, true);
} catch (JMSException e) {
synchronized (producerLock) {
boolean ok = newWriteConnection();
if (!ok) {
return producer;
}
}
session = newSession(producerConnection(), Session.AUTO_ACKNOWLEDGE, true);
logger.info(nameString() + " established a connection to the broker.");
}
messageProducer = newProducer(session);
producer.setConnection(producerConnection())
.setSession(session)
.setProducer(messageProducer);
} 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, session, true);
producerConnection = null;
}
return producer;
}
if (logger.isDebugEnabled()) {
logger.debug(nameString() + " created message producer " + producer.getId());
}
if (cacheLevel == 1) {
cachedProducer = producer;
}
return producer;
}
public MessageConsumer getConsumer() {
JmsConsumer consumer = new JmsConsumer(this);
consumer.setId(nextConsumerId());
Connection connection = null;
try {
// Had to add a condition to avoid piling up log files with the error message and throttle retries.
// need to improve this to allow the client to configure it.
if ((System.currentTimeMillis() - retryTime) >= 3000) {
connection = newConnection();
retryTime = -1;
}
} catch (JMSException 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;
}
Session session;
try {
session = newSession(connection, Session.CLIENT_ACKNOWLEDGE, false);
} catch (JMSException e) {
if (logger.isDebugEnabled()) {
logger.error("Could not create a Message Consumer for "
+ nameString() + ". Could not create session.");
}
return consumer;
}
if (session == null) {
return consumer;
}
javax.jms.MessageConsumer c;
try {
c = newConsumer(session);
} catch (JMSException e) {
if (logger.isDebugEnabled()) {
logger.error("Could not create a Message Consumer for "
+ nameString() + ". Could not create consumer.");
}
return consumer;
}
consumer.setConnection(connection)
.setSession(session)
.setConsumer(c);
if (logger.isDebugEnabled()) {
logger.debug(nameString() + " created message consumer " + consumer.getId());
}
return consumer;
}
public int getType() {
return Constants.JMS_MS;
}
/** JMS Message store does not support following operations. */
public MessageContext remove() throws NoSuchElementException {
return null;
}
public void clear() {
}
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;
} /** End of unsupported operations. */
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...");
}
}
public void destroy() {
// do whatever...
if (logger.isDebugEnabled()) {
logger.debug("Destroying " + nameString() + "...");
}
closeWriteConnection();
super.destroy();
}
/**
* Creates a new JMS Connection.
*
* @return A connection to the JMS Queue used as the store of this message store.
* @throws JMSException
*/
public Connection newConnection() throws JMSException {
Connection connection;
if (connectionFactory == null) {
logger.error(nameString() + ". Could not create a new connection to the broker." +
" Initial Context Factory:[" + parameters.get(NAMING_FACTORY_INITIAL) + "]; Provider URL:[" + parameters.get(PROVIDER_URL) + "]; Connection Factory:[null].");
return null;
}
if (isVersion11) {
if (userName != null && password != null) {
connection = connectionFactory.createConnection(userName, password);
} else {
connection = connectionFactory.createConnection();
}
} else {
QueueConnectionFactory connectionFactory;
connectionFactory = (QueueConnectionFactory) this.connectionFactory;
if (userName != null && password != null) {
connection = connectionFactory.createQueueConnection(userName, password);
} else {
connection = connectionFactory.createQueueConnection();
}
}
connection.start();
if (logger.isDebugEnabled()) {
logger.debug(nameString() + ". Created JMS Connection.");
}
return connection;
}
/**
* Creates a new JMS Session.
*
* @param connection The JMS Connection that must be used when creating the session.
* @param mode Acknowledgement mode that must be used for this session.
* @param isProducerSession Type of the session going to create
* @return A JMS Session.
* @throws JMSException
*/
public Session newSession(Connection connection, int mode, boolean isProducerSession) throws JMSException {
if (connection == null) {
logger.error(nameString() + " cannot create JMS Session. Invalid connection.");
return null;
}
Session session;
if (isVersion11) {
if (isGuaranteedDeliveryEnable && isProducerSession) {
session = connection.createSession(true, Session.SESSION_TRANSACTED);
} else {
session = connection.createSession(false, mode);
}
} else {
if (isGuaranteedDeliveryEnable && isProducerSession) {
session = ((QueueConnection) connection).createQueueSession(true, Session.SESSION_TRANSACTED);
} else {
session = ((QueueConnection) connection).createQueueSession(false, mode);
}
}
if (logger.isDebugEnabled()) {
logger.debug(nameString() + ". Created JMS Session.");
}
return session;
}
/**
* Creates a new JMS Message Producer.
*
* @param session A JMS Session.
* @return A JMS Message Producer.
* @throws JMSException
*/
public javax.jms.MessageProducer newProducer(Session session) throws JMSException {
if (session == null) {
logger.error(nameString() + " cannot create JMS Producer. Invalid session.");
return null;
}
if (!createDestIfAbsent(session)) {
logger.error(nameString() + " cannot create JMS Producer. " +
"Destination queue is invalid.");
return null;
}
javax.jms.MessageProducer producer;
if (isVersion11) {
producer = session.createProducer(queue);
} else {
producer = ((QueueSession) session).createSender((javax.jms.Queue) queue);
}
if (logger.isDebugEnabled()) {
logger.debug(nameString() + " created JMS Message Producer to destination ["
+ queue.toString() + "].");
}
return producer;
}
/**
* Returns a new JMS Message Consumer.
* @param session A JMS Session
* @return A JMS Message Consumer
* @throws JMSException
*/
public javax.jms.MessageConsumer newConsumer(Session session) throws JMSException {
if (session == null) {
logger.error(nameString() + " cannot create JMS Consumer. Invalid session.");
return null;
}
if (!createDestIfAbsent(session)) {
logger.error(nameString() + " cannot create JMS Consumer. " +
"Destination queue is invalid.");
return null;
}
javax.jms.MessageConsumer consumer;
if(isVersion11) {
consumer = session.createConsumer(queue);
} else {
consumer = ((QueueSession) session).createReceiver((Queue) queue);
}
if (logger.isDebugEnabled()) {
logger.debug(nameString() + " created JMS Message Consumer to destination ["
+ queue.toString() + "].");
}
return consumer;
}
/**
* Creates a new JMS Message producer connection.
*
* @return true if new producer connection was successfully created, <br/>
* false otherwise.
*/
public boolean newWriteConnection() {
synchronized (producerLock) {
if (producerConnection != null) {
if (!closeConnection(producerConnection)) {
return false;
}
}
try {
producerConnection = newConnection();
} catch (JMSException e) {
logger.error(nameString() + " cannot create connection to the broker. Error:"
+ e.getLocalizedMessage()
+ ". Initial Context Factory:[" + parameters.get(NAMING_FACTORY_INITIAL) + "]; Provider URL:[" + parameters.get(PROVIDER_URL) + "]; Connection Factory:[" + connectionFactory + "].");
producerConnection = null;
}
}
return producerConnection != null;
}
/**
* Closes the existing JMS message producer connection.
*
* @return true if the producer connection was closed without any error, <br/>
* false otherwise.
*/
public boolean closeWriteConnection() {
synchronized (producerLock) {
if (producerConnection != null) {
if (!closeConnection(producerConnection)) {
return false;
}
}
}
return true;
}
/**
* Returns the existing JMS message producer connection.
*
* @return The current JMS Connection used to create message producers.
*/
public Connection producerConnection() {
return producerConnection;
}
/**
* Closes the given JMS Connection.
*
* @param connection The JMS Connection to be closed.
* @return true if the connection was successfully closed. false otherwise.
*/
public boolean closeConnection(Connection connection) {
try {
connection.close();
if (logger.isDebugEnabled()) {
logger.debug(nameString() + " closed connection to JMS broker.");
}
} catch (JMSException e) {
return false;
}
return true;
}
/**
* Resets the JMS session for next message
*
* @param connection JMS Connection
* @param session JMS Session associated with the given connection
* @param error Is this method called upon an error
* @return {@code true} if the reset is successful. {@code false} otherwise.
*/
public boolean reset(Connection connection, Session session, boolean error) {
if (cacheLevel == 1 && !error) {
return false;
} else {
return cleanup(connection, session, error);
}
}
/**
* Cleans up the JMS Connection and Session associated with a JMS client.
*
* @param connection JMS Connection
* @param session JMS Session associated with the given 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, Session session, boolean error) {
cachedProducer = null;
if (connection == null && error) {
return true;
}
try {
if (session != null) {
session.close();
}
} catch (JMSException e) {
return false;
}
try {
if (connection != null && error) {
connection.close();
}
} catch (JMSException e) {
return false;
}
return true;
}
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 void setParameters(Map<String, Object> parameters) {
if (parameters == null || parameters.isEmpty()) {
throw new SynapseException("Cannot initialize JMS Store [" + getName() +
"]. Required parameters are not available.");
}
super.setParameters(parameters);
}
public void setCachedProducer(MessageProducer cachedProducer) {
this.cachedProducer = cachedProducer;
}
private boolean initme() {
Set<Map.Entry<String, Object>> mapSet = parameters.entrySet();
for (Map.Entry<String, Object> e : mapSet) {
if (e.getValue() instanceof String) {
connectionProperties.put(e.getKey(), e.getValue());
}
}
userName = (String) parameters.get(USERNAME);
password = (String) parameters.get(PASSWORD);
String conCaching = (String) parameters.get(CACHE);
if ("true".equals(conCaching)) {
if (logger.isDebugEnabled()) {
logger.debug(nameString() + " enabling connection Caching");
}
cacheLevel = 1;
}
String destination = (String) parameters.get(DESTINATION);
if (destination != null) {
this.destination = destination;
} else {
String name = getName();
String defaultDest;
if (name != null && !name.isEmpty()) {
defaultDest = name + "_Queue";
} else {
defaultDest = "JmsStore_" + System.currentTimeMillis() + "_Queue";
}
logger.warn(nameString() + ". Destination not provided. " +
"Setting default destination to [" + defaultDest + "].");
this.destination = defaultDest;
}
destinationType = "queue";
String version = (String) parameters.get(JMS_VERSION);
if (version != null) {
if (!JMS_SPEC_11.equals(version)) {
isVersion11 = false;
}
}
if (parameters != null && !parameters.isEmpty() && parameters.get(GUARANTEED_DELIVERY_ENABLE) != null) {
isGuaranteedDeliveryEnable = Boolean.valueOf(parameters.get(GUARANTEED_DELIVERY_ENABLE).toString());
}
String consumerReceiveTimeOut = (String) parameters.get(CONSUMER_TIMEOUT);
int consumerReceiveTimeOutI = 6000;
if (consumerReceiveTimeOut != null) {
try {
consumerReceiveTimeOutI = Integer.parseInt(consumerReceiveTimeOut);
} catch (NumberFormatException e) {
//logger.error(nameString() + ". Error parsing consumer receive time out value. " +
// "Set to 60s.");
}
} //else {
//logger.warn(nameString() + ". Consumer Receiving time out not passed in. " +
// "Set to 60s.");
//}
String connectionFac = null;
try {
context = new InitialContext(connectionProperties);
connectionFac = (String) parameters.get(CONN_FACTORY);
if (connectionFac == null) {
connectionFac = "QueueConnectionFactory";
}
connectionFactory = lookup(context, javax.jms.ConnectionFactory.class, connectionFac);
if (connectionFactory == null) {
throw new SynapseException(nameString() + " could not initialize JMS Connection Factory. " +
"Connection factory not found : " + connectionFac);
}
createDestIfAbsent(null);
if (queue == null) {
logger.warn(nameString() + ". JMS Destination [" + destination + "] does not exist.");
}
} catch (NamingException e) {
logger.error(nameString() + ". Could not initialize JMS Message Store. Error:"
+ e.getLocalizedMessage() + ". Initial Context Factory:[" + parameters.get(NAMING_FACTORY_INITIAL) + "]; Provider URL:[" + parameters.get(PROVIDER_URL) + "]; Connection Factory:[" + connectionFac + "].", e);
} catch (Throwable t) {
logger.error(nameString() + ". Could not initialize JMS Message Store. Error:"
+ t.getMessage() + ". Initial Context Factory:[" + parameters.get(NAMING_FACTORY_INITIAL) + "]; Provider URL:[" + parameters.get(PROVIDER_URL) + "]; Connection Factory:[" + connectionFac + "].",t);
}
if (!newWriteConnection()) {
logger.warn(nameString() + ". Starting with a faulty connection to the broker.");
return false;
}
return true;
}
private Destination getDestination(Session session) {
Destination dest = queue;
if (dest != null) {
return dest;
}
InitialContext newContext = null;
try {
dest = lookup(context, javax.jms.Destination.class, destination);
} catch (NamingException e) {
if (logger.isDebugEnabled()) {
logger.debug(nameString() + ". Could not lookup destination [" + destination
+ "]. Message: " + e.getLocalizedMessage());
}
newContext = newContext();
}
try {
if (newContext != null) {
dest = lookup(newContext, javax.jms.Destination.class, destination);
}
} catch (Throwable t) {
logger.info(nameString() + ". Destination [" + destination
+ "] not defined in JNDI context. Message:" + t.getLocalizedMessage());
}
if (dest == null && session != null) {
try {
dest = session.createQueue(destination);
if (logger.isDebugEnabled()) {
logger.debug(nameString() + " created destination ["
+ destination + "] from session object.");
}
} catch (JMSException e) {
logger.error(nameString() + " could not create destination ["
+ destination + "]. Error:" + e.getLocalizedMessage(), e);
dest = null;
}
}
if (dest == null && session == null) {
if (logger.isDebugEnabled()) {
logger.debug(nameString() + ". Both destination and session is null." +
" Could not create destination.");
}
}
synchronized (queueLock) {
queue = dest;
}
return dest;
}
private InitialContext newContext() {
Properties properties = new Properties();
InitialContext newContext;
Map env;
try {
env = context.getEnvironment();
Object o = env.get(NAMING_FACTORY_INITIAL);
if (o != null) {
properties.put(NAMING_FACTORY_INITIAL, o);
}
o = env.get(CONNECTION_STRING);
if (o != null) {
properties.put(CONNECTION_STRING, o);
}
o = env.get(PROVIDER_URL);
if (o != null) {
properties.put(PROVIDER_URL, o);
}
properties.put(QUEUE_PREFIX + destination, destination);
newContext = new InitialContext(properties);
} catch (NamingException e) {
logger.info(nameString() + " could not create a new Context. Message:"
+ e.getLocalizedMessage());
return null;
}
if (logger.isDebugEnabled()) {
logger.debug(nameString() + " Created a new Context.");
}
return newContext;
}
private <T> T lookup(Context context, Class<T> clazz, String name)
throws NamingException {
if (context == null) {
logger.error(nameString() + ". Cannot perform JNDI lookup. Invalid context.");
return null;
}
if (name == null || "".equals(name)) {
logger.error(nameString() + ". Cannot perform JNDI lookup. Invalid name.");
return null;
}
Object object = context.lookup(name);
try {
return clazz.cast(object);
} catch (ClassCastException e) {
logger.error(nameString() + ". Cannot perform JNDI lookup for the name ["
+ name + "].", e);
return null;
}
}
private boolean destinationNonNull() {
synchronized (queueLock) {
return queue != null;
}
}
private boolean createDestIfAbsent(Session session) {
synchronized (queueLock) {
return getDestination(session) != null;
}
}
private String nameString() {
return "Store [" + getName() + "]";
}
}