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.store.kahadb;
018
019import java.io.DataInputStream;
020import java.io.IOException;
021import java.io.InterruptedIOException;
022import java.util.ArrayList;
023import java.util.HashMap;
024import java.util.HashSet;
025import java.util.Iterator;
026import java.util.LinkedHashSet;
027import java.util.LinkedList;
028import java.util.List;
029import java.util.Map;
030import java.util.Map.Entry;
031import java.util.Set;
032import java.util.concurrent.BlockingQueue;
033import java.util.concurrent.ExecutorService;
034import java.util.concurrent.FutureTask;
035import java.util.concurrent.LinkedBlockingQueue;
036import java.util.concurrent.Semaphore;
037import java.util.concurrent.ThreadFactory;
038import java.util.concurrent.ThreadPoolExecutor;
039import java.util.concurrent.TimeUnit;
040import java.util.concurrent.TimeoutException;
041import java.util.concurrent.atomic.AtomicBoolean;
042import java.util.concurrent.atomic.AtomicInteger;
043import java.util.concurrent.atomic.AtomicLong;
044import java.util.concurrent.atomic.AtomicReference;
045
046import org.apache.activemq.broker.ConnectionContext;
047import org.apache.activemq.broker.region.BaseDestination;
048import org.apache.activemq.broker.scheduler.JobSchedulerStore;
049import org.apache.activemq.command.ActiveMQDestination;
050import org.apache.activemq.command.ActiveMQQueue;
051import org.apache.activemq.command.ActiveMQTempQueue;
052import org.apache.activemq.command.ActiveMQTempTopic;
053import org.apache.activemq.command.ActiveMQTopic;
054import org.apache.activemq.command.Message;
055import org.apache.activemq.command.MessageAck;
056import org.apache.activemq.command.MessageId;
057import org.apache.activemq.command.ProducerId;
058import org.apache.activemq.command.SubscriptionInfo;
059import org.apache.activemq.command.TransactionId;
060import org.apache.activemq.openwire.OpenWireFormat;
061import org.apache.activemq.protobuf.Buffer;
062import org.apache.activemq.store.AbstractMessageStore;
063import org.apache.activemq.store.IndexListener;
064import org.apache.activemq.store.ListenableFuture;
065import org.apache.activemq.store.MessageRecoveryListener;
066import org.apache.activemq.store.MessageStore;
067import org.apache.activemq.store.MessageStoreStatistics;
068import org.apache.activemq.store.MessageStoreSubscriptionStatistics;
069import org.apache.activemq.store.NoLocalSubscriptionAware;
070import org.apache.activemq.store.PersistenceAdapter;
071import org.apache.activemq.store.ProxyMessageStore;
072import org.apache.activemq.store.TopicMessageStore;
073import org.apache.activemq.store.TransactionIdTransformer;
074import org.apache.activemq.store.TransactionStore;
075import org.apache.activemq.store.kahadb.data.KahaAddMessageCommand;
076import org.apache.activemq.store.kahadb.data.KahaDestination;
077import org.apache.activemq.store.kahadb.data.KahaDestination.DestinationType;
078import org.apache.activemq.store.kahadb.data.KahaLocation;
079import org.apache.activemq.store.kahadb.data.KahaRemoveDestinationCommand;
080import org.apache.activemq.store.kahadb.data.KahaRemoveMessageCommand;
081import org.apache.activemq.store.kahadb.data.KahaSubscriptionCommand;
082import org.apache.activemq.store.kahadb.data.KahaUpdateMessageCommand;
083import org.apache.activemq.store.kahadb.disk.journal.Location;
084import org.apache.activemq.store.kahadb.disk.page.Transaction;
085import org.apache.activemq.store.kahadb.disk.util.SequenceSet;
086import org.apache.activemq.store.kahadb.scheduler.JobSchedulerStoreImpl;
087import org.apache.activemq.usage.MemoryUsage;
088import org.apache.activemq.usage.SystemUsage;
089import org.apache.activemq.util.IOExceptionSupport;
090import org.apache.activemq.util.ServiceStopper;
091import org.apache.activemq.util.ThreadPoolUtils;
092import org.apache.activemq.wireformat.WireFormat;
093import org.slf4j.Logger;
094import org.slf4j.LoggerFactory;
095
096public class KahaDBStore extends MessageDatabase implements PersistenceAdapter, NoLocalSubscriptionAware {
097    static final Logger LOG = LoggerFactory.getLogger(KahaDBStore.class);
098    private static final int MAX_ASYNC_JOBS = BaseDestination.MAX_AUDIT_DEPTH;
099
100    public static final String PROPERTY_CANCELED_TASK_MOD_METRIC = "org.apache.activemq.store.kahadb.CANCELED_TASK_MOD_METRIC";
101    public static final int cancelledTaskModMetric = Integer.parseInt(System.getProperty(
102            PROPERTY_CANCELED_TASK_MOD_METRIC, "0"), 10);
103    public static final String PROPERTY_ASYNC_EXECUTOR_MAX_THREADS = "org.apache.activemq.store.kahadb.ASYNC_EXECUTOR_MAX_THREADS";
104    private static final int asyncExecutorMaxThreads = Integer.parseInt(System.getProperty(
105            PROPERTY_ASYNC_EXECUTOR_MAX_THREADS, "1"), 10);;
106
107    protected ExecutorService queueExecutor;
108    protected ExecutorService topicExecutor;
109    protected final List<Map<AsyncJobKey, StoreTask>> asyncQueueMaps = new LinkedList<Map<AsyncJobKey, StoreTask>>();
110    protected final List<Map<AsyncJobKey, StoreTask>> asyncTopicMaps = new LinkedList<Map<AsyncJobKey, StoreTask>>();
111    final WireFormat wireFormat = new OpenWireFormat();
112    private SystemUsage usageManager;
113    private LinkedBlockingQueue<Runnable> asyncQueueJobQueue;
114    private LinkedBlockingQueue<Runnable> asyncTopicJobQueue;
115    Semaphore globalQueueSemaphore;
116    Semaphore globalTopicSemaphore;
117    private boolean concurrentStoreAndDispatchQueues = true;
118    // when true, message order may be compromised when cache is exhausted if store is out
119    // or order w.r.t cache
120    private boolean concurrentStoreAndDispatchTopics = false;
121    private final boolean concurrentStoreAndDispatchTransactions = false;
122    private int maxAsyncJobs = MAX_ASYNC_JOBS;
123    private final KahaDBTransactionStore transactionStore;
124    private TransactionIdTransformer transactionIdTransformer;
125
126    public KahaDBStore() {
127        this.transactionStore = new KahaDBTransactionStore(this);
128        this.transactionIdTransformer = new TransactionIdTransformer() {
129            @Override
130            public TransactionId transform(TransactionId txid) {
131                return txid;
132            }
133        };
134    }
135
136    @Override
137    public String toString() {
138        return "KahaDB:[" + directory.getAbsolutePath() + "]";
139    }
140
141    @Override
142    public void setBrokerName(String brokerName) {
143    }
144
145    @Override
146    public void setUsageManager(SystemUsage usageManager) {
147        this.usageManager = usageManager;
148    }
149
150    public SystemUsage getUsageManager() {
151        return this.usageManager;
152    }
153
154    /**
155     * @return the concurrentStoreAndDispatch
156     */
157    public boolean isConcurrentStoreAndDispatchQueues() {
158        return this.concurrentStoreAndDispatchQueues;
159    }
160
161    /**
162     * @param concurrentStoreAndDispatch
163     *            the concurrentStoreAndDispatch to set
164     */
165    public void setConcurrentStoreAndDispatchQueues(boolean concurrentStoreAndDispatch) {
166        this.concurrentStoreAndDispatchQueues = concurrentStoreAndDispatch;
167    }
168
169    /**
170     * @return the concurrentStoreAndDispatch
171     */
172    public boolean isConcurrentStoreAndDispatchTopics() {
173        return this.concurrentStoreAndDispatchTopics;
174    }
175
176    /**
177     * @param concurrentStoreAndDispatch
178     *            the concurrentStoreAndDispatch to set
179     */
180    public void setConcurrentStoreAndDispatchTopics(boolean concurrentStoreAndDispatch) {
181        this.concurrentStoreAndDispatchTopics = concurrentStoreAndDispatch;
182    }
183
184    public boolean isConcurrentStoreAndDispatchTransactions() {
185        return this.concurrentStoreAndDispatchTransactions;
186    }
187
188    /**
189     * @return the maxAsyncJobs
190     */
191    public int getMaxAsyncJobs() {
192        return this.maxAsyncJobs;
193    }
194
195    /**
196     * @param maxAsyncJobs
197     *            the maxAsyncJobs to set
198     */
199    public void setMaxAsyncJobs(int maxAsyncJobs) {
200        this.maxAsyncJobs = maxAsyncJobs;
201    }
202
203
204    @Override
205    protected void configureMetadata() {
206        if (brokerService != null) {
207            metadata.openwireVersion = brokerService.getStoreOpenWireVersion();
208            wireFormat.setVersion(metadata.openwireVersion);
209
210            if (LOG.isDebugEnabled()) {
211                LOG.debug("Store OpenWire version configured as: {}", metadata.openwireVersion);
212            }
213
214        }
215    }
216
217    @Override
218    public void doStart() throws Exception {
219        //configure the metadata before start, right now
220        //this is just the open wire version
221        configureMetadata();
222
223        super.doStart();
224
225        if (brokerService != null) {
226            // In case the recovered store used a different OpenWire version log a warning
227            // to assist in determining why journal reads fail.
228            if (metadata.openwireVersion != brokerService.getStoreOpenWireVersion()) {
229                LOG.warn("Existing Store uses a different OpenWire version[{}] " +
230                         "than the version configured[{}] reverting to the version " +
231                         "used by this store, some newer broker features may not work" +
232                         "as expected.",
233                         metadata.openwireVersion, brokerService.getStoreOpenWireVersion());
234
235                // Update the broker service instance to the actual version in use.
236                wireFormat.setVersion(metadata.openwireVersion);
237                brokerService.setStoreOpenWireVersion(metadata.openwireVersion);
238            }
239        }
240
241        this.globalQueueSemaphore = new Semaphore(getMaxAsyncJobs());
242        this.globalTopicSemaphore = new Semaphore(getMaxAsyncJobs());
243        this.asyncQueueJobQueue = new LinkedBlockingQueue<Runnable>(getMaxAsyncJobs());
244        this.asyncTopicJobQueue = new LinkedBlockingQueue<Runnable>(getMaxAsyncJobs());
245        this.queueExecutor = new StoreTaskExecutor(1, asyncExecutorMaxThreads, 0L, TimeUnit.MILLISECONDS,
246            asyncQueueJobQueue, new ThreadFactory() {
247                @Override
248                public Thread newThread(Runnable runnable) {
249                    Thread thread = new Thread(runnable, "ConcurrentQueueStoreAndDispatch");
250                    thread.setDaemon(true);
251                    return thread;
252                }
253            });
254        this.topicExecutor = new StoreTaskExecutor(1, asyncExecutorMaxThreads, 0L, TimeUnit.MILLISECONDS,
255            asyncTopicJobQueue, new ThreadFactory() {
256                @Override
257                public Thread newThread(Runnable runnable) {
258                    Thread thread = new Thread(runnable, "ConcurrentTopicStoreAndDispatch");
259                    thread.setDaemon(true);
260                    return thread;
261                }
262            });
263    }
264
265    @Override
266    public void doStop(ServiceStopper stopper) throws Exception {
267        // drain down async jobs
268        LOG.info("Stopping async queue tasks");
269        if (this.globalQueueSemaphore != null) {
270            this.globalQueueSemaphore.tryAcquire(this.maxAsyncJobs, 60, TimeUnit.SECONDS);
271        }
272        synchronized (this.asyncQueueMaps) {
273            for (Map<AsyncJobKey, StoreTask> m : asyncQueueMaps) {
274                synchronized (m) {
275                    for (StoreTask task : m.values()) {
276                        task.cancel();
277                    }
278                }
279            }
280            this.asyncQueueMaps.clear();
281        }
282        LOG.info("Stopping async topic tasks");
283        if (this.globalTopicSemaphore != null) {
284            this.globalTopicSemaphore.tryAcquire(this.maxAsyncJobs, 60, TimeUnit.SECONDS);
285        }
286        synchronized (this.asyncTopicMaps) {
287            for (Map<AsyncJobKey, StoreTask> m : asyncTopicMaps) {
288                synchronized (m) {
289                    for (StoreTask task : m.values()) {
290                        task.cancel();
291                    }
292                }
293            }
294            this.asyncTopicMaps.clear();
295        }
296        if (this.globalQueueSemaphore != null) {
297            this.globalQueueSemaphore.drainPermits();
298        }
299        if (this.globalTopicSemaphore != null) {
300            this.globalTopicSemaphore.drainPermits();
301        }
302        if (this.queueExecutor != null) {
303            ThreadPoolUtils.shutdownNow(queueExecutor);
304            queueExecutor = null;
305        }
306        if (this.topicExecutor != null) {
307            ThreadPoolUtils.shutdownNow(topicExecutor);
308            topicExecutor = null;
309        }
310        LOG.info("Stopped KahaDB");
311        super.doStop(stopper);
312    }
313
314    private Location findMessageLocation(final String key, final KahaDestination destination) throws IOException {
315        return pageFile.tx().execute(new Transaction.CallableClosure<Location, IOException>() {
316            @Override
317            public Location execute(Transaction tx) throws IOException {
318                StoredDestination sd = getStoredDestination(destination, tx);
319                Long sequence = sd.messageIdIndex.get(tx, key);
320                if (sequence == null) {
321                    return null;
322                }
323                return sd.orderIndex.get(tx, sequence).location;
324            }
325        });
326    }
327
328    protected StoreQueueTask removeQueueTask(KahaDBMessageStore store, MessageId id) {
329        StoreQueueTask task = null;
330        synchronized (store.asyncTaskMap) {
331            task = (StoreQueueTask) store.asyncTaskMap.remove(new AsyncJobKey(id, store.getDestination()));
332        }
333        return task;
334    }
335
336    // with asyncTaskMap locked
337    protected void addQueueTask(KahaDBMessageStore store, StoreQueueTask task) throws IOException {
338        store.asyncTaskMap.put(new AsyncJobKey(task.getMessage().getMessageId(), store.getDestination()), task);
339        this.queueExecutor.execute(task);
340    }
341
342    protected StoreTopicTask removeTopicTask(KahaDBTopicMessageStore store, MessageId id) {
343        StoreTopicTask task = null;
344        synchronized (store.asyncTaskMap) {
345            task = (StoreTopicTask) store.asyncTaskMap.remove(new AsyncJobKey(id, store.getDestination()));
346        }
347        return task;
348    }
349
350    protected void addTopicTask(KahaDBTopicMessageStore store, StoreTopicTask task) throws IOException {
351        synchronized (store.asyncTaskMap) {
352            store.asyncTaskMap.put(new AsyncJobKey(task.getMessage().getMessageId(), store.getDestination()), task);
353        }
354        this.topicExecutor.execute(task);
355    }
356
357    @Override
358    public TransactionStore createTransactionStore() throws IOException {
359        return this.transactionStore;
360    }
361
362    public boolean getForceRecoverIndex() {
363        return this.forceRecoverIndex;
364    }
365
366    public void setForceRecoverIndex(boolean forceRecoverIndex) {
367        this.forceRecoverIndex = forceRecoverIndex;
368    }
369
370    public void forgetRecoveredAcks(ArrayList<MessageAck> preparedAcks, boolean isRollback) throws IOException {
371        if (preparedAcks != null) {
372            Map<ActiveMQDestination, KahaDBMessageStore> stores = new HashMap<>();
373            for (MessageAck ack : preparedAcks) {
374                stores.put(ack.getDestination(), findMatchingStore(ack.getDestination()));
375            }
376            ArrayList<MessageAck> perStoreAcks = new ArrayList<>();
377            for (Entry<ActiveMQDestination, KahaDBMessageStore> entry : stores.entrySet()) {
378                for (MessageAck ack : preparedAcks) {
379                    if (entry.getKey().equals(ack.getDestination())) {
380                        perStoreAcks.add(ack);
381                    }
382                }
383                entry.getValue().forgetRecoveredAcks(perStoreAcks, isRollback);
384                perStoreAcks.clear();
385            }
386        }
387    }
388
389    public void trackRecoveredAcks(ArrayList<MessageAck> preparedAcks) throws IOException {
390        Map<ActiveMQDestination, KahaDBMessageStore> stores = new HashMap<>();
391        for (MessageAck ack : preparedAcks) {
392            stores.put(ack.getDestination(), findMatchingStore(ack.getDestination()));
393        }
394        ArrayList<MessageAck> perStoreAcks = new ArrayList<>();
395        for (Entry<ActiveMQDestination, KahaDBMessageStore> entry : stores.entrySet()) {
396            for (MessageAck ack : preparedAcks) {
397                if (entry.getKey().equals(ack.getDestination())) {
398                    perStoreAcks.add(ack);
399                }
400            }
401            entry.getValue().trackRecoveredAcks(perStoreAcks);
402            perStoreAcks.clear();
403        }
404    }
405
406    private KahaDBMessageStore findMatchingStore(ActiveMQDestination activeMQDestination) throws IOException {
407        ProxyMessageStore store = (ProxyMessageStore) storeCache.get(key(convert(activeMQDestination)));
408        if (store == null) {
409            if (activeMQDestination.isQueue()) {
410                store = (ProxyMessageStore) createQueueMessageStore((ActiveMQQueue) activeMQDestination);
411            } else {
412                store = (ProxyMessageStore) createTopicMessageStore((ActiveMQTopic) activeMQDestination);
413            }
414        }
415        return (KahaDBMessageStore) store.getDelegate();
416    }
417
418    public class KahaDBMessageStore extends AbstractMessageStore {
419        protected final Map<AsyncJobKey, StoreTask> asyncTaskMap = new HashMap<AsyncJobKey, StoreTask>();
420        protected KahaDestination dest;
421        private final int maxAsyncJobs;
422        private final Semaphore localDestinationSemaphore;
423        protected final HashMap<String, Set<String>> ackedAndPreparedMap = new HashMap<String, Set<String>>();
424        protected final HashMap<String, Set<String>> rolledBackAcksMap = new HashMap<String, Set<String>>();
425
426        double doneTasks, canceledTasks = 0;
427
428        public KahaDBMessageStore(ActiveMQDestination destination) {
429            super(destination);
430            this.dest = convert(destination);
431            this.maxAsyncJobs = getMaxAsyncJobs();
432            this.localDestinationSemaphore = new Semaphore(this.maxAsyncJobs);
433        }
434
435        @Override
436        public ActiveMQDestination getDestination() {
437            return destination;
438        }
439
440
441        private final String recoveredTxStateMapKey(ActiveMQDestination destination, MessageAck ack) {
442            return destination.isQueue() ? destination.getPhysicalName() : ack.getConsumerId().getConnectionId();
443        }
444
445        // messages that have prepared (pending) acks cannot be re-dispatched unless the outcome is rollback,
446        // till then they are skipped by the store.
447        // 'at most once' XA guarantee
448        public void trackRecoveredAcks(ArrayList<MessageAck> acks) {
449            indexLock.writeLock().lock();
450            try {
451                for (MessageAck ack : acks) {
452                    final String key = recoveredTxStateMapKey(destination, ack);
453                    Set ackedAndPrepared = ackedAndPreparedMap.get(key);
454                    if (ackedAndPrepared == null) {
455                        ackedAndPrepared = new LinkedHashSet<String>();
456                        ackedAndPreparedMap.put(key, ackedAndPrepared);
457                    }
458                    ackedAndPrepared.add(ack.getLastMessageId().toProducerKey());
459                }
460            } finally {
461                indexLock.writeLock().unlock();
462            }
463        }
464
465        public void forgetRecoveredAcks(ArrayList<MessageAck> acks, boolean rollback) throws IOException {
466            if (acks != null) {
467                indexLock.writeLock().lock();
468                try {
469                    for (MessageAck ack : acks) {
470                        final String id = ack.getLastMessageId().toProducerKey();
471                        final String key = recoveredTxStateMapKey(destination, ack);
472                        Set ackedAndPrepared = ackedAndPreparedMap.get(key);
473                        if (ackedAndPrepared != null) {
474                            ackedAndPrepared.remove(id);
475                            if (ackedAndPreparedMap.isEmpty()) {
476                                ackedAndPreparedMap.remove(key);
477                            }
478                        }
479                        if (rollback) {
480                            Set rolledBackAcks = rolledBackAcksMap.get(key);
481                            if (rolledBackAcks == null) {
482                                rolledBackAcks = new LinkedHashSet<String>();
483                                rolledBackAcksMap.put(key, rolledBackAcks);
484                            }
485                            rolledBackAcks.add(id);
486                            pageFile.tx().execute(tx -> {
487                                incrementAndAddSizeToStoreStat(tx, dest, 0);
488                            });
489                        }
490                    }
491                } finally {
492                    indexLock.writeLock().unlock();
493                }
494            }
495        }
496
497        @Override
498        public ListenableFuture<Object> asyncAddQueueMessage(final ConnectionContext context, final Message message)
499                throws IOException {
500            if (isConcurrentStoreAndDispatchQueues()) {
501                message.beforeMarshall(wireFormat);
502                StoreQueueTask result = new StoreQueueTask(this, context, message);
503                ListenableFuture<Object> future = result.getFuture();
504                message.getMessageId().setFutureOrSequenceLong(future);
505                message.setRecievedByDFBridge(true); // flag message as concurrentStoreAndDispatch
506                result.aquireLocks();
507                synchronized (asyncTaskMap) {
508                    addQueueTask(this, result);
509                    if (indexListener != null) {
510                        indexListener.onAdd(new IndexListener.MessageContext(context, message, null));
511                    }
512                }
513                return future;
514            } else {
515                return super.asyncAddQueueMessage(context, message);
516            }
517        }
518
519        @Override
520        public void removeAsyncMessage(ConnectionContext context, MessageAck ack) throws IOException {
521            if (isConcurrentStoreAndDispatchQueues()) {
522                AsyncJobKey key = new AsyncJobKey(ack.getLastMessageId(), getDestination());
523                StoreQueueTask task = null;
524                synchronized (asyncTaskMap) {
525                    task = (StoreQueueTask) asyncTaskMap.get(key);
526                }
527                if (task != null) {
528                    if (ack.isInTransaction() || !task.cancel()) {
529                        try {
530                            task.future.get();
531                        } catch (InterruptedException e) {
532                            throw new InterruptedIOException(e.toString());
533                        } catch (Exception ignored) {
534                            LOG.debug("removeAsync: cannot cancel, waiting for add resulted in ex", ignored);
535                        }
536                        removeMessage(context, ack);
537                    } else {
538                        indexLock.writeLock().lock();
539                        try {
540                            metadata.producerSequenceIdTracker.isDuplicate(ack.getLastMessageId());
541                        } finally {
542                            indexLock.writeLock().unlock();
543                        }
544                        synchronized (asyncTaskMap) {
545                            asyncTaskMap.remove(key);
546                        }
547                    }
548                } else {
549                    removeMessage(context, ack);
550                }
551            } else {
552                removeMessage(context, ack);
553            }
554        }
555
556        @Override
557        public void addMessage(final ConnectionContext context, final Message message) throws IOException {
558            final KahaAddMessageCommand command = new KahaAddMessageCommand();
559            command.setDestination(dest);
560            command.setMessageId(message.getMessageId().toProducerKey());
561            command.setTransactionInfo(TransactionIdConversion.convert(transactionIdTransformer.transform(message.getTransactionId())));
562            command.setPriority(message.getPriority());
563            command.setPrioritySupported(isPrioritizedMessages());
564            org.apache.activemq.util.ByteSequence packet = wireFormat.marshal(message);
565            command.setMessage(new Buffer(packet.getData(), packet.getOffset(), packet.getLength()));
566            store(command, isEnableJournalDiskSyncs() && message.isResponseRequired(), new IndexAware() {
567                // sync add? (for async, future present from getFutureOrSequenceLong)
568                Object possibleFuture = message.getMessageId().getFutureOrSequenceLong();
569
570                @Override
571                public void sequenceAssignedWithIndexLocked(final long sequence) {
572                    message.getMessageId().setFutureOrSequenceLong(sequence);
573                    if (indexListener != null) {
574                        if (possibleFuture == null) {
575                            trackPendingAdd(dest, sequence);
576                            indexListener.onAdd(new IndexListener.MessageContext(context, message, new Runnable() {
577                                @Override
578                                public void run() {
579                                    trackPendingAddComplete(dest, sequence);
580                                }
581                            }));
582                        }
583                    }
584                }
585            }, null);
586        }
587
588        @Override
589        public void updateMessage(Message message) throws IOException {
590            if (LOG.isTraceEnabled()) {
591                LOG.trace("updating: " + message.getMessageId() + " with deliveryCount: " + message.getRedeliveryCounter());
592            }
593            KahaUpdateMessageCommand updateMessageCommand = new KahaUpdateMessageCommand();
594            KahaAddMessageCommand command = new KahaAddMessageCommand();
595            command.setDestination(dest);
596            command.setMessageId(message.getMessageId().toProducerKey());
597            command.setPriority(message.getPriority());
598            command.setPrioritySupported(prioritizedMessages);
599            org.apache.activemq.util.ByteSequence packet = wireFormat.marshal(message);
600            command.setMessage(new Buffer(packet.getData(), packet.getOffset(), packet.getLength()));
601            updateMessageCommand.setMessage(command);
602            store(updateMessageCommand, isEnableJournalDiskSyncs(), null, null);
603        }
604
605        @Override
606        public void removeMessage(ConnectionContext context, MessageAck ack) throws IOException {
607            KahaRemoveMessageCommand command = new KahaRemoveMessageCommand();
608            command.setDestination(dest);
609            command.setMessageId(ack.getLastMessageId().toProducerKey());
610            command.setTransactionInfo(TransactionIdConversion.convert(transactionIdTransformer.transform(ack.getTransactionId())));
611
612            org.apache.activemq.util.ByteSequence packet = wireFormat.marshal(ack);
613            command.setAck(new Buffer(packet.getData(), packet.getOffset(), packet.getLength()));
614            store(command, isEnableJournalDiskSyncs() && ack.isResponseRequired(), null, null);
615        }
616
617        @Override
618        public void removeAllMessages(ConnectionContext context) throws IOException {
619            KahaRemoveDestinationCommand command = new KahaRemoveDestinationCommand();
620            command.setDestination(dest);
621            store(command, true, null, null);
622        }
623
624        @Override
625        public Message getMessage(MessageId identity) throws IOException {
626            final String key = identity.toProducerKey();
627
628            // Hopefully one day the page file supports concurrent read
629            // operations... but for now we must
630            // externally synchronize...
631            Location location;
632            indexLock.writeLock().lock();
633            try {
634                location = findMessageLocation(key, dest);
635            } finally {
636                indexLock.writeLock().unlock();
637            }
638            if (location == null) {
639                return null;
640            }
641
642            return loadMessage(location);
643        }
644
645        @Override
646        public boolean isEmpty() throws IOException {
647            indexLock.writeLock().lock();
648            try {
649                return pageFile.tx().execute(new Transaction.CallableClosure<Boolean, IOException>() {
650                    @Override
651                    public Boolean execute(Transaction tx) throws IOException {
652                        // Iterate through all index entries to get a count of
653                        // messages in the destination.
654                        StoredDestination sd = getStoredDestination(dest, tx);
655                        return sd.locationIndex.isEmpty(tx);
656                    }
657                });
658            } finally {
659                indexLock.writeLock().unlock();
660            }
661        }
662
663        @Override
664        public void recover(final MessageRecoveryListener listener) throws Exception {
665            // recovery may involve expiry which will modify
666            indexLock.writeLock().lock();
667            try {
668                pageFile.tx().execute(new Transaction.Closure<Exception>() {
669                    @Override
670                    public void execute(Transaction tx) throws Exception {
671                        StoredDestination sd = getStoredDestination(dest, tx);
672                        recoverRolledBackAcks(destination.getPhysicalName(), sd, tx, Integer.MAX_VALUE, listener);
673                        sd.orderIndex.resetCursorPosition();
674                        for (Iterator<Entry<Long, MessageKeys>> iterator = sd.orderIndex.iterator(tx); listener.hasSpace() && iterator
675                                .hasNext(); ) {
676                            Entry<Long, MessageKeys> entry = iterator.next();
677                            Set ackedAndPrepared = ackedAndPreparedMap.get(destination.getPhysicalName());
678                            if (ackedAndPrepared != null && ackedAndPrepared.contains(entry.getValue().messageId)) {
679                                continue;
680                            }
681                            Message msg = loadMessage(entry.getValue().location);
682                            listener.recoverMessage(msg);
683                        }
684                    }
685                });
686            } finally {
687                indexLock.writeLock().unlock();
688            }
689        }
690
691        @Override
692        public void recoverNextMessages(final int maxReturned, final MessageRecoveryListener listener) throws Exception {
693            indexLock.writeLock().lock();
694            try {
695                pageFile.tx().execute(new Transaction.Closure<Exception>() {
696                    @Override
697                    public void execute(Transaction tx) throws Exception {
698                        StoredDestination sd = getStoredDestination(dest, tx);
699                        Entry<Long, MessageKeys> entry = null;
700                        int counter = recoverRolledBackAcks(destination.getPhysicalName(), sd, tx, maxReturned, listener);
701                        Set ackedAndPrepared = ackedAndPreparedMap.get(destination.getPhysicalName());
702                        for (Iterator<Entry<Long, MessageKeys>> iterator = sd.orderIndex.iterator(tx); iterator.hasNext(); ) {
703                            entry = iterator.next();
704                            if (ackedAndPrepared != null && ackedAndPrepared.contains(entry.getValue().messageId)) {
705                                continue;
706                            }
707                            Message msg = loadMessage(entry.getValue().location);
708                            msg.getMessageId().setFutureOrSequenceLong(entry.getKey());
709                            listener.recoverMessage(msg);
710                            counter++;
711                            if (counter >= maxReturned || !listener.canRecoveryNextMessage()) {
712                                break;
713                            }
714                        }
715                        sd.orderIndex.stoppedIterating();
716                    }
717                });
718            } finally {
719                indexLock.writeLock().unlock();
720            }
721        }
722
723        protected int recoverRolledBackAcks(String recoveredTxStateMapKey, StoredDestination sd, Transaction tx, int maxReturned, MessageRecoveryListener listener) throws Exception {
724            int counter = 0;
725            String id;
726
727            Set rolledBackAcks = rolledBackAcksMap.get(recoveredTxStateMapKey);
728            if (rolledBackAcks == null) {
729                return counter;
730            }
731            for (Iterator<String> iterator = rolledBackAcks.iterator(); iterator.hasNext(); ) {
732                id = iterator.next();
733                iterator.remove();
734                Long sequence = sd.messageIdIndex.get(tx, id);
735                if (sequence != null) {
736                    if (sd.orderIndex.alreadyDispatched(sequence)) {
737                        listener.recoverMessage(loadMessage(sd.orderIndex.get(tx, sequence).location));
738                        counter++;
739                        if (counter >= maxReturned) {
740                            break;
741                        }
742                    } else {
743                        LOG.debug("rolledback ack message {} with seq {} will be picked up in future batch {}", id, sequence, sd.orderIndex.cursor);
744                    }
745                } else {
746                    LOG.warn("Failed to locate rolled back ack message {} in {}", id, sd);
747                }
748            }
749            if (rolledBackAcks.isEmpty()) {
750                rolledBackAcksMap.remove(recoveredTxStateMapKey);
751            }
752            return counter;
753        }
754
755
756        @Override
757        public void resetBatching() {
758            if (pageFile.isLoaded()) {
759                indexLock.writeLock().lock();
760                try {
761                    pageFile.tx().execute(new Transaction.Closure<Exception>() {
762                        @Override
763                        public void execute(Transaction tx) throws Exception {
764                            StoredDestination sd = getExistingStoredDestination(dest, tx);
765                            if (sd != null) {
766                                sd.orderIndex.resetCursorPosition();}
767                            }
768                        });
769                } catch (Exception e) {
770                    LOG.error("Failed to reset batching",e);
771                } finally {
772                    indexLock.writeLock().unlock();
773                }
774            }
775        }
776
777        @Override
778        public void setBatch(final MessageId identity) throws IOException {
779            indexLock.writeLock().lock();
780            try {
781                pageFile.tx().execute(new Transaction.Closure<IOException>() {
782                    @Override
783                    public void execute(Transaction tx) throws IOException {
784                        StoredDestination sd = getStoredDestination(dest, tx);
785                        Long location = (Long) identity.getFutureOrSequenceLong();
786                        Long pending = sd.orderIndex.minPendingAdd();
787                        if (pending != null) {
788                            location = Math.min(location, pending-1);
789                        }
790                        sd.orderIndex.setBatch(tx, location);
791                    }
792                });
793            } finally {
794                indexLock.writeLock().unlock();
795            }
796        }
797
798        @Override
799        public void setMemoryUsage(MemoryUsage memoryUsage) {
800        }
801        @Override
802        public void start() throws Exception {
803            super.start();
804        }
805        @Override
806        public void stop() throws Exception {
807            super.stop();
808        }
809
810        protected void lockAsyncJobQueue() {
811            try {
812                if (!this.localDestinationSemaphore.tryAcquire(this.maxAsyncJobs, 60, TimeUnit.SECONDS)) {
813                    throw new TimeoutException(this +" timeout waiting for localDestSem:" + this.localDestinationSemaphore);
814                }
815            } catch (Exception e) {
816                LOG.error("Failed to lock async jobs for " + this.destination, e);
817            }
818        }
819
820        protected void unlockAsyncJobQueue() {
821            this.localDestinationSemaphore.release(this.maxAsyncJobs);
822        }
823
824        protected void acquireLocalAsyncLock() {
825            try {
826                this.localDestinationSemaphore.acquire();
827            } catch (InterruptedException e) {
828                LOG.error("Failed to aquire async lock for " + this.destination, e);
829            }
830        }
831
832        protected void releaseLocalAsyncLock() {
833            this.localDestinationSemaphore.release();
834        }
835
836        @Override
837        public String toString(){
838            return "permits:" + this.localDestinationSemaphore.availablePermits() + ",sd=" + storedDestinations.get(key(dest));
839        }
840
841        @Override
842        protected void recoverMessageStoreStatistics() throws IOException {
843            try {
844                MessageStoreStatistics recoveredStatistics;
845                lockAsyncJobQueue();
846                indexLock.writeLock().lock();
847                try {
848                    recoveredStatistics = pageFile.tx().execute(new Transaction.CallableClosure<MessageStoreStatistics, IOException>() {
849                        @Override
850                        public MessageStoreStatistics execute(Transaction tx) throws IOException {
851                            MessageStoreStatistics statistics = getStoredMessageStoreStatistics(dest, tx);
852
853                            // Iterate through all index entries to get the size of each message
854                            if (statistics == null) {
855                                StoredDestination sd = getStoredDestination(dest, tx);
856                                statistics = new MessageStoreStatistics();
857                                for (Iterator<Entry<Location, Long>> iterator = sd.locationIndex.iterator(tx); iterator.hasNext(); ) {
858                                    int locationSize = iterator.next().getKey().getSize();
859                                    statistics.getMessageCount().increment();
860                                    statistics.getMessageSize().addSize(locationSize > 0 ? locationSize : 0);
861                                }
862                            }
863                            return statistics;
864                        }
865                    });
866                    Set ackedAndPrepared = ackedAndPreparedMap.get(destination.getPhysicalName());
867                    if (ackedAndPrepared != null) {
868                        recoveredStatistics.getMessageCount().subtract(ackedAndPrepared.size());
869                    }
870                    getMessageStoreStatistics().getMessageCount().setCount(recoveredStatistics.getMessageCount().getCount());
871                    getMessageStoreStatistics().getMessageSize().setTotalSize(recoveredStatistics.getMessageSize().getTotalSize());
872                } finally {
873                    indexLock.writeLock().unlock();
874                }
875            } finally {
876                unlockAsyncJobQueue();
877            }
878        }
879    }
880
881    class KahaDBTopicMessageStore extends KahaDBMessageStore implements TopicMessageStore {
882        private final AtomicInteger subscriptionCount = new AtomicInteger();
883        protected final MessageStoreSubscriptionStatistics messageStoreSubStats =
884                new MessageStoreSubscriptionStatistics(isEnableSubscriptionStatistics());
885
886        public KahaDBTopicMessageStore(ActiveMQTopic destination) throws IOException {
887            super(destination);
888            this.subscriptionCount.set(getAllSubscriptions().length);
889            if (isConcurrentStoreAndDispatchTopics()) {
890                asyncTopicMaps.add(asyncTaskMap);
891            }
892        }
893
894        @Override
895        protected void recoverMessageStoreStatistics() throws IOException {
896            super.recoverMessageStoreStatistics();
897            this.recoverMessageStoreSubMetrics();
898        }
899
900        @Override
901        public ListenableFuture<Object> asyncAddTopicMessage(final ConnectionContext context, final Message message)
902                throws IOException {
903            if (isConcurrentStoreAndDispatchTopics()) {
904                message.beforeMarshall(wireFormat);
905                StoreTopicTask result = new StoreTopicTask(this, context, message, subscriptionCount.get());
906                result.aquireLocks();
907                addTopicTask(this, result);
908                return result.getFuture();
909            } else {
910                return super.asyncAddTopicMessage(context, message);
911            }
912        }
913
914        @Override
915        public void acknowledge(ConnectionContext context, String clientId, String subscriptionName,
916                                MessageId messageId, MessageAck ack) throws IOException {
917            String subscriptionKey = subscriptionKey(clientId, subscriptionName).toString();
918            if (isConcurrentStoreAndDispatchTopics()) {
919                AsyncJobKey key = new AsyncJobKey(messageId, getDestination());
920                StoreTopicTask task = null;
921                synchronized (asyncTaskMap) {
922                    task = (StoreTopicTask) asyncTaskMap.get(key);
923                }
924                if (task != null) {
925                    if (task.addSubscriptionKey(subscriptionKey)) {
926                        removeTopicTask(this, messageId);
927                        if (task.cancel()) {
928                            synchronized (asyncTaskMap) {
929                                asyncTaskMap.remove(key);
930                            }
931                        }
932                    }
933                } else {
934                    doAcknowledge(context, subscriptionKey, messageId, ack);
935                }
936            } else {
937                doAcknowledge(context, subscriptionKey, messageId, ack);
938            }
939        }
940
941        protected void doAcknowledge(ConnectionContext context, String subscriptionKey, MessageId messageId, MessageAck ack)
942                throws IOException {
943            KahaRemoveMessageCommand command = new KahaRemoveMessageCommand();
944            command.setDestination(dest);
945            command.setSubscriptionKey(subscriptionKey);
946            command.setMessageId(messageId.toProducerKey());
947            command.setTransactionInfo(ack != null ? TransactionIdConversion.convert(transactionIdTransformer.transform(ack.getTransactionId())) : null);
948            if (ack != null && ack.isUnmatchedAck()) {
949                command.setAck(UNMATCHED);
950            } else {
951                org.apache.activemq.util.ByteSequence packet = wireFormat.marshal(ack);
952                command.setAck(new Buffer(packet.getData(), packet.getOffset(), packet.getLength()));
953            }
954            store(command, false, null, null);
955        }
956
957        @Override
958        public void addSubscription(SubscriptionInfo subscriptionInfo, boolean retroactive) throws IOException {
959            String subscriptionKey = subscriptionKey(subscriptionInfo.getClientId(), subscriptionInfo
960                    .getSubscriptionName());
961            KahaSubscriptionCommand command = new KahaSubscriptionCommand();
962            command.setDestination(dest);
963            command.setSubscriptionKey(subscriptionKey.toString());
964            command.setRetroactive(retroactive);
965            org.apache.activemq.util.ByteSequence packet = wireFormat.marshal(subscriptionInfo);
966            command.setSubscriptionInfo(new Buffer(packet.getData(), packet.getOffset(), packet.getLength()));
967            store(command, isEnableJournalDiskSyncs() && true, null, null);
968            this.subscriptionCount.incrementAndGet();
969        }
970
971        @Override
972        public void deleteSubscription(String clientId, String subscriptionName) throws IOException {
973            KahaSubscriptionCommand command = new KahaSubscriptionCommand();
974            command.setDestination(dest);
975            command.setSubscriptionKey(subscriptionKey(clientId, subscriptionName).toString());
976            store(command, isEnableJournalDiskSyncs() && true, null, null);
977            this.subscriptionCount.decrementAndGet();
978        }
979
980        @Override
981        public SubscriptionInfo[] getAllSubscriptions() throws IOException {
982
983            final ArrayList<SubscriptionInfo> subscriptions = new ArrayList<SubscriptionInfo>();
984            indexLock.writeLock().lock();
985            try {
986                pageFile.tx().execute(new Transaction.Closure<IOException>() {
987                    @Override
988                    public void execute(Transaction tx) throws IOException {
989                        StoredDestination sd = getStoredDestination(dest, tx);
990                        for (Iterator<Entry<String, KahaSubscriptionCommand>> iterator = sd.subscriptions.iterator(tx); iterator
991                                .hasNext();) {
992                            Entry<String, KahaSubscriptionCommand> entry = iterator.next();
993                            SubscriptionInfo info = (SubscriptionInfo) wireFormat.unmarshal(new DataInputStream(entry
994                                    .getValue().getSubscriptionInfo().newInput()));
995                            subscriptions.add(info);
996
997                        }
998                    }
999                });
1000            } finally {
1001                indexLock.writeLock().unlock();
1002            }
1003
1004            SubscriptionInfo[] rc = new SubscriptionInfo[subscriptions.size()];
1005            subscriptions.toArray(rc);
1006            return rc;
1007        }
1008
1009        @Override
1010        public SubscriptionInfo lookupSubscription(String clientId, String subscriptionName) throws IOException {
1011            final String subscriptionKey = subscriptionKey(clientId, subscriptionName);
1012            indexLock.writeLock().lock();
1013            try {
1014                return pageFile.tx().execute(new Transaction.CallableClosure<SubscriptionInfo, IOException>() {
1015                    @Override
1016                    public SubscriptionInfo execute(Transaction tx) throws IOException {
1017                        StoredDestination sd = getStoredDestination(dest, tx);
1018                        KahaSubscriptionCommand command = sd.subscriptions.get(tx, subscriptionKey);
1019                        if (command == null) {
1020                            return null;
1021                        }
1022                        return (SubscriptionInfo) wireFormat.unmarshal(new DataInputStream(command
1023                                .getSubscriptionInfo().newInput()));
1024                    }
1025                });
1026            } finally {
1027                indexLock.writeLock().unlock();
1028            }
1029        }
1030
1031        @Override
1032        public int getMessageCount(String clientId, String subscriptionName) throws IOException {
1033            final String subscriptionKey = subscriptionKey(clientId, subscriptionName);
1034
1035            if (isEnableSubscriptionStatistics()) {
1036                return (int)this.messageStoreSubStats.getMessageCount(subscriptionKey).getCount();
1037            } else {
1038
1039                indexLock.writeLock().lock();
1040                try {
1041                    return pageFile.tx().execute(new Transaction.CallableClosure<Integer, IOException>() {
1042                        @Override
1043                        public Integer execute(Transaction tx) throws IOException {
1044                            StoredDestination sd = getStoredDestination(dest, tx);
1045                            LastAck cursorPos = getLastAck(tx, sd, subscriptionKey);
1046                            if (cursorPos == null) {
1047                                // The subscription might not exist.
1048                                return 0;
1049                            }
1050
1051                            return (int) getStoredMessageCount(tx, sd, subscriptionKey);
1052                        }
1053                    });
1054                } finally {
1055                    indexLock.writeLock().unlock();
1056                }
1057            }
1058        }
1059
1060
1061        @Override
1062        public long getMessageSize(String clientId, String subscriptionName) throws IOException {
1063            final String subscriptionKey = subscriptionKey(clientId, subscriptionName);
1064            if (isEnableSubscriptionStatistics()) {
1065                return this.messageStoreSubStats.getMessageSize(subscriptionKey).getTotalSize();
1066            } else {
1067                indexLock.writeLock().lock();
1068                try {
1069                    return pageFile.tx().execute(new Transaction.CallableClosure<Long, IOException>() {
1070                        @Override
1071                        public Long execute(Transaction tx) throws IOException {
1072                            StoredDestination sd = getStoredDestination(dest, tx);
1073                            LastAck cursorPos = getLastAck(tx, sd, subscriptionKey);
1074                            if (cursorPos == null) {
1075                                // The subscription might not exist.
1076                                return 0l;
1077                            }
1078
1079                            return getStoredMessageSize(tx, sd, subscriptionKey);
1080                        }
1081                    });
1082                } finally {
1083                    indexLock.writeLock().unlock();
1084                }
1085            }
1086        }
1087
1088        protected void recoverMessageStoreSubMetrics() throws IOException {
1089            if (isEnableSubscriptionStatistics()) {
1090                final MessageStoreSubscriptionStatistics statistics = getMessageStoreSubStatistics();
1091                indexLock.writeLock().lock();
1092                try {
1093                    pageFile.tx().execute(new Transaction.Closure<IOException>() {
1094                        @Override
1095                        public void execute(Transaction tx) throws IOException {
1096                            StoredDestination sd = getStoredDestination(dest, tx);
1097
1098                            List<String> subscriptionKeys = new ArrayList<>();
1099                            for (Iterator<Entry<String, KahaSubscriptionCommand>> iterator = sd.subscriptions
1100                                    .iterator(tx); iterator.hasNext();) {
1101                                Entry<String, KahaSubscriptionCommand> entry = iterator.next();
1102
1103                                final String subscriptionKey = entry.getKey();
1104                                final LastAck cursorPos = getLastAck(tx, sd, subscriptionKey);
1105                                if (cursorPos != null) {
1106                                    //add the subscriptions to a list for recovering pending sizes below
1107                                    subscriptionKeys.add(subscriptionKey);
1108                                    //recover just the count here as that is fast
1109                                    statistics.getMessageCount(subscriptionKey)
1110                                            .setCount(getStoredMessageCount(tx, sd, subscriptionKey));
1111                                }
1112                            }
1113
1114                            //Recover the message sizes for each subscription by iterating only 1 time over the order index
1115                            //to speed up recovery
1116                            final Map<String, AtomicLong> subPendingMessageSizes = getStoredMessageSize(tx, sd, subscriptionKeys);
1117                            subPendingMessageSizes.forEach((k,v) -> {
1118                                statistics.getMessageSize(k).addSize(v.get() > 0 ? v.get() : 0);
1119                            });
1120                        }
1121                    });
1122                } finally {
1123                    indexLock.writeLock().unlock();
1124                }
1125            }
1126        }
1127
1128        @Override
1129        public void recoverSubscription(String clientId, String subscriptionName, final MessageRecoveryListener listener)
1130                throws Exception {
1131            final String subscriptionKey = subscriptionKey(clientId, subscriptionName);
1132            @SuppressWarnings("unused")
1133            final SubscriptionInfo info = lookupSubscription(clientId, subscriptionName);
1134            indexLock.writeLock().lock();
1135            try {
1136                pageFile.tx().execute(new Transaction.Closure<Exception>() {
1137                    @Override
1138                    public void execute(Transaction tx) throws Exception {
1139                        StoredDestination sd = getStoredDestination(dest, tx);
1140                        LastAck cursorPos = getLastAck(tx, sd, subscriptionKey);
1141                        SequenceSet subAckPositions = getSequenceSet(tx, sd, subscriptionKey);
1142                        //If we have ackPositions tracked then compare the first one as individual acknowledge mode
1143                        //may have bumped lastAck even though there are earlier messages to still consume
1144                        if (subAckPositions != null && !subAckPositions.isEmpty()
1145                                && subAckPositions.getHead().getFirst() < cursorPos.lastAckedSequence) {
1146                            //we have messages to ack before lastAckedSequence
1147                            sd.orderIndex.setBatch(tx, subAckPositions.getHead().getFirst() - 1);
1148                        } else {
1149                            subAckPositions = null;
1150                            sd.orderIndex.setBatch(tx, cursorPos);
1151                        }
1152                        recoverRolledBackAcks(subscriptionKey, sd, tx, Integer.MAX_VALUE, listener);
1153                        Set ackedAndPrepared = ackedAndPreparedMap.get(subscriptionKey);
1154                        for (Iterator<Entry<Long, MessageKeys>> iterator = sd.orderIndex.iterator(tx); iterator
1155                                .hasNext();) {
1156                            Entry<Long, MessageKeys> entry = iterator.next();
1157                            if (ackedAndPrepared != null && ackedAndPrepared.contains(entry.getValue().messageId)) {
1158                                continue;
1159                            }
1160                            //If subAckPositions is set then verify the sequence set contains the message still
1161                            //and if it doesn't skip it
1162                            if (subAckPositions != null && !subAckPositions.contains(entry.getKey())) {
1163                                continue;
1164                            }
1165                            listener.recoverMessage(loadMessage(entry.getValue().location));
1166                        }
1167                        sd.orderIndex.resetCursorPosition();
1168                    }
1169                });
1170            } finally {
1171                indexLock.writeLock().unlock();
1172            }
1173        }
1174
1175        @Override
1176        public void recoverNextMessages(String clientId, String subscriptionName, final int maxReturned,
1177                final MessageRecoveryListener listener) throws Exception {
1178            final String subscriptionKey = subscriptionKey(clientId, subscriptionName);
1179            @SuppressWarnings("unused")
1180            final SubscriptionInfo info = lookupSubscription(clientId, subscriptionName);
1181            indexLock.writeLock().lock();
1182            try {
1183                pageFile.tx().execute(new Transaction.Closure<Exception>() {
1184                    @Override
1185                    public void execute(Transaction tx) throws Exception {
1186                        StoredDestination sd = getStoredDestination(dest, tx);
1187                        sd.orderIndex.resetCursorPosition();
1188                        MessageOrderCursor moc = sd.subscriptionCursors.get(subscriptionKey);
1189                        SequenceSet subAckPositions = null;
1190                        if (moc == null) {
1191                            LastAck pos = getLastAck(tx, sd, subscriptionKey);
1192                            if (pos == null) {
1193                                // sub deleted
1194                                return;
1195                            }
1196                            subAckPositions = getSequenceSet(tx, sd, subscriptionKey);
1197                            //If we have ackPositions tracked then compare the first one as individual acknowledge mode
1198                            //may have bumped lastAck even though there are earlier messages to still consume
1199                            if (subAckPositions != null && !subAckPositions.isEmpty()
1200                                    && subAckPositions.getHead().getFirst() < pos.lastAckedSequence) {
1201                                //we have messages to ack before lastAckedSequence
1202                                sd.orderIndex.setBatch(tx, subAckPositions.getHead().getFirst() - 1);
1203                            } else {
1204                                subAckPositions = null;
1205                                sd.orderIndex.setBatch(tx, pos);
1206                            }
1207                            moc = sd.orderIndex.cursor;
1208                        } else {
1209                            sd.orderIndex.cursor.sync(moc);
1210                        }
1211
1212                        Entry<Long, MessageKeys> entry = null;
1213                        int counter = recoverRolledBackAcks(subscriptionKey, sd, tx, maxReturned, listener);
1214                        Set ackedAndPrepared = ackedAndPreparedMap.get(subscriptionKey);
1215                        for (Iterator<Entry<Long, MessageKeys>> iterator = sd.orderIndex.iterator(tx, moc); iterator
1216                                .hasNext();) {
1217                            entry = iterator.next();
1218                            if (ackedAndPrepared != null && ackedAndPrepared.contains(entry.getValue().messageId)) {
1219                                continue;
1220                            }
1221                            //If subAckPositions is set then verify the sequence set contains the message still
1222                            //and if it doesn't skip it
1223                            if (subAckPositions != null && !subAckPositions.contains(entry.getKey())) {
1224                                continue;
1225                            }
1226                            if (listener.recoverMessage(loadMessage(entry.getValue().location))) {
1227                                counter++;
1228                            }
1229                            if (counter >= maxReturned || listener.hasSpace() == false) {
1230                                break;
1231                            }
1232                        }
1233                        sd.orderIndex.stoppedIterating();
1234                        if (entry != null) {
1235                            MessageOrderCursor copy = sd.orderIndex.cursor.copy();
1236                            sd.subscriptionCursors.put(subscriptionKey, copy);
1237                        }
1238                    }
1239                });
1240            } finally {
1241                indexLock.writeLock().unlock();
1242            }
1243        }
1244
1245        @Override
1246        public void resetBatching(String clientId, String subscriptionName) {
1247            try {
1248                final String subscriptionKey = subscriptionKey(clientId, subscriptionName);
1249                indexLock.writeLock().lock();
1250                try {
1251                    pageFile.tx().execute(new Transaction.Closure<IOException>() {
1252                        @Override
1253                        public void execute(Transaction tx) throws IOException {
1254                            StoredDestination sd = getStoredDestination(dest, tx);
1255                            sd.subscriptionCursors.remove(subscriptionKey);
1256                        }
1257                    });
1258                }finally {
1259                    indexLock.writeLock().unlock();
1260                }
1261            } catch (IOException e) {
1262                throw new RuntimeException(e);
1263            }
1264        }
1265
1266        @Override
1267        public MessageStoreSubscriptionStatistics getMessageStoreSubStatistics() {
1268            return messageStoreSubStats;
1269        }
1270    }
1271
1272    String subscriptionKey(String clientId, String subscriptionName) {
1273        return clientId + ":" + subscriptionName;
1274    }
1275
1276    @Override
1277    public MessageStore createQueueMessageStore(ActiveMQQueue destination) throws IOException {
1278        String key = key(convert(destination));
1279        MessageStore store = storeCache.get(key(convert(destination)));
1280        if (store == null) {
1281            final MessageStore queueStore = this.transactionStore.proxy(new KahaDBMessageStore(destination));
1282            store = storeCache.putIfAbsent(key, queueStore);
1283            if (store == null) {
1284                store = queueStore;
1285            }
1286        }
1287
1288        return store;
1289    }
1290
1291    @Override
1292    public TopicMessageStore createTopicMessageStore(ActiveMQTopic destination) throws IOException {
1293        String key = key(convert(destination));
1294        MessageStore store = storeCache.get(key(convert(destination)));
1295        if (store == null) {
1296            final TopicMessageStore topicStore = this.transactionStore.proxy(new KahaDBTopicMessageStore(destination));
1297            store = storeCache.putIfAbsent(key, topicStore);
1298            if (store == null) {
1299                store = topicStore;
1300            }
1301        }
1302
1303        return (TopicMessageStore) store;
1304    }
1305
1306    /**
1307     * Cleanup method to remove any state associated with the given destination.
1308     * This method does not stop the message store (it might not be cached).
1309     *
1310     * @param destination
1311     *            Destination to forget
1312     */
1313    @Override
1314    public void removeQueueMessageStore(ActiveMQQueue destination) {
1315    }
1316
1317    /**
1318     * Cleanup method to remove any state associated with the given destination
1319     * This method does not stop the message store (it might not be cached).
1320     *
1321     * @param destination
1322     *            Destination to forget
1323     */
1324    @Override
1325    public void removeTopicMessageStore(ActiveMQTopic destination) {
1326    }
1327
1328    @Override
1329    public void deleteAllMessages() throws IOException {
1330        deleteAllMessages = true;
1331    }
1332
1333    @Override
1334    public Set<ActiveMQDestination> getDestinations() {
1335        try {
1336            final HashSet<ActiveMQDestination> rc = new HashSet<ActiveMQDestination>();
1337            indexLock.writeLock().lock();
1338            try {
1339                pageFile.tx().execute(new Transaction.Closure<IOException>() {
1340                    @Override
1341                    public void execute(Transaction tx) throws IOException {
1342                        for (Iterator<Entry<String, StoredDestination>> iterator = metadata.destinations.iterator(tx); iterator
1343                                .hasNext();) {
1344                            Entry<String, StoredDestination> entry = iterator.next();
1345                            //Removing isEmpty topic check - see AMQ-5875
1346                            rc.add(convert(entry.getKey()));
1347                        }
1348                    }
1349                });
1350            }finally {
1351                indexLock.writeLock().unlock();
1352            }
1353            return rc;
1354        } catch (IOException e) {
1355            throw new RuntimeException(e);
1356        }
1357    }
1358
1359    @Override
1360    public long getLastMessageBrokerSequenceId() throws IOException {
1361        return 0;
1362    }
1363
1364    @Override
1365    public long getLastProducerSequenceId(ProducerId id) {
1366        indexLock.writeLock().lock();
1367        try {
1368            return metadata.producerSequenceIdTracker.getLastSeqId(id);
1369        } finally {
1370            indexLock.writeLock().unlock();
1371        }
1372    }
1373
1374    @Override
1375    public long size() {
1376        try {
1377            return journalSize.get() + getPageFile().getDiskSize();
1378        } catch (IOException e) {
1379            throw new RuntimeException(e);
1380        }
1381    }
1382
1383    @Override
1384    public void beginTransaction(ConnectionContext context) throws IOException {
1385        throw new IOException("Not yet implemented.");
1386    }
1387    @Override
1388    public void commitTransaction(ConnectionContext context) throws IOException {
1389        throw new IOException("Not yet implemented.");
1390    }
1391    @Override
1392    public void rollbackTransaction(ConnectionContext context) throws IOException {
1393        throw new IOException("Not yet implemented.");
1394    }
1395
1396    @Override
1397    public void checkpoint(boolean sync) throws IOException {
1398        super.checkpointCleanup(sync);
1399    }
1400
1401    // /////////////////////////////////////////////////////////////////
1402    // Internal helper methods.
1403    // /////////////////////////////////////////////////////////////////
1404
1405    /**
1406     * @param location
1407     * @return
1408     * @throws IOException
1409     */
1410    Message loadMessage(Location location) throws IOException {
1411        try {
1412            JournalCommand<?> command = load(location);
1413            KahaAddMessageCommand addMessage = null;
1414            switch (command.type()) {
1415                case KAHA_UPDATE_MESSAGE_COMMAND:
1416                    addMessage = ((KahaUpdateMessageCommand) command).getMessage();
1417                    break;
1418                case KAHA_ADD_MESSAGE_COMMAND:
1419                    addMessage = (KahaAddMessageCommand) command;
1420                    break;
1421                default:
1422                    throw new IOException("Could not load journal record, unexpected command type: " + command.type() + " at location: " + location);
1423            }
1424            if (!addMessage.hasMessage()) {
1425                throw new IOException("Could not load journal record, null message content at location: " + location);
1426            }
1427            Message msg = (Message) wireFormat.unmarshal(new DataInputStream(addMessage.getMessage().newInput()));
1428            return msg;
1429        } catch (Throwable t) {
1430            IOException ioe = IOExceptionSupport.create("Unexpected error on journal read at: " + location , t);
1431            LOG.error("Failed to load message at: {}", location , ioe);
1432            brokerService.handleIOException(ioe);
1433            throw ioe;
1434        }
1435    }
1436
1437    // /////////////////////////////////////////////////////////////////
1438    // Internal conversion methods.
1439    // /////////////////////////////////////////////////////////////////
1440
1441    KahaLocation convert(Location location) {
1442        KahaLocation rc = new KahaLocation();
1443        rc.setLogId(location.getDataFileId());
1444        rc.setOffset(location.getOffset());
1445        return rc;
1446    }
1447
1448    KahaDestination convert(ActiveMQDestination dest) {
1449        KahaDestination rc = new KahaDestination();
1450        rc.setName(dest.getPhysicalName());
1451        switch (dest.getDestinationType()) {
1452        case ActiveMQDestination.QUEUE_TYPE:
1453            rc.setType(DestinationType.QUEUE);
1454            return rc;
1455        case ActiveMQDestination.TOPIC_TYPE:
1456            rc.setType(DestinationType.TOPIC);
1457            return rc;
1458        case ActiveMQDestination.TEMP_QUEUE_TYPE:
1459            rc.setType(DestinationType.TEMP_QUEUE);
1460            return rc;
1461        case ActiveMQDestination.TEMP_TOPIC_TYPE:
1462            rc.setType(DestinationType.TEMP_TOPIC);
1463            return rc;
1464        default:
1465            return null;
1466        }
1467    }
1468
1469    ActiveMQDestination convert(String dest) {
1470        int p = dest.indexOf(":");
1471        if (p < 0) {
1472            throw new IllegalArgumentException("Not in the valid destination format");
1473        }
1474        int type = Integer.parseInt(dest.substring(0, p));
1475        String name = dest.substring(p + 1);
1476        return convert(type, name);
1477    }
1478
1479    private ActiveMQDestination convert(KahaDestination commandDestination) {
1480        return convert(commandDestination.getType().getNumber(), commandDestination.getName());
1481    }
1482
1483    private ActiveMQDestination convert(int type, String name) {
1484        switch (KahaDestination.DestinationType.valueOf(type)) {
1485        case QUEUE:
1486            return new ActiveMQQueue(name);
1487        case TOPIC:
1488            return new ActiveMQTopic(name);
1489        case TEMP_QUEUE:
1490            return new ActiveMQTempQueue(name);
1491        case TEMP_TOPIC:
1492            return new ActiveMQTempTopic(name);
1493        default:
1494            throw new IllegalArgumentException("Not in the valid destination format");
1495        }
1496    }
1497
1498    public TransactionIdTransformer getTransactionIdTransformer() {
1499        return transactionIdTransformer;
1500    }
1501
1502    public void setTransactionIdTransformer(TransactionIdTransformer transactionIdTransformer) {
1503        this.transactionIdTransformer = transactionIdTransformer;
1504    }
1505
1506    static class AsyncJobKey {
1507        MessageId id;
1508        ActiveMQDestination destination;
1509
1510        AsyncJobKey(MessageId id, ActiveMQDestination destination) {
1511            this.id = id;
1512            this.destination = destination;
1513        }
1514
1515        @Override
1516        public boolean equals(Object obj) {
1517            if (obj == this) {
1518                return true;
1519            }
1520            return obj instanceof AsyncJobKey && id.equals(((AsyncJobKey) obj).id)
1521                    && destination.equals(((AsyncJobKey) obj).destination);
1522        }
1523
1524        @Override
1525        public int hashCode() {
1526            return id.hashCode() + destination.hashCode();
1527        }
1528
1529        @Override
1530        public String toString() {
1531            return destination.getPhysicalName() + "-" + id;
1532        }
1533    }
1534
1535    public interface StoreTask {
1536        public boolean cancel();
1537
1538        public void aquireLocks();
1539
1540        public void releaseLocks();
1541    }
1542
1543    class StoreQueueTask implements Runnable, StoreTask {
1544        protected final Message message;
1545        protected final ConnectionContext context;
1546        protected final KahaDBMessageStore store;
1547        protected final InnerFutureTask future;
1548        protected final AtomicBoolean done = new AtomicBoolean();
1549        protected final AtomicBoolean locked = new AtomicBoolean();
1550
1551        public StoreQueueTask(KahaDBMessageStore store, ConnectionContext context, Message message) {
1552            this.store = store;
1553            this.context = context;
1554            this.message = message;
1555            this.future = new InnerFutureTask(this);
1556        }
1557
1558        public ListenableFuture<Object> getFuture() {
1559            return this.future;
1560        }
1561
1562        @Override
1563        public boolean cancel() {
1564            if (this.done.compareAndSet(false, true)) {
1565                return this.future.cancel(false);
1566            }
1567            return false;
1568        }
1569
1570        @Override
1571        public void aquireLocks() {
1572            if (this.locked.compareAndSet(false, true)) {
1573                try {
1574                    globalQueueSemaphore.acquire();
1575                    store.acquireLocalAsyncLock();
1576                    message.incrementReferenceCount();
1577                } catch (InterruptedException e) {
1578                    LOG.warn("Failed to aquire lock", e);
1579                }
1580            }
1581
1582        }
1583
1584        @Override
1585        public void releaseLocks() {
1586            if (this.locked.compareAndSet(true, false)) {
1587                store.releaseLocalAsyncLock();
1588                globalQueueSemaphore.release();
1589                message.decrementReferenceCount();
1590            }
1591        }
1592
1593        @Override
1594        public void run() {
1595            this.store.doneTasks++;
1596            try {
1597                if (this.done.compareAndSet(false, true)) {
1598                    this.store.addMessage(context, message);
1599                    removeQueueTask(this.store, this.message.getMessageId());
1600                    this.future.complete();
1601                } else if (cancelledTaskModMetric > 0 && (++this.store.canceledTasks) % cancelledTaskModMetric == 0) {
1602                    System.err.println(this.store.dest.getName() + " cancelled: "
1603                            + (this.store.canceledTasks / this.store.doneTasks) * 100);
1604                    this.store.canceledTasks = this.store.doneTasks = 0;
1605                }
1606            } catch (Throwable t) {
1607                this.future.setException(t);
1608                removeQueueTask(this.store, this.message.getMessageId());
1609            }
1610        }
1611
1612        protected Message getMessage() {
1613            return this.message;
1614        }
1615
1616        private class InnerFutureTask extends FutureTask<Object> implements ListenableFuture<Object>  {
1617
1618            private final AtomicReference<Runnable> listenerRef = new AtomicReference<>();
1619
1620            public InnerFutureTask(Runnable runnable) {
1621                super(runnable, null);
1622            }
1623
1624            @Override
1625            public void setException(final Throwable e) {
1626                super.setException(e);
1627            }
1628
1629            public void complete() {
1630                super.set(null);
1631            }
1632
1633            @Override
1634            public void done() {
1635                fireListener();
1636            }
1637
1638            @Override
1639            public void addListener(Runnable listener) {
1640                this.listenerRef.set(listener);
1641                if (isDone()) {
1642                    fireListener();
1643                }
1644            }
1645
1646            private void fireListener() {
1647                Runnable listener = listenerRef.getAndSet(null);
1648                if (listener != null) {
1649                    try {
1650                        listener.run();
1651                    } catch (Exception ignored) {
1652                        LOG.warn("Unexpected exception from future {} listener callback {}", this, listener, ignored);
1653                    }
1654                }
1655            }
1656        }
1657    }
1658
1659    class StoreTopicTask extends StoreQueueTask {
1660        private final int subscriptionCount;
1661        private final List<String> subscriptionKeys = new ArrayList<String>(1);
1662        private final KahaDBTopicMessageStore topicStore;
1663        public StoreTopicTask(KahaDBTopicMessageStore store, ConnectionContext context, Message message,
1664                int subscriptionCount) {
1665            super(store, context, message);
1666            this.topicStore = store;
1667            this.subscriptionCount = subscriptionCount;
1668
1669        }
1670
1671        @Override
1672        public void aquireLocks() {
1673            if (this.locked.compareAndSet(false, true)) {
1674                try {
1675                    globalTopicSemaphore.acquire();
1676                    store.acquireLocalAsyncLock();
1677                    message.incrementReferenceCount();
1678                } catch (InterruptedException e) {
1679                    LOG.warn("Failed to aquire lock", e);
1680                }
1681            }
1682        }
1683
1684        @Override
1685        public void releaseLocks() {
1686            if (this.locked.compareAndSet(true, false)) {
1687                message.decrementReferenceCount();
1688                store.releaseLocalAsyncLock();
1689                globalTopicSemaphore.release();
1690            }
1691        }
1692
1693        /**
1694         * add a key
1695         *
1696         * @param key
1697         * @return true if all acknowledgements received
1698         */
1699        public boolean addSubscriptionKey(String key) {
1700            synchronized (this.subscriptionKeys) {
1701                this.subscriptionKeys.add(key);
1702            }
1703            return this.subscriptionKeys.size() >= this.subscriptionCount;
1704        }
1705
1706        @Override
1707        public void run() {
1708            this.store.doneTasks++;
1709            try {
1710                if (this.done.compareAndSet(false, true)) {
1711                    this.topicStore.addMessage(context, message);
1712                    // apply any acks we have
1713                    synchronized (this.subscriptionKeys) {
1714                        for (String key : this.subscriptionKeys) {
1715                            this.topicStore.doAcknowledge(context, key, this.message.getMessageId(), null);
1716
1717                        }
1718                    }
1719                    removeTopicTask(this.topicStore, this.message.getMessageId());
1720                    this.future.complete();
1721                } else if (cancelledTaskModMetric > 0 && this.store.canceledTasks++ % cancelledTaskModMetric == 0) {
1722                    System.err.println(this.store.dest.getName() + " cancelled: "
1723                            + (this.store.canceledTasks / this.store.doneTasks) * 100);
1724                    this.store.canceledTasks = this.store.doneTasks = 0;
1725                }
1726            } catch (Throwable t) {
1727                this.future.setException(t);
1728                removeTopicTask(this.topicStore, this.message.getMessageId());
1729            }
1730        }
1731    }
1732
1733    public class StoreTaskExecutor extends ThreadPoolExecutor {
1734
1735        public StoreTaskExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit timeUnit, BlockingQueue<Runnable> queue, ThreadFactory threadFactory) {
1736            super(corePoolSize, maximumPoolSize, keepAliveTime, timeUnit, queue, threadFactory);
1737        }
1738
1739        @Override
1740        protected void afterExecute(Runnable runnable, Throwable throwable) {
1741            super.afterExecute(runnable, throwable);
1742
1743            if (runnable instanceof StoreTask) {
1744               ((StoreTask)runnable).releaseLocks();
1745            }
1746        }
1747    }
1748
1749    @Override
1750    public JobSchedulerStore createJobSchedulerStore() throws IOException, UnsupportedOperationException {
1751        return new JobSchedulerStoreImpl();
1752    }
1753
1754    /* (non-Javadoc)
1755     * @see org.apache.activemq.store.NoLocalSubscriptionAware#isPersistNoLocal()
1756     */
1757    @Override
1758    public boolean isPersistNoLocal() {
1759        // Prior to v11 the broker did not store the noLocal value for durable subs.
1760        return brokerService.getStoreOpenWireVersion() >= 11;
1761    }
1762}