net.timewalker.ffmq4.local.session.LocalMessageConsumer Maven / Gradle / Ivy
/*
* This file is part of FFMQ.
*
* FFMQ is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* FFMQ is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with FFMQ; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
package net.timewalker.ffmq4.local.session;
import javax.jms.Destination;
import javax.jms.InvalidDestinationException;
import javax.jms.JMSException;
import javax.jms.MessageConsumer;
import javax.jms.MessageListener;
import javax.jms.Queue;
import javax.jms.Topic;
import net.timewalker.ffmq4.FFMQConstants;
import net.timewalker.ffmq4.FFMQCoreSettings;
import net.timewalker.ffmq4.FFMQException;
import net.timewalker.ffmq4.common.message.AbstractMessage;
import net.timewalker.ffmq4.common.message.MessageSelector;
import net.timewalker.ffmq4.common.message.MessageTools;
import net.timewalker.ffmq4.common.session.AbstractMessageConsumer;
import net.timewalker.ffmq4.local.FFMQEngine;
import net.timewalker.ffmq4.local.TransactionSet;
import net.timewalker.ffmq4.local.connection.LocalConnection;
import net.timewalker.ffmq4.local.destination.LocalQueue;
import net.timewalker.ffmq4.local.destination.LocalTopic;
import net.timewalker.ffmq4.local.destination.notification.NotificationProxy;
import net.timewalker.ffmq4.management.destination.definition.QueueDefinition;
import net.timewalker.ffmq4.management.destination.definition.TopicDefinition;
import net.timewalker.ffmq4.security.Action;
import net.timewalker.ffmq4.security.Resource;
import net.timewalker.ffmq4.utils.ErrorTools;
import net.timewalker.ffmq4.utils.Settings;
import net.timewalker.ffmq4.utils.StringTools;
import net.timewalker.ffmq4.utils.async.AsyncTask;
import net.timewalker.ffmq4.utils.id.IntegerID;
import net.timewalker.ffmq4.utils.id.UUIDProvider;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* Implementation of a local JMS {@link MessageConsumer}
*/
public class LocalMessageConsumer extends AbstractMessageConsumer
{
private static final Log log = LogFactory.getLog(LocalMessageConsumer.class);
// Parent engine
protected FFMQEngine engine;
// Parsed selector
protected MessageSelector parsedSelector;
// Runtime
private String subscriberId;
private LocalQueue localQueue;
private LocalTopic localTopic;
private boolean traceEnabled;
private boolean receiving;
private TransactionSet transactionSet;
// Specific to receive mode
private Object receiveLock = new Object();
// Used for interaction with the remote layer
private NotificationProxy notificationProxy;
private int prefetchSize;
private int prefetchCapacity;
private Object prefetchLock = new Object();
// Settings
private boolean logListenersFailures;
/**
* Constructor
*/
public LocalMessageConsumer(FFMQEngine engine,
LocalSession session,
Destination destination,
String messageSelector,
boolean noLocal,
IntegerID consumerId,
String subscriberId) throws JMSException
{
super(session,destination,messageSelector,noLocal,consumerId);
this.engine = engine;
this.session = session;
this.parsedSelector =
StringTools.isNotEmpty(messageSelector) ?
new MessageSelector(messageSelector) : null;
this.traceEnabled = log.isTraceEnabled();
this.transactionSet = session.getTransactionSet();
this.notificationProxy = session.getNotificationProxy();
this.prefetchCapacity = this.prefetchSize = engine.getSetup().getConsumerPrefetchSize();
this.logListenersFailures = getSettings().getBooleanProperty(FFMQCoreSettings.DELIVERY_LOG_LISTENERS_FAILURES, false);
this.subscriberId = subscriberId != null ? subscriberId : UUIDProvider.getInstance().getShortUUID();
}
/*
* (non-Javadoc)
* @see net.timewalker.ffmq4.common.session.AbstractMessageConsumer#shouldLogListenersFailures()
*/
@Override
protected final boolean shouldLogListenersFailures()
{
return logListenersFailures;
}
/**
* @return the prefetchSize
*/
public final int getPrefetchSize()
{
return prefetchSize;
}
protected final Settings getSettings()
{
return engine.getSetup().getSettings();
}
protected final void initDestination() throws JMSException
{
// Security : a consumer destination may only be set at creation time
// so we check permissions here once and for all.
LocalConnection conn = (LocalConnection)session.getConnection();
if (conn.isSecurityEnabled())
{
if (destination instanceof Queue)
{
String queueName = ((Queue)destination).getQueueName();
if (conn.isRegisteredTemporaryQueue(queueName))
{
// OK, temporary destination
}
else
if (queueName.equals(FFMQConstants.ADM_REQUEST_QUEUE))
{
// Only the internal admin thread can consume on this queue
if (conn.getSecurityContext() != null)
throw new FFMQException("Access denied to administration queue "+queueName,"ACCESS_DENIED");
}
else
if (queueName.equals(FFMQConstants.ADM_REPLY_QUEUE))
{
conn.checkPermission(Resource.SERVER, Action.REMOTE_ADMIN);
}
else
{
// Standard queue
conn.checkPermission(destination,Action.CONSUME);
}
}
else
if (destination instanceof Topic)
{
String topicName = ((Topic)destination).getTopicName();
if (conn.isRegisteredTemporaryTopic(topicName))
{
// OK, temporary destination
}
else
{
// Standard topic
conn.checkPermission(destination,Action.CONSUME);
}
}
}
// Lookup a local destination object from the given reference
if (destination instanceof Queue)
{
Queue queueRef = (Queue)destination;
this.localQueue = engine.getLocalQueue(queueRef.getQueueName());
// Check temporary destinations scope (JMS Spec 4.4.3 p2)
session.checkTemporaryDestinationScope(localQueue);
this.localQueue.registerConsumer(this);
}
else
if (destination instanceof Topic)
{
Topic topicRef = (Topic)destination;
this.localTopic = engine.getLocalTopic(topicRef.getTopicName());
// Check temporary destinations scope (JMS Spec 4.4.3 p2)
session.checkTemporaryDestinationScope(localTopic);
// Deploy a local queue for this consumer
TopicDefinition topicDef = this.localTopic.getDefinition();
QueueDefinition tempDef = topicDef.createQueueDefinition(topicRef.getTopicName(), subscriberId, !isDurable());
if (engine.localQueueExists(tempDef.getName()))
this.localQueue = engine.getLocalQueue(tempDef.getName());
else
this.localQueue = engine.createQueue(tempDef);
// Register on both the queue and topic
this.localQueue.registerConsumer(this);
this.localTopic.registerConsumer(this);
}
else
throw new InvalidDestinationException("Unsupported destination : "+destination);
}
private void unregister()
{
if (localTopic != null)
localTopic.unregisterConsumer(this);
if (localQueue != null)
{
localQueue.unregisterConsumer(this);
try
{
// Drop volatile topic subscriber queue
if ((destination instanceof Topic) && !isDurable())
{
localQueue.close();
((LocalSession)session).deleteQueue(localQueue.getName());
}
}
catch (JMSException e)
{
ErrorTools.log(e, log);
}
}
}
/* (non-Javadoc)
* @see javax.jms.MessageConsumer#setMessageListener(javax.jms.MessageListener)
*/
@Override
public final void setMessageListener(MessageListener messageListener) throws JMSException
{
super.setMessageListener(messageListener);
// If the connection was already started, wake up the new listener in case there were messages
// waiting in the destination
if (messageListener != null && connection.isStarted())
engine.getDeliveryAsyncTaskManager().execute(wakeUpAsyncTask);
}
/**
* Test if the consumer is durable
*/
public boolean isDurable()
{
return false;
}
/**
* Get the parsed message selector for this consumer
* @return the parsedSelector
*/
public final MessageSelector getParsedSelector()
{
return parsedSelector;
}
/**
* Get the parsed message selector for this consumer to be used
* when receiving a message from the local queue
* @return the parsedSelector
*/
public final MessageSelector getReceiveSelector()
{
return localTopic == null ? parsedSelector : null;
}
/* (non-Javadoc)
* @see net.timewalker.ffmq4.common.session.AbstractMessageConsumer#onConsumerClose()
*/
@Override
protected final void onConsumerClose()
{
super.onConsumerClose();
// Unregister the consumer from the associated destination
unregister();
try
{
engine.getDeliveryAsyncTaskManager().cancelTask(wakeUpAsyncTask);
}
catch (JMSException e)
{
ErrorTools.log(e, log);
}
}
/* (non-Javadoc)
* @see net.timewalker.ffmq4.common.session.AbstractMessageConsumer#onConsumerClosed()
*/
@Override
protected final void onConsumerClosed()
{
// Wake up blocked listeners
synchronized (receiveLock)
{
receiveLock.notifyAll();
}
}
/* (non-Javadoc)
* @see net.timewalker.ffmq4.common.session.AbstractMessageConsumer#receiveFromDestination(long, boolean)
*/
@Override
public final AbstractMessage receiveFromDestination(long timeout, boolean duplicateRequired) throws JMSException
{
synchronized (receiveLock)
{
if (closed)
return null; // [JMS SPEC]
if (receiving)
throw new FFMQException("Consumer should not be accessed by more than one thread","ILLEGAL_USAGE");
receiving = true;
try
{
MessageSelector selector = getReceiveSelector();
// No-wait simplified case
if (timeout == 0)
{
if (!connection.isStarted())
return null;
AbstractMessage message = localQueue.get((LocalSession)session,
transactionSet,
selector);
if (message == null)
return null;
if (traceEnabled)
log.trace(session+" [GET] in "+localQueue+" - "+message);
if (duplicateRequired)
message = MessageTools.duplicate(message);
message.markAsReadOnly();
return message;
}
// Wait loop
long now = System.currentTimeMillis();
long startTime = now;
// Passive wait
while (!closed && (timeout < 0 || (now - startTime < timeout)))
{
// Don't do anything if connection is not started
if (connection.isStarted())
{
// Try obtaining something from target queue
AbstractMessage message = localQueue.get((LocalSession)session,
transactionSet,
selector);
if (message != null)
{
if (traceEnabled)
log.trace(session+" [GET] in "+localQueue+" - "+message);
if (duplicateRequired)
message = MessageTools.duplicate(message);
message.markAsReadOnly();
return message;
}
}
if (traceEnabled)
log.trace("Entering passive wait on "+localQueue+" (timeout="+timeout+")");
try
{
if (timeout <= 0)
receiveLock.wait();
else
receiveLock.wait(timeout - (now - startTime));
}
catch (InterruptedException e)
{
return null;
}
now = System.currentTimeMillis();
}
return null;
}
finally
{
receiving = false;
}
}
}
/*
* (non-Javadoc)
* @see net.timewalker.ffmq4.common.session.AbstractMessageConsumer#wakeUp()
*/
@Override
public final void wakeUp() throws JMSException
{
// Check that consumer is not closed
if (closed)
return;
// Check that the connection is properly started
if (!connection.isStarted())
return;
propagateNotification();
}
public final void prefetchMore() throws JMSException
{
synchronized (prefetchLock)
{
this.prefetchCapacity = prefetchSize; // Reset capacity to maximum
}
wakeUp();
}
public final void restorePrefetchCapacity( int amount ) throws JMSException
{
synchronized (prefetchLock)
{
this.prefetchCapacity += amount;
}
wakeUp();
}
private void propagateNotification() throws JMSException
{
// If a notification proxy is registered, that mean we are
// actually serving a remote consumer
if (notificationProxy != null)
{
int count = 0;
synchronized (prefetchLock)
{
if (prefetchCapacity < prefetchSize)
return; // Consumer is busy, we can give up immediately because it will get back to us later anyway
// Push up to 'prefetchCapacity' messages to the remote consumer
while (prefetchCapacity > 0)
{
AbstractMessage message = receiveFromDestination(0, false);
if (message != null)
{
count++;
prefetchCapacity--;
notificationProxy.addNotification(id,message);
}
else
break;
}
}
// Flush
if (count > 0)
notificationProxy.flush();
}
else
{
// Is there a local consumer to wake up ?
if (messageListener != null)
{
// Dispatch to listener in another thread to avoid deadlocks
engine.getDeliveryAsyncTaskManager().execute(wakeUpAsyncTask);
}
else
{
synchronized (receiveLock)
{
// Wake up a random thread blocked in a receive() call
if (receiving)
receiveLock.notify();
}
}
}
}
/*
* (non-Javadoc)
* @see javax.jms.TopicSubscriber#getNoLocal()
*/
public final boolean getNoLocal()
{
return noLocal;
}
/**
* Get the local queue associated to this consumer
*/
public final LocalQueue getLocalQueue()
{
return localQueue;
}
/**
* @return the subscriberId
*/
public final String getSubscriberId()
{
return subscriberId;
}
//--------------------------------------------------------------------
private final WakeUpAsyncTask wakeUpAsyncTask = new WakeUpAsyncTask();
private final class WakeUpAsyncTask implements AsyncTask
{
/**
* Constructor
*/
public WakeUpAsyncTask()
{
super();
}
/* (non-Javadoc)
* @see net.timewalker.ffmq4.utils.async.AsyncTask#isMergeable()
*/
@Override
public final boolean isMergeable()
{
return true;
}
/* (non-Javadoc)
* @see net.timewalker.ffmq4.utils.async.AsyncTask#execute()
*/
@Override
public final void execute()
{
wakeUpMessageListener();
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy