All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.timewalker.ffmq4.remote.session.RemoteMessageConsumer Maven / Gradle / Ivy

There is a newer version: 4.0.14
Show newest version
/*
 * 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.remote.session;

import java.util.LinkedList;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;

import javax.jms.Destination;
import javax.jms.JMSException;
import javax.jms.MessageListener;

import net.timewalker.ffmq4.FFMQCoreSettings;
import net.timewalker.ffmq4.client.ClientEnvironment;
import net.timewalker.ffmq4.common.message.AbstractMessage;
import net.timewalker.ffmq4.common.session.AbstractMessageConsumer;
import net.timewalker.ffmq4.storage.message.MessageSerializationLevel;
import net.timewalker.ffmq4.transport.PacketTransportEndpoint;
import net.timewalker.ffmq4.transport.packet.query.CloseConsumerQuery;
import net.timewalker.ffmq4.transport.packet.query.CreateConsumerQuery;
import net.timewalker.ffmq4.transport.packet.query.PrefetchQuery;
import net.timewalker.ffmq4.utils.ErrorTools;
import net.timewalker.ffmq4.utils.Settings;
import net.timewalker.ffmq4.utils.async.AsyncTask;
import net.timewalker.ffmq4.utils.async.AsyncTaskManager;
import net.timewalker.ffmq4.utils.id.IntegerID;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * RemoteMessageConsumer
 */
public class RemoteMessageConsumer extends AbstractMessageConsumer
{
    private static final Log log = LogFactory.getLog(RemoteMessageConsumer.class);
    
    // Parent remote connection
    protected PacketTransportEndpoint transportEndpoint;
    
    // Runtime
    private boolean traceEnabled;
    private LinkedList prefetchQueue = new LinkedList<>();
    private Semaphore prefetchSemaphore = new Semaphore(0);
    protected boolean donePrefetching = false;
    private AsyncTaskManager asyncTaskManager;
    
    // Settings
    private boolean logListenersFailures;
    
    /**
     * Constructor
     */
    public RemoteMessageConsumer(IntegerID consumerId,
    		                     RemoteSession session, 
                                 Destination destination, 
                                 String messageSelector, 
                                 boolean noLocal) throws JMSException
    {
        super(session,destination,messageSelector,noLocal,consumerId);
        this.transportEndpoint = session.getTransportEndpoint();
        this.asyncTaskManager = ClientEnvironment.getAsyncTaskManager();
        this.traceEnabled = log.isTraceEnabled();
        this.logListenersFailures = getSettings().getBooleanProperty(FFMQCoreSettings.DELIVERY_LOG_LISTENERS_FAILURES, false);
        log.debug("New remote consumer ID is "+consumerId);
    }
        
    /**
     * Initialize the remote endpoint for this consumer
     */
    protected void remoteInit() throws JMSException
    {
        CreateConsumerQuery query = new CreateConsumerQuery();
        query.setConsumerId(id);
        query.setSessionId(session.getId());
        query.setDestination(destination);
        query.setMessageSelector(messageSelector);
        query.setNoLocal(noLocal);
        transportEndpoint.blockingRequest(query);        
    }

    /*
     * (non-Javadoc)
     * @see net.timewalker.ffmq4.common.session.AbstractMessageConsumer#shouldLogListenersFailures()
     */
    @Override
	protected final boolean shouldLogListenersFailures()
    {
        return logListenersFailures;
    }
    
    private final Settings getSettings()
    {
        return ClientEnvironment.getSettings();
    }
    
    /* (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 && session.getConnection().isStarted())
        	wakeUpMessageListenerAsync();
    }
    
    /* (non-Javadoc)
     * @see net.timewalker.ffmq4.common.session.AbstractMessageConsumer#onConsumerClose()
     */
    @Override
	protected final void onConsumerClose()
    {
    	super.onConsumerClose();

    	// Unlock waiting threads
    	prefetchSemaphore.release();
    	
    	try
    	{
        	CloseConsumerQuery query = new CloseConsumerQuery();
    		query.setSessionId(session.getId());
    		query.setConsumerId(id);
    		
    		// Append message IDs to rollback
    		synchronized (prefetchQueue)
            {
    		    while (!prefetchQueue.isEmpty())
    		    {
    		    	AbstractMessage msg = prefetchQueue.removeFirst();
    		        query.addUndeliveredMessageID(msg.getJMSMessageID());
    		    }
            }
    		
    		transportEndpoint.blockingRequest(query);
    	}
        catch (JMSException e)
        {
            ErrorTools.log(e, log);
        }
    }
    
    public final boolean addToPrefetchQueue( AbstractMessage prefetchedMessage , boolean donePrefetching )
    {
    	externalAccessLock.readLock().lock();
    	try
        {
            if (closed)
                return false; // Consumer is closed, refuse prefetched messages
            
        	synchronized (prefetchQueue)
    		{
    			if (traceEnabled)
            		log.trace("#"+id+" [PREFETCHED] from "+destination+" - "+prefetchedMessage);
    			
        		prefetchQueue.add(prefetchedMessage);
        		this.donePrefetching = donePrefetching;
    		}
        }
    	finally
    	{
    		externalAccessLock.readLock().unlock();
    	}
        prefetchSemaphore.release();
        
        // Wake up listener asynchronously
		if (messageListener != null)
			wakeUpMessageListenerAsync();
		
		return true;
    }
    
    private void wakeUpMessageListenerAsync()
    {
    	try
		{
			asyncTaskManager.execute(wakeUpTask);
		}
		catch (JMSException e)
		{
			ErrorTools.log(e, log);
		}
    }
    
    private AbstractMessage getFromPrefetchQueue( long timeout )
    {
    	boolean shouldPrefetchMore = false;
    	synchronized (prefetchQueue)
        {
    		if (donePrefetching && prefetchQueue.isEmpty())
    		{
    			shouldPrefetchMore = true;
    			donePrefetching = false;
    		}
        }
    	
    	// Ask for more messages if necessary (asynchronous)
    	if (shouldPrefetchMore)
    	{
	    	try
	    	{
		    	prefetchFromDestination();
	    	}
	    	catch (JMSException e)
	    	{
	    		log.error("Cannot prefetch more messages from remote server",e);
	    	}
    	}
    	
    	// Wait for a message to be available
    	try
    	{
    		if (timeout >= 0)
    		{
		    	if (!prefetchSemaphore.tryAcquire(timeout,TimeUnit.MILLISECONDS))
					return null;
    		}
    		else
    			prefetchSemaphore.acquire();
    	}
    	catch (InterruptedException e)
    	{
    		return null; // Abort
    	}
    		
    	// Get the message from the queue
    	AbstractMessage message;
    	externalAccessLock.readLock().lock();
    	try
        {
    		if (closed)
    			return null; // [JMS SPEC]
    		
	    	synchronized (prefetchQueue)
	        {
	    		// Consistency check
	        	if (prefetchQueue.isEmpty())
	        		throw new IllegalStateException("Prefetch queue is empty");
	        	
	        	message = prefetchQueue.removeFirst();
	        }
        }
    	finally
    	{
    		externalAccessLock.readLock().unlock();
    	}

    	((RemoteSession)session).notifyDeliveredMessage(message.getJMSMessageID());
    	
     	if (traceEnabled)
            log.trace("#"+id+" [GET PREFETCHED] in "+destination+" - "+message);

        // Make sure the message is fully deserialized and marked as read-only
     	message.ensureDeserializationLevel(MessageSerializationLevel.FULL);
		message.markAsReadOnly();
		
        return message;
    }
    
    /**
     * Prefetch messages from destination
     * @throws JMSException
     */
    private void prefetchFromDestination() throws JMSException
    {
        // Lazy test, do not synchronize here but on response (see addToPrefetchQueue())
    	if (closed)
            return;

    	if (traceEnabled)
    		log.trace("#"+id+" Prefetching more from destination "+destination);
    	
    	// Ask for more
        PrefetchQuery query = new PrefetchQuery();
        query.setSessionId(session.getId());
        query.setConsumerId(id);
        transportEndpoint.nonBlockingRequest(query);
    }
    
    /* (non-Javadoc)
     * @see net.timewalker.ffmq4.common.session.AbstractMessageConsumer#receiveFromDestination(long, boolean)
     */
    @Override
	protected final AbstractMessage receiveFromDestination(long timeout, boolean duplicateRequired) throws JMSException
    {
        if (closed)
            return null; // [JMS SPEC]

        // Get something from preftech queue
        return getFromPrefetchQueue(timeout);
    }
    
    /*
     * (non-Javadoc)
     * @see net.timewalker.ffmq4.common.session.AbstractMessageConsumer#wakeUp()
     */
    @Override
	protected final void wakeUp()
    {
    	// Note : Only called asynchronously by a wake up task after a notification packet has arrived
    	
    	// Check that the consumer is not closed
		if (closed)
			return;
    		
        // Check that the connection is started
        if (!session.getConnection().isStarted())
            return;
	        
        if (messageListener != null)
        {
        	wakeUpMessageListener();
        }
        else
        	throw new IllegalStateException("Unexpected message availability notification");
    }
    
    //----------------------------------------------------------------------------------------
    
    private final WakeUpTask wakeUpTask = new WakeUpTask();
    
    private final class WakeUpTask implements AsyncTask
    {
    	/**
		 * Constructor
		 */
		public WakeUpTask()
		{
			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()
        {
        	wakeUp();
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy