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

net.timewalker.ffmq4.remote.session.RemoteSession 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.ArrayList;
import java.util.List;
import java.util.Vector;

import javax.jms.DeliveryMode;
import javax.jms.Destination;
import javax.jms.IllegalStateException;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageConsumer;
import javax.jms.MessageProducer;
import javax.jms.Queue;
import javax.jms.QueueBrowser;
import javax.jms.Session;
import javax.jms.TemporaryQueue;
import javax.jms.TemporaryTopic;
import javax.jms.Topic;
import javax.jms.TopicSubscriber;

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

import net.timewalker.ffmq4.FFMQClientSettings;
import net.timewalker.ffmq4.FFMQException;
import net.timewalker.ffmq4.client.ClientEnvironment;
import net.timewalker.ffmq4.common.destination.DestinationTools;
import net.timewalker.ffmq4.common.destination.TemporaryQueueRef;
import net.timewalker.ffmq4.common.destination.TemporaryTopicRef;
import net.timewalker.ffmq4.common.message.AbstractMessage;
import net.timewalker.ffmq4.common.message.MessageTools;
import net.timewalker.ffmq4.common.session.AbstractSession;
import net.timewalker.ffmq4.remote.connection.RemoteConnection;
import net.timewalker.ffmq4.storage.data.DataStoreFullException;
import net.timewalker.ffmq4.transport.PacketTransportEndpoint;
import net.timewalker.ffmq4.transport.packet.AbstractQueryPacket;
import net.timewalker.ffmq4.transport.packet.query.AcknowledgeQuery;
import net.timewalker.ffmq4.transport.packet.query.CloseSessionQuery;
import net.timewalker.ffmq4.transport.packet.query.CommitQuery;
import net.timewalker.ffmq4.transport.packet.query.CreateSessionQuery;
import net.timewalker.ffmq4.transport.packet.query.CreateTemporaryQueueQuery;
import net.timewalker.ffmq4.transport.packet.query.CreateTemporaryTopicQuery;
import net.timewalker.ffmq4.transport.packet.query.PutQuery;
import net.timewalker.ffmq4.transport.packet.query.RecoverQuery;
import net.timewalker.ffmq4.transport.packet.query.RollbackQuery;
import net.timewalker.ffmq4.transport.packet.query.UnsubscribeQuery;
import net.timewalker.ffmq4.transport.packet.response.CreateTemporaryQueueResponse;
import net.timewalker.ffmq4.transport.packet.response.CreateTemporaryTopicResponse;
import net.timewalker.ffmq4.utils.ErrorTools;
import net.timewalker.ffmq4.utils.StringTools;
import net.timewalker.ffmq4.utils.id.IntegerID;

/**
 * RemoteSession
 */
public class RemoteSession extends AbstractSession
{
    private static final Log log = LogFactory.getLog(RemoteSession.class);
    
    // Parent connection
    protected final PacketTransportEndpoint transportEndpoint;
    
    // Settings
    private boolean sendAcksAsync;
    private boolean allowSendAsync;
    private boolean retryOnQueueFull;
    private long retryTimeout;
    
    // Runtime
    private List deliveredMessageIDs = new Vector<>();
    private Object retryLock = new Object();
    private boolean debugEnabled = log.isDebugEnabled();
    private boolean synchronousAckRequired;
    
    /**
     * Constructor
     */
    public RemoteSession( IntegerID sessionId , RemoteConnection connection , PacketTransportEndpoint transportEndpoint , boolean transacted , int acknowledgeMode )
    {
        super(sessionId,connection,transacted,acknowledgeMode);
        this.transportEndpoint = transportEndpoint;
        
        // Load settings
        this.sendAcksAsync = ClientEnvironment.getSettings().getBooleanProperty(FFMQClientSettings.CONSUMER_SEND_ACKS_ASYNC, true);
        this.allowSendAsync = ClientEnvironment.getSettings().getBooleanProperty(FFMQClientSettings.PRODUCER_ALLOW_SEND_ASYNC, true);
        this.retryOnQueueFull = ClientEnvironment.getSettings().getBooleanProperty(FFMQClientSettings.PRODUCER_RETRY_ON_QUEUE_FULL, true);
        this.retryTimeout = ClientEnvironment.getSettings().getLongProperty(FFMQClientSettings.PRODUCER_RETRY_TIMEOUT, 30*1000);        
        log.debug("New remote session ID is "+sessionId);
    }

