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