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