    /**
     * Initialize the remote endpoint for this session
     */
    public void remoteInit() throws JMSException
    {
        CreateSessionQuery query = new CreateSessionQuery();
        query.setSessionId(id);
        query.setTransacted(transacted);
        query.setAcknowledgeMode(acknowledgeMode);
        transportEndpoint.blockingRequest(query);
    }
    
    public final PacketTransportEndpoint getTransportEndpoint()
    {
        return transportEndpoint;
    }
    
    protected final void dispatch( Message message ) throws JMSException
    {
    	if (debugEnabled)
    		log.debug("#"+id+" Sending message "+message.getJMSMessageID());
    	
    	boolean asyncDispatch = transacted || (allowSendAsync && message.getJMSDeliveryMode() == DeliveryMode.NON_PERSISTENT); 
    			    	
        PutQuery query = new PutQuery();
        query.setSessionId(id);
        
        if (asyncDispatch)
        {
        	// Create a message copy to make sure the message is not modified concurrently
        	Message msgCopy = MessageTools.makeInternalCopy(message);
        	query.setMessage((AbstractMessage)msgCopy);
        }
        else
        	query.setMessage((AbstractMessage)message);

        if (asyncDispatch) 
        	transportEndpoint.nonBlockingRequest(query);
        else
        {
        	if (retryOnQueueFull)
        		retriableBlockingQuery(query, retryTimeout);
        	else
        		transportEndpoint.blockingRequest(query);
        }
    }
    
    /**
     * Add a delivered message ID
     * @param deliveredMessageID
     */
    public final void notifyDeliveredMessage( String deliveredMessageID )
    {
    	externalAccessLock.readLock().lock();
    	try
		{
	    	if (closed)
	    		return;
	    	
	    	if (debugEnabled)
	    		log.debug(this+" Adding delivered message ID : "+deliveredMessageID);
	    	
	    	this.deliveredMessageIDs.add(deliveredMessageID);
	    	if (!transacted && acknowledgeMode != Session.DUPS_OK_ACKNOWLEDGE)
	    		synchronousAckRequired = true;
		}
    	finally
    	{
    		externalAccessLock.readLock().unlock();
    	}
    }
    
    /* (non-Javadoc)
     * @see javax.jms.Session#createBrowser(javax.jms.Queue, java.lang.String)
     */
    @Override
	public QueueBrowser createBrowser(Queue queue, String messageSelector) throws JMSException
    {
    	externalAccessLock.readLock().lock();
    	try
		{
		    checkNotClosed();
		    RemoteQueueBrowser browser = new RemoteQueueBrowser(idProvider.createID(),
		    		                                            this,
		    		                                            queue,
		    		                                            messageSelector);
		    registerBrowser(browser);
		    browser.remoteInit();
		    return browser;
		}
    	finally
    	{
    		externalAccessLock.readLock().unlock();
    	}
    }

    /* (non-Javadoc)
     * @see javax.jms.Session#createConsumer(javax.jms.Destination, java.lang.String, boolean)
     */
    @Override
	public final MessageConsumer createConsumer(Destination destination, String messageSelector, boolean noLocal) throws JMSException
    {
    	externalAccessLock.readLock().lock();
    	try
		{
	        checkNotClosed();
	        RemoteMessageConsumer consumer =  new RemoteMessageConsumer(idProvider.createID(),
	        		                                                    this,
			                                                            DestinationTools.asRef(destination),
			                                                            messageSelector,
			                                                            noLocal);
	        registerConsumer(consumer);
	        consumer.remoteInit();
	        return consumer;
		}
    	finally
    	{
    		externalAccessLock.readLock().unlock();
    	}
    }

    /* (non-Javadoc)
     * @see javax.jms.Session#createDurableSubscriber(javax.jms.Topic, java.lang.String, java.lang.String, boolean)
     */
    @Override
	public TopicSubscriber createDurableSubscriber(Topic topic, String name, String messageSelector, boolean noLocal) throws JMSException
    {
    	externalAccessLock.readLock().lock();
    	try
		{
    		checkNotClosed();
	        RemoteDurableTopicSubscriber subscriber = new RemoteDurableTopicSubscriber(idProvider.createID(),
	        		                                                                   this,
	                                                                                   topic,
	                                                                                   messageSelector,
	                                                                                   noLocal,
	                                                                                   name);
	        registerConsumer(subscriber);
	        subscriber.remoteInit();
	        return subscriber;
		}
    	finally
    	{
    		externalAccessLock.readLock().unlock();
    	}
    }

