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

org.fusesource.stomp.jms.StompJmsMessageConsumer Maven / Gradle / Ivy

There is a newer version: 1.19
Show newest version
/**
 * Copyright (C) 2010-2011, FuseSource Corp.  All rights reserved.
 *
 *     http://fusesource.com
 *
 * The software in this package is published under the terms of the
 * CDDL license a copy of which has been included with this distribution
 * in the license.txt file.
 */

package org.fusesource.stomp.jms;

import org.fusesource.hawtbuf.AsciiBuffer;
import org.fusesource.hawtbuf.Buffer;
import org.fusesource.hawtdispatch.CustomDispatchSource;
import org.fusesource.hawtdispatch.Dispatch;
import org.fusesource.hawtdispatch.OrderedEventAggregator;
import org.fusesource.hawtdispatch.Task;
import org.fusesource.stomp.client.Promise;
import org.fusesource.stomp.codec.StompFrame;
import org.fusesource.stomp.jms.message.StompJmsMessage;

import javax.jms.IllegalStateException;
import javax.jms.*;
import java.io.IOException;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import static org.fusesource.stomp.client.Constants.*;

/**
 * implementation of a Jms Message Consumer
 */
public class StompJmsMessageConsumer implements MessageConsumer, StompJmsMessageListener {
    
    final StompJmsSession session;
    final StompJmsDestination destination;
    final AsciiBuffer id;
    final AtomicBoolean closed = new AtomicBoolean();
    boolean started;
    MessageListener messageListener;
    final String messageSelector;
    final MessageQueue messageQueue;
    final Lock lock = new ReentrantLock();
    final AtomicBoolean suspendedConnection = new AtomicBoolean();


    final CustomDispatchSource ackSource;

    class AckCallbackFuture extends Promise {
        AsciiBuffer id;
        private final AsciiBuffer tx;

        public AckCallbackFuture(AsciiBuffer id, AsciiBuffer tx) {
            this.id = id;
            this.tx = tx;
        }
    }

    protected StompJmsMessageConsumer(final AsciiBuffer id, StompJmsSession s, StompJmsDestination destination, String selector) throws JMSException {
        this.id = id;
        this.session = s;
        this.destination = destination;
        this.messageSelector = selector;

        if(  session.acknowledgementMode==Session.SESSION_TRANSACTED ) {
            this.messageQueue = new TxMessageQueue(session.consumerMessageBufferSize);
        } else {
            this.messageQueue = new MessageQueue(session.consumerMessageBufferSize);
        }

        if(  session.acknowledgementMode==StompJmsSession.SERVER_AUTO_ACKNOWLEDGE ) {
            // Then the STOMP client does not need to issue acks to the server, we suspend
            // TCP reads to avoid memory overruns.
            ackSource = null;
        } else {
            ackSource = Dispatch.createSource(new OrderedEventAggregator() {
                public AckCallbackFuture mergeEvent(AckCallbackFuture previous, AckCallbackFuture events) {
                    return events;
                }
                public AckCallbackFuture mergeEvents(AckCallbackFuture previous, AckCallbackFuture events) {
                    return events;
                }
            }, session.getChannel().connection.getDispatchQueue());

            ackSource.setEventHandler(new Task() {
                public void run() {
                    AckCallbackFuture ack = ackSource.getData();
                    try {
                        switch( session.acknowledgementMode ) {
                            case Session.CLIENT_ACKNOWLEDGE:
                                session.channel.ackMessage(id, ack.id, null, ack);
                                break;
                            case Session.AUTO_ACKNOWLEDGE:
                                session.channel.ackMessage(id, ack.id, null, ack);
                                break;
                            case Session.DUPS_OK_ACKNOWLEDGE:
                                session.channel.ackMessage(id, ack.id, null, null);
                                ack.onSuccess(null);
                                break;
                            case Session.SESSION_TRANSACTED:
                                session.channel.ackMessage(id, ack.id, ack.tx, null);
                                ack.onSuccess(null);
                                break;
                            case StompJmsSession.SERVER_AUTO_ACKNOWLEDGE:
                                throw new IllegalStateException("This should never get called.");
                        }
                    } catch (JMSException e) {
                        ack.onFailure(e);
                        session.connection.onException(e);
                    }
                }
            });
            ackSource.resume();
        }
    }

    public void init() throws JMSException {
        session.add(this);
    }

    public boolean isDurableSubscription() {
        return false;
    }


    public boolean isBrowser() {
        return false;
    }

    /**
     * @throws JMSException
     * @see javax.jms.MessageConsumer#close()
     */
    public void close() throws JMSException {
        if(closed.compareAndSet(false, true)) {
            this.session.remove(this);
            if( suspendedConnection.compareAndSet(true, false) ) {
                session.channel.connection().resume();
            }
        }
    }


    public MessageListener getMessageListener() throws JMSException {
        checkClosed();
        return this.messageListener;
    }

    /**
     * @return the Message Selector
     * @throws JMSException
     * @see javax.jms.MessageConsumer#getMessageSelector()
     */
    public String getMessageSelector() throws JMSException {
        checkClosed();
        return this.messageSelector;
    }

    /**
     * @return a Message or null if closed during the operation
     * @throws JMSException
     * @see javax.jms.MessageConsumer#receive()
     */
    public Message receive() throws JMSException {
        checkClosed();
        try {
            return copy(ack(this.messageQueue.dequeue(-1)));
        } catch (Exception e) {
            throw StompJmsExceptionSupport.create(e);
        }
    }

