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