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