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