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