001/**
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.activemq;
018
019import java.io.IOException;
020import java.util.ArrayList;
021import java.util.Collections;
022import java.util.HashMap;
023import java.util.Iterator;
024import java.util.LinkedList;
025import java.util.List;
026import java.util.Map;
027import java.util.Map.Entry;
028import java.util.concurrent.ExecutorService;
029import java.util.concurrent.Executors;
030import java.util.concurrent.atomic.AtomicBoolean;
031import java.util.concurrent.atomic.AtomicInteger;
032import java.util.concurrent.atomic.AtomicReference;
033
034import javax.jms.IllegalStateException;
035import javax.jms.InvalidDestinationException;
036import javax.jms.JMSException;
037import javax.jms.Message;
038import javax.jms.MessageConsumer;
039import javax.jms.MessageListener;
040import javax.jms.TransactionRolledBackException;
041
042import org.apache.activemq.blob.BlobDownloader;
043import org.apache.activemq.command.ActiveMQBlobMessage;
044import org.apache.activemq.command.ActiveMQDestination;
045import org.apache.activemq.command.ActiveMQMessage;
046import org.apache.activemq.command.ActiveMQObjectMessage;
047import org.apache.activemq.command.ActiveMQTempDestination;
048import org.apache.activemq.command.CommandTypes;
049import org.apache.activemq.command.ConsumerId;
050import org.apache.activemq.command.ConsumerInfo;
051import org.apache.activemq.command.MessageAck;
052import org.apache.activemq.command.MessageDispatch;
053import org.apache.activemq.command.MessageId;
054import org.apache.activemq.command.MessagePull;
055import org.apache.activemq.command.RemoveInfo;
056import org.apache.activemq.command.TransactionId;
057import org.apache.activemq.management.JMSConsumerStatsImpl;
058import org.apache.activemq.management.StatsCapable;
059import org.apache.activemq.management.StatsImpl;
060import org.apache.activemq.selector.SelectorParser;
061import org.apache.activemq.transaction.Synchronization;
062import org.apache.activemq.util.Callback;
063import org.apache.activemq.util.IntrospectionSupport;
064import org.apache.activemq.util.JMSExceptionSupport;
065import org.apache.activemq.util.ThreadPoolUtils;
066import org.slf4j.Logger;
067import org.slf4j.LoggerFactory;
068
069/**
070 * A client uses a <CODE>MessageConsumer</CODE> object to receive messages
071 * from a destination. A <CODE> MessageConsumer</CODE> object is created by
072 * passing a <CODE>Destination</CODE> object to a message-consumer creation
073 * method supplied by a session.
074 * <P>
075 * <CODE>MessageConsumer</CODE> is the parent interface for all message
076 * consumers.
077 * <P>
078 * A message consumer can be created with a message selector. A message selector
079 * allows the client to restrict the messages delivered to the message consumer
080 * to those that match the selector.
081 * <P>
082 * A client may either synchronously receive a message consumer's messages or
083 * have the consumer asynchronously deliver them as they arrive.
084 * <P>
085 * For synchronous receipt, a client can request the next message from a message
086 * consumer using one of its <CODE> receive</CODE> methods. There are several
087 * variations of <CODE>receive</CODE> that allow a client to poll or wait for
088 * the next message.
089 * <P>
090 * For asynchronous delivery, a client can register a
091 * <CODE>MessageListener</CODE> object with a message consumer. As messages
092 * arrive at the message consumer, it delivers them by calling the
093 * <CODE>MessageListener</CODE>'s<CODE>
094 * onMessage</CODE> method.
095 * <P>
096 * It is a client programming error for a <CODE>MessageListener</CODE> to
097 * throw an exception.
098 *
099 *
100 * @see javax.jms.MessageConsumer
101 * @see javax.jms.QueueReceiver
102 * @see javax.jms.TopicSubscriber
103 * @see javax.jms.Session
104 */
105public class ActiveMQMessageConsumer implements MessageAvailableConsumer, StatsCapable, ActiveMQDispatcher {
106
107    @SuppressWarnings("serial")
108    class PreviouslyDeliveredMap<K, V> extends HashMap<K, V> {
109        final TransactionId transactionId;
110        public PreviouslyDeliveredMap(TransactionId transactionId) {
111            this.transactionId = transactionId;
112        }
113    }
114    class PreviouslyDelivered {
115        org.apache.activemq.command.Message message;
116        boolean redelivered;
117
118        PreviouslyDelivered(MessageDispatch messageDispatch) {
119            message = messageDispatch.getMessage();
120        }
121
122        PreviouslyDelivered(MessageDispatch messageDispatch, boolean redelivered) {
123            message = messageDispatch.getMessage();
124            this.redelivered = redelivered;
125        }
126    }
127
128    private static final Logger LOG = LoggerFactory.getLogger(ActiveMQMessageConsumer.class);
129    protected final ActiveMQSession session;
130    protected final ConsumerInfo info;
131
132    // These are the messages waiting to be delivered to the client
133    protected final MessageDispatchChannel unconsumedMessages;
134
135    // The are the messages that were delivered to the consumer but that have
136    // not been acknowledged. It's kept in reverse order since we
137    // Always walk list in reverse order.
138    protected final LinkedList<MessageDispatch> deliveredMessages = new LinkedList<MessageDispatch>();
139    // track duplicate deliveries in a transaction such that the tx integrity can be validated
140    private PreviouslyDeliveredMap<MessageId, PreviouslyDelivered> previouslyDeliveredMessages;
141    private int deliveredCounter;
142    private int additionalWindowSize;
143    private long redeliveryDelay;
144    private int ackCounter;
145    private int dispatchedCount;
146    private final AtomicReference<MessageListener> messageListener = new AtomicReference<MessageListener>();
147    private final JMSConsumerStatsImpl stats;
148
149    private final String selector;
150    private boolean synchronizationRegistered;
151    private final AtomicBoolean started = new AtomicBoolean(false);
152
153    private MessageAvailableListener availableListener;
154
155    private RedeliveryPolicy redeliveryPolicy;
156    private boolean optimizeAcknowledge;
157    private final AtomicBoolean deliveryingAcknowledgements = new AtomicBoolean();
158    private ExecutorService executorService;
159    private MessageTransformer transformer;
160    private volatile boolean clearDeliveredList;
161    AtomicInteger inProgressClearRequiredFlag = new AtomicInteger(0);
162
163    private MessageAck pendingAck;
164    private long lastDeliveredSequenceId = -1;
165
166    private IOException failureError;
167
168    private long optimizeAckTimestamp = System.currentTimeMillis();
169    private long optimizeAcknowledgeTimeOut = 0;
170    private long optimizedAckScheduledAckInterval = 0;
171    private Runnable optimizedAckTask;
172    private long failoverRedeliveryWaitPeriod = 0;
173    private boolean transactedIndividualAck = false;
174    private boolean nonBlockingRedelivery = false;
175    private boolean consumerExpiryCheckEnabled = true;
176
177    /**
178     * Create a MessageConsumer
179     *
180     * @param session
181     * @param dest
182     * @param name
183     * @param selector
184     * @param prefetch
185     * @param maximumPendingMessageCount
186     * @param noLocal
187     * @param browser
188     * @param dispatchAsync
189     * @param messageListener
190     * @throws JMSException
191     */
192    public ActiveMQMessageConsumer(ActiveMQSession session, ConsumerId consumerId, ActiveMQDestination dest,
193            String name, String selector, int prefetch,
194            int maximumPendingMessageCount, boolean noLocal, boolean browser,
195            boolean dispatchAsync, MessageListener messageListener) throws JMSException {
196        if (dest == null) {
197            throw new InvalidDestinationException("Don't understand null destinations");
198        } else if (dest.getPhysicalName() == null) {
199            throw new InvalidDestinationException("The destination object was not given a physical name.");
200        } else if (dest.isTemporary()) {
201            String physicalName = dest.getPhysicalName();
202
203            if (physicalName == null) {
204                throw new IllegalArgumentException("Physical name of Destination should be valid: " + dest);
205            }
206
207            String connectionID = session.connection.getConnectionInfo().getConnectionId().getValue();
208
209            if (physicalName.indexOf(connectionID) < 0) {
210                throw new InvalidDestinationException("Cannot use a Temporary destination from another Connection");
211            }
212
213            if (session.connection.isDeleted(dest)) {
214                throw new InvalidDestinationException("Cannot use a Temporary destination that has been deleted");
215            }
216            if (prefetch < 0) {
217                throw new JMSException("Cannot have a prefetch size less than zero");
218            }
219        }
220        if (session.connection.isMessagePrioritySupported()) {
221            this.unconsumedMessages = new SimplePriorityMessageDispatchChannel();
222        }else {
223            this.unconsumedMessages = new FifoMessageDispatchChannel();
224        }
225
226        this.session = session;
227        this.redeliveryPolicy = session.connection.getRedeliveryPolicyMap().getEntryFor(dest);
228        if (this.redeliveryPolicy == null) {
229            this.redeliveryPolicy = new RedeliveryPolicy();
230        }
231        setTransformer(session.getTransformer());
232
233        this.info = new ConsumerInfo(consumerId);
234        this.info.setExclusive(this.session.connection.isExclusiveConsumer());
235        this.info.setClientId(this.session.connection.getClientID());
236        this.info.setSubscriptionName(name);
237        this.info.setPrefetchSize(prefetch);
238        this.info.setCurrentPrefetchSize(prefetch);
239        this.info.setMaximumPendingMessageLimit(maximumPendingMessageCount);
240        this.info.setNoLocal(noLocal);
241        this.info.setDispatchAsync(dispatchAsync);
242        this.info.setRetroactive(this.session.connection.isUseRetroactiveConsumer());
243        this.info.setSelector(null);
244
245        // Allows the options on the destination to configure the consumerInfo
246        if (dest.getOptions() != null) {
247            Map<String, Object> options = IntrospectionSupport.extractProperties(
248                new HashMap<String, Object>(dest.getOptions()), "consumer.");
249            IntrospectionSupport.setProperties(this.info, options);
250            if (options.size() > 0) {
251                String msg = "There are " + options.size()
252                    + " consumer options that couldn't be set on the consumer."
253                    + " Check the options are spelled correctly."
254                    + " Unknown parameters=[" + options + "]."
255                    + " This consumer cannot be started.";
256                LOG.warn(msg);
257                throw new ConfigurationException(msg);
258            }
259        }
260
261        this.info.setDestination(dest);
262        this.info.setBrowser(browser);
263        if (selector != null && selector.trim().length() != 0) {
264            // Validate the selector
265            SelectorParser.parse(selector);
266            this.info.setSelector(selector);
267            this.selector = selector;
268        } else if (info.getSelector() != null) {
269            // Validate the selector
270            SelectorParser.parse(this.info.getSelector());
271            this.selector = this.info.getSelector();
272        } else {
273            this.selector = null;
274        }
275
276        this.stats = new JMSConsumerStatsImpl(session.getSessionStats(), dest);
277        this.optimizeAcknowledge = session.connection.isOptimizeAcknowledge() && session.isAutoAcknowledge()
278                                   && !info.isBrowser();
279        if (this.optimizeAcknowledge) {
280            this.optimizeAcknowledgeTimeOut = session.connection.getOptimizeAcknowledgeTimeOut();
281            setOptimizedAckScheduledAckInterval(session.connection.getOptimizedAckScheduledAckInterval());
282        }
283
284        this.info.setOptimizedAcknowledge(this.optimizeAcknowledge);
285        this.failoverRedeliveryWaitPeriod = session.connection.getConsumerFailoverRedeliveryWaitPeriod();
286        this.nonBlockingRedelivery = session.connection.isNonBlockingRedelivery();
287        this.transactedIndividualAck = session.connection.isTransactedIndividualAck()
288                        || this.nonBlockingRedelivery
289                        || session.connection.isMessagePrioritySupported();
290        this.consumerExpiryCheckEnabled = session.connection.isConsumerExpiryCheckEnabled();
291        if (messageListener != null) {
292            setMessageListener(messageListener);
293        }
294        try {
295            this.session.addConsumer(this);
296            this.session.syncSendPacket(info);
297        } catch (JMSException e) {
298            this.session.removeConsumer(this);
299            throw e;
300        }
301
302        if (session.connection.isStarted()) {
303            start();
304        }
305    }
306
307    private boolean isAutoAcknowledgeEach() {
308        return session.isAutoAcknowledge() || ( session.isDupsOkAcknowledge() && getDestination().isQueue() );
309    }
310
311    private boolean isAutoAcknowledgeBatch() {
312        return session.isDupsOkAcknowledge() && !getDestination().isQueue() ;
313    }
314
315    @Override
316    public StatsImpl getStats() {
317        return stats;
318    }
319
320    public JMSConsumerStatsImpl getConsumerStats() {
321        return stats;
322    }
323
324    public RedeliveryPolicy getRedeliveryPolicy() {
325        return redeliveryPolicy;
326    }
327
328    /**
329     * Sets the redelivery policy used when messages are redelivered
330     */
331    public void setRedeliveryPolicy(RedeliveryPolicy redeliveryPolicy) {
332        this.redeliveryPolicy = redeliveryPolicy;
333    }
334
335    public MessageTransformer getTransformer() {
336        return transformer;
337    }
338
339    /**
340     * Sets the transformer used to transform messages before they are sent on
341     * to the JMS bus
342     */
343    public void setTransformer(MessageTransformer transformer) {
344        this.transformer = transformer;
345    }
346
347    /**
348     * @return Returns the value.
349     */
350    public ConsumerId getConsumerId() {
351        return info.getConsumerId();
352    }
353
354    /**
355     * @return the consumer name - used for durable consumers
356     */
357    public String getConsumerName() {
358        return this.info.getSubscriptionName();
359    }
360
361    /**
362     * @return true if this consumer does not accept locally produced messages
363     */
364    protected boolean isNoLocal() {
365        return info.isNoLocal();
366    }
367
368    /**
369     * Retrieve is a browser
370     *
371     * @return true if a browser
372     */
373    protected boolean isBrowser() {
374        return info.isBrowser();
375    }
376
377    /**
378     * @return ActiveMQDestination
379     */
380    protected ActiveMQDestination getDestination() {
381        return info.getDestination();
382    }
383
384    /**
385     * @return Returns the prefetchNumber.
386     */
387    public int getPrefetchNumber() {
388        return info.getPrefetchSize();
389    }
390
391    /**
392     * @return true if this is a durable topic subscriber
393     */
394    public boolean isDurableSubscriber() {
395        return info.getSubscriptionName() != null && info.getDestination().isTopic();
396    }
397
398    /**
399     * Gets this message consumer's message selector expression.
400     *
401     * @return this message consumer's message selector, or null if no message
402     *         selector exists for the message consumer (that is, if the message
403     *         selector was not set or was set to null or the empty string)
404     * @throws JMSException if the JMS provider fails to receive the next
405     *                 message due to some internal error.
406     */
407    @Override
408    public String getMessageSelector() throws JMSException {
409        checkClosed();
410        return selector;
411    }
412
413    /**
414     * Gets the message consumer's <CODE>MessageListener</CODE>.
415     *
416     * @return the listener for the message consumer, or null if no listener is
417     *         set
418     * @throws JMSException if the JMS provider fails to get the message
419     *                 listener due to some internal error.
420     * @see javax.jms.MessageConsumer#setMessageListener(javax.jms.MessageListener)
421     */
422    @Override
423    public MessageListener getMessageListener() throws JMSException {
424        checkClosed();
425        return this.messageListener.get();
426    }
427
428    /**
429     * Sets the message consumer's <CODE>MessageListener</CODE>.
430     * <P>
431     * Setting the message listener to null is the equivalent of unsetting the
432     * message listener for the message consumer.
433     * <P>
434     * The effect of calling <CODE>MessageConsumer.setMessageListener</CODE>
435     * while messages are being consumed by an existing listener or the consumer
436     * is being used to consume messages synchronously is undefined.
437     *
438     * @param listener the listener to which the messages are to be delivered
439     * @throws JMSException if the JMS provider fails to receive the next
440     *                 message due to some internal error.
441     * @see javax.jms.MessageConsumer#getMessageListener
442     */
443    @Override
444    public void setMessageListener(MessageListener listener) throws JMSException {
445        checkClosed();
446        if (info.getPrefetchSize() == 0) {
447            throw new JMSException("Illegal prefetch size of zero. This setting is not supported for asynchronous consumers please set a value of at least 1");
448        }
449        if (listener != null) {
450            boolean wasRunning = session.isRunning();
451            if (wasRunning) {
452                session.stop();
453            }
454
455            this.messageListener.set(listener);
456            session.redispatch(this, unconsumedMessages);
457
458            if (wasRunning) {
459                session.start();
460            }
461        } else {
462            this.messageListener.set(null);
463        }
464    }
465
466    @Override
467    public MessageAvailableListener getAvailableListener() {
468        return availableListener;
469    }
470
471    /**
472     * Sets the listener used to notify synchronous consumers that there is a
473     * message available so that the {@link MessageConsumer#receiveNoWait()} can
474     * be called.
475     */
476    @Override
477    public void setAvailableListener(MessageAvailableListener availableListener) {
478        this.availableListener = availableListener;
479    }
480
481    /**
482     * Used to get an enqueued message from the unconsumedMessages list. The
483     * amount of time this method blocks is based on the timeout value. - if
484     * timeout==-1 then it blocks until a message is received. - if timeout==0
485     * then it it tries to not block at all, it returns a message if it is
486     * available - if timeout>0 then it blocks up to timeout amount of time.
487     * Expired messages will consumed by this method.
488     *
489     * @throws JMSException
490     * @return null if we timeout or if the consumer is closed.
491     */
492    private MessageDispatch dequeue(long timeout) throws JMSException {
493        try {
494            long deadline = 0;
495            if (timeout > 0) {
496                deadline = System.currentTimeMillis() + timeout;
497            }
498            while (true) {
499                MessageDispatch md = unconsumedMessages.dequeue(timeout);
500                if (md == null) {
501                    if (timeout > 0 && !unconsumedMessages.isClosed()) {
502                        timeout = Math.max(deadline - System.currentTimeMillis(), 0);
503                    } else {
504                        if (failureError != null) {
505                            throw JMSExceptionSupport.create(failureError);
506                        } else {
507                            return null;
508                        }
509                    }
510                } else if (md.getMessage() == null) {
511                    return null;
512                } else if (consumeExpiredMessage(md)) {
513                    LOG.debug("{} received expired message: {}", getConsumerId(), md);
514                    beforeMessageIsConsumed(md);
515                    afterMessageIsConsumed(md, true);
516                    if (timeout > 0) {
517                        timeout = Math.max(deadline - System.currentTimeMillis(), 0);
518                    }
519                    sendPullCommand(timeout);
520                } else if (redeliveryExceeded(md)) {
521                    LOG.debug("{} received with excessive redelivered: {}", getConsumerId(), md);
522                    poisonAck(md, "Dispatch[" + md.getRedeliveryCounter() + "] to " + getConsumerId() + " exceeds redelivery policy limit:" + redeliveryPolicy);
523                    if (timeout > 0) {
524                        timeout = Math.max(deadline - System.currentTimeMillis(), 0);
525                    }
526                    sendPullCommand(timeout);
527                } else {
528                    if (LOG.isTraceEnabled()) {
529                        LOG.trace(getConsumerId() + " received message: " + md);
530                    }
531                    return md;
532                }
533            }
534        } catch (InterruptedException e) {
535            Thread.currentThread().interrupt();
536            throw JMSExceptionSupport.create(e);
537        }
538    }
539
540    private boolean consumeExpiredMessage(MessageDispatch dispatch) {
541        return isConsumerExpiryCheckEnabled() && dispatch.getMessage().isExpired();
542    }
543
544    private void poisonAck(MessageDispatch md, String cause) throws JMSException {
545        MessageAck poisonAck = new MessageAck(md, MessageAck.POISON_ACK_TYPE, 1);
546        poisonAck.setFirstMessageId(md.getMessage().getMessageId());
547        poisonAck.setPoisonCause(new Throwable(cause));
548        session.sendAck(poisonAck);
549    }
550
551    private boolean redeliveryExceeded(MessageDispatch md) {
552        try {
553            return session.getTransacted()
554                    && redeliveryPolicy != null
555                    && redeliveryPolicy.isPreDispatchCheck()
556                    && redeliveryPolicy.getMaximumRedeliveries() != RedeliveryPolicy.NO_MAXIMUM_REDELIVERIES
557                    && md.getRedeliveryCounter() > redeliveryPolicy.getMaximumRedeliveries()
558                    // redeliveryCounter > x expected after resend via brokerRedeliveryPlugin
559                    && md.getMessage().getProperty("redeliveryDelay") == null;
560        } catch (Exception ignored) {
561            return false;
562        }
563    }
564
565    /**
566     * Receives the next message produced for this message consumer.
567     * <P>
568     * This call blocks indefinitely until a message is produced or until this
569     * message consumer is closed.
570     * <P>
571     * If this <CODE>receive</CODE> is done within a transaction, the consumer
572     * retains the message until the transaction commits.
573     *
574     * @return the next message produced for this message consumer, or null if
575     *         this message consumer is concurrently closed
576     */
577    @Override
578    public Message receive() throws JMSException {
579        checkClosed();
580        checkMessageListener();
581
582        sendPullCommand(0);
583        MessageDispatch md = dequeue(-1);
584        if (md == null) {
585            return null;
586        }
587
588        beforeMessageIsConsumed(md);
589        afterMessageIsConsumed(md, false);
590
591        return createActiveMQMessage(md);
592    }
593
594    /**
595     * @param md
596     *      the MessageDispatch that arrived from the Broker.
597     *
598     * @return an ActiveMQMessage initialized from the Message in the dispatch.
599     */
600    private ActiveMQMessage createActiveMQMessage(final MessageDispatch md) throws JMSException {
601        ActiveMQMessage m = (ActiveMQMessage)md.getMessage().copy();
602        if (m.getDataStructureType()==CommandTypes.ACTIVEMQ_BLOB_MESSAGE) {
603            ((ActiveMQBlobMessage)m).setBlobDownloader(new BlobDownloader(session.getBlobTransferPolicy()));
604        }
605        if (m.getDataStructureType() == CommandTypes.ACTIVEMQ_OBJECT_MESSAGE) {
606            ((ActiveMQObjectMessage)m).setTrustAllPackages(session.getConnection().isTrustAllPackages());
607            ((ActiveMQObjectMessage)m).setTrustedPackages(session.getConnection().getTrustedPackages());
608        }
609        if (transformer != null) {
610            Message transformedMessage = transformer.consumerTransform(session, this, m);
611            if (transformedMessage != null) {
612                m = ActiveMQMessageTransformation.transformMessage(transformedMessage, session.connection);
613            }
614        }
615        if (session.isClientAcknowledge()) {
616            m.setAcknowledgeCallback(new Callback() {
617                @Override
618                public void execute() throws Exception {
619                    checkClosed();
620                    session.checkClosed();
621                    session.acknowledge();
622                }
623            });
624        } else if (session.isIndividualAcknowledge()) {
625            m.setAcknowledgeCallback(new Callback() {
626                @Override
627                public void execute() throws Exception {
628                    checkClosed();
629                    session.checkClosed();
630                    acknowledge(md);
631                }
632            });
633        }
634        return m;
635    }
636
637    /**
638     * Receives the next message that arrives within the specified timeout
639     * interval.
640     * <P>
641     * This call blocks until a message arrives, the timeout expires, or this
642     * message consumer is closed. A <CODE>timeout</CODE> of zero never
643     * expires, and the call blocks indefinitely.
644     *
645     * @param timeout the timeout value (in milliseconds), a time out of zero
646     *                never expires.
647     * @return the next message produced for this message consumer, or null if
648     *         the timeout expires or this message consumer is concurrently
649     *         closed
650     */
651    @Override
652    public Message receive(long timeout) throws JMSException {
653        checkClosed();
654        checkMessageListener();
655        if (timeout == 0) {
656            return this.receive();
657        }
658
659        sendPullCommand(timeout);
660        while (timeout > 0) {
661
662            MessageDispatch md;
663            if (info.getPrefetchSize() == 0) {
664                md = dequeue(-1); // We let the broker let us know when we timeout.
665            } else {
666                md = dequeue(timeout);
667            }
668
669            if (md == null) {
670                return null;
671            }
672
673            beforeMessageIsConsumed(md);
674            afterMessageIsConsumed(md, false);
675            return createActiveMQMessage(md);
676        }
677        return null;
678    }
679
680    /**
681     * Receives the next message if one is immediately available.
682     *
683     * @return the next message produced for this message consumer, or null if
684     *         one is not available
685     * @throws JMSException if the JMS provider fails to receive the next
686     *                 message due to some internal error.
687     */
688    @Override
689    public Message receiveNoWait() throws JMSException {
690        checkClosed();
691        checkMessageListener();
692        sendPullCommand(-1);
693
694        MessageDispatch md;
695        if (info.getPrefetchSize() == 0) {
696            md = dequeue(-1); // We let the broker let us know when we
697            // timeout.
698        } else {
699            md = dequeue(0);
700        }
701
702        if (md == null) {
703            return null;
704        }
705
706        beforeMessageIsConsumed(md);
707        afterMessageIsConsumed(md, false);
708        return createActiveMQMessage(md);
709    }
710
711    /**
712     * Closes the message consumer.
713     * <P>
714     * Since a provider may allocate some resources on behalf of a <CODE>
715     * MessageConsumer</CODE>
716     * outside the Java virtual machine, clients should close them when they are
717     * not needed. Relying on garbage collection to eventually reclaim these
718     * resources may not be timely enough.
719     * <P>
720     * This call blocks until a <CODE>receive</CODE> or message listener in
721     * progress has completed. A blocked message consumer <CODE>receive </CODE>
722     * call returns null when this message consumer is closed.
723     *
724     * @throws JMSException if the JMS provider fails to close the consumer due
725     *                 to some internal error.
726     */
727    @Override
728    public void close() throws JMSException {
729        if (!unconsumedMessages.isClosed()) {
730            if (!deliveredMessages.isEmpty() && session.getTransactionContext().isInTransaction()) {
731                session.getTransactionContext().addSynchronization(new Synchronization() {
732                    @Override
733                    public void afterCommit() throws Exception {
734                        doClose();
735                    }
736
737                    @Override
738                    public void afterRollback() throws Exception {
739                        doClose();
740                    }
741                });
742            } else {
743                doClose();
744            }
745        }
746    }
747
748    void doClose() throws JMSException {
749        dispose();
750        RemoveInfo removeCommand = info.createRemoveCommand();
751        LOG.debug("remove: {}, lastDeliveredSequenceId: {}", getConsumerId(), lastDeliveredSequenceId);
752        removeCommand.setLastDeliveredSequenceId(lastDeliveredSequenceId);
753        this.session.asyncSendPacket(removeCommand);
754    }
755
756    void inProgressClearRequired() {
757        inProgressClearRequiredFlag.incrementAndGet();
758        // deal with delivered messages async to avoid lock contention with in progress acks
759        clearDeliveredList = true;
760        // force a rollback if we will be acking in a transaction after/during failover
761        // bc acks are async they may not get there reliably on reconnect and the consumer
762        // may not be aware of the reconnect in a timely fashion if in onMessage
763        if (!deliveredMessages.isEmpty() && session.getTransactionContext().isInTransaction()) {
764            session.getTransactionContext().setRollbackOnly(true);
765        }
766    }
767
768    void clearMessagesInProgress() {
769        if (inProgressClearRequiredFlag.get() > 0) {
770            synchronized (unconsumedMessages.getMutex()) {
771                if (inProgressClearRequiredFlag.get() > 0) {
772                    LOG.debug("{} clearing unconsumed list ({}) on transport interrupt", getConsumerId(), unconsumedMessages.size());
773                    // ensure unconsumed are rolledback up front as they may get redelivered to another consumer
774                    List<MessageDispatch> list = unconsumedMessages.removeAll();
775                    if (!this.info.isBrowser()) {
776                        for (MessageDispatch old : list) {
777                            session.connection.rollbackDuplicate(this, old.getMessage());
778                        }
779                    }
780                    // allow dispatch on this connection to resume
781                    session.connection.transportInterruptionProcessingComplete();
782                    inProgressClearRequiredFlag.set(0);
783
784                    // Wake up any blockers and allow them to recheck state.
785                    unconsumedMessages.getMutex().notifyAll();
786                }
787            }
788            clearDeliveredList();
789        }
790    }
791
792    void deliverAcks() {
793        MessageAck ack = null;
794        if (deliveryingAcknowledgements.compareAndSet(false, true)) {
795            synchronized(deliveredMessages) {
796                if (isAutoAcknowledgeEach()) {
797                    ack = makeAckForAllDeliveredMessages(MessageAck.STANDARD_ACK_TYPE);
798                    if (ack != null) {
799                        deliveredMessages.clear();
800                        ackCounter = 0;
801                    } else {
802                        ack = pendingAck;
803                        pendingAck = null;
804                    }
805                } else if (pendingAck != null && pendingAck.isStandardAck()) {
806                    ack = pendingAck;
807                    pendingAck = null;
808                }
809            }
810            if (ack != null) {
811                final MessageAck ackToSend = ack;
812
813                if (executorService == null) {
814                    executorService = Executors.newSingleThreadExecutor();
815                }
816                executorService.submit(new Runnable() {
817                    @Override
818                    public void run() {
819                        try {
820                            session.sendAck(ackToSend,true);
821                        } catch (JMSException e) {
822                            LOG.error(getConsumerId() + " failed to deliver acknowledgements", e);
823                        } finally {
824                            deliveryingAcknowledgements.set(false);
825                        }
826                    }
827                });
828            } else {
829                deliveryingAcknowledgements.set(false);
830            }
831        }
832    }
833
834    public void dispose() throws JMSException {
835        if (!unconsumedMessages.isClosed()) {
836
837            // Do we have any acks we need to send out before closing?
838            // Ack any delivered messages now.
839            if (!session.getTransacted()) {
840                deliverAcks();
841                if (isAutoAcknowledgeBatch()) {
842                    acknowledge();
843                }
844            }
845            if (executorService != null) {
846                ThreadPoolUtils.shutdownGraceful(executorService, 60000L);
847                executorService = null;
848            }
849            if (optimizedAckTask != null) {
850                this.session.connection.getScheduler().cancel(optimizedAckTask);
851                optimizedAckTask = null;
852            }
853
854            if (session.isClientAcknowledge() || session.isIndividualAcknowledge()) {
855                if (!this.info.isBrowser()) {
856                    // rollback duplicates that aren't acknowledged
857                    List<MessageDispatch> tmp = null;
858                    synchronized (this.deliveredMessages) {
859                        tmp = new ArrayList<MessageDispatch>(this.deliveredMessages);
860                    }
861                    for (MessageDispatch old : tmp) {
862                        this.session.connection.rollbackDuplicate(this, old.getMessage());
863                    }
864                    tmp.clear();
865                }
866            }
867            if (!session.isTransacted()) {
868                synchronized(deliveredMessages) {
869                    deliveredMessages.clear();
870                }
871            }
872            unconsumedMessages.close();
873            this.session.removeConsumer(this);
874            List<MessageDispatch> list = unconsumedMessages.removeAll();
875            if (!this.info.isBrowser()) {
876                for (MessageDispatch old : list) {
877                    // ensure we don't filter this as a duplicate
878                    if (old.getMessage() != null) {
879                        LOG.debug("on close, rollback duplicate: {}", old.getMessage().getMessageId());
880                    }
881                    session.connection.rollbackDuplicate(this, old.getMessage());
882                }
883            }
884        }
885        if (previouslyDeliveredMessages != null) {
886            for (PreviouslyDelivered previouslyDelivered : previouslyDeliveredMessages.values()) {
887                session.connection.rollbackDuplicate(this, previouslyDelivered.message);
888            }
889        }
890        clearPreviouslyDelivered();
891    }
892
893    /**
894     * @throws IllegalStateException
895     */
896    protected void checkClosed() throws IllegalStateException {
897        if (unconsumedMessages.isClosed()) {
898            throw new IllegalStateException("The Consumer is closed");
899        }
900    }
901
902    /**
903     * If we have a zero prefetch specified then send a pull command to the
904     * broker to pull a message we are about to receive
905     */
906    protected void sendPullCommand(long timeout) throws JMSException {
907        clearDeliveredList();
908        if (info.getCurrentPrefetchSize() == 0 && unconsumedMessages.isEmpty()) {
909            MessagePull messagePull = new MessagePull();
910            messagePull.configure(info);
911            messagePull.setTimeout(timeout);
912            session.asyncSendPacket(messagePull);
913        }
914    }
915
916    protected void checkMessageListener() throws JMSException {
917        session.checkMessageListener();
918    }
919
920    protected void setOptimizeAcknowledge(boolean value) {
921        if (optimizeAcknowledge && !value) {
922            deliverAcks();
923        }
924        optimizeAcknowledge = value;
925    }
926
927    protected void setPrefetchSize(int prefetch) {
928        deliverAcks();
929        this.info.setCurrentPrefetchSize(prefetch);
930    }
931
932    private void beforeMessageIsConsumed(MessageDispatch md) throws JMSException {
933        md.setDeliverySequenceId(session.getNextDeliveryId());
934        lastDeliveredSequenceId = md.getMessage().getMessageId().getBrokerSequenceId();
935        if (!isAutoAcknowledgeBatch()) {
936            synchronized(deliveredMessages) {
937                deliveredMessages.addFirst(md);
938            }
939            if (session.getTransacted()) {
940                if (transactedIndividualAck) {
941                    immediateIndividualTransactedAck(md);
942                } else {
943                    ackLater(md, MessageAck.DELIVERED_ACK_TYPE);
944                }
945            }
946        }
947    }
948
949    private void immediateIndividualTransactedAck(MessageDispatch md) throws JMSException {
950        // acks accumulate on the broker pending transaction completion to indicate
951        // delivery status
952        registerSync();
953        MessageAck ack = new MessageAck(md, MessageAck.INDIVIDUAL_ACK_TYPE, 1);
954        ack.setTransactionId(session.getTransactionContext().getTransactionId());
955        session.sendAck(ack);
956    }
957
958    private void afterMessageIsConsumed(MessageDispatch md, boolean messageExpired) throws JMSException {
959        if (unconsumedMessages.isClosed()) {
960            return;
961        }
962        if (messageExpired) {
963            acknowledge(md, MessageAck.EXPIRED_ACK_TYPE);
964            stats.getExpiredMessageCount().increment();
965        } else {
966            stats.onMessage();
967            if (session.getTransacted()) {
968                // Do nothing.
969            } else if (isAutoAcknowledgeEach()) {
970                if (deliveryingAcknowledgements.compareAndSet(false, true)) {
971                    synchronized (deliveredMessages) {
972                        if (!deliveredMessages.isEmpty()) {
973                            if (optimizeAcknowledge) {
974                                ackCounter++;
975
976                                // AMQ-3956 evaluate both expired and normal msgs as
977                                // otherwise consumer may get stalled
978                                if (ackCounter + deliveredCounter >= (info.getPrefetchSize() * .65) || (optimizeAcknowledgeTimeOut > 0 && System.currentTimeMillis() >= (optimizeAckTimestamp + optimizeAcknowledgeTimeOut))) {
979                                    MessageAck ack = makeAckForAllDeliveredMessages(MessageAck.STANDARD_ACK_TYPE);
980                                    if (ack != null) {
981                                        deliveredMessages.clear();
982                                        ackCounter = 0;
983                                        session.sendAck(ack);
984                                        optimizeAckTimestamp = System.currentTimeMillis();
985                                    }
986                                    // AMQ-3956 - as further optimization send
987                                    // ack for expired msgs when there are any.
988                                    // This resets the deliveredCounter to 0 so that
989                                    // we won't sent standard acks with every msg just
990                                    // because the deliveredCounter just below
991                                    // 0.5 * prefetch as used in ackLater()
992                                    if (pendingAck != null && deliveredCounter > 0) {
993                                        session.sendAck(pendingAck);
994                                        pendingAck = null;
995                                        deliveredCounter = 0;
996                                    }
997                                }
998                            } else {
999                                MessageAck ack = makeAckForAllDeliveredMessages(MessageAck.STANDARD_ACK_TYPE);
1000                                if (ack!=null) {
1001                                    deliveredMessages.clear();
1002                                    session.sendAck(ack);
1003                                }
1004                            }
1005                        }
1006                    }
1007                    deliveryingAcknowledgements.set(false);
1008                }
1009            } else if (isAutoAcknowledgeBatch()) {
1010                ackLater(md, MessageAck.STANDARD_ACK_TYPE);
1011            } else if (session.isClientAcknowledge()||session.isIndividualAcknowledge()) {
1012                boolean messageUnackedByConsumer = false;
1013                synchronized (deliveredMessages) {
1014                    messageUnackedByConsumer = deliveredMessages.contains(md);
1015                }
1016                if (messageUnackedByConsumer) {
1017                    ackLater(md, MessageAck.DELIVERED_ACK_TYPE);
1018                }
1019            }
1020            else {
1021                throw new IllegalStateException("Invalid session state.");
1022            }
1023        }
1024    }
1025
1026    /**
1027     * Creates a MessageAck for all messages contained in deliveredMessages.
1028     * Caller should hold the lock for deliveredMessages.
1029     *
1030     * @param type Ack-Type (i.e. MessageAck.STANDARD_ACK_TYPE)
1031     * @return <code>null</code> if nothing to ack.
1032     */
1033    private MessageAck makeAckForAllDeliveredMessages(byte type) {
1034        synchronized (deliveredMessages) {
1035            if (deliveredMessages.isEmpty()) {
1036                return null;
1037            }
1038
1039            MessageDispatch md = deliveredMessages.getFirst();
1040            MessageAck ack = new MessageAck(md, type, deliveredMessages.size());
1041            ack.setFirstMessageId(deliveredMessages.getLast().getMessage().getMessageId());
1042            return ack;
1043        }
1044    }
1045
1046    private void ackLater(MessageDispatch md, byte ackType) throws JMSException {
1047
1048        // Don't acknowledge now, but we may need to let the broker know the
1049        // consumer got the message to expand the pre-fetch window
1050        if (session.getTransacted()) {
1051            registerSync();
1052        }
1053
1054        deliveredCounter++;
1055
1056        synchronized(deliveredMessages) {
1057            MessageAck oldPendingAck = pendingAck;
1058            pendingAck = new MessageAck(md, ackType, deliveredCounter);
1059            pendingAck.setTransactionId(session.getTransactionContext().getTransactionId());
1060            if( oldPendingAck==null ) {
1061                pendingAck.setFirstMessageId(pendingAck.getLastMessageId());
1062            } else if ( oldPendingAck.getAckType() == pendingAck.getAckType() ) {
1063                pendingAck.setFirstMessageId(oldPendingAck.getFirstMessageId());
1064            } else {
1065                // old pending ack being superseded by ack of another type, if is is not a delivered
1066                // ack and hence important, send it now so it is not lost.
1067                if (!oldPendingAck.isDeliveredAck()) {
1068                    LOG.debug("Sending old pending ack {}, new pending: {}", oldPendingAck, pendingAck);
1069                    session.sendAck(oldPendingAck);
1070                } else {
1071                    LOG.debug("dropping old pending ack {}, new pending: {}", oldPendingAck, pendingAck);
1072                }
1073            }
1074            // AMQ-3956 evaluate both expired and normal msgs as
1075            // otherwise consumer may get stalled
1076            if ((0.5 * info.getPrefetchSize()) <= (deliveredCounter + ackCounter - additionalWindowSize)) {
1077                LOG.debug("ackLater: sending: {}", pendingAck);
1078                session.sendAck(pendingAck);
1079                pendingAck=null;
1080                deliveredCounter = 0;
1081                additionalWindowSize = 0;
1082            }
1083        }
1084    }
1085
1086    private void registerSync() throws JMSException {
1087        session.doStartTransaction();
1088        if (!synchronizationRegistered) {
1089            synchronizationRegistered = true;
1090            session.getTransactionContext().addSynchronization(new Synchronization() {
1091                @Override
1092                public void beforeEnd() throws Exception {
1093                    if (transactedIndividualAck) {
1094                        clearDeliveredList();
1095                        waitForRedeliveries();
1096                        synchronized(deliveredMessages) {
1097                            rollbackOnFailedRecoveryRedelivery();
1098                        }
1099                    } else {
1100                        acknowledge();
1101                    }
1102                    synchronizationRegistered = false;
1103                }
1104
1105                @Override
1106                public void afterCommit() throws Exception {
1107                    commit();
1108                    synchronizationRegistered = false;
1109                }
1110
1111                @Override
1112                public void afterRollback() throws Exception {
1113                    rollback();
1114                    synchronizationRegistered = false;
1115                }
1116            });
1117        }
1118    }
1119
1120    /**
1121     * Acknowledge all the messages that have been delivered to the client up to
1122     * this point.
1123     *
1124     * @throws JMSException
1125     */
1126    public void acknowledge() throws JMSException {
1127        clearDeliveredList();
1128        waitForRedeliveries();
1129        synchronized(deliveredMessages) {
1130            // Acknowledge all messages so far.
1131            MessageAck ack = makeAckForAllDeliveredMessages(MessageAck.STANDARD_ACK_TYPE);
1132            if (ack == null) {
1133                return; // no msgs
1134            }
1135
1136            if (session.getTransacted()) {
1137                rollbackOnFailedRecoveryRedelivery();
1138                session.doStartTransaction();
1139                ack.setTransactionId(session.getTransactionContext().getTransactionId());
1140            }
1141
1142            pendingAck = null;
1143            session.sendAck(ack);
1144
1145            // Adjust the counters
1146            deliveredCounter = Math.max(0, deliveredCounter - deliveredMessages.size());
1147            additionalWindowSize = Math.max(0, additionalWindowSize - deliveredMessages.size());
1148
1149            if (!session.getTransacted()) {
1150                deliveredMessages.clear();
1151            }
1152        }
1153    }
1154
1155    private void waitForRedeliveries() {
1156        if (failoverRedeliveryWaitPeriod > 0 && previouslyDeliveredMessages != null) {
1157            long expiry = System.currentTimeMillis() + failoverRedeliveryWaitPeriod;
1158            int numberNotReplayed;
1159            do {
1160                numberNotReplayed = 0;
1161                synchronized(deliveredMessages) {
1162                    if (previouslyDeliveredMessages != null) {
1163                        for (PreviouslyDelivered entry: previouslyDeliveredMessages.values()) {
1164                            if (!entry.redelivered) {
1165                                numberNotReplayed++;
1166                            }
1167                        }
1168                    }
1169                }
1170                if (numberNotReplayed > 0) {
1171                    LOG.info("waiting for redelivery of {} in transaction: {}, to consumer: {}",
1172                             numberNotReplayed, this.getConsumerId(), previouslyDeliveredMessages.transactionId);
1173                    try {
1174                        Thread.sleep(Math.max(500, failoverRedeliveryWaitPeriod/4));
1175                    } catch (InterruptedException outOfhere) {
1176                        break;
1177                    }
1178                }
1179            } while (numberNotReplayed > 0 && expiry - System.currentTimeMillis() < 0);
1180        }
1181    }
1182
1183    /*
1184     * called with deliveredMessages locked
1185     */
1186    private void rollbackOnFailedRecoveryRedelivery() throws JMSException {
1187        if (previouslyDeliveredMessages != null) {
1188            // if any previously delivered messages was not re-delivered, transaction is invalid and must rollback
1189            // as messages have been dispatched else where.
1190            int numberNotReplayed = 0;
1191            for (PreviouslyDelivered entry: previouslyDeliveredMessages.values()) {
1192                if (!entry.redelivered) {
1193                    numberNotReplayed++;
1194                    LOG.debug("previously delivered message has not been replayed in transaction: {}, messageId: {}",
1195                              previouslyDeliveredMessages.transactionId, entry.message.getMessageId());
1196                }
1197            }
1198            if (numberNotReplayed > 0) {
1199                String message = "rolling back transaction ("
1200                    + previouslyDeliveredMessages.transactionId + ") post failover recovery. " + numberNotReplayed
1201                    + " previously delivered message(s) not replayed to consumer: " + this.getConsumerId();
1202                LOG.warn(message);
1203                throw new TransactionRolledBackException(message);
1204            }
1205        }
1206    }
1207
1208    void acknowledge(MessageDispatch md) throws JMSException {
1209        acknowledge(md, MessageAck.INDIVIDUAL_ACK_TYPE);
1210    }
1211
1212    void acknowledge(MessageDispatch md, byte ackType) throws JMSException {
1213        MessageAck ack = new MessageAck(md, ackType, 1);
1214        if (ack.isExpiredAck()) {
1215            ack.setFirstMessageId(ack.getLastMessageId());
1216        }
1217        session.sendAck(ack);
1218        synchronized(deliveredMessages){
1219            deliveredMessages.remove(md);
1220        }
1221    }
1222
1223    public void commit() throws JMSException {
1224        synchronized (deliveredMessages) {
1225            deliveredMessages.clear();
1226            clearPreviouslyDelivered();
1227        }
1228        redeliveryDelay = 0;
1229    }
1230
1231    public void rollback() throws JMSException {
1232        clearDeliveredList();
1233        synchronized (unconsumedMessages.getMutex()) {
1234            if (optimizeAcknowledge) {
1235                // remove messages read but not acked at the broker yet through
1236                // optimizeAcknowledge
1237                if (!this.info.isBrowser()) {
1238                    synchronized(deliveredMessages) {
1239                        for (int i = 0; (i < deliveredMessages.size()) && (i < ackCounter); i++) {
1240                            // ensure we don't filter this as a duplicate
1241                            MessageDispatch md = deliveredMessages.removeLast();
1242                            session.connection.rollbackDuplicate(this, md.getMessage());
1243                        }
1244                    }
1245                }
1246            }
1247            synchronized(deliveredMessages) {
1248                rollbackPreviouslyDeliveredAndNotRedelivered();
1249                if (deliveredMessages.isEmpty()) {
1250                    return;
1251                }
1252
1253                // use initial delay for first redelivery
1254                MessageDispatch lastMd = deliveredMessages.getFirst();
1255                final int currentRedeliveryCount = lastMd.getMessage().getRedeliveryCounter();
1256                if (currentRedeliveryCount > 0) {
1257                    redeliveryDelay = redeliveryPolicy.getNextRedeliveryDelay(redeliveryDelay);
1258                } else {
1259                    redeliveryDelay = redeliveryPolicy.getInitialRedeliveryDelay();
1260                }
1261                MessageId firstMsgId = deliveredMessages.getLast().getMessage().getMessageId();
1262
1263                for (Iterator<MessageDispatch> iter = deliveredMessages.iterator(); iter.hasNext();) {
1264                    MessageDispatch md = iter.next();
1265                    md.getMessage().onMessageRolledBack();
1266                    // ensure we don't filter this as a duplicate
1267                    session.connection.rollbackDuplicate(this, md.getMessage());
1268                }
1269
1270                if (redeliveryPolicy.getMaximumRedeliveries() != RedeliveryPolicy.NO_MAXIMUM_REDELIVERIES
1271                    && lastMd.getMessage().getRedeliveryCounter() > redeliveryPolicy.getMaximumRedeliveries()) {
1272                    // We need to NACK the messages so that they get sent to the
1273                    // DLQ.
1274                    // Acknowledge the last message.
1275
1276                    MessageAck ack = new MessageAck(lastMd, MessageAck.POISON_ACK_TYPE, deliveredMessages.size());
1277                    ack.setFirstMessageId(firstMsgId);
1278                    ack.setPoisonCause(new Throwable("Delivery[" + lastMd.getMessage().getRedeliveryCounter()  + "] exceeds redelivery policy limit:" + redeliveryPolicy
1279                            + ", cause:" + lastMd.getRollbackCause(), lastMd.getRollbackCause()));
1280                    session.sendAck(ack,true);
1281                    // Adjust the window size.
1282                    additionalWindowSize = Math.max(0, additionalWindowSize - deliveredMessages.size());
1283                    redeliveryDelay = 0;
1284
1285                    deliveredCounter -= deliveredMessages.size();
1286                    deliveredMessages.clear();
1287
1288                } else {
1289
1290                    // only redelivery_ack after first delivery
1291                    if (currentRedeliveryCount > 0) {
1292                        MessageAck ack = new MessageAck(lastMd, MessageAck.REDELIVERED_ACK_TYPE, deliveredMessages.size());
1293                        ack.setFirstMessageId(firstMsgId);
1294                        session.sendAck(ack,true);
1295                    }
1296
1297                    final LinkedList<MessageDispatch> pendingSessionRedelivery =
1298                            new LinkedList<MessageDispatch>(deliveredMessages);
1299
1300                    captureDeliveredMessagesForDuplicateSuppressionWithRequireRedelivery(false);
1301
1302                    deliveredCounter -= deliveredMessages.size();
1303                    deliveredMessages.clear();
1304
1305                    if (!unconsumedMessages.isClosed()) {
1306
1307                        if (nonBlockingRedelivery) {
1308                            Collections.reverse(pendingSessionRedelivery);
1309
1310                            // Start up the delivery again a little later.
1311                            session.getScheduler().executeAfterDelay(new Runnable() {
1312                                @Override
1313                                public void run() {
1314                                    try {
1315                                        if (!unconsumedMessages.isClosed()) {
1316                                            for(MessageDispatch dispatch : pendingSessionRedelivery) {
1317                                                session.dispatch(dispatch);
1318                                            }
1319                                        }
1320                                    } catch (Exception e) {
1321                                        session.connection.onAsyncException(e);
1322                                    }
1323                                }
1324                            }, redeliveryDelay);
1325
1326                        } else {
1327                            // stop the delivery of messages.
1328                            unconsumedMessages.stop();
1329
1330                            final ActiveMQMessageConsumer dispatcher = this;
1331
1332                            Runnable redispatchWork = new Runnable() {
1333                                @Override
1334                                public void run() {
1335                                    try {
1336                                        if (!unconsumedMessages.isClosed()) {
1337                                            synchronized (unconsumedMessages.getMutex()) {
1338                                                for (MessageDispatch md : pendingSessionRedelivery) {
1339                                                    unconsumedMessages.enqueueFirst(md);
1340                                                }
1341
1342                                                if (messageListener.get() != null) {
1343                                                    session.redispatch(dispatcher, unconsumedMessages);
1344                                                }
1345                                            }
1346                                            if (started.get()) {
1347                                                start();
1348                                            }
1349                                        }
1350                                    } catch (JMSException e) {
1351                                        session.connection.onAsyncException(e);
1352                                    }
1353                                }
1354                            };
1355
1356                            if (redeliveryDelay > 0 && !unconsumedMessages.isClosed()) {
1357                                // Start up the delivery again a little later.
1358                                session.getScheduler().executeAfterDelay(redispatchWork, redeliveryDelay);
1359                            } else {
1360                                redispatchWork.run();
1361                            }
1362                        }
1363                    } else {
1364                        for (MessageDispatch md : pendingSessionRedelivery) {
1365                            session.connection.rollbackDuplicate(this, md.getMessage());
1366                        }
1367                    }
1368                }
1369            }
1370        }
1371    }
1372
1373    /*
1374     * called with unconsumedMessages && deliveredMessages locked
1375     * remove any message not re-delivered as they can't be replayed to this
1376     * consumer on rollback
1377     */
1378    private void rollbackPreviouslyDeliveredAndNotRedelivered() {
1379        if (previouslyDeliveredMessages != null) {
1380            for (PreviouslyDelivered entry: previouslyDeliveredMessages.values()) {
1381                if (!entry.redelivered) {
1382                    LOG.trace("rollback non redelivered: {}", entry.message.getMessageId());
1383                    removeFromDeliveredMessages(entry.message.getMessageId());
1384                }
1385            }
1386            clearPreviouslyDelivered();
1387        }
1388    }
1389
1390    /*
1391     * called with deliveredMessages locked
1392     */
1393    private void removeFromDeliveredMessages(MessageId key) {
1394        Iterator<MessageDispatch> iterator = deliveredMessages.iterator();
1395        while (iterator.hasNext()) {
1396            MessageDispatch candidate = iterator.next();
1397            if (key.equals(candidate.getMessage().getMessageId())) {
1398                session.connection.rollbackDuplicate(this, candidate.getMessage());
1399                iterator.remove();
1400                break;
1401            }
1402        }
1403    }
1404
1405    /*
1406     * called with deliveredMessages locked
1407     */
1408    private void clearPreviouslyDelivered() {
1409        if (previouslyDeliveredMessages != null) {
1410            previouslyDeliveredMessages.clear();
1411            previouslyDeliveredMessages = null;
1412        }
1413    }
1414
1415    @Override
1416    public void dispatch(MessageDispatch md) {
1417        MessageListener listener = this.messageListener.get();
1418        try {
1419            clearMessagesInProgress();
1420            clearDeliveredList();
1421            synchronized (unconsumedMessages.getMutex()) {
1422                if (!unconsumedMessages.isClosed()) {
1423                    // deliverySequenceId non zero means previously queued dispatch
1424                    if (this.info.isBrowser() || md.getDeliverySequenceId() != 0l || !session.connection.isDuplicate(this, md.getMessage())) {
1425                        if (listener != null && unconsumedMessages.isRunning()) {
1426                            if (redeliveryExceeded(md)) {
1427                                poisonAck(md, "listener dispatch[" + md.getRedeliveryCounter() + "] to " + getConsumerId() + " exceeds redelivery policy limit:" + redeliveryPolicy);
1428                                return;
1429                            }
1430                            ActiveMQMessage message = createActiveMQMessage(md);
1431                            beforeMessageIsConsumed(md);
1432                            try {
1433                                boolean expired = isConsumerExpiryCheckEnabled() && message.isExpired();
1434                                if (!expired) {
1435                                    listener.onMessage(message);
1436                                }
1437                                afterMessageIsConsumed(md, expired);
1438                            } catch (RuntimeException e) {
1439                                LOG.error("{} Exception while processing message: {}", getConsumerId(), md.getMessage().getMessageId(), e);
1440                                md.setRollbackCause(e);
1441                                if (isAutoAcknowledgeBatch() || isAutoAcknowledgeEach() || session.isIndividualAcknowledge()) {
1442                                    // schedual redelivery and possible dlq processing
1443                                    rollback();
1444                                } else {
1445                                    // Transacted or Client ack: Deliver the next message.
1446                                    afterMessageIsConsumed(md, false);
1447                                }
1448                            }
1449                        } else {
1450                            md.setDeliverySequenceId(-1); // skip duplicate check on subsequent queued delivery
1451                            if (md.getMessage() == null) {
1452                                // End of browse or pull request timeout.
1453                                unconsumedMessages.enqueue(md);
1454                            } else {
1455                                if (!consumeExpiredMessage(md)) {
1456                                    unconsumedMessages.enqueue(md);
1457                                    if (availableListener != null) {
1458                                        availableListener.onMessageAvailable(this);
1459                                    }
1460                                } else {
1461                                    beforeMessageIsConsumed(md);
1462                                    afterMessageIsConsumed(md, true);
1463
1464                                    // Pull consumer needs to check if pull timed out and send
1465                                    // a new pull command if not.
1466                                    if (info.getCurrentPrefetchSize() == 0) {
1467                                        unconsumedMessages.enqueue(null);
1468                                    }
1469                                }
1470                            }
1471                        }
1472                    } else {
1473                        // deal with duplicate delivery
1474                        ConsumerId consumerWithPendingTransaction;
1475                        if (redeliveryExpectedInCurrentTransaction(md, true)) {
1476                            LOG.debug("{} tracking transacted redelivery {}", getConsumerId(), md.getMessage());
1477                            if (transactedIndividualAck) {
1478                                immediateIndividualTransactedAck(md);
1479                            } else {
1480                                session.sendAck(new MessageAck(md, MessageAck.DELIVERED_ACK_TYPE, 1));
1481                            }
1482                        } else if ((consumerWithPendingTransaction = redeliveryPendingInCompetingTransaction(md)) != null) {
1483                            LOG.warn("{} delivering duplicate {}, pending transaction completion on {} will rollback", getConsumerId(), md.getMessage(), consumerWithPendingTransaction);
1484                            session.getConnection().rollbackDuplicate(this, md.getMessage());
1485                            dispatch(md);
1486                        } else {
1487                            LOG.warn("{} suppressing duplicate delivery on connection, poison acking: {}", getConsumerId(), md);
1488                            poisonAck(md, "Suppressing duplicate delivery on connection, consumer " + getConsumerId());
1489                        }
1490                    }
1491                }
1492            }
1493            if (++dispatchedCount % 1000 == 0) {
1494                dispatchedCount = 0;
1495                Thread.yield();
1496            }
1497        } catch (Exception e) {
1498            session.connection.onClientInternalException(e);
1499        }
1500    }
1501
1502    private boolean redeliveryExpectedInCurrentTransaction(MessageDispatch md, boolean markReceipt) {
1503        if (session.isTransacted()) {
1504            synchronized (deliveredMessages) {
1505                if (previouslyDeliveredMessages != null) {
1506                    PreviouslyDelivered entry;
1507                    if ((entry = previouslyDeliveredMessages.get(md.getMessage().getMessageId())) != null) {
1508                        if (markReceipt) {
1509                            entry.redelivered = true;
1510                        }
1511                        return true;
1512                    }
1513                }
1514            }
1515        }
1516        return false;
1517    }
1518
1519    private ConsumerId redeliveryPendingInCompetingTransaction(MessageDispatch md) {
1520        for (ActiveMQSession activeMQSession: session.connection.getSessions()) {
1521            for (ActiveMQMessageConsumer activeMQMessageConsumer : activeMQSession.consumers) {
1522                if (activeMQMessageConsumer.redeliveryExpectedInCurrentTransaction(md, false)) {
1523                    return activeMQMessageConsumer.getConsumerId();
1524                }
1525            }
1526        }
1527        return null;
1528    }
1529
1530    // async (on next call) clear or track delivered as they may be flagged as duplicates if they arrive again
1531    private void clearDeliveredList() {
1532        if (clearDeliveredList) {
1533            synchronized (deliveredMessages) {
1534                if (clearDeliveredList) {
1535                    if (!deliveredMessages.isEmpty()) {
1536                        if (session.isTransacted()) {
1537                            captureDeliveredMessagesForDuplicateSuppression();
1538                        } else {
1539                            if (session.isClientAcknowledge()) {
1540                                LOG.debug("{} rolling back delivered list ({}) on transport interrupt", getConsumerId(), deliveredMessages.size());
1541                                // allow redelivery
1542                                if (!this.info.isBrowser()) {
1543                                    for (MessageDispatch md: deliveredMessages) {
1544                                        this.session.connection.rollbackDuplicate(this, md.getMessage());
1545                                    }
1546                                }
1547                            }
1548                            LOG.debug("{} clearing delivered list ({}) on transport interrupt", getConsumerId(), deliveredMessages.size());
1549                            deliveredMessages.clear();
1550                            pendingAck = null;
1551                        }
1552                    }
1553                    clearDeliveredList = false;
1554                }
1555            }
1556        }
1557    }
1558
1559    // called with deliveredMessages locked
1560    private void captureDeliveredMessagesForDuplicateSuppression() {
1561        captureDeliveredMessagesForDuplicateSuppressionWithRequireRedelivery (true);
1562    }
1563
1564    private void captureDeliveredMessagesForDuplicateSuppressionWithRequireRedelivery(boolean requireRedelivery) {
1565        if (previouslyDeliveredMessages == null) {
1566            previouslyDeliveredMessages = new PreviouslyDeliveredMap<MessageId, PreviouslyDelivered>(session.getTransactionContext().getTransactionId());
1567        }
1568        for (MessageDispatch delivered : deliveredMessages) {
1569            previouslyDeliveredMessages.put(delivered.getMessage().getMessageId(), new PreviouslyDelivered(delivered, !requireRedelivery));
1570        }
1571        LOG.trace("{} tracking existing transacted {} delivered list({})", getConsumerId(), previouslyDeliveredMessages.transactionId, deliveredMessages.size());
1572    }
1573
1574    public int getMessageSize() {
1575        return unconsumedMessages.size();
1576    }
1577
1578    public void start() throws JMSException {
1579        if (unconsumedMessages.isClosed()) {
1580            return;
1581        }
1582        started.set(true);
1583        unconsumedMessages.start();
1584        session.executor.wakeup();
1585    }
1586
1587    public void stop() {
1588        started.set(false);
1589        unconsumedMessages.stop();
1590    }
1591
1592    @Override
1593    public String toString() {
1594        return "ActiveMQMessageConsumer { value=" + info.getConsumerId() + ", started=" + started.get()
1595               + " }";
1596    }
1597
1598    /**
1599     * Delivers a message to the message listener.
1600     *
1601     * @return true if another execution is needed.
1602     *
1603     * @throws JMSException
1604     */
1605    public boolean iterate() {
1606        MessageListener listener = this.messageListener.get();
1607        if (listener != null) {
1608            MessageDispatch md = unconsumedMessages.dequeueNoWait();
1609            if (md != null) {
1610                dispatch(md);
1611                return true;
1612            }
1613        }
1614        return false;
1615    }
1616
1617    public boolean isInUse(ActiveMQTempDestination destination) {
1618        return info.getDestination().equals(destination);
1619    }
1620
1621    public long getLastDeliveredSequenceId() {
1622        return lastDeliveredSequenceId;
1623    }
1624
1625    public IOException getFailureError() {
1626        return failureError;
1627    }
1628
1629    public void setFailureError(IOException failureError) {
1630        this.failureError = failureError;
1631    }
1632
1633    /**
1634     * @return the optimizedAckScheduledAckInterval
1635     */
1636    public long getOptimizedAckScheduledAckInterval() {
1637        return optimizedAckScheduledAckInterval;
1638    }
1639
1640    /**
1641     * @param optimizedAckScheduledAckInterval the optimizedAckScheduledAckInterval to set
1642     */
1643    public void setOptimizedAckScheduledAckInterval(long optimizedAckScheduledAckInterval) throws JMSException {
1644        this.optimizedAckScheduledAckInterval = optimizedAckScheduledAckInterval;
1645
1646        if (this.optimizedAckTask != null) {
1647            try {
1648                this.session.connection.getScheduler().cancel(optimizedAckTask);
1649            } catch (JMSException e) {
1650                LOG.debug("Caught exception while cancelling old optimized ack task", e);
1651                throw e;
1652            }
1653            this.optimizedAckTask = null;
1654        }
1655
1656        // Should we periodically send out all outstanding acks.
1657        if (this.optimizeAcknowledge && this.optimizedAckScheduledAckInterval > 0) {
1658            this.optimizedAckTask = new Runnable() {
1659
1660                @Override
1661                public void run() {
1662                    try {
1663                        if (optimizeAcknowledge && !unconsumedMessages.isClosed()) {
1664                            LOG.info("Consumer:{} is performing scheduled delivery of outstanding optimized Acks", info.getConsumerId());
1665                            deliverAcks();
1666                        }
1667                    } catch (Exception e) {
1668                        LOG.debug("Optimized Ack Task caught exception during ack", e);
1669                    }
1670                }
1671            };
1672
1673            try {
1674                this.session.connection.getScheduler().executePeriodically(optimizedAckTask, optimizedAckScheduledAckInterval);
1675            } catch (JMSException e) {
1676                LOG.debug("Caught exception while scheduling new optimized ack task", e);
1677                throw e;
1678            }
1679        }
1680    }
1681
1682    public boolean hasMessageListener() {
1683        return messageListener.get() != null;
1684    }
1685
1686    public boolean isConsumerExpiryCheckEnabled() {
1687        return consumerExpiryCheckEnabled;
1688    }
1689
1690    public void setConsumerExpiryCheckEnabled(boolean consumerExpiryCheckEnabled) {
1691        this.consumerExpiryCheckEnabled = consumerExpiryCheckEnabled;
1692    }
1693}