package org.jboss.soa.esb.services.jbpm.integration.command;
import java.io.Serializable;
import javax.jms.Destination;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageListener;
import javax.jms.MessageProducer;
import javax.jms.Session;
import org.apache.log4j.Logger;
import org.hibernate.StaleStateException;
import org.jboss.soa.esb.common.TransactionStrategy;
import org.jboss.soa.esb.common.TransactionStrategyException;
import org.jboss.soa.esb.services.jbpm.integration.msg.JmsMessageService;
import org.jboss.soa.esb.services.jbpm.integration.msg.JmsMessageServiceFactory;
import org.jbpm.JbpmConfiguration;
import org.jbpm.JbpmContext;
import org.jbpm.JbpmException;
import org.jbpm.command.Command;
import org.jbpm.persistence.JbpmPersistenceException;
import org.jbpm.persistence.db.DbPersistenceService;
import org.jbpm.svc.Services;
import org.jbpm.tx.TxService;
/**
* Based on the jBPM enterprise code, this abstract base class handles most of the
* processing required by job/command executors.
*
* @author <a href='kevin.conner@jboss.com'>Kevin Conner</a>
*/
abstract class AbstractMessageListener implements MessageListener
{
/**
* The logger for this class.
*/
protected final Logger log = Logger.getLogger(getClass()) ;
/**
* Handle the delivery of a job message.
* @param message the JMS message containing the job.
*/
public void onMessage(final Message message)
{
if (log.isDebugEnabled())
{
log.debug("Processing message " + message) ;
}
try
{
final JbpmContext jbpmContext = JbpmConfiguration.getInstance().createJbpmContext();
try
{
// extract command from message
Command command = extractCommand(message);
if (log.isDebugEnabled())
{
log.debug("Extracted command " + command) ;
}
if (command == null)
{
discard(jbpmContext, message);
return;
}
final Object result = command.execute(jbpmContext);
// send a response back if a "reply to" destination is set
final Destination replyTo = message.getJMSReplyTo();
if (replyTo != null && (result instanceof Serializable || result == null))
{
sendResult(jbpmContext, (Serializable)result, replyTo, message.getJMSMessageID());
}
if (log.isDebugEnabled())
{
log.debug("Executed command " + command) ;
}
jbpmContext.getSession().flush() ;
}
finally
{
final TxService txService = jbpmContext.getServices().getTxService() ;
final boolean exceptionExpected = txService.isRollbackOnly() ;
try
{
jbpmContext.close() ;
}
catch (final JbpmException je)
{
if (exceptionExpected)
{
rollback(je) ;
}
else
{
throw je ;
}
}
}
}
catch (final StaleStateException sse)
{
if (log.isDebugEnabled())
{
log.debug("Stale state raised, rolling back transaction") ;
}
rollback(sse) ;
}
catch (final JbpmPersistenceException jpe)
{
if (!DbPersistenceService.isLockingException(jpe))
{
throw jpe ;
}
if (log.isDebugEnabled())
{
log.debug("Stale state raised, rolling back transaction") ;
}
rollback(jpe) ;
}
catch (final JbpmException je)
{
throw je ;
}
catch (final Exception ex)
{
throw new JbpmException("could not process message " + message, ex);
}
}
/**
* Extract a jBPM command from the message.
* @param message The JMS message.
* @return The jBPM command.
* @throws JMSException for errors during extraction.
*/
protected abstract Command extractCommand(Message message)
throws JMSException ;
/**
* Discard the message by sending to a specified DLQ.
* @param jbpmContext The current jBPM context.
* @param message The message to discard.
* @throws JMSException For errors during JMS delivery.
*/
private void discard(final JbpmContext jbpmContext, final Message message)
throws JMSException
{
final Services services = jbpmContext.getServices();
final JmsMessageServiceFactory messageServiceFactory = (JmsMessageServiceFactory) services.getServiceFactory(Services.SERVICENAME_MESSAGE);
final Destination destination = messageServiceFactory.getDLQDestination();
final JmsMessageService messageService = (JmsMessageService)services.getMessageService();
final Session session = messageService.getSession();
final MessageProducer producer = session.createProducer(destination);
try {
producer.send(message);
}
finally {
producer.close();
}
}
/**
* Send a result to a specified destination.
* @param jbpmContext The current jBPM context.
* @param result The serializable result.
* @param destination The destination for the message.
* @param correlationId The associated correlation ID.
* @throws JMSException For errors during JMS delivery.
*/
private void sendResult(final JbpmContext jbpmContext, final Serializable result, final Destination destination, final String correlationId)
throws JMSException
{
if (log.isDebugEnabled())
{
log.debug("sending result " + result + " to " + destination);
}
final Services services = jbpmContext.getServices();
final JmsMessageService messageService = (JmsMessageService) services.getMessageService();
final Session session = messageService.getSession();
final MessageProducer producer = session.createProducer(destination);
try
{
Message resultMessage = session.createObjectMessage(result);
resultMessage.setJMSCorrelationID(correlationId);
producer.send(resultMessage);
} finally {
producer.close();
}
}
/**
* Rollback the encompassing JTA transaction.
* @param re The original exception prompting the rollback, thrown should rollback fail.
*/
private void rollback(final RuntimeException re)
{
try
{
TransactionStrategy.getTransactionStrategy(true).rollbackOnly() ;
}
catch (final TransactionStrategyException tse)
{
if (log.isDebugEnabled())
{
log.debug("Failed to rollback transaction, throwing original", tse) ;
}
throw re ;
}
}
}