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