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.broker.region;
018
019import java.io.IOException;
020import java.util.ArrayList;
021import java.util.LinkedList;
022import java.util.List;
023import java.util.Map;
024import java.util.concurrent.CancellationException;
025import java.util.concurrent.ConcurrentHashMap;
026import java.util.concurrent.ConcurrentMap;
027import java.util.concurrent.CopyOnWriteArrayList;
028import java.util.concurrent.Future;
029import java.util.concurrent.atomic.AtomicBoolean;
030import java.util.concurrent.locks.ReentrantReadWriteLock;
031
032import org.apache.activemq.advisory.AdvisorySupport;
033import org.apache.activemq.broker.BrokerService;
034import org.apache.activemq.broker.ConnectionContext;
035import org.apache.activemq.broker.ProducerBrokerExchange;
036import org.apache.activemq.broker.region.policy.DispatchPolicy;
037import org.apache.activemq.broker.region.policy.LastImageSubscriptionRecoveryPolicy;
038import org.apache.activemq.broker.region.policy.RetainedMessageSubscriptionRecoveryPolicy;
039import org.apache.activemq.broker.region.policy.SimpleDispatchPolicy;
040import org.apache.activemq.broker.region.policy.SubscriptionRecoveryPolicy;
041import org.apache.activemq.broker.util.InsertionCountList;
042import org.apache.activemq.command.ActiveMQDestination;
043import org.apache.activemq.command.ExceptionResponse;
044import org.apache.activemq.command.Message;
045import org.apache.activemq.command.MessageAck;
046import org.apache.activemq.command.MessageId;
047import org.apache.activemq.command.ProducerAck;
048import org.apache.activemq.command.ProducerInfo;
049import org.apache.activemq.command.Response;
050import org.apache.activemq.command.SubscriptionInfo;
051import org.apache.activemq.filter.MessageEvaluationContext;
052import org.apache.activemq.filter.NonCachedMessageEvaluationContext;
053import org.apache.activemq.store.MessageRecoveryListener;
054import org.apache.activemq.store.TopicMessageStore;
055import org.apache.activemq.thread.Task;
056import org.apache.activemq.thread.TaskRunner;
057import org.apache.activemq.thread.TaskRunnerFactory;
058import org.apache.activemq.transaction.Synchronization;
059import org.apache.activemq.util.SubscriptionKey;
060import org.slf4j.Logger;
061import org.slf4j.LoggerFactory;
062
063import javax.jms.JMSException;
064
065import static org.apache.activemq.transaction.Transaction.IN_USE_STATE;
066
067/**
068 * The Topic is a destination that sends a copy of a message to every active
069 * Subscription registered.
070 */
071public class Topic extends BaseDestination implements Task {
072    protected static final Logger LOG = LoggerFactory.getLogger(Topic.class);
073    private final TopicMessageStore topicStore;
074    protected final CopyOnWriteArrayList<Subscription> consumers = new CopyOnWriteArrayList<Subscription>();
075    private final ReentrantReadWriteLock dispatchLock = new ReentrantReadWriteLock();
076    private DispatchPolicy dispatchPolicy = new SimpleDispatchPolicy();
077    private SubscriptionRecoveryPolicy subscriptionRecoveryPolicy;
078    private final ConcurrentMap<SubscriptionKey, DurableTopicSubscription> durableSubscribers = new ConcurrentHashMap<SubscriptionKey, DurableTopicSubscription>();
079    private final TaskRunner taskRunner;
080    private final TaskRunnerFactory taskRunnerFactor;
081    private final LinkedList<Runnable> messagesWaitingForSpace = new LinkedList<Runnable>();
082    private final Runnable sendMessagesWaitingForSpaceTask = new Runnable() {
083        @Override
084        public void run() {
085            try {
086                Topic.this.taskRunner.wakeup();
087            } catch (InterruptedException e) {
088            }
089        }
090    };
091
092    public Topic(BrokerService brokerService, ActiveMQDestination destination, TopicMessageStore store,
093            DestinationStatistics parentStats, TaskRunnerFactory taskFactory) throws Exception {
094        super(brokerService, store, destination, parentStats);
095        this.topicStore = store;
096        subscriptionRecoveryPolicy = new RetainedMessageSubscriptionRecoveryPolicy(null);
097        this.taskRunner = taskFactory.createTaskRunner(this, "Topic  " + destination.getPhysicalName());
098        this.taskRunnerFactor = taskFactory;
099    }
100
101    @Override
102    public void initialize() throws Exception {
103        super.initialize();
104        // set non default subscription recovery policy (override policyEntries)
105        if (AdvisorySupport.isMasterBrokerAdvisoryTopic(destination)) {
106            subscriptionRecoveryPolicy = new LastImageSubscriptionRecoveryPolicy();
107            setAlwaysRetroactive(true);
108        }
109        if (store != null) {
110            // AMQ-2586: Better to leave this stat at zero than to give the user
111            // misleading metrics.
112            // int messageCount = store.getMessageCount();
113            // destinationStatistics.getMessages().setCount(messageCount);
114        }
115    }
116
117    @Override
118    public List<Subscription> getConsumers() {
119        synchronized (consumers) {
120            return new ArrayList<Subscription>(consumers);
121        }
122    }
123
124    public boolean lock(MessageReference node, LockOwner sub) {
125        return true;
126    }
127
128    @Override
129    public void addSubscription(ConnectionContext context, final Subscription sub) throws Exception {
130        if (!sub.getConsumerInfo().isDurable()) {
131
132            // Do a retroactive recovery if needed.
133            if (sub.getConsumerInfo().isRetroactive() || isAlwaysRetroactive()) {
134
135                // synchronize with dispatch method so that no new messages are sent
136                // while we are recovering a subscription to avoid out of order messages.
137                dispatchLock.writeLock().lock();
138                try {
139                    boolean applyRecovery = false;
140                    synchronized (consumers) {
141                        if (!consumers.contains(sub)){
142                            sub.add(context, this);
143                            consumers.add(sub);
144                            applyRecovery=true;
145                            super.addSubscription(context, sub);
146                        }
147                    }
148                    if (applyRecovery){
149                        subscriptionRecoveryPolicy.recover(context, this, sub);
150                    }
151                } finally {
152                    dispatchLock.writeLock().unlock();
153                }
154
155            } else {
156                synchronized (consumers) {
157                    if (!consumers.contains(sub)){
158                        sub.add(context, this);
159                        consumers.add(sub);
160                        super.addSubscription(context, sub);
161                    }
162                }
163            }
164        } else {
165            DurableTopicSubscription dsub = (DurableTopicSubscription) sub;
166            super.addSubscription(context, sub);
167            sub.add(context, this);
168            if(dsub.isActive()) {
169                synchronized (consumers) {
170                    boolean hasSubscription = false;
171
172                    if (consumers.size() == 0) {
173                        hasSubscription = false;
174                    } else {
175                        for (Subscription currentSub : consumers) {
176                            if (currentSub.getConsumerInfo().isDurable()) {
177                                DurableTopicSubscription dcurrentSub = (DurableTopicSubscription) currentSub;
178                                if (dcurrentSub.getSubscriptionKey().equals(dsub.getSubscriptionKey())) {
179                                    hasSubscription = true;
180                                    break;
181                                }
182                            }
183                        }
184                    }
185
186                    if (!hasSubscription) {
187                        consumers.add(sub);
188                    }
189                }
190            }
191            durableSubscribers.put(dsub.getSubscriptionKey(), dsub);
192        }
193    }
194
195    @Override
196    public void removeSubscription(ConnectionContext context, Subscription sub, long lastDeliveredSequenceId) throws Exception {
197        if (!sub.getConsumerInfo().isDurable()) {
198            boolean removed = false;
199            synchronized (consumers) {
200                removed = consumers.remove(sub);
201            }
202            if (removed) {
203                super.removeSubscription(context, sub, lastDeliveredSequenceId);
204            }
205        }
206        sub.remove(context, this);
207    }
208
209    public void deleteSubscription(ConnectionContext context, SubscriptionKey key) throws Exception {
210        if (topicStore != null) {
211            topicStore.deleteSubscription(key.clientId, key.subscriptionName);
212            DurableTopicSubscription removed = durableSubscribers.remove(key);
213            if (removed != null) {
214                destinationStatistics.getConsumers().decrement();
215                // deactivate and remove
216                removed.deactivate(false, 0l);
217                consumers.remove(removed);
218            }
219        }
220    }
221
222    public void activate(ConnectionContext context, final DurableTopicSubscription subscription) throws Exception {
223        // synchronize with dispatch method so that no new messages are sent
224        // while we are recovering a subscription to avoid out of order messages.
225        dispatchLock.writeLock().lock();
226        try {
227
228            if (topicStore == null) {
229                return;
230            }
231
232            // Recover the durable subscription.
233            String clientId = subscription.getSubscriptionKey().getClientId();
234            String subscriptionName = subscription.getSubscriptionKey().getSubscriptionName();
235            String selector = subscription.getConsumerInfo().getSelector();
236            SubscriptionInfo info = topicStore.lookupSubscription(clientId, subscriptionName);
237            if (info != null) {
238                // Check to see if selector changed.
239                String s1 = info.getSelector();
240                if (s1 == null ^ selector == null || (s1 != null && !s1.equals(selector))) {
241                    // Need to delete the subscription
242                    topicStore.deleteSubscription(clientId, subscriptionName);
243                    info = null;
244                    synchronized (consumers) {
245                        consumers.remove(subscription);
246                    }
247                } else {
248                    synchronized (consumers) {
249                        if (!consumers.contains(subscription)) {
250                            consumers.add(subscription);
251                        }
252                    }
253                }
254            }
255
256            // Do we need to create the subscription?
257            if (info == null) {
258                info = new SubscriptionInfo();
259                info.setClientId(clientId);
260                info.setSelector(selector);
261                info.setSubscriptionName(subscriptionName);
262                info.setDestination(getActiveMQDestination());
263                // This destination is an actual destination id.
264                info.setSubscribedDestination(subscription.getConsumerInfo().getDestination());
265                // This destination might be a pattern
266                synchronized (consumers) {
267                    consumers.add(subscription);
268                    topicStore.addSubscription(info, subscription.getConsumerInfo().isRetroactive());
269                }
270            }
271
272            final MessageEvaluationContext msgContext = new NonCachedMessageEvaluationContext();
273            msgContext.setDestination(destination);
274            if (subscription.isRecoveryRequired()) {
275                topicStore.recoverSubscription(clientId, subscriptionName, new MessageRecoveryListener() {
276                    @Override
277                    public boolean recoverMessage(Message message) throws Exception {
278                        message.setRegionDestination(Topic.this);
279                        try {
280                            msgContext.setMessageReference(message);
281                            if (subscription.matches(message, msgContext)) {
282                                subscription.add(message);
283                            }
284                        } catch (IOException e) {
285                            LOG.error("Failed to recover this message {}", message, e);
286                        }
287                        return true;
288                    }
289
290                    @Override
291                    public boolean recoverMessageReference(MessageId messageReference) throws Exception {
292                        throw new RuntimeException("Should not be called.");
293                    }
294
295                    @Override
296                    public boolean hasSpace() {
297                        return true;
298                    }
299
300                    @Override
301                    public boolean isDuplicate(MessageId id) {
302                        return false;
303                    }
304                });
305            }
306        } finally {
307            dispatchLock.writeLock().unlock();
308        }
309    }
310
311    public void deactivate(ConnectionContext context, DurableTopicSubscription sub, List<MessageReference> dispatched) throws Exception {
312        synchronized (consumers) {
313            consumers.remove(sub);
314        }
315        sub.remove(context, this, dispatched);
316    }
317
318    public void recoverRetroactiveMessages(ConnectionContext context, Subscription subscription) throws Exception {
319        if (subscription.getConsumerInfo().isRetroactive()) {
320            subscriptionRecoveryPolicy.recover(context, this, subscription);
321        }
322    }
323
324    @Override
325    public void send(final ProducerBrokerExchange producerExchange, final Message message) throws Exception {
326        final ConnectionContext context = producerExchange.getConnectionContext();
327
328        final ProducerInfo producerInfo = producerExchange.getProducerState().getInfo();
329        producerExchange.incrementSend();
330        final boolean sendProducerAck = !message.isResponseRequired() && producerInfo.getWindowSize() > 0
331                && !context.isInRecoveryMode();
332
333        message.setRegionDestination(this);
334
335        // There is delay between the client sending it and it arriving at the
336        // destination.. it may have expired.
337        if (message.isExpired()) {
338            broker.messageExpired(context, message, null);
339            getDestinationStatistics().getExpired().increment();
340            if (sendProducerAck) {
341                ProducerAck ack = new ProducerAck(producerInfo.getProducerId(), message.getSize());
342                context.getConnection().dispatchAsync(ack);
343            }
344            return;
345        }
346
347        if (memoryUsage.isFull()) {
348            isFull(context, memoryUsage);
349            fastProducer(context, producerInfo);
350
351            if (isProducerFlowControl() && context.isProducerFlowControl()) {
352
353                if (isFlowControlLogRequired()) {
354                    LOG.warn("{}, Usage Manager memory limit reached {}. Producers will be throttled to the rate at which messages are removed from this destination to prevent flooding it. See http://activemq.apache.org/producer-flow-control.html for more info.",
355                            getActiveMQDestination().getQualifiedName(), memoryUsage.getLimit());
356                } else {
357                    LOG.debug("{}, Usage Manager memory limit reached {}. Producers will be throttled to the rate at which messages are removed from this destination to prevent flooding it. See http://activemq.apache.org/producer-flow-control.html for more info.",
358                            getActiveMQDestination().getQualifiedName(), memoryUsage.getLimit());
359                }
360
361                if (!context.isNetworkConnection() && systemUsage.isSendFailIfNoSpace()) {
362                    throw new javax.jms.ResourceAllocationException("Usage Manager memory limit ("
363                            + memoryUsage.getLimit() + ") reached. Rejecting send for producer (" + message.getProducerId()
364                            + ") to prevent flooding " + getActiveMQDestination().getQualifiedName() + "."
365                            + " See http://activemq.apache.org/producer-flow-control.html for more info");
366                }
367
368                // We can avoid blocking due to low usage if the producer is sending a sync message or
369                // if it is using a producer window
370                if (producerInfo.getWindowSize() > 0 || message.isResponseRequired()) {
371                    synchronized (messagesWaitingForSpace) {
372                        messagesWaitingForSpace.add(new Runnable() {
373                            @Override
374                            public void run() {
375                                try {
376
377                                    // While waiting for space to free up...
378                                    // the transaction may be done
379                                    if (message.isInTransaction()) {
380                                        if (context.getTransaction() == null || context.getTransaction().getState() > IN_USE_STATE) {
381                                            throw new JMSException("Send transaction completed while waiting for space");
382                                        }
383                                    }
384
385                                    // the message may have expired.
386                                    if (message.isExpired()) {
387                                        broker.messageExpired(context, message, null);
388                                        getDestinationStatistics().getExpired().increment();
389                                    } else {
390                                        doMessageSend(producerExchange, message);
391                                    }
392
393                                    if (sendProducerAck) {
394                                        ProducerAck ack = new ProducerAck(producerInfo.getProducerId(), message
395                                                .getSize());
396                                        context.getConnection().dispatchAsync(ack);
397                                    } else {
398                                        Response response = new Response();
399                                        response.setCorrelationId(message.getCommandId());
400                                        context.getConnection().dispatchAsync(response);
401                                    }
402
403                                } catch (Exception e) {
404                                    if (!sendProducerAck && !context.isInRecoveryMode()) {
405                                        ExceptionResponse response = new ExceptionResponse(e);
406                                        response.setCorrelationId(message.getCommandId());
407                                        context.getConnection().dispatchAsync(response);
408                                    }
409                                }
410                            }
411                        });
412
413                        registerCallbackForNotFullNotification();
414                        context.setDontSendReponse(true);
415                        return;
416                    }
417
418                } else {
419                    // Producer flow control cannot be used, so we have do the flow control
420                    // at the broker by blocking this thread until there is space available.
421
422                    if (memoryUsage.isFull()) {
423                        if (context.isInTransaction()) {
424
425                            int count = 0;
426                            while (!memoryUsage.waitForSpace(1000)) {
427                                if (context.getStopping().get()) {
428                                    throw new IOException("Connection closed, send aborted.");
429                                }
430                                if (count > 2 && context.isInTransaction()) {
431                                    count = 0;
432                                    int size = context.getTransaction().size();
433                                    LOG.warn("Waiting for space to send transacted message - transaction elements = {} need more space to commit. Message = {}", size, message);
434                                }
435                                count++;
436                            }
437                        } else {
438                            waitForSpace(
439                                    context,
440                                    producerExchange,
441                                    memoryUsage,
442                                    "Usage Manager Memory Usage limit reached. Stopping producer ("
443                                            + message.getProducerId()
444                                            + ") to prevent flooding "
445                                            + getActiveMQDestination().getQualifiedName()
446                                            + "."
447                                            + " See http://activemq.apache.org/producer-flow-control.html for more info");
448                        }
449                    }
450
451                    // The usage manager could have delayed us by the time
452                    // we unblock the message could have expired..
453                    if (message.isExpired()) {
454                        getDestinationStatistics().getExpired().increment();
455                        LOG.debug("Expired message: {}", message);
456                        return;
457                    }
458                }
459            }
460        }
461
462        doMessageSend(producerExchange, message);
463        messageDelivered(context, message);
464        if (sendProducerAck) {
465            ProducerAck ack = new ProducerAck(producerInfo.getProducerId(), message.getSize());
466            context.getConnection().dispatchAsync(ack);
467        }
468    }
469
470    /**
471     * do send the message - this needs to be synchronized to ensure messages
472     * are stored AND dispatched in the right order
473     *
474     * @param producerExchange
475     * @param message
476     * @throws IOException
477     * @throws Exception
478     */
479    synchronized void doMessageSend(final ProducerBrokerExchange producerExchange, final Message message)
480            throws IOException, Exception {
481        final ConnectionContext context = producerExchange.getConnectionContext();
482        message.getMessageId().setBrokerSequenceId(getDestinationSequenceId());
483        Future<Object> result = null;
484
485        if (topicStore != null && message.isPersistent() && !canOptimizeOutPersistence()) {
486            if (systemUsage.getStoreUsage().isFull(getStoreUsageHighWaterMark())) {
487                final String logMessage = "Persistent store is Full, " + getStoreUsageHighWaterMark() + "% of "
488                        + systemUsage.getStoreUsage().getLimit() + ". Stopping producer (" + message.getProducerId()
489                        + ") to prevent flooding " + getActiveMQDestination().getQualifiedName() + "."
490                        + " See http://activemq.apache.org/producer-flow-control.html for more info";
491                if (!context.isNetworkConnection() && systemUsage.isSendFailIfNoSpace()) {
492                    throw new javax.jms.ResourceAllocationException(logMessage);
493                }
494
495                waitForSpace(context,producerExchange, systemUsage.getStoreUsage(), getStoreUsageHighWaterMark(), logMessage);
496            }
497            result = topicStore.asyncAddTopicMessage(context, message,isOptimizeStorage());
498        }
499
500        message.incrementReferenceCount();
501
502        if (context.isInTransaction()) {
503            context.getTransaction().addSynchronization(new Synchronization() {
504                @Override
505                public void afterCommit() throws Exception {
506                    // It could take while before we receive the commit
507                    // operation.. by that time the message could have
508                    // expired..
509                    if (message.isExpired()) {
510                        if (broker.isExpired(message)) {
511                            getDestinationStatistics().getExpired().increment();
512                            broker.messageExpired(context, message, null);
513                        }
514                        message.decrementReferenceCount();
515                        return;
516                    }
517                    try {
518                        dispatch(context, message);
519                    } finally {
520                        message.decrementReferenceCount();
521                    }
522                }
523
524                @Override
525                public void afterRollback() throws Exception {
526                    message.decrementReferenceCount();
527                }
528            });
529
530        } else {
531            try {
532                dispatch(context, message);
533            } finally {
534                message.decrementReferenceCount();
535            }
536        }
537
538        if (result != null && !result.isCancelled()) {
539            try {
540                result.get();
541            } catch (CancellationException e) {
542                // ignore - the task has been cancelled if the message
543                // has already been deleted
544            }
545        }
546    }
547
548    private boolean canOptimizeOutPersistence() {
549        return durableSubscribers.size() == 0;
550    }
551
552    @Override
553    public String toString() {
554        return "Topic: destination=" + destination.getPhysicalName() + ", subscriptions=" + consumers.size();
555    }
556
557    @Override
558    public void acknowledge(ConnectionContext context, Subscription sub, final MessageAck ack,
559            final MessageReference node) throws IOException {
560        if (topicStore != null && node.isPersistent()) {
561            DurableTopicSubscription dsub = (DurableTopicSubscription) sub;
562            SubscriptionKey key = dsub.getSubscriptionKey();
563            topicStore.acknowledge(context, key.getClientId(), key.getSubscriptionName(), node.getMessageId(),
564                    convertToNonRangedAck(ack, node));
565        }
566        messageConsumed(context, node);
567    }
568
569    @Override
570    public void gc() {
571    }
572
573    public Message loadMessage(MessageId messageId) throws IOException {
574        return topicStore != null ? topicStore.getMessage(messageId) : null;
575    }
576
577    @Override
578    public void start() throws Exception {
579        if (started.compareAndSet(false, true)) {
580            this.subscriptionRecoveryPolicy.start();
581            if (memoryUsage != null) {
582                memoryUsage.start();
583            }
584
585            if (getExpireMessagesPeriod() > 0 && !AdvisorySupport.isAdvisoryTopic(getActiveMQDestination())) {
586                scheduler.executePeriodically(expireMessagesTask, getExpireMessagesPeriod());
587            }
588        }
589    }
590
591    @Override
592    public void stop() throws Exception {
593        if (started.compareAndSet(true, false)) {
594            if (taskRunner != null) {
595                taskRunner.shutdown();
596            }
597            this.subscriptionRecoveryPolicy.stop();
598            if (memoryUsage != null) {
599                memoryUsage.stop();
600            }
601            if (this.topicStore != null) {
602                this.topicStore.stop();
603            }
604
605            scheduler.cancel(expireMessagesTask);
606        }
607    }
608
609    @Override
610    public Message[] browse() {
611        final List<Message> result = new ArrayList<Message>();
612        doBrowse(result, getMaxBrowsePageSize());
613        return result.toArray(new Message[result.size()]);
614    }
615
616    private void doBrowse(final List<Message> browseList, final int max) {
617        try {
618            if (topicStore != null) {
619                final List<Message> toExpire = new ArrayList<Message>();
620                topicStore.recover(new MessageRecoveryListener() {
621                    @Override
622                    public boolean recoverMessage(Message message) throws Exception {
623                        if (message.isExpired()) {
624                            toExpire.add(message);
625                        }
626                        browseList.add(message);
627                        return true;
628                    }
629
630                    @Override
631                    public boolean recoverMessageReference(MessageId messageReference) throws Exception {
632                        return true;
633                    }
634
635                    @Override
636                    public boolean hasSpace() {
637                        return browseList.size() < max;
638                    }
639
640                    @Override
641                    public boolean isDuplicate(MessageId id) {
642                        return false;
643                    }
644                });
645                final ConnectionContext connectionContext = createConnectionContext();
646                for (Message message : toExpire) {
647                    for (DurableTopicSubscription sub : durableSubscribers.values()) {
648                        if (!sub.isActive()) {
649                            message.setRegionDestination(this);
650                            messageExpired(connectionContext, sub, message);
651                        }
652                    }
653                }
654                Message[] msgs = subscriptionRecoveryPolicy.browse(getActiveMQDestination());
655                if (msgs != null) {
656                    for (int i = 0; i < msgs.length && browseList.size() < max; i++) {
657                        browseList.add(msgs[i]);
658                    }
659                }
660            }
661        } catch (Throwable e) {
662            LOG.warn("Failed to browse Topic: {}", getActiveMQDestination().getPhysicalName(), e);
663        }
664    }
665
666    @Override
667    public boolean iterate() {
668        synchronized (messagesWaitingForSpace) {
669            while (!memoryUsage.isFull() && !messagesWaitingForSpace.isEmpty()) {
670                Runnable op = messagesWaitingForSpace.removeFirst();
671                op.run();
672            }
673
674            if (!messagesWaitingForSpace.isEmpty()) {
675                registerCallbackForNotFullNotification();
676            }
677        }
678        return false;
679    }
680
681    private void registerCallbackForNotFullNotification() {
682        // If the usage manager is not full, then the task will not
683        // get called..
684        if (!memoryUsage.notifyCallbackWhenNotFull(sendMessagesWaitingForSpaceTask)) {
685            // so call it directly here.
686            sendMessagesWaitingForSpaceTask.run();
687        }
688    }
689
690    // Properties
691    // -------------------------------------------------------------------------
692
693    public DispatchPolicy getDispatchPolicy() {
694        return dispatchPolicy;
695    }
696
697    public void setDispatchPolicy(DispatchPolicy dispatchPolicy) {
698        this.dispatchPolicy = dispatchPolicy;
699    }
700
701    public SubscriptionRecoveryPolicy getSubscriptionRecoveryPolicy() {
702        return subscriptionRecoveryPolicy;
703    }
704
705    public void setSubscriptionRecoveryPolicy(SubscriptionRecoveryPolicy recoveryPolicy) {
706        if (this.subscriptionRecoveryPolicy != null && this.subscriptionRecoveryPolicy instanceof RetainedMessageSubscriptionRecoveryPolicy) {
707            // allow users to combine retained message policy with other ActiveMQ policies
708            RetainedMessageSubscriptionRecoveryPolicy policy = (RetainedMessageSubscriptionRecoveryPolicy) this.subscriptionRecoveryPolicy;
709            policy.setWrapped(recoveryPolicy);
710        } else {
711            this.subscriptionRecoveryPolicy = recoveryPolicy;
712        }
713    }
714
715    // Implementation methods
716    // -------------------------------------------------------------------------
717
718    @Override
719    public final void wakeup() {
720    }
721
722    protected void dispatch(final ConnectionContext context, Message message) throws Exception {
723        // AMQ-2586: Better to leave this stat at zero than to give the user
724        // misleading metrics.
725        // destinationStatistics.getMessages().increment();
726        destinationStatistics.getEnqueues().increment();
727        destinationStatistics.getMessageSize().addSize(message.getSize());
728        MessageEvaluationContext msgContext = null;
729
730        dispatchLock.readLock().lock();
731        try {
732            if (!subscriptionRecoveryPolicy.add(context, message)) {
733                return;
734            }
735            synchronized (consumers) {
736                if (consumers.isEmpty()) {
737                    onMessageWithNoConsumers(context, message);
738                    return;
739                }
740            }
741            msgContext = context.getMessageEvaluationContext();
742            msgContext.setDestination(destination);
743            msgContext.setMessageReference(message);
744            if (!dispatchPolicy.dispatch(message, msgContext, consumers)) {
745                onMessageWithNoConsumers(context, message);
746            }
747
748        } finally {
749            dispatchLock.readLock().unlock();
750            if (msgContext != null) {
751                msgContext.clear();
752            }
753        }
754    }
755
756    private final AtomicBoolean expiryTaskInProgress = new AtomicBoolean(false);
757    private final Runnable expireMessagesWork = new Runnable() {
758        @Override
759        public void run() {
760            List<Message> browsedMessages = new InsertionCountList<Message>();
761            doBrowse(browsedMessages, getMaxExpirePageSize());
762            expiryTaskInProgress.set(false);
763        }
764    };
765    private final Runnable expireMessagesTask = new Runnable() {
766        @Override
767        public void run() {
768            if (expiryTaskInProgress.compareAndSet(false, true)) {
769                taskRunnerFactor.execute(expireMessagesWork);
770            }
771        }
772    };
773
774    @Override
775    public void messageExpired(ConnectionContext context, Subscription subs, MessageReference reference) {
776        broker.messageExpired(context, reference, subs);
777        // AMQ-2586: Better to leave this stat at zero than to give the user
778        // misleading metrics.
779        // destinationStatistics.getMessages().decrement();
780        destinationStatistics.getExpired().increment();
781        MessageAck ack = new MessageAck();
782        ack.setAckType(MessageAck.STANDARD_ACK_TYPE);
783        ack.setDestination(destination);
784        ack.setMessageID(reference.getMessageId());
785        try {
786            if (subs instanceof DurableTopicSubscription) {
787                ((DurableTopicSubscription)subs).removePending(reference);
788            }
789            acknowledge(context, subs, ack, reference);
790        } catch (Exception e) {
791            LOG.error("Failed to remove expired Message from the store ", e);
792        }
793    }
794
795    @Override
796    protected Logger getLog() {
797        return LOG;
798    }
799
800    protected boolean isOptimizeStorage(){
801        boolean result = false;
802
803        if (isDoOptimzeMessageStorage() && durableSubscribers.isEmpty()==false){
804                result = true;
805                for (DurableTopicSubscription s : durableSubscribers.values()) {
806                    if (s.isActive()== false){
807                        result = false;
808                        break;
809                    }
810                    if (s.getPrefetchSize()==0){
811                        result = false;
812                        break;
813                    }
814                    if (s.isSlowConsumer()){
815                        result = false;
816                        break;
817                    }
818                    if (s.getInFlightUsage() > getOptimizeMessageStoreInFlightLimit()){
819                        result = false;
820                        break;
821                    }
822                }
823        }
824        return result;
825    }
826
827    /**
828     * force a reread of the store - after transaction recovery completion
829     * @param pendingAdditionsCount
830     */
831    @Override
832    public void clearPendingMessages(int pendingAdditionsCount) {
833        dispatchLock.readLock().lock();
834        try {
835            for (DurableTopicSubscription durableTopicSubscription : durableSubscribers.values()) {
836                clearPendingAndDispatch(durableTopicSubscription);
837            }
838        } finally {
839            dispatchLock.readLock().unlock();
840        }
841    }
842
843    private void clearPendingAndDispatch(DurableTopicSubscription durableTopicSubscription) {
844        synchronized (durableTopicSubscription.pendingLock) {
845            durableTopicSubscription.pending.clear();
846            try {
847                durableTopicSubscription.dispatchPending();
848            } catch (IOException exception) {
849                LOG.warn("After clear of pending, failed to dispatch to: {}, for: {}, pending: {}", new Object[]{
850                        durableTopicSubscription,
851                        destination,
852                        durableTopicSubscription.pending }, exception);
853            }
854        }
855    }
856
857    private void rollback(MessageId poisoned) {
858        dispatchLock.readLock().lock();
859        try {
860            for (DurableTopicSubscription durableTopicSubscription : durableSubscribers.values()) {
861                durableTopicSubscription.getPending().rollback(poisoned);
862            }
863        } finally {
864            dispatchLock.readLock().unlock();
865        }
866    }
867
868    public Map<SubscriptionKey, DurableTopicSubscription> getDurableTopicSubs() {
869        return durableSubscribers;
870    }
871}