// Copyright 2010-2011 NexJ Systems Inc. This software is licensed under the terms of the Eclipse Public License 1.0
package nexj.core.rpc.jms;
import javax.jms.Connection;
import javax.jms.ConnectionFactory;
import javax.jms.Destination;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageListener;
import javax.jms.MessageProducer;
import javax.jms.Queue;
import javax.jms.Session;
import javax.jms.Topic;
import javax.jms.XAQueueConnection;
import javax.jms.XAQueueConnectionFactory;
import javax.jms.XASession;
import javax.transaction.Status;
import javax.transaction.TransactionManager;
import nexj.core.meta.Repository;
import nexj.core.meta.integration.channel.jms.MessageQueue;
import nexj.core.rpc.IntegrationMDB;
import nexj.core.rpc.ServerException;
import nexj.core.rpc.jms.JMSUtil.MessagePropertyUpdater;
import nexj.core.util.Logger;
import nexj.core.util.ObjUtil;
/**
* The JMS server Message Driven Bean
*/
public class JMSServerMDB extends IntegrationMDB implements JMSListener, MessageListener
{
// constants
/**
* Serialization version.
*/
private final static long serialVersionUID = 6229679996585443650L;
// associations
/**
* The channel. A temporary variable that is set during message delivery and cleared at the end
* of message delivery.
*/
protected MessageQueue m_channel;
/**
* The MDB logger.
*/
protected final static Logger s_logger = Logger.getLogger(JMSServerMDB.class);
/**
* Message rejection message updater.
*/
protected final static MessagePropertyUpdater s_redirectionMessageUpdater = new MessagePropertyUpdater()
{
public void getProperties(Message message) throws JMSException
{
}
public void setProperties(Message message) throws JMSException
{
message.setBooleanProperty(JMS.REDIRECT, false);
}
};
// operations
/**
* @see nexj.core.rpc.ServerMDB#getLogger()
*/
protected Logger getLogger()
{
return s_logger;
}
/**
* @see javax.jms.MessageListener#onMessage(javax.jms.Message)
*/
public void onMessage(Message message)
{
boolean bReject = false;
boolean bRedirect = false;
try
{
bReject = message.propertyExists(JMS.MESSAGE_REJECTED) && message.getBooleanProperty(JMS.MESSAGE_REJECTED);
}
catch (JMSException e)
{
s_logger.error("Unable to retrieve property " + JMS.MESSAGE_REJECTED + " from message " + JMSUtil.getMessageId(message) + " in " + this, e);
}
try
{
bRedirect = message.propertyExists(JMS.REDIRECT) && message.getBooleanProperty(JMS.REDIRECT);
}
catch (JMSException e)
{
s_logger.error("Unable to retrieve property " + JMS.REDIRECT + " from message " + JMSUtil.getMessageId(message) + " in " + this, e);
}
try
{
m_channel = (MessageQueue)Repository.getMetadata().getChannel(m_sChannelName);
if (bReject)
{
try
{
redeliver(message);
}
catch (Throwable t)
{
s_logger.error("Unable to reject message " + JMSUtil.getMessageId(message) + " in " + this, t);
}
}
else if (bRedirect)
{
Connection connection = null;
Session session = null;
try
{
connection = createConnection(m_channel);
session = getSession(connection);
redirect(message, getDestination(m_channel), session, s_redirectionMessageUpdater);
}
catch (Throwable t)
{
s_logger.error("Unable to redirect message " + JMSUtil.getMessageId(message) + " in class " + this, t);
bRedirect = false;
}
finally
{
close(connection, session);
}
}
if (!bReject && !bRedirect)
{
try
{
MessageListener receiver = (MessageListener)m_channel.getReceiver().getInstance(null);
if (!message.getJMSRedelivered() || redeliver(message))
{
receiver.onMessage(message);
}
}
catch (Throwable t)
{
if (!(t instanceof ServerException) && s_logger.isDebugEnabled())
{
s_logger.debug("Error in " + this, t);
}
boolean bDone = false;
try
{
TransactionManager manager = getTransactionManager();
if (manager.getStatus() != Status.STATUS_NO_TRANSACTION)
{
bDone = true;
if (s_logger.isDebugEnabled())
{
s_logger.debug("Rolling back the transaction in " + this);
}
manager.setRollbackOnly();
}
}
catch (Throwable t2)
{
if (s_logger.isDebugEnabled())
{
s_logger.debug("Unable to mark the transaction for rollback in " + this, t2);
}
}
if (!bDone)
{
ObjUtil.rethrow(t);
}
}
}
}
finally
{
m_channel = null;
}
}
/**
* Redelivers the message.
* @param message The message to process.
* @return True if the message should still be delivered to the MessageListener.
*/
public boolean redeliver(Message message) throws Exception
{
boolean bDone = false;
Connection connection = null;
Session session = null;
Connection errorConnection = null;
Session errorSession = null;
try
{
int nMaxErrorCount = JMSUtil.getMaxErrorCount(m_channel, message);
int nErrorCount = JMSUtil.getErrorCount(m_channel, message, nMaxErrorCount, true) + 1; // the error count of the next re-submission
if (nErrorCount > nMaxErrorCount)
{
if (m_channel.getErrorQueue() != null)
{
if (s_logger.isDebugEnabled())
{
s_logger.debug("Redirecting message " + JMSUtil.getMessageId(message) + " to the error queue of " + this);
s_logger.dump(message);
}
errorConnection = createConnection(m_channel.getErrorQueue());
errorSession = createSession(errorConnection, false, Session.AUTO_ACKNOWLEDGE);
if (errorSession instanceof XASession)
{
TransactionManager tm = getTransactionManager();
if (tm.getStatus() == Status.STATUS_ACTIVE)
{
tm.getTransaction().enlistResource(((XASession)errorSession).getXAResource());
}
}
redirect(message, getDestination(m_channel.getErrorQueue()), errorSession,
new MessagePropertyUpdater()
{
protected String m_sOldMessageId;
protected String m_sOldDestination;
public void getProperties(Message message) throws JMSException
{
m_sOldMessageId = message.getJMSMessageID();
Destination destination = message.getJMSDestination();
m_sOldDestination = (destination instanceof Queue) ?
"queue:" + ((Queue)destination).getQueueName() :
"topic:" + ((Topic)destination).getTopicName();
}
public void setProperties(Message message) throws JMSException
{
message.setStringProperty(JMS.OLD_MESSAGE_ID, m_sOldMessageId);
message.setStringProperty(JMS.OLD_DESTINATION, m_sOldDestination);
}
});
bDone = true;
return false;
}
}
else if (message.propertyExists(JMS.BACKOFF_DELAY))
{
if (m_channel.isBroadcast())
{
s_logger.error("Message back-off not supported for topics (message " +
JMSUtil.getMessageId(message) + " in " + this + ")");
s_logger.dump(message);
}
else
{
if (s_logger.isDebugEnabled())
{
s_logger.debug("Delaying message " + JMSUtil.getMessageId(message) + " for " + this);
s_logger.dump(message);
}
connection = createConnection(m_channel);
session = getSession(connection);
redirect(message, getDestination(m_channel), session,
new MessagePropertyUpdater()
{
protected long m_lBackoffDelay;
protected long m_lMaxBackoffDelay;
protected int m_nErrorCount;
public void getProperties(Message message) throws JMSException
{
m_lBackoffDelay = message.getLongProperty(JMS.BACKOFF_DELAY);
if (message.propertyExists(JMS.MAX_BACKOFF_DELAY))
{
m_lMaxBackoffDelay = message.getLongProperty(JMS.MAX_BACKOFF_DELAY);
}
if (message.propertyExists(JMS.ERROR_COUNT))
{
m_nErrorCount = message.getIntProperty(JMS.ERROR_COUNT);
}
}
public void setProperties(Message message) throws JMSException
{
message.setLongProperty(JMS.BACKOFF_DELAY, limitBackoffDelay(m_lBackoffDelay << 1));
message.setIntProperty(JMS.ERROR_COUNT, m_nErrorCount + 1);
if (message.propertyExists(JMS.JBOSS_REDELIVERY_COUNT) || message.getClass().getName().startsWith("org.jboss."))
{
message.setLongProperty(JMS.JBOSS_SCHEDULED_DELIVERY, System.currentTimeMillis() + limitBackoffDelay(m_lBackoffDelay));
}
else if (message.propertyExists(JMS.ACTIVEMQ_BROKER_IN_TIME))
{
message.setLongProperty(JMS.ACTIVEMQ_SCHEDULED_DELAY, limitBackoffDelay(m_lBackoffDelay));
}
else
{
message.setLongProperty(JMS.DELIVERY_TIME, System.currentTimeMillis() + limitBackoffDelay(m_lBackoffDelay));
}
}
private long limitBackoffDelay(long lBackoffDelay)
{
if (m_lMaxBackoffDelay <= 0 || lBackoffDelay <= m_lMaxBackoffDelay)
{
return lBackoffDelay;
}
return m_lMaxBackoffDelay;
}
});
bDone = true;
return false;
}
}
bDone = true;
}
finally
{
if (!bDone)
{
if (((JMSSender)m_channel.getSender().getInstance(null)).getConnectionFactoryObject() instanceof XAQueueConnectionFactory)
{
TransactionManager tm = getTransactionManager();
switch (tm.getStatus())
{
case Status.STATUS_ACTIVE:
case Status.STATUS_MARKED_ROLLBACK:
tm.rollback();
break;
}
}
}
close(connection, session);
close(errorConnection, errorSession);
}
return true;
}
/**
* Redirects a message to a given destination.
* @param message The message to redirect.
* @param destination The new destination.
* @param session The session to use for sending the message.
* @param updater The message property updater strategy.
*/
protected void redirect(Message message, Destination destination, Session session, MessagePropertyUpdater updater) throws Exception
{
MessageProducer producer = null;
try
{
long lExpiration = message.getJMSExpiration();
long lTTL = (lExpiration == 0) ? Message.DEFAULT_TIME_TO_LIVE : lExpiration - System.currentTimeMillis();
if (lExpiration != 0 && lTTL <= 0)
{
if (s_logger.isDebugEnabled())
{
s_logger.debug("Message " + JMSUtil.getMessageId(message) + " expired in " + this);
s_logger.dump(message);
}
}
else
{
int nDeliveryMode = message.getJMSDeliveryMode();
int nPriority = message.getJMSPriority();
JMSUtil.setMessageProperties(message, updater);
producer = session.createProducer(destination);
producer.send(message, nDeliveryMode, nPriority, lTTL);
}
}
finally
{
if (producer != null)
{
try
{
producer.close();
}
catch (Throwable t)
{
if (s_logger.isWarnEnabled())
{
s_logger.warn("Unable to close the producer in " + this, t);
}
}
}
}
}
/**
* Retrieves the destination from the MessageQueue sender.
* @param mq The message queue from which to retrieve the destination.
* @return The Destination.
*/
protected Destination getDestination(MessageQueue mq)
{
return ((JMSSender)mq.getSender().getInstance(null)).getDestinationObject();
}
/**
* Creates a JMS connection for a sender for the specified MessageQueue.
* @param mq The message queue for which to create the connection.
* @return The JMS connection.
*/
protected Connection createConnection(MessageQueue mq) throws JMSException
{
boolean bDone = false;
Connection connection = null;
JMSSender sender = (JMSSender)mq.getSender().getInstance(null);
try
{
ConnectionFactory cf = sender.getConnectionFactoryObject();
if (mq.getUser() != null)
{
connection = cf.createConnection(mq.getUser(), mq.getPassword());
}
else
{
connection = cf.createConnection();
}
if (mq.getClientId() != null)
{
connection.setClientID(mq.getClientId());
}
connection.start();
bDone = true;
return connection;
}
catch (JMSException e)
{
throw e;
}
catch (Throwable t)
{
JMSException x = new JMSException("Unable to create connection");
x.initCause(t);
throw x;
}
finally
{
if (!bDone)
{
close(connection, null);
}
}
}
/**
* Creates a JMS session for m_channel.
* @param connection The connection from which to create the session.
* @return The JMS session
*/
protected Session getSession(Connection connection) throws JMSException
{
return createSession(connection, m_channel.isTransactional(), m_channel.getAckMode());
}
/**
* Creates a JMS session for given queue connection.
* @param connection The JMS connection
* @param bTransactional True if session should be transactional.
* @param nAckMode The acknowledgement mode of the session.
* @return The JMS session.
*/
protected Session createSession(Connection connection, boolean bTransactional, int nAckMode) throws JMSException
{
boolean bDone = false;
Session session = null;
try
{
if (connection instanceof XAQueueConnection)
{
session = ((XAQueueConnection)connection).createXAQueueSession();
}
else
{
session = connection.createSession(bTransactional, nAckMode);
}
bDone = true;
return session;
}
catch (JMSException e)
{
throw e;
}
catch (Throwable t)
{
JMSException x = new JMSException("Unable to create the queue session");
x.initCause(t);
throw x;
}
finally
{
if (!bDone)
{
close(connection, session);
}
}
}
/**
* Closes JMS objects.
* @param connection The JMS connection
* @param session The JMS session
*/
protected void close(Connection connection, Session session)
{
if (session != null)
{
try
{
session.close();
}
catch (Throwable t)
{
if (s_logger.isWarnEnabled())
{
s_logger.warn("Unable to close queue session of " + this, t);
}
}
}
if (connection != null)
{
try
{
connection.close();
}
catch (Throwable t)
{
if (s_logger.isWarnEnabled())
{
s_logger.warn("Unable to close queue connection of " + this, t);
}
}
}
}
/**
* @return The transaction manager
*/
protected TransactionManager getTransactionManager()
{
return (TransactionManager)m_channel.getType().getMetadata()
.getComponent("System.TransactionManager").getInstance(null);
}
}