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        this.subscriptionRecoveryPolicy.start();
563        if (memoryUsage != null) {
564            memoryUsage.start();
565        }
566
567        if (getExpireMessagesPeriod() > 0 && !AdvisorySupport.isAdvisoryTopic(getActiveMQDestination())) {
568            scheduler.executePeriodically(expireMessagesTask, getExpireMessagesPeriod());
569        }
570    }
571
572    @Override
573    public void stop() throws Exception {
574        if (taskRunner != null) {
575            taskRunner.shutdown();
576        }
577        this.subscriptionRecoveryPolicy.stop();
578        if (memoryUsage != null) {
579            memoryUsage.stop();
580        }
581        if (this.topicStore != null) {
582            this.topicStore.stop();
583        }
584
585         scheduler.cancel(expireMessagesTask);
586    }
587
588    @Override
589    public Message[] browse() {
590        final List<Message> result = new ArrayList<Message>();
591        doBrowse(result, getMaxBrowsePageSize());
592        return result.toArray(new Message[result.size()]);
593    }
594
595    private void doBrowse(final List<Message> browseList, final int max) {
596        try {
597            if (topicStore != null) {
598                final List<Message> toExpire = new ArrayList<Message>();
599                topicStore.recover(new MessageRecoveryListener() {
600                    @Override
601                    public boolean recoverMessage(Message message) throws Exception {
602                        if (message.isExpired()) {
603                            toExpire.add(message);
604                        }
605                        browseList.add(message);
606                        return true;
607                    }
608
609                    @Override
610                    public boolean recoverMessageReference(MessageId messageReference) throws Exception {
611                        return true;
612                    }
613
614                    @Override
615                    public boolean hasSpace() {
616                        return browseList.size() < max;
617                    }
618
619                    @Override
620                    public boolean isDuplicate(MessageId id) {
621                        return false;
622                    }
623                });
624                final ConnectionContext connectionContext = createConnectionContext();
625                for (Message message : toExpire) {
626                    for (DurableTopicSubscription sub : durableSubscribers.values()) {
627                        if (!sub.isActive()) {
628                            message.setRegionDestination(this);
629                            messageExpired(connectionContext, sub, message);
630                        }
631                    }
632                }
633                Message[] msgs = subscriptionRecoveryPolicy.browse(getActiveMQDestination());
634                if (msgs != null) {
635                    for (int i = 0; i < msgs.length && browseList.size() < max; i++) {
636                        browseList.add(msgs[i]);
637                    }
638                }
639            }
640        } catch (Throwable e) {
641            LOG.warn("Failed to browse Topic: {}", getActiveMQDestination().getPhysicalName(), e);
642        }
643    }
644
645    @Override
646    public boolean iterate() {
647        synchronized (messagesWaitingForSpace) {
648            while (!memoryUsage.isFull() && !messagesWaitingForSpace.isEmpty()) {
649                Runnable op = messagesWaitingForSpace.removeFirst();
650                op.run();
651            }
652
653            if (!messagesWaitingForSpace.isEmpty()) {
654                registerCallbackForNotFullNotification();
655            }
656        }
657        return false;
658    }
659
660    private void registerCallbackForNotFullNotification() {
661        // If the usage manager is not full, then the task will not
662        // get called..
663        if (!memoryUsage.notifyCallbackWhenNotFull(sendMessagesWaitingForSpaceTask)) {
664            // so call it directly here.
665            sendMessagesWaitingForSpaceTask.run();
666        }
667    }
668
669    // Properties
670    // -------------------------------------------------------------------------
671
672    public DispatchPolicy getDispatchPolicy() {
673        return dispatchPolicy;
674    }
675
676    public void setDispatchPolicy(DispatchPolicy dispatchPolicy) {
677        this.dispatchPolicy = dispatchPolicy;
678    }
679
680    public SubscriptionRecoveryPolicy getSubscriptionRecoveryPolicy() {
681        return subscriptionRecoveryPolicy;
682    }
683
684    public void setSubscriptionRecoveryPolicy(SubscriptionRecoveryPolicy recoveryPolicy) {
685        if (this.subscriptionRecoveryPolicy != null && this.subscriptionRecoveryPolicy instanceof RetainedMessageSubscriptionRecoveryPolicy) {
686            // allow users to combine retained message policy with other ActiveMQ policies
687            RetainedMessageSubscriptionRecoveryPolicy policy = (RetainedMessageSubscriptionRecoveryPolicy) this.subscriptionRecoveryPolicy;
688            policy.setWrapped(recoveryPolicy);
689        } else {
690            this.subscriptionRecoveryPolicy = recoveryPolicy;
691        }
692    }
693
694    // Implementation methods
695    // -------------------------------------------------------------------------
696
697    @Override
698    public final void wakeup() {
699    }
700
701    protected void dispatch(final ConnectionContext context, Message message) throws Exception {
702        // AMQ-2586: Better to leave this stat at zero than to give the user
703        // misleading metrics.
704        // destinationStatistics.getMessages().increment();
705        destinationStatistics.getEnqueues().increment();
706        destinationStatistics.getMessageSize().addSize(message.getSize());
707        MessageEvaluationContext msgContext = null;
708
709        dispatchLock.readLock().lock();
710        try {
711            if (!subscriptionRecoveryPolicy.add(context, message)) {
712                return;
713            }
714            synchronized (consumers) {
715                if (consumers.isEmpty()) {
716                    onMessageWithNoConsumers(context, message);
717                    return;
718                }
719            }
720            msgContext = context.getMessageEvaluationContext();
721            msgContext.setDestination(destination);
722            msgContext.setMessageReference(message);
723            if (!dispatchPolicy.dispatch(message, msgContext, consumers)) {
724                onMessageWithNoConsumers(context, message);
725            }
726
727        } finally {
728            dispatchLock.readLock().unlock();
729            if (msgContext != null) {
730                msgContext.clear();
731            }
732        }
733    }
734
735    private final Runnable expireMessagesTask = new Runnable() {
736        @Override
737        public void run() {
738            List<Message> browsedMessages = new InsertionCountList<Message>();
739            doBrowse(browsedMessages, getMaxExpirePageSize());
740        }
741    };
742
743    @Override
744    public void messageExpired(ConnectionContext context, Subscription subs, MessageReference reference) {
745        broker.messageExpired(context, reference, subs);
746        // AMQ-2586: Better to leave this stat at zero than to give the user
747        // misleading metrics.
748        // destinationStatistics.getMessages().decrement();
749        destinationStatistics.getExpired().increment();
750        MessageAck ack = new MessageAck();
751        ack.setAckType(MessageAck.STANDARD_ACK_TYPE);
752        ack.setDestination(destination);
753        ack.setMessageID(reference.getMessageId());
754        try {
755            if (subs instanceof DurableTopicSubscription) {
756                ((DurableTopicSubscription)subs).removePending(reference);
757            }
758            acknowledge(context, subs, ack, reference);
759        } catch (Exception e) {
760            LOG.error("Failed to remove expired Message from the store ", e);
761        }
762    }
763
764    @Override
765    protected Logger getLog() {
766        return LOG;
767    }
768
769    protected boolean isOptimizeStorage(){
770        boolean result = false;
771
772        if (isDoOptimzeMessageStorage() && durableSubscribers.isEmpty()==false){
773                result = true;
774                for (DurableTopicSubscription s : durableSubscribers.values()) {
775                    if (s.isActive()== false){
776                        result = false;
777                        break;
778                    }
779                    if (s.getPrefetchSize()==0){
780                        result = false;
781                        break;
782                    }
783                    if (s.isSlowConsumer()){
784                        result = false;
785                        break;
786                    }
787                    if (s.getInFlightUsage() > getOptimizeMessageStoreInFlightLimit()){
788                        result = false;
789                        break;
790                    }
791                }
792        }
793        return result;
794    }
795
796    /**
797     * force a reread of the store - after transaction recovery completion
798     */
799    @Override
800    public void clearPendingMessages() {
801        dispatchLock.readLock().lock();
802        try {
803            for (DurableTopicSubscription durableTopicSubscription : durableSubscribers.values()) {
804                clearPendingAndDispatch(durableTopicSubscription);
805            }
806        } finally {
807            dispatchLock.readLock().unlock();
808        }
809    }
810
811    private void clearPendingAndDispatch(DurableTopicSubscription durableTopicSubscription) {
812        synchronized (durableTopicSubscription.pendingLock) {
813            durableTopicSubscription.pending.clear();
814            try {
815                durableTopicSubscription.dispatchPending();
816            } catch (IOException exception) {
817                LOG.warn("After clear of pending, failed to dispatch to: {}, for: {}, pending: {}", new Object[]{
818                        durableTopicSubscription,
819                        destination,
820                        durableTopicSubscription.pending }, exception);
821            }
822        }
823    }
824
825    private void rollback(MessageId poisoned) {
826        dispatchLock.readLock().lock();
827        try {
828            for (DurableTopicSubscription durableTopicSubscription : durableSubscribers.values()) {
829                durableTopicSubscription.getPending().rollback(poisoned);
830            }
831        } finally {
832            dispatchLock.readLock().unlock();
833        }
834    }
835
836    public Map<SubscriptionKey, DurableTopicSubscription> getDurableTopicSubs() {
837        return durableSubscribers;
838    }
839}