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