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