    /* (non-Javadoc)
     * @see javax.jms.Session#createProducer(javax.jms.Destination)
     */
    @Override
	public final MessageProducer createProducer(Destination destination) throws JMSException
    {
    	externalAccessLock.readLock().lock();
    	try
		{
	        checkNotClosed();
	        RemoteMessageProducer producer = new RemoteMessageProducer(this,
	                                                                   DestinationTools.asRef(destination),
	                                                                   idProvider.createID());
	        registerProducer(producer);
	        return producer;
		}
    	finally
    	{
    		externalAccessLock.readLock().unlock();
    	}
    }

    /* (non-Javadoc)
     * @see javax.jms.Session#createTemporaryQueue()
     */
    @Override
	public TemporaryQueue createTemporaryQueue() throws JMSException
    {
    	externalAccessLock.readLock().lock();
    	try
		{
	        checkNotClosed();
	        
	        CreateTemporaryQueueQuery query = new CreateTemporaryQueueQuery();
	        query.setSessionId(id);
	        CreateTemporaryQueueResponse response = (CreateTemporaryQueueResponse)transportEndpoint.blockingRequest(query);
	        
	        return new TemporaryQueueRef(connection,response.getQueueName());
		}
    	finally
    	{
    		externalAccessLock.readLock().unlock();
    	}
    }

    /* (non-Javadoc)
     * @see javax.jms.Session#createTemporaryTopic()
     */
    @Override
	public TemporaryTopic createTemporaryTopic() throws JMSException
    {
    	externalAccessLock.readLock().lock();
    	try
		{
	        checkNotClosed();
	        
	        CreateTemporaryTopicQuery query = new CreateTemporaryTopicQuery();
	        query.setSessionId(id);
	        CreateTemporaryTopicResponse response = 
	            (CreateTemporaryTopicResponse)transportEndpoint.blockingRequest(query);
	        
	        return new TemporaryTopicRef(connection,response.getTopicName());
		}
    	finally
    	{
    		externalAccessLock.readLock().unlock();
    	}
    }

    /* (non-Javadoc)
     * @see javax.jms.Session#recover()
     */
    @Override
	public final void recover() throws JMSException
    {
    	if (transacted)
            throw new IllegalStateException("Session is transacted"); // [JMS SPEC]
    	
    	externalAccessLock.readLock().lock();
    	try
		{
	        checkNotClosed();

	        RecoverQuery query = new RecoverQuery();
	        query.setSessionId(id);
	        query.setDeliveredMessageIDs(deliveredMessageIDs);
	        transportEndpoint.blockingRequest(query);
	        deliveredMessageIDs.clear();
	        
	        synchronousAckRequired = false;
		}
    	finally
    	{
    		externalAccessLock.readLock().unlock();
    	}
    }

    /* (non-Javadoc)
     * @see javax.jms.Session#unsubscribe(java.lang.String)
     */
    @Override
	public void unsubscribe(String subscriptionName) throws JMSException
    {
    	if (StringTools.isEmpty(subscriptionName))
            throw new FFMQException("Empty subscription name","INVALID_SUBSCRIPTION_NAME");
    	
    	externalAccessLock.readLock().lock();
    	try
		{
	        checkNotClosed();
	        
	        UnsubscribeQuery query = new UnsubscribeQuery();
	        query.setSessionId(id);
	        query.setSubscriptionName(subscriptionName);
	        transportEndpoint.blockingRequest(query);
		}
    	finally
    	{
    		externalAccessLock.readLock().unlock();
    	}
    }

    /*
     * (non-Javadoc)
     * @see javax.jms.Session#commit()
     */
    @Override
	public final void commit() throws JMSException
    {
    	if (!transacted)
            throw new IllegalStateException("Session is not transacted"); // [JMS SPEC]
    	
    	log.debug("#"+id+" commit()");
    	
    	externalAccessLock.readLock().lock();
    	try
		{
	        checkNotClosed();
	        
	        final CommitQuery query = new CommitQuery();
	        query.setSessionId(id);
	        query.setDeliveredMessageIDs(deliveredMessageIDs);
	        
	        if (retryOnQueueFull)
	        	retriableBlockingQuery(query,retryTimeout);
	        else
		        transportEndpoint.blockingRequest(query);
	      
	        deliveredMessageIDs.clear();
		}
    	finally
    	{
    		externalAccessLock.readLock().unlock();
    	}
    }

