net.timewalker.ffmq4.local.session.LocalMessageConsumer.java Source code

Java tutorial

Introduction

Here is the source code for net.timewalker.ffmq4.local.session.LocalMessageConsumer.java

Source

/*
 * 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;

/**
 * <p>Implementation of a local JMS {@link MessageConsumer}</p>
 */
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();
        }
    }
}