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