    private void retriableBlockingQuery( AbstractQueryPacket query , long retryTimeout ) throws JMSException
    {
    	long retryWait = 50; // ms
    	long totalWait = 0;
    	
        while (true)
        {
	        try
	        {
	        	transportEndpoint.blockingRequest(query);
		        break;
	        }
	        catch (DataStoreFullException e)
	        {
	        	if (retryTimeout <= 0 || totalWait < retryTimeout)
	        	{
	        		// Release lock during passive wait
	        		externalAccessLock.readLock().unlock();
	        		try
					{
	        			synchronized (retryLock)
						{
	        				retryLock.wait(retryWait);
						}
					}
					catch (InterruptedException ex)
					{
						// Interrupted --> give up immediatly
						throw e;
					}
					finally
					{
						// Re-acquire lock during passive wait
						externalAccessLock.readLock().lock();
					}
					
					// Concurrently closed ?
        			if (closed)
        				throw new IllegalStateException("Session is closed");
					
        			// Update total wait time
					totalWait += retryWait;
					
					// Exponential wait delay growth
					if (totalWait < retryTimeout)
					{
						retryWait = retryWait*2;
						if (retryWait > 2000)
							retryWait = 2000;
						if (retryWait > retryTimeout-totalWait)
							retryWait = retryTimeout-totalWait;
					}
					
					continue; // Try again
	        	}

	        	// Give up ...
	        	throw e;
	        }
        }
    }
    
    /*
     * (non-Javadoc)
     * @see javax.jms.Session#rollback()
     */
    @Override
	public final void rollback() throws JMSException
    {
    	if (!transacted)
            throw new IllegalStateException("Session is not transacted"); // [JMS SPEC]
    	
    	log.debug("#"+id+" rollback()");
    	
    	externalAccessLock.readLock().lock();
    	try
		{
	        checkNotClosed();
	        
	        RollbackQuery query = new RollbackQuery();
	        query.setSessionId(id);
	        query.setDeliveredMessageIDs(deliveredMessageIDs);
	        transportEndpoint.blockingRequest(query);
	        deliveredMessageIDs.clear();
		}
    	finally
    	{
    		externalAccessLock.readLock().unlock();
    	}
    }

    /* (non-Javadoc)
     * @see net.timewalker.ffmq4.common.session.AbstractSession#onSessionClose()
     */
    @Override
	protected void onSessionClose()
    {
    	super.onSessionClose();
    	
    	if (retryOnQueueFull)
    	{
	    	synchronized (retryLock)
			{
	    		retryLock.notifyAll();	
			}
    	}
    	
    	try
    	{
        	CloseSessionQuery query = new CloseSessionQuery();
    	    query.setSessionId(id);
    	    transportEndpoint.blockingRequest(query);
    	}
        catch (JMSException e)
        {
            ErrorTools.log(e, log);
        }
    }
    
    /*
     * (non-Javadoc)
     * @see net.timewalker.ffmq4.common.session.AbstractSession#onSessionClosed()
     */
    @Override
	protected void onSessionClosed()
    {
        super.onSessionClosed();
        transportEndpoint.close();
    }
    
    /*
     * (non-Javadoc)
     * @see net.timewalker.ffmq4.common.session.AbstractSession#acknowledge()
     */
    @Override
	public final void acknowledge() throws JMSException
    {
    	if (transacted)
            throw new IllegalStateException("Session is transacted"); // [JMS SPEC]
    	
    	externalAccessLock.readLock().lock();
    	try
		{
	        checkNotClosed();
	        if (deliveredMessageIDs.isEmpty())
	        	throw new FFMQException("No received message to acknowledge","INTERNAL_ERROR");
	        
	        if (sendAcksAsync && !synchronousAckRequired)
	        {
	        	// Copy message list
	        	List messageIDs = new ArrayList<>(deliveredMessageIDs.size());
	        	for(int n=0;n




© 2015 - 2024 Weber Informatics LLC | Privacy Policy