    /**
     * @param timeout
     * @return a MEssage or null
     * @throws JMSException
     * @see javax.jms.MessageConsumer#receive(long)
     */
    public Message receive(long timeout) throws JMSException {
        checkClosed();
        try {
            return copy(ack(this.messageQueue.dequeue(timeout)));
        } catch (InterruptedException e) {
            throw StompJmsExceptionSupport.create(e);
        }
    }

    /**
     * @return a Message or null
     * @throws JMSException
     * @see javax.jms.MessageConsumer#receiveNoWait()
     */
    public Message receiveNoWait() throws JMSException {
        checkClosed();
        Message result = copy(ack(this.messageQueue.dequeueNoWait()));
        return result;
    }

    /**
     * @param listener
     * @throws JMSException
     * @see javax.jms.MessageConsumer#setMessageListener(javax.jms.MessageListener)
     */
    public void setMessageListener(MessageListener listener) throws JMSException {
        checkClosed();
        this.messageListener = listener;
        drainMessageQueueToListener();

    }


    protected void checkClosed() throws IllegalStateException {
        if (this.closed.get()) {
            throw new IllegalStateException("The MessageProducer is closed");
        }
    }

    StompJmsMessage copy(final StompJmsMessage message) throws JMSException {
        if( message == null ) {
            return null;
        }
        return message.copy();
    }

    StompJmsMessage ack(final StompJmsMessage message) {
        if( message!=null ) {
            if( message.getAcknowledgeCallback()!=null ) {
                // Message has been received by the app.. expand the credit window
                // so that we receive more messages.
                if( session.connection.isConnnectedToApolloServer ) {

                    final Buffer content = message.getFrame().content();
                    String credit = "1";
                    if( content!=null ) {
                        credit += ","+content.length();
                    }

                    StompFrame frame = new StompFrame();
                    frame.action(ACK);
                    frame.headerMap().put(SUBSCRIPTION, id);
                    frame.headerMap().put(CREDIT, AsciiBuffer.ascii(credit));

                    try {
                        session.channel.sendFrame(frame);
                    } catch (IOException ignore) {
                    }
                }
                // don't actually ack yet.. client code does it.
                return message;
            }
            doAck(message);
        }
        return message;
    }

    private void doAck(final StompJmsMessage message) {
        if( ackSource==null ) {
            // We may need to resume the message flow.
            if( !this.messageQueue.isFull() ) {
                if( suspendedConnection.compareAndSet(true, false) ) {
                    session.channel.connection().resume();
                }
            }
        } else {
            final AckCallbackFuture future = new AckCallbackFuture(message.getMessageID(), session.currentTransactionId);
            ackSource.merge(future);
            try {
                future.await();
            } catch (Exception e) {
                throw new RuntimeException("Exception occurred sending ACK for message id : " + message.getMessageID(), e);
            }
        }
    }

    /**
     * @param message
     */
    public void onMessage(final StompJmsMessage message) {
        lock.lock();
        try {
            if( session.acknowledgementMode == Session.CLIENT_ACKNOWLEDGE ) {
                message.setAcknowledgeCallback(new Callable(){
                    public Void call() throws Exception {
                        if( session.channel == null ) {
                            throw new javax.jms.IllegalStateException("Session closed.");
                        }
                        doAck(message);
                        return null;
                    }
                });
            }
//            System.out.println(""+session.channel.getSocket().getLocalAddress() +" recv "+ message.getMessageID());
            this.messageQueue.enqueue(message);
            // We may need to suspend the message flow.
            if( ackSource==null && this.messageQueue.isFull() ) {
                if(suspendedConnection.compareAndSet(false, true) ) {
                    session.channel.connection().suspend();
                }
            }
        } finally {
            lock.unlock();
        }
        if (this.messageListener != null && this.started) {
            session.getExecutor().execute(new Runnable() {
                public void run() {
                    StompJmsMessage message;
                    while( session.isStarted() && (message=messageQueue.dequeueNoWait()) !=null ) {
                        try {
                            messageListener.onMessage(copy(ack(message)));
                        } catch (Exception e) {
                            session.connection.onException(e);
                        }
                    }
                }
            });
        }
    }

    /**
     * @return the id
     */
    public AsciiBuffer getId() {
        return this.id;
    }

    /**
     * @return the Destination
     */
    public StompJmsDestination getDestination() {
        return this.destination;
    }

    public void start() {
        lock.lock();
        try {
            this.started = true;
            this.messageQueue.start();
            drainMessageQueueToListener();
        } finally {
            lock.unlock();
        }
    }

    public void stop() {
        lock.lock();
        try {
            this.started = false;
            this.messageQueue.stop();
        } finally {
            lock.unlock();
        }
    }

    void rollback() {
        ((TxMessageQueue)this.messageQueue).rollback();
    }

    void commit() {
        ((TxMessageQueue)this.messageQueue).commit();
    }

    void drainMessageQueueToListener() {
        MessageListener listener = this.messageListener;
        if (listener != null) {
            if (!this.messageQueue.isEmpty()) {
                List drain = this.messageQueue.removeAll();
                for (StompJmsMessage m : drain) {
                    final StompJmsMessage copy;
                    try {
                        listener.onMessage(copy(ack(m)));
                    } catch (Exception e) {
                        session.connection.onException(e);
                    }
                }
                drain.clear();
            }
        }
    }

    protected int getMessageQueueSize() {
        return this.messageQueue.size();
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy