/*
* $Id: MultiConsumerJmsMessageReceiver.java 21946 2011-05-18 15:26:14Z tcarlson $
* --------------------------------------------------------------------------------------
* Copyright (c) MuleSoft, Inc. All rights reserved. http://www.mulesoft.com
*
* The software in this package is published under the terms of the CPAL v1.0
* license, a copy of which has been included with this distribution in the
* LICENSE.txt file.
*/
package org.mule.transport.jms;
import org.mule.api.MessagingException;
import org.mule.api.MuleException;
import org.mule.api.construct.FlowConstruct;
import org.mule.api.endpoint.InboundEndpoint;
import org.mule.api.lifecycle.CreateException;
import org.mule.api.lifecycle.LifecycleException;
import org.mule.api.transaction.RollbackMethod;
import org.mule.api.transaction.Transaction;
import org.mule.api.transaction.TransactionException;
import org.mule.api.transport.Connector;
import org.mule.transaction.TransactionCollection;
import org.mule.transport.AbstractMessageReceiver;
import org.mule.transport.AbstractReceiverWorker;
import org.mule.transport.ConnectException;
import org.mule.transport.jms.filters.JmsSelectorFilter;
import org.mule.transport.jms.redelivery.RedeliveryHandler;
import org.mule.util.ClassUtils;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import javax.jms.Destination;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageConsumer;
import javax.jms.MessageListener;
import javax.jms.Session;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* In Mule an endpoint corresponds to a single receiver. It's up to the receiver to do multithreaded consumption and
* resource allocation, if needed. This class honors the <code>numberOfConcurrentTransactedReceivers</code> strictly
* and will create exactly this number of consumers.
*/
public class MultiConsumerJmsMessageReceiver extends AbstractMessageReceiver
{
protected final List<SubReceiver> consumers;
protected final int receiversCount;
private final JmsConnector jmsConnector;
private Session session;
public MultiConsumerJmsMessageReceiver(Connector connector, FlowConstruct flowConstruct, InboundEndpoint endpoint)
throws CreateException
{
super(connector, flowConstruct, endpoint);
jmsConnector = (JmsConnector) connector;
final boolean isTopic = jmsConnector.getTopicResolver().isTopic(endpoint, true);
if (isTopic && jmsConnector.getNumberOfConsumers() != 1)
{
if (logger.isInfoEnabled())
{
logger.info("Destination " + getEndpoint().getEndpointURI() + " is a topic, but " + jmsConnector.getNumberOfConsumers() +
" receivers have been requested. Will configure only 1.");
}
receiversCount = 1;
}
else
{
receiversCount = jmsConnector.getNumberOfConsumers();
}
if (logger.isDebugEnabled())
{
logger.debug("Creating " + receiversCount + " sub-receivers for " + endpoint.getEndpointURI());
}
consumers = new CopyOnWriteArrayList();
}
@Override
protected void doStart() throws MuleException
{
logger.debug("doStart()");
SubReceiver sub;
for (Iterator<SubReceiver> it = consumers.iterator(); it.hasNext();)
{
sub = it.next();
sub.doStart();
}
}
@Override
protected void doStop() throws MuleException
{
logger.debug("doStop()");
if (consumers != null)
{
SubReceiver sub;
for (Iterator<SubReceiver> it = consumers.iterator(); it.hasNext();)
{
sub = it.next();
sub.doStop(true);
}
}
}
@Override
protected void doConnect() throws Exception
{
logger.debug("doConnect()");
if (!consumers.isEmpty())
{
throw new IllegalStateException("List should be empty, there may be a concurrency issue here (see EE-1275)");
}
// Create session if none exists
if (session == null)
{
session = jmsConnector.getSession(endpoint);
}
SubReceiver sub;
for (int i = 0; i < receiversCount; i++)
{
sub = new SubReceiver();
sub.doConnect();
consumers.add(sub);
}
}
@Override
protected void doDisconnect() throws Exception
{
logger.debug("doDisconnect()");
SubReceiver sub;
for (Iterator<SubReceiver> it = consumers.iterator(); it.hasNext();)
{
sub = it.next();
try
{
sub.doDisconnect();
}
finally
{
sub = null;
}
}
consumers.clear();
if (session != null)
{
try
{
session.close();
}
catch (Exception e)
{
logger.warn("Failed to close jms session: " + e.getMessage());
}
finally
{
session = null;
}
}
}
@Override
protected void doDispose()
{
logger.debug("doDispose()");
// Note: the session was probably already disposed by doDisconnect()
if (session != null)
{
try
{
session.close();
}
catch (Exception e)
{
logger.warn("Failed to close jms session: " + e.getMessage());
}
finally
{
session = null;
}
}
}
private class SubReceiver implements MessageListener
{
private final Log subLogger = LogFactory.getLog(getClass());
private volatile MessageConsumer consumer;
protected volatile boolean connected;
protected volatile boolean started;
protected void doConnect() throws MuleException
{
subLogger.debug("SUB doConnect()");
try
{
createConsumer();
}
catch (Exception e)
{
throw new LifecycleException(e, this);
}
connected = true;
}
protected void doDisconnect() throws MuleException
{
subLogger.debug("SUB doDisconnect()");
if (started)
{
doStop(true);
}
closeConsumer();
connected = false;
}
protected void closeConsumer()
{
jmsConnector.closeQuietly(consumer);
consumer = null;
}
protected void doStart() throws MuleException
{
subLogger.debug("SUB doStart()");
if (!connected)
{
doConnect();
}
try
{
consumer.setMessageListener(this);
started = true;
}
catch (JMSException e)
{
throw new LifecycleException(e, this);
}
}
/**
* Stop the subreceiver.
* @param force - if true, any exceptions will be logged but the subreceiver will be considered stopped regardless
* @throws MuleException only if force = false
*/
protected void doStop(boolean force) throws MuleException
{
subLogger.debug("SUB doStop()");
if (consumer != null)
{
try
{
consumer.setMessageListener(null);
started = false;
}
catch (JMSException e)
{
if (force)
{
logger.warn("Unable to cleanly stop subreceiver: " + e.getMessage());
started = false;
}
else
{
throw new LifecycleException(e, this);
}
}
}
}
/**
* Create a consumer for the jms destination.
*/
protected void createConsumer() throws Exception
{
subLogger.debug("SUB createConsumer()");
try
{
JmsSupport jmsSupport = jmsConnector.getJmsSupport();
boolean topic = jmsConnector.getTopicResolver().isTopic(endpoint, true);
// Create destination
Destination dest = jmsSupport.createDestination(session, endpoint);
// Extract jms selector
String selector = null;
if (endpoint.getFilter() != null && endpoint.getFilter() instanceof JmsSelectorFilter)
{
selector = ((JmsSelectorFilter) endpoint.getFilter()).getExpression();
}
else
{
if (endpoint.getProperties() != null)
{
// still allow the selector to be set as a property on the endpoint
// to be backward compatable
selector = (String) endpoint.getProperties().get(JmsConstants.JMS_SELECTOR_PROPERTY);
}
}
String tempDurable = (String) endpoint.getProperties().get(JmsConstants.DURABLE_PROPERTY);
boolean durable = jmsConnector.isDurable();
if (tempDurable != null)
{
durable = Boolean.valueOf(tempDurable);
}
// Get the durable subscriber name if there is one
String durableName = (String) endpoint.getProperties().get(JmsConstants.DURABLE_NAME_PROPERTY);
if (durableName == null && durable && topic)
{
durableName = "mule." + jmsConnector.getName() + "." + endpoint.getEndpointURI().getAddress();
logger.debug("Jms Connector for this receiver is durable but no durable name has been specified. Defaulting to: "
+ durableName);
}
// Create consumer
consumer = jmsSupport.createConsumer(session, dest, selector, jmsConnector.isNoLocal(), durableName,
topic, endpoint);
}
catch (JMSException e)
{
throw new ConnectException(e, MultiConsumerJmsMessageReceiver.this);
}
}
public void onMessage(final Message message)
{
try
{
// Note: Despite the name "Worker", there is no new thread created here in order to maintain synchronicity for exception handling.
JmsWorker worker = new JmsWorker(message, MultiConsumerJmsMessageReceiver.this, this);
worker.processMessages();
}
catch (Exception e)
{
// Use this rollback method in case a transaction has not been configured on the endpoint.
RollbackMethod rollbackMethod = new RollbackMethod()
{
public void rollback()
{
try
{
session.recover();
}
catch (JMSException jmsEx)
{
logger.error(jmsEx);
}
}
};
if (e instanceof MessagingException)
{
getFlowConstruct().getExceptionListener().handleException(e, ((MessagingException) e).getEvent(), rollbackMethod);
}
else
{
getConnector().getMuleContext().getExceptionListener().handleException(e, rollbackMethod);
}
}
}
}
protected class JmsWorker extends AbstractReceiverWorker
{
private final SubReceiver subReceiver;
public JmsWorker(Message message, AbstractMessageReceiver receiver, SubReceiver subReceiver)
{
super(new ArrayList<Object>(1), receiver);
this.subReceiver = subReceiver;
messages.add(message);
}
@Override
protected Object preProcessMessage(Object message) throws Exception
{
Message m = (Message) message;
if (logger.isDebugEnabled())
{
logger.debug("Message received it is of type: " +
ClassUtils.getSimpleName(message.getClass()));
if (m.getJMSDestination() != null)
{
logger.debug("Message received on " + m.getJMSDestination() + " ("
+ m.getJMSDestination().getClass().getName() + ")");
}
else
{
logger.debug("Message received on unknown destination");
}
logger.debug("Message CorrelationId is: " + m.getJMSCorrelationID());
logger.debug("Jms Message Id is: " + m.getJMSMessageID());
}
if (m.getJMSRedelivered())
{
// lazily create the redelivery handler
RedeliveryHandler redeliveryHandler = jmsConnector.getRedeliveryHandlerFactory().create();
redeliveryHandler.setConnector(jmsConnector);
if (logger.isDebugEnabled())
{
logger.debug("Message with correlationId: " + m.getJMSCorrelationID()
+ " has redelivered flag set, handing off to Redelivery Handler");
}
redeliveryHandler.handleRedelivery(m, receiver.getEndpoint(), receiver.getFlowConstruct());
}
return m;
}
@Override
protected void bindTransaction(Transaction tx) throws TransactionException
{
if (tx instanceof JmsTransaction || tx instanceof TransactionCollection)
{
if (logger.isDebugEnabled())
{
logger.debug("Binding " + session + " to " + jmsConnector.getConnection());
}
tx.bindResource(jmsConnector.getConnection(), session);
}
else
{
if (tx instanceof JmsClientAcknowledgeTransaction)
{
//We should still bind the session to the transaction, but we also need the message itself
//since that is the object that gets Acknowledged
//tx.bindResource(jmsConnector.getConnection(), session);
((JmsClientAcknowledgeTransaction) tx).setMessage((Message) messages.get(0));
}
}
}
}
}