You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@activemq.apache.org by ha...@apache.org on 2014/12/16 23:09:49 UTC

[01/16] activemq git commit: Fix the getProxyToTopic method.

Repository: activemq
Updated Branches:
  refs/heads/activemq-5.10.x 3ab3f04f9 -> fc244f48e


Fix the getProxyToTopic method.


Project: http://git-wip-us.apache.org/repos/asf/activemq/repo
Commit: http://git-wip-us.apache.org/repos/asf/activemq/commit/327e19e6
Tree: http://git-wip-us.apache.org/repos/asf/activemq/tree/327e19e6
Diff: http://git-wip-us.apache.org/repos/asf/activemq/diff/327e19e6

Branch: refs/heads/activemq-5.10.x
Commit: 327e19e6863798929178b258aab30da11dc4e6bf
Parents: 3ab3f04
Author: Timothy Bish <ta...@gmail.com>
Authored: Tue Jun 24 14:24:39 2014 -0400
Committer: Hadrian Zbarcea <ha...@apache.org>
Committed: Mon Dec 15 18:50:35 2014 -0500

----------------------------------------------------------------------
 .../apache/activemq/transport/stomp/StompTestSupport.java | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/activemq/blob/327e19e6/activemq-stomp/src/test/java/org/apache/activemq/transport/stomp/StompTestSupport.java
----------------------------------------------------------------------
diff --git a/activemq-stomp/src/test/java/org/apache/activemq/transport/stomp/StompTestSupport.java b/activemq-stomp/src/test/java/org/apache/activemq/transport/stomp/StompTestSupport.java
index 5d6183b..3cf1356 100644
--- a/activemq-stomp/src/test/java/org/apache/activemq/transport/stomp/StompTestSupport.java
+++ b/activemq-stomp/src/test/java/org/apache/activemq/transport/stomp/StompTestSupport.java
@@ -35,6 +35,7 @@ import org.apache.activemq.broker.BrokerService;
 import org.apache.activemq.broker.TransportConnector;
 import org.apache.activemq.broker.jmx.BrokerViewMBean;
 import org.apache.activemq.broker.jmx.QueueViewMBean;
+import org.apache.activemq.broker.jmx.TopicViewMBean;
 import org.apache.activemq.filter.DestinationMapEntry;
 import org.apache.activemq.security.AuthenticationUser;
 import org.apache.activemq.security.AuthorizationEntry;
@@ -317,11 +318,10 @@ public class StompTestSupport {
         return proxy;
     }
 
-    protected QueueViewMBean getProxyToTopic(String name) throws MalformedObjectNameException, JMSException {
-        ObjectName queueViewMBeanName = new ObjectName("org.apache.activemq:type=Broker,brokerName=localhost,destinationType=Topic,destinationName="+name);
-        QueueViewMBean proxy = (QueueViewMBean) brokerService.getManagementContext()
-                .newProxyInstance(queueViewMBeanName, QueueViewMBean.class, true);
+    protected TopicViewMBean getProxyToTopic(String name) throws MalformedObjectNameException, JMSException {
+        ObjectName topicViewMBeanName = new ObjectName("org.apache.activemq:type=Broker,brokerName=localhost,destinationType=Topic,destinationName="+name);
+        TopicViewMBean proxy = (TopicViewMBean) brokerService.getManagementContext()
+                .newProxyInstance(topicViewMBeanName, TopicViewMBean.class, true);
         return proxy;
     }
-
 }


[09/16] activemq git commit: Cleanup in ConnectTest to do two things: 1) connect/disconnect 500 (which still seems high) times instead of as many as possible in 25seconds. On fast machines, this is MUCH faster. 2) Actually call Thead.start(), not Thread

Posted by ha...@apache.org.
Cleanup in ConnectTest to do two things:
1) connect/disconnect 500 (which still seems high) times instead of as many as possible in 25seconds.  On fast machines, this is MUCH faster.
2) Actually call Thead.start(), not Thread.run() in cases where it should be on a background thread.


Project: http://git-wip-us.apache.org/repos/asf/activemq/repo
Commit: http://git-wip-us.apache.org/repos/asf/activemq/commit/f8ccdfbf
Tree: http://git-wip-us.apache.org/repos/asf/activemq/tree/f8ccdfbf
Diff: http://git-wip-us.apache.org/repos/asf/activemq/diff/f8ccdfbf

Branch: refs/heads/activemq-5.10.x
Commit: f8ccdfbfaa5ad588404f5d2b620351c34e9504cc
Parents: 569a677
Author: Daniel Kulp <dk...@apache.org>
Authored: Thu Jul 10 15:28:43 2014 -0400
Committer: Hadrian Zbarcea <ha...@apache.org>
Committed: Mon Dec 15 19:26:25 2014 -0500

----------------------------------------------------------------------
 .../activemq/transport/stomp/ConnectTest.java   | 40 ++++++++------------
 1 file changed, 15 insertions(+), 25 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/activemq/blob/f8ccdfbf/activemq-stomp/src/test/java/org/apache/activemq/transport/stomp/ConnectTest.java
----------------------------------------------------------------------
diff --git a/activemq-stomp/src/test/java/org/apache/activemq/transport/stomp/ConnectTest.java b/activemq-stomp/src/test/java/org/apache/activemq/transport/stomp/ConnectTest.java
index 1e13143..e95fd92 100644
--- a/activemq-stomp/src/test/java/org/apache/activemq/transport/stomp/ConnectTest.java
+++ b/activemq-stomp/src/test/java/org/apache/activemq/transport/stomp/ConnectTest.java
@@ -29,9 +29,11 @@ import org.apache.activemq.broker.BrokerPlugin;
 import org.apache.activemq.broker.BrokerService;
 import org.apache.activemq.security.JaasDualAuthenticationPlugin;
 import org.apache.activemq.util.Wait;
+
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
+
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -62,28 +64,16 @@ public class ConnectTest {
         brokerService.addConnector("stomp://0.0.0.0:0?transport.soLinger=0");
         brokerService.start();
 
-        Thread t1 = new Thread() {
-            StompConnection connection = new StompConnection();
-
-            @Override
-            public void run() {
-                try {
-                    connection.open("localhost", brokerService.getTransportConnectors().get(0).getConnectUri().getPort());
-                    connection.connect("system", "manager");
-                    connection.disconnect();
-                } catch (Exception ex) {
-                    LOG.error("unexpected exception on connect/disconnect", ex);
-                    exceptions.add(ex);
-                }
-            }
-        };
-
-        int i = 0;
-        long done = System.currentTimeMillis() + (15 * 1000);
-        while (System.currentTimeMillis() < done) {
-            t1.run();
-            if (++i % 5000 == 0) {
-                LOG.info("connection count on stomp connector:" + brokerService.getTransportConnectors().get(0).connectionCount());
+        StompConnection connection = new StompConnection();
+        //test 500 connect/disconnects
+        for (int x = 0; x < 500; x++) {
+            try {
+                connection.open("localhost", brokerService.getTransportConnectors().get(0).getConnectUri().getPort());
+                connection.connect("system", "manager");
+                connection.disconnect();
+            } catch (Exception ex) {
+                LOG.error("unexpected exception on connect/disconnect", ex);
+                exceptions.add(ex);
             }
         }
 
@@ -119,7 +109,7 @@ public class ConnectTest {
             }
         };
 
-        t1.run();
+        t1.start();
 
         assertTrue("one connection", Wait.waitFor(new Wait.Condition() {
             @Override
@@ -143,7 +133,7 @@ public class ConnectTest {
     @Test
     public void testInactivityMonitor() throws Exception {
 
-        brokerService.addConnector("stomp://0.0.0.0:0?transport.defaultHeartBeat=5000,0&transport.useKeepAlive=false");
+        brokerService.addConnector("stomp://0.0.0.0:0?transport.defaultHeartBeat=1000,0&transport.useKeepAlive=false");
         brokerService.start();
 
         Thread t1 = new Thread() {
@@ -161,7 +151,7 @@ public class ConnectTest {
             }
         };
 
-        t1.run();
+        t1.start();
 
         assertTrue("one connection", Wait.waitFor(new Wait.Condition() {
                  @Override


[04/16] activemq git commit: AMQ-5086. Fix the timeout arithmetic

Posted by ha...@apache.org.
AMQ-5086. Fix the timeout arithmetic


Project: http://git-wip-us.apache.org/repos/asf/activemq/repo
Commit: http://git-wip-us.apache.org/repos/asf/activemq/commit/59109a66
Tree: http://git-wip-us.apache.org/repos/asf/activemq/tree/59109a66
Diff: http://git-wip-us.apache.org/repos/asf/activemq/diff/59109a66

Branch: refs/heads/activemq-5.10.x
Commit: 59109a669fe7de89ad833d3369e6fb68f3d93fc9
Parents: 4298966
Author: Hadrian Zbarcea <ha...@apache.org>
Authored: Thu Jul 3 14:53:30 2014 -0400
Committer: Hadrian Zbarcea <ha...@apache.org>
Committed: Mon Dec 15 19:00:06 2014 -0500

----------------------------------------------------------------------
 .../apache/activemq/broker/BrokerService.java    | 19 ++++++++++++++++---
 1 file changed, 16 insertions(+), 3 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/activemq/blob/59109a66/activemq-broker/src/main/java/org/apache/activemq/broker/BrokerService.java
----------------------------------------------------------------------
diff --git a/activemq-broker/src/main/java/org/apache/activemq/broker/BrokerService.java b/activemq-broker/src/main/java/org/apache/activemq/broker/BrokerService.java
index 3c542ce..00d4abd 100644
--- a/activemq-broker/src/main/java/org/apache/activemq/broker/BrokerService.java
+++ b/activemq-broker/src/main/java/org/apache/activemq/broker/BrokerService.java
@@ -137,6 +137,7 @@ public class BrokerService implements Service {
     public static final String BROKER_VERSION;
     public static final String DEFAULT_BROKER_NAME = "localhost";
     public static final int DEFAULT_MAX_FILE_LENGTH = 1024 * 1024 * 32;
+    public static final long DEFAULT_START_TIMEOUT = 600000L;
 
     private static final Logger LOG = LoggerFactory.getLogger(BrokerService.class);
 
@@ -156,7 +157,7 @@ public class BrokerService implements Service {
     private boolean shutdownOnMasterFailure;
     private boolean shutdownOnSlaveFailure;
     private boolean waitForSlave;
-    private long waitForSlaveTimeout = 600000L;
+    private long waitForSlaveTimeout = DEFAULT_START_TIMEOUT;
     private boolean passiveSlave;
     private String brokerName = DEFAULT_BROKER_NAME;
     private File dataDirectoryFile;
@@ -248,7 +249,6 @@ public class BrokerService implements Service {
     private boolean restartRequested = false;
 
     private int storeOpenWireVersion = OpenWireFormat.DEFAULT_VERSION;
-    private String configurationUrl;
 
     static {
 
@@ -914,8 +914,21 @@ public class BrokerService implements Service {
      * @return boolean true if wait succeeded false if broker was not started or was stopped
      */
     public boolean waitUntilStarted() {
+        return waitUntilStarted(DEFAULT_START_TIMEOUT);
+    }
+
+    /**
+     * A helper method to block the caller thread until the broker has fully started
+     *
+     * @param timeout
+     *        the amount of time to wait before giving up and returning false.
+     *
+     * @return boolean true if wait succeeded false if broker was not started or was stopped
+     */
+    public boolean waitUntilStarted(long timeout) {
         boolean waitSucceeded = isStarted();
-        while (!isStarted() && !stopped.get() && !waitSucceeded) {
+        long expiration = Math.max(0, timeout + System.currentTimeMillis());
+        while (!isStarted() && !stopped.get() && !waitSucceeded && expiration > System.currentTimeMillis()) {
             try {
                 if (startException != null) {
                     return waitSucceeded;


[14/16] activemq git commit: https://issues.apache.org/jira/browse/AMQ-3758

Posted by ha...@apache.org.
http://git-wip-us.apache.org/repos/asf/activemq/blob/fc244f48/activemq-kahadb-store/src/main/java/org/apache/activemq/store/kahadb/scheduler/JobLocationsMarshaller.java
----------------------------------------------------------------------
diff --git a/activemq-kahadb-store/src/main/java/org/apache/activemq/store/kahadb/scheduler/JobLocationsMarshaller.java b/activemq-kahadb-store/src/main/java/org/apache/activemq/store/kahadb/scheduler/JobLocationsMarshaller.java
new file mode 100644
index 0000000..e22e4df
--- /dev/null
+++ b/activemq-kahadb-store/src/main/java/org/apache/activemq/store/kahadb/scheduler/JobLocationsMarshaller.java
@@ -0,0 +1,53 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.activemq.store.kahadb.scheduler;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.activemq.store.kahadb.disk.util.VariableMarshaller;
+
+/**
+ * A VariableMarshaller instance that performs the read and write of a list of
+ * JobLocation objects using the JobLocation's built in read and write methods.
+ */
+class JobLocationsMarshaller extends VariableMarshaller<List<JobLocation>> {
+    static JobLocationsMarshaller INSTANCE = new JobLocationsMarshaller();
+
+    @Override
+    public List<JobLocation> readPayload(DataInput dataIn) throws IOException {
+        List<JobLocation> result = new ArrayList<JobLocation>();
+        int size = dataIn.readInt();
+        for (int i = 0; i < size; i++) {
+            JobLocation jobLocation = new JobLocation();
+            jobLocation.readExternal(dataIn);
+            result.add(jobLocation);
+        }
+        return result;
+    }
+
+    @Override
+    public void writePayload(List<JobLocation> value, DataOutput dataOut) throws IOException {
+        dataOut.writeInt(value.size());
+        for (JobLocation jobLocation : value) {
+            jobLocation.writeExternal(dataOut);
+        }
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/activemq/blob/fc244f48/activemq-kahadb-store/src/main/java/org/apache/activemq/store/kahadb/scheduler/JobSchedulerImpl.java
----------------------------------------------------------------------
diff --git a/activemq-kahadb-store/src/main/java/org/apache/activemq/store/kahadb/scheduler/JobSchedulerImpl.java b/activemq-kahadb-store/src/main/java/org/apache/activemq/store/kahadb/scheduler/JobSchedulerImpl.java
index 455801a..bcb819c 100644
--- a/activemq-kahadb-store/src/main/java/org/apache/activemq/store/kahadb/scheduler/JobSchedulerImpl.java
+++ b/activemq-kahadb-store/src/main/java/org/apache/activemq/store/kahadb/scheduler/JobSchedulerImpl.java
@@ -32,11 +32,15 @@ import org.apache.activemq.broker.scheduler.CronParser;
 import org.apache.activemq.broker.scheduler.Job;
 import org.apache.activemq.broker.scheduler.JobListener;
 import org.apache.activemq.broker.scheduler.JobScheduler;
+import org.apache.activemq.protobuf.Buffer;
+import org.apache.activemq.store.kahadb.data.KahaAddScheduledJobCommand;
+import org.apache.activemq.store.kahadb.data.KahaRemoveScheduledJobCommand;
+import org.apache.activemq.store.kahadb.data.KahaRemoveScheduledJobsCommand;
+import org.apache.activemq.store.kahadb.data.KahaRescheduleJobCommand;
 import org.apache.activemq.store.kahadb.disk.index.BTreeIndex;
 import org.apache.activemq.store.kahadb.disk.journal.Location;
 import org.apache.activemq.store.kahadb.disk.page.Transaction;
 import org.apache.activemq.store.kahadb.disk.util.LongMarshaller;
-import org.apache.activemq.store.kahadb.disk.util.VariableMarshaller;
 import org.apache.activemq.util.ByteSequence;
 import org.apache.activemq.util.IdGenerator;
 import org.apache.activemq.util.ServiceStopper;
@@ -44,12 +48,13 @@ import org.apache.activemq.util.ServiceSupport;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-class JobSchedulerImpl extends ServiceSupport implements Runnable, JobScheduler {
+public class JobSchedulerImpl extends ServiceSupport implements Runnable, JobScheduler {
+
     private static final Logger LOG = LoggerFactory.getLogger(JobSchedulerImpl.class);
-    final JobSchedulerStoreImpl store;
+    private final JobSchedulerStoreImpl store;
     private final AtomicBoolean running = new AtomicBoolean();
     private String name;
-    BTreeIndex<Long, List<JobLocation>> index;
+    private BTreeIndex<Long, List<JobLocation>> index;
     private Thread thread;
     private final AtomicBoolean started = new AtomicBoolean(false);
     private final List<JobListener> jobListeners = new CopyOnWriteArrayList<JobListener>();
@@ -64,233 +69,163 @@ class JobSchedulerImpl extends ServiceSupport implements Runnable, JobScheduler
         this.name = name;
     }
 
-    /*
-     * (non-Javadoc)
-     *
-     * @see org.apache.activemq.beanstalk.JobScheduler#getName()
-     */
     @Override
     public String getName() {
         return this.name;
     }
 
-    /*
-     * (non-Javadoc)
-     *
-     * @see org.apache.activemq.beanstalk.JobScheduler#addListener(org.apache.activemq .beanstalk.JobListener)
-     */
     @Override
     public void addListener(JobListener l) {
         this.jobListeners.add(l);
     }
 
-    /*
-     * (non-Javadoc)
-     *
-     * @see org.apache.activemq.beanstalk.JobScheduler#removeListener(org.apache. activemq.beanstalk.JobListener)
-     */
     @Override
     public void removeListener(JobListener l) {
         this.jobListeners.remove(l);
     }
 
     @Override
-    public synchronized void schedule(final String jobId, final ByteSequence payload, final long delay) throws IOException {
-        this.store.getPageFile().tx().execute(new Transaction.Closure<IOException>() {
-            @Override
-            public void execute(Transaction tx) throws IOException {
-                schedule(tx, jobId, payload, "", 0, delay, 0);
-            }
-        });
+    public void schedule(final String jobId, final ByteSequence payload, final long delay) throws IOException {
+        doSchedule(jobId, payload, "", 0, delay, 0);
     }
 
     @Override
-    public synchronized void schedule(final String jobId, final ByteSequence payload, final String cronEntry) throws Exception {
-        this.store.getPageFile().tx().execute(new Transaction.Closure<IOException>() {
-            @Override
-            public void execute(Transaction tx) throws IOException {
-                schedule(tx, jobId, payload, cronEntry, 0, 0, 0);
-            }
-        });
+    public void schedule(final String jobId, final ByteSequence payload, final String cronEntry) throws Exception {
+        doSchedule(jobId, payload, cronEntry, 0, 0, 0);
     }
 
     @Override
-    public synchronized void schedule(final String jobId, final ByteSequence payload, final String cronEntry, final long delay, final long period,
-        final int repeat) throws IOException {
-        this.store.getPageFile().tx().execute(new Transaction.Closure<IOException>() {
-            @Override
-            public void execute(Transaction tx) throws IOException {
-                schedule(tx, jobId, payload, cronEntry, delay, period, repeat);
-            }
-        });
+    public void schedule(final String jobId, final ByteSequence payload, final String cronEntry, final long delay, final long period, final int repeat) throws IOException {
+        doSchedule(jobId, payload, cronEntry, delay, period, repeat);
     }
 
-    /*
-     * (non-Javadoc)
-     *
-     * @see org.apache.activemq.beanstalk.JobScheduler#remove(long)
-     */
     @Override
-    public synchronized void remove(final long time) throws IOException {
-        this.store.getPageFile().tx().execute(new Transaction.Closure<IOException>() {
-            @Override
-            public void execute(Transaction tx) throws IOException {
-                remove(tx, time);
-            }
-        });
-    }
-
-    synchronized void removeFromIndex(final long time, final String jobId) throws IOException {
-        this.store.getPageFile().tx().execute(new Transaction.Closure<IOException>() {
-            @Override
-            public void execute(Transaction tx) throws IOException {
-                removeFromIndex(tx, time, jobId);
-            }
-        });
-    }
-
-    /*
-     * (non-Javadoc)
-     *
-     * @see org.apache.activemq.beanstalk.JobScheduler#remove(long, java.lang.String)
-     */
-    public synchronized void remove(final long time, final String jobId) throws IOException {
-        this.store.getPageFile().tx().execute(new Transaction.Closure<IOException>() {
-            @Override
-            public void execute(Transaction tx) throws IOException {
-                remove(tx, time, jobId);
-            }
-        });
+    public void remove(final long time) throws IOException {
+        doRemoveRange(time, time);
     }
 
-    synchronized void remove(final long time, final List<JobLocation> jobIds) throws IOException {
-        this.store.getPageFile().tx().execute(new Transaction.Closure<IOException>() {
-            @Override
-            public void execute(Transaction tx) throws IOException {
-                remove(tx, time, jobIds);
-            }
-        });
+    @Override
+    public void remove(final String jobId) throws IOException {
+        doRemove(-1, jobId);
     }
 
-    /*
-     * (non-Javadoc)
-     *
-     * @see org.apache.activemq.beanstalk.JobScheduler#remove(java.lang.String)
-     */
     @Override
-    public synchronized void remove(final String jobId) throws IOException {
-        this.store.getPageFile().tx().execute(new Transaction.Closure<IOException>() {
-            @Override
-            public void execute(Transaction tx) throws IOException {
-                remove(tx, jobId);
-            }
-        });
+    public void removeAllJobs() throws IOException {
+        doRemoveRange(0, Long.MAX_VALUE);
     }
 
     @Override
-    public synchronized long getNextScheduleTime() throws IOException {
-        Map.Entry<Long, List<JobLocation>> first = this.index.getFirst(this.store.getPageFile().tx());
-        return first != null ? first.getKey() : -1l;
+    public void removeAllJobs(final long start, final long finish) throws IOException {
+        doRemoveRange(start, finish);
     }
 
-    /*
-     * (non-Javadoc)
-     *
-     * @see org.apache.activemq.beanstalk.JobScheduler#getNextScheduleJobs()
-     */
     @Override
-    public synchronized List<Job> getNextScheduleJobs() throws IOException {
-        final List<Job> result = new ArrayList<Job>();
-
-        this.store.getPageFile().tx().execute(new Transaction.Closure<IOException>() {
-            @Override
-            public void execute(Transaction tx) throws IOException {
-                Map.Entry<Long, List<JobLocation>> first = index.getFirst(store.getPageFile().tx());
-                if (first != null) {
-                    for (JobLocation jl : first.getValue()) {
-                        ByteSequence bs = getPayload(jl.getLocation());
-                        Job job = new JobImpl(jl, bs);
-                        result.add(job);
-                    }
-                }
-            }
-        });
-        return result;
+    public long getNextScheduleTime() throws IOException {
+        this.store.readLockIndex();
+        try {
+            Map.Entry<Long, List<JobLocation>> first = this.index.getFirst(this.store.getPageFile().tx());
+            return first != null ? first.getKey() : -1l;
+        } finally {
+            this.store.readUnlockIndex();
+        }
     }
 
     @Override
-    public synchronized List<Job> getAllJobs() throws IOException {
+    public List<Job> getNextScheduleJobs() throws IOException {
         final List<Job> result = new ArrayList<Job>();
-        this.store.getPageFile().tx().execute(new Transaction.Closure<IOException>() {
-            @Override
-            public void execute(Transaction tx) throws IOException {
-                Iterator<Map.Entry<Long, List<JobLocation>>> iter = index.iterator(store.getPageFile().tx());
-                while (iter.hasNext()) {
-                    Map.Entry<Long, List<JobLocation>> next = iter.next();
-                    if (next != null) {
-                        for (JobLocation jl : next.getValue()) {
+        this.store.readLockIndex();
+        try {
+            this.store.getPageFile().tx().execute(new Transaction.Closure<IOException>() {
+                @Override
+                public void execute(Transaction tx) throws IOException {
+                    Map.Entry<Long, List<JobLocation>> first = index.getFirst(tx);
+                    if (first != null) {
+                        for (JobLocation jl : first.getValue()) {
                             ByteSequence bs = getPayload(jl.getLocation());
                             Job job = new JobImpl(jl, bs);
                             result.add(job);
                         }
-                    } else {
-                        break;
                     }
                 }
-            }
-        });
+            });
+        } finally {
+            this.store.readUnlockIndex();
+        }
         return result;
     }
 
+    private Map.Entry<Long, List<JobLocation>> getNextToSchedule() throws IOException {
+        this.store.readLockIndex();
+        try {
+            if (!this.store.isStopped() && !this.store.isStopping()) {
+                Map.Entry<Long, List<JobLocation>> first = this.index.getFirst(this.store.getPageFile().tx());
+                return first;
+            }
+        } finally {
+            this.store.readUnlockIndex();
+        }
+        return null;
+    }
+
     @Override
-    public synchronized List<Job> getAllJobs(final long start, final long finish) throws IOException {
+    public List<Job> getAllJobs() throws IOException {
         final List<Job> result = new ArrayList<Job>();
-        this.store.getPageFile().tx().execute(new Transaction.Closure<IOException>() {
-            @Override
-            public void execute(Transaction tx) throws IOException {
-                Iterator<Map.Entry<Long, List<JobLocation>>> iter = index.iterator(store.getPageFile().tx(), start);
-                while (iter.hasNext()) {
-                    Map.Entry<Long, List<JobLocation>> next = iter.next();
-                    if (next != null && next.getKey().longValue() <= finish) {
-                        for (JobLocation jl : next.getValue()) {
-                            ByteSequence bs = getPayload(jl.getLocation());
-                            Job job = new JobImpl(jl, bs);
-                            result.add(job);
+        this.store.readLockIndex();
+        try {
+            this.store.getPageFile().tx().execute(new Transaction.Closure<IOException>() {
+                @Override
+                public void execute(Transaction tx) throws IOException {
+                    Iterator<Map.Entry<Long, List<JobLocation>>> iter = index.iterator(store.getPageFile().tx());
+                    while (iter.hasNext()) {
+                        Map.Entry<Long, List<JobLocation>> next = iter.next();
+                        if (next != null) {
+                            for (JobLocation jl : next.getValue()) {
+                                ByteSequence bs = getPayload(jl.getLocation());
+                                Job job = new JobImpl(jl, bs);
+                                result.add(job);
+                            }
+                        } else {
+                            break;
                         }
-                    } else {
-                        break;
                     }
                 }
-            }
-        });
+            });
+        } finally {
+            this.store.readUnlockIndex();
+        }
         return result;
     }
 
     @Override
-    public synchronized void removeAllJobs() throws IOException {
-        this.store.getPageFile().tx().execute(new Transaction.Closure<IOException>() {
-            @Override
-            public void execute(Transaction tx) throws IOException {
-                destroy(tx);
-            }
-        });
-    }
-
-    @Override
-    public synchronized void removeAllJobs(final long start, final long finish) throws IOException {
-        this.store.getPageFile().tx().execute(new Transaction.Closure<IOException>() {
-            @Override
-            public void execute(Transaction tx) throws IOException {
-                destroy(tx, start, finish);
-            }
-        });
-    }
-
-    ByteSequence getPayload(Location location) throws IllegalStateException, IOException {
-        return this.store.getPayload(location);
+    public List<Job> getAllJobs(final long start, final long finish) throws IOException {
+        final List<Job> result = new ArrayList<Job>();
+        this.store.readLockIndex();
+        try {
+            this.store.getPageFile().tx().execute(new Transaction.Closure<IOException>() {
+                @Override
+                public void execute(Transaction tx) throws IOException {
+                    Iterator<Map.Entry<Long, List<JobLocation>>> iter = index.iterator(tx, start);
+                    while (iter.hasNext()) {
+                        Map.Entry<Long, List<JobLocation>> next = iter.next();
+                        if (next != null && next.getKey().longValue() <= finish) {
+                            for (JobLocation jl : next.getValue()) {
+                                ByteSequence bs = getPayload(jl.getLocation());
+                                Job job = new JobImpl(jl, bs);
+                                result.add(job);
+                            }
+                        } else {
+                            break;
+                        }
+                    }
+                }
+            });
+        } finally {
+            this.store.readUnlockIndex();
+        }
+        return result;
     }
 
-    void schedule(Transaction tx, String jobId, ByteSequence payload, String cronEntry, long delay, long period, int repeat) throws IOException {
+    private void doSchedule(final String jobId, final ByteSequence payload, final String cronEntry, long delay, long period, int repeat) throws IOException {
         long startTime = System.currentTimeMillis();
         // round startTime - so we can schedule more jobs
         // at the same time
@@ -308,38 +243,86 @@ class JobSchedulerImpl extends ServiceSupport implements Runnable, JobScheduler
             // start time not set by CRON - so it it to the current time
             time = startTime;
         }
+
         if (delay > 0) {
             time += delay;
         } else {
             time += period;
         }
 
-        Location location = this.store.write(payload, false);
-        JobLocation jobLocation = new JobLocation(location);
-        this.store.incrementJournalCount(tx, location);
-        jobLocation.setJobId(jobId);
-        jobLocation.setStartTime(startTime);
-        jobLocation.setCronEntry(cronEntry);
-        jobLocation.setDelay(delay);
-        jobLocation.setPeriod(period);
-        jobLocation.setRepeat(repeat);
-        if (LOG.isDebugEnabled()) {
-            LOG.debug("Scheduling " + jobLocation);
-        }
-        storeJob(tx, jobLocation, time);
-        this.scheduleTime.newJob();
-    }
-
-    synchronized void storeJob(final JobLocation jobLocation, final long nextExecutionTime) throws IOException {
-        this.store.getPageFile().tx().execute(new Transaction.Closure<IOException>() {
-            @Override
-            public void execute(Transaction tx) throws IOException {
-                storeJob(tx, jobLocation, nextExecutionTime);
-            }
-        });
+        KahaAddScheduledJobCommand newJob = new KahaAddScheduledJobCommand();
+        newJob.setScheduler(name);
+        newJob.setJobId(jobId);
+        newJob.setStartTime(startTime);
+        newJob.setCronEntry(cronEntry);
+        newJob.setDelay(delay);
+        newJob.setPeriod(period);
+        newJob.setRepeat(repeat);
+        newJob.setNextExecutionTime(time);
+        newJob.setPayload(new Buffer(payload.getData(), payload.getOffset(), payload.getLength()));
+
+        this.store.store(newJob);
+    }
+
+    private void doReschedule(final String jobId, long executionTime, long nextExecutionTime, int rescheduledCount) throws IOException {
+        KahaRescheduleJobCommand update = new KahaRescheduleJobCommand();
+        update.setScheduler(name);
+        update.setJobId(jobId);
+        update.setExecutionTime(executionTime);
+        update.setNextExecutionTime(nextExecutionTime);
+        update.setRescheduledCount(rescheduledCount);
+        this.store.store(update);
+    }
+
+    private void doRemove(final long executionTime, final List<JobLocation> jobs) throws IOException {
+        for (JobLocation job : jobs) {
+            doRemove(executionTime, job.getJobId());
+        }
+    }
+
+    private void doRemove(long executionTime, final String jobId) throws IOException {
+        KahaRemoveScheduledJobCommand remove = new KahaRemoveScheduledJobCommand();
+        remove.setScheduler(name);
+        remove.setJobId(jobId);
+        remove.setNextExecutionTime(executionTime);
+        this.store.store(remove);
     }
 
-    void storeJob(final Transaction tx, final JobLocation jobLocation, final long nextExecutionTime) throws IOException {
+    private void doRemoveRange(long start, long end) throws IOException {
+        KahaRemoveScheduledJobsCommand destroy = new KahaRemoveScheduledJobsCommand();
+        destroy.setScheduler(name);
+        destroy.setStartTime(start);
+        destroy.setEndTime(end);
+        this.store.store(destroy);
+    }
+
+    /**
+     * Adds a new Scheduled job to the index.  Must be called under index lock.
+     *
+     * This method must ensure that a duplicate add is not processed into the scheduler.  On index
+     * recover some adds may be replayed and we don't allow more than one instance of a JobId to
+     * exist at any given scheduled time, so filter these out to ensure idempotence.
+     *
+     * @param tx
+     *      Transaction in which the update is performed.
+     * @param command
+     *      The new scheduled job command to process.
+     * @param location
+     *      The location where the add command is stored in the journal.
+     *
+     * @throws IOException if an error occurs updating the index.
+     */
+    protected void process(final Transaction tx, final KahaAddScheduledJobCommand command, Location location) throws IOException {
+        JobLocation jobLocation = new JobLocation(location);
+        jobLocation.setJobId(command.getJobId());
+        jobLocation.setStartTime(command.getStartTime());
+        jobLocation.setCronEntry(command.getCronEntry());
+        jobLocation.setDelay(command.getDelay());
+        jobLocation.setPeriod(command.getPeriod());
+        jobLocation.setRepeat(command.getRepeat());
+
+        long nextExecutionTime = command.getNextExecutionTime();
+
         List<JobLocation> values = null;
         jobLocation.setNextTime(nextExecutionTime);
         if (this.index.containsKey(tx, nextExecutionTime)) {
@@ -348,106 +331,239 @@ class JobSchedulerImpl extends ServiceSupport implements Runnable, JobScheduler
         if (values == null) {
             values = new ArrayList<JobLocation>();
         }
-        values.add(jobLocation);
-        this.index.put(tx, nextExecutionTime, values);
-    }
 
-    void remove(Transaction tx, long time, String jobId) throws IOException {
-        JobLocation result = removeFromIndex(tx, time, jobId);
-        if (result != null) {
-            this.store.decrementJournalCount(tx, result.getLocation());
+        // There can never be more than one instance of the same JobId scheduled at any
+        // given time, when it happens its probably the result of index recovery and this
+        // method must be idempotent so check for it first.
+        if (!values.contains(jobLocation)) {
+            values.add(jobLocation);
+
+            // Reference the log file where the add command is stored to prevent GC.
+            this.store.incrementJournalCount(tx, location);
+            this.index.put(tx, nextExecutionTime, values);
+            this.scheduleTime.newJob();
+        } else {
+            this.index.put(tx, nextExecutionTime, values);
+            LOG.trace("Job {} already in scheduler at this time {}",
+                      jobLocation.getJobId(), jobLocation.getNextTime());
         }
     }
 
-    JobLocation removeFromIndex(Transaction tx, long time, String jobId) throws IOException {
+    /**
+     * Reschedules a Job after it has be fired.
+     *
+     * For jobs that are repeating this method updates the job in the index by adding it to the
+     * jobs list for the new execution time.  If the job is not a cron type job then this method
+     * will reduce the repeat counter if the job has a fixed number of repeats set.  The Job will
+     * be removed from the jobs list it just executed on.
+     *
+     * This method must also update the value of the last update location in the JobLocation
+     * instance so that the checkpoint worker doesn't drop the log file in which that command lives.
+     *
+     * This method must ensure that an reschedule command that references a job that doesn't exist
+     * does not cause an error since it's possible that on recover the original add might be gone
+     * and so the job should not reappear in the scheduler.
+     *
+     * @param tx
+     *      The TX under which the index is updated.
+     * @param command
+     *      The reschedule command to process.
+     * @param location
+     *      The location in the index where the reschedule command was stored.
+     *
+     * @throws IOException if an error occurs during the reschedule.
+     */
+    protected void process(final Transaction tx, final KahaRescheduleJobCommand command, Location location) throws IOException {
         JobLocation result = null;
-        List<JobLocation> values = this.index.remove(tx, time);
-        if (values != null) {
-            for (int i = 0; i < values.size(); i++) {
-                JobLocation jl = values.get(i);
-                if (jl.getJobId().equals(jobId)) {
-                    values.remove(i);
-                    if (!values.isEmpty()) {
-                        this.index.put(tx, time, values);
+        final List<JobLocation> current = this.index.remove(tx, command.getExecutionTime());
+        if (current != null) {
+            for (int i = 0; i < current.size(); i++) {
+                JobLocation jl = current.get(i);
+                if (jl.getJobId().equals(command.getJobId())) {
+                    current.remove(i);
+                    if (!current.isEmpty()) {
+                        this.index.put(tx, command.getExecutionTime(), current);
                     }
                     result = jl;
                     break;
                 }
             }
+        } else {
+            LOG.debug("Process reschedule command for job {} non-existent executime time {}.",
+                      command.getJobId(), command.getExecutionTime());
         }
-        return result;
-    }
 
-    private void remove(Transaction tx, long time, List<JobLocation> jobIds) throws IOException {
-        List<JobLocation> result = removeFromIndex(tx, time, jobIds);
         if (result != null) {
-            for (JobLocation jl : result) {
-                this.store.decrementJournalCount(tx, jl.getLocation());
+            Location previousUpdate = result.getLastUpdate();
+
+            List<JobLocation> target = null;
+            result.setNextTime(command.getNextExecutionTime());
+            result.setLastUpdate(location);
+            result.setRescheduledCount(command.getRescheduledCount());
+            if (!result.isCron() && result.getRepeat() > 0) {
+                result.setRepeat(result.getRepeat() - 1);
             }
+            if (this.index.containsKey(tx, command.getNextExecutionTime())) {
+                target = this.index.remove(tx, command.getNextExecutionTime());
+            }
+            if (target == null) {
+                target = new ArrayList<JobLocation>();
+            }
+            target.add(result);
+
+            // Track the location of the last reschedule command and release the log file
+            // reference for the previous one if there was one.
+            this.store.incrementJournalCount(tx, location);
+            if (previousUpdate != null) {
+                this.store.decrementJournalCount(tx, previousUpdate);
+            }
+
+            this.index.put(tx, command.getNextExecutionTime(), target);
+            this.scheduleTime.newJob();
+        } else {
+            LOG.debug("Process reschedule command for non-scheduled job {} at executime time {}.",
+                      command.getJobId(), command.getExecutionTime());
         }
     }
 
-    private List<JobLocation> removeFromIndex(Transaction tx, long time, List<JobLocation> Jobs) throws IOException {
-        List<JobLocation> result = null;
-        List<JobLocation> values = this.index.remove(tx, time);
-        if (values != null) {
-            result = new ArrayList<JobLocation>(values.size());
+    /**
+     * Removes a scheduled job from the scheduler.
+     *
+     * The remove operation can be of two forms.  The first is that there is a job Id but no set time
+     * (-1) in which case the jobs index is searched until the target job Id is located.  The alternate
+     * form is that a job Id and execution time are both set in which case the given time is checked
+     * for a job matching that Id.  In either case once an execution time is identified the job is
+     * removed and the index updated.
+     *
+     * This method should ensure that if the matching job is not found that no error results as it
+     * is possible that on a recover the initial add command could be lost so the job may not be
+     * rescheduled.
+     *
+     * @param tx
+     *      The transaction under which the index is updated.
+     * @param command
+     *      The remove command to process.
+     * @param location
+     *      The location of the remove command in the Journal.
+     *
+     * @throws IOException if an error occurs while updating the scheduler index.
+     */
+    void process(final Transaction tx, final KahaRemoveScheduledJobCommand command, Location location) throws IOException {
 
-            for (JobLocation job : Jobs) {
-                if (values.remove(job)) {
-                    result.add(job);
-                }
-            }
+        // Case 1: JobId and no time value means find the job and remove it.
+        // Case 2: JobId and a time value means find exactly this scheduled job.
 
-            if (!values.isEmpty()) {
-                this.index.put(tx, time, values);
+        Long executionTime = command.getNextExecutionTime();
+
+        List<JobLocation> values = null;
+
+        if (executionTime == -1) {
+            for (Iterator<Map.Entry<Long, List<JobLocation>>> i = this.index.iterator(tx); i.hasNext();) {
+                Map.Entry<Long, List<JobLocation>> entry = i.next();
+                List<JobLocation> candidates = entry.getValue();
+                if (candidates != null) {
+                    for (JobLocation jl : candidates) {
+                        if (jl.getJobId().equals(command.getJobId())) {
+                            LOG.trace("Entry {} contains the remove target: {}", entry.getKey(), command.getJobId());
+                            executionTime = entry.getKey();
+                            values = this.index.remove(tx, executionTime);
+                            break;
+                        }
+                    }
+                }
             }
+        } else {
+            values = this.index.remove(tx, executionTime);
         }
-        return result;
-    }
 
-    void remove(Transaction tx, long time) throws IOException {
-        List<JobLocation> values = this.index.remove(tx, time);
+        JobLocation removed = null;
+
+        // Remove the job and update the index if there are any other jobs scheduled at this time.
         if (values != null) {
-            for (JobLocation jl : values) {
-                this.store.decrementJournalCount(tx, jl.getLocation());
+            for (JobLocation job : values) {
+                if (job.getJobId().equals(command.getJobId())) {
+                    removed = job;
+                    values.remove(removed);
+                    break;
+                }
             }
-        }
-    }
 
-    void remove(Transaction tx, String id) throws IOException {
-        for (Iterator<Map.Entry<Long, List<JobLocation>>> i = this.index.iterator(tx); i.hasNext();) {
-            Map.Entry<Long, List<JobLocation>> entry = i.next();
-            List<JobLocation> values = entry.getValue();
-            if (values != null) {
-                for (JobLocation jl : values) {
-                    if (jl.getJobId().equals(id)) {
-                        remove(tx, entry.getKey(), id);
-                        return;
-                    }
-                }
+            if (!values.isEmpty()) {
+                this.index.put(tx, executionTime, values);
             }
         }
-    }
 
-    synchronized void destroy(Transaction tx) throws IOException {
-        List<Long> keys = new ArrayList<Long>();
-        for (Iterator<Map.Entry<Long, List<JobLocation>>> i = this.index.iterator(tx); i.hasNext();) {
-            Map.Entry<Long, List<JobLocation>> entry = i.next();
-            keys.add(entry.getKey());
-        }
+        if (removed != null) {
+            LOG.trace("{} removed from scheduler {}", removed, this);
 
-        for (Long l : keys) {
-            List<JobLocation> values = this.index.remove(tx, l);
-            if (values != null) {
-                for (JobLocation jl : values) {
-                    this.store.decrementJournalCount(tx, jl.getLocation());
-                }
+            // Remove the references for add and reschedule commands for this job
+            // so that those logs can be GC'd when free.
+            this.store.decrementJournalCount(tx, removed.getLocation());
+            if (removed.getLastUpdate() != null) {
+                this.store.decrementJournalCount(tx, removed.getLastUpdate());
             }
+
+            // now that the job is removed from the index we can store the remove info and
+            // then dereference the log files that hold the initial add command and the most
+            // recent update command.
+            this.store.referenceRemovedLocation(tx, location, removed);
         }
     }
 
-    synchronized void destroy(Transaction tx, long start, long finish) throws IOException {
+    /**
+     * Removes all scheduled jobs within a given time range.
+     *
+     * The method can be used to clear the entire scheduler index by specifying a range that
+     * encompasses all time [0...Long.MAX_VALUE] or a single execution time can be removed by
+     * setting start and end time to the same value.
+     *
+     * @param tx
+     *      The transaction under which the index is updated.
+     * @param command
+     *      The remove command to process.
+     * @param location
+     *      The location of the remove command in the Journal.
+     *
+     * @throws IOException if an error occurs while updating the scheduler index.
+     */
+    protected void process(final Transaction tx, final KahaRemoveScheduledJobsCommand command, Location location) throws IOException {
+        removeInRange(tx, command.getStartTime(), command.getEndTime(), location);
+    }
+
+    /**
+     * Removes all jobs from the schedulers index.  Must be called with the index locked.
+     *
+     * @param tx
+     *      The transaction under which the index entries for this scheduler are removed.
+     *
+     * @throws IOException if an error occurs removing the jobs from the scheduler index.
+     */
+    protected void removeAll(Transaction tx) throws IOException {
+        this.removeInRange(tx, 0, Long.MAX_VALUE, null);
+    }
+
+    /**
+     * Removes all scheduled jobs within the target range.
+     *
+     * This method can be used to remove all the stored jobs by passing a range of [0...Long.MAX_VALUE]
+     * or it can be used to remove all jobs at a given scheduled time by passing the same time value
+     * for both start and end.  If the optional location parameter is set then this method will update
+     * the store's remove location tracker with the location value and the Jobs that are being removed.
+     *
+     * This method must be called with the store index locked for writes.
+     *
+     * @param tx
+     *      The transaction under which the index is to be updated.
+     * @param start
+     *      The start time for the remove operation.
+     * @param finish
+     *      The end time for the remove operation.
+     * @param location (optional)
+     *      The location of the remove command that triggered this remove.
+     *
+     * @throws IOException if an error occurs during the remove operation.
+     */
+    protected void removeInRange(Transaction tx, long start, long finish, Location location) throws IOException {
         List<Long> keys = new ArrayList<Long>();
         for (Iterator<Map.Entry<Long, List<JobLocation>>> i = this.index.iterator(tx, start); i.hasNext();) {
             Map.Entry<Long, List<JobLocation>> entry = i.next();
@@ -458,32 +574,97 @@ class JobSchedulerImpl extends ServiceSupport implements Runnable, JobScheduler
             }
         }
 
-        for (Long l : keys) {
-            List<JobLocation> values = this.index.remove(tx, l);
-            if (values != null) {
-                for (JobLocation jl : values) {
-                    this.store.decrementJournalCount(tx, jl.getLocation());
+        for (Long executionTime : keys) {
+            List<JobLocation> values = this.index.remove(tx, executionTime);
+            if (location != null) {
+                for (JobLocation job : values) {
+                    LOG.trace("Removing {} scheduled at: {}", job, executionTime);
+
+                    // Remove the references for add and reschedule commands for this job
+                    // so that those logs can be GC'd when free.
+                    this.store.decrementJournalCount(tx, job.getLocation());
+                    if (job.getLastUpdate() != null) {
+                        this.store.decrementJournalCount(tx, job.getLastUpdate());
+                    }
+
+                    // now that the job is removed from the index we can store the remove info and
+                    // then dereference the log files that hold the initial add command and the most
+                    // recent update command.
+                    this.store.referenceRemovedLocation(tx, location, job);
                 }
             }
         }
     }
 
-    private synchronized Map.Entry<Long, List<JobLocation>> getNextToSchedule() throws IOException {
-        if (!this.store.isStopped() && !this.store.isStopping()) {
-            Map.Entry<Long, List<JobLocation>> first = this.index.getFirst(this.store.getPageFile().tx());
-            return first;
+    /**
+     * Removes a Job from the index using it's Id value and the time it is currently set to
+     * be executed.  This method will only remove the Job if it is found at the given execution
+     * time.
+     *
+     * This method must be called under index lock.
+     *
+     * @param tx
+     *        the transaction under which this method is being executed.
+     * @param jobId
+     *        the target Job Id to remove.
+     * @param executionTime
+     *        the scheduled time that for the Job Id that is being removed.
+     *
+     * @returns true if the Job was removed or false if not found at the given time.
+     *
+     * @throws IOException if an error occurs while removing the Job.
+     */
+    protected boolean removeJobAtTime(Transaction tx, String jobId, long executionTime) throws IOException {
+        boolean result = false;
+
+        List<JobLocation> jobs = this.index.remove(tx, executionTime);
+        Iterator<JobLocation> jobsIter = jobs.iterator();
+        while (jobsIter.hasNext()) {
+            JobLocation job = jobsIter.next();
+            if (job.getJobId().equals(jobId)) {
+                jobsIter.remove();
+                // Remove the references for add and reschedule commands for this job
+                // so that those logs can be GC'd when free.
+                this.store.decrementJournalCount(tx, job.getLocation());
+                if (job.getLastUpdate() != null) {
+                    this.store.decrementJournalCount(tx, job.getLastUpdate());
+                }
+                result = true;
+                break;
+            }
         }
-        return null;
+
+        // Return the list to the index modified or unmodified.
+        this.index.put(tx, executionTime, jobs);
+
+        return result;
     }
 
-    void fireJob(JobLocation job) throws IllegalStateException, IOException {
-        if (LOG.isDebugEnabled()) {
-            LOG.debug("Firing " + job);
-        }
-        ByteSequence bs = this.store.getPayload(job.getLocation());
-        for (JobListener l : jobListeners) {
-            l.scheduledJob(job.getJobId(), bs);
+    /**
+     * Walks the Scheduled Job Tree and collects the add location and last update location
+     * for all scheduled jobs.
+     *
+     * This method must be called with the index locked.
+     *
+     * @param tx
+     *        the transaction under which this operation was invoked.
+     *
+     * @return a list of all referenced Location values for this JobSchedulerImpl
+     *
+     * @throws IOException if an error occurs walking the scheduler tree.
+     */
+    protected List<JobLocation> getAllScheduledJobs(Transaction tx) throws IOException {
+        List<JobLocation> references = new ArrayList<JobLocation>();
+
+        for (Iterator<Map.Entry<Long, List<JobLocation>>> i = this.index.iterator(tx); i.hasNext();) {
+            Map.Entry<Long, List<JobLocation>> entry = i.next();
+            List<JobLocation> scheduled = entry.getValue();
+            for (JobLocation job : scheduled) {
+                references.add(job);
+            }
         }
+
+        return references;
     }
 
     @Override
@@ -492,14 +673,14 @@ class JobSchedulerImpl extends ServiceSupport implements Runnable, JobScheduler
             mainLoop();
         } catch (Throwable e) {
             if (this.running.get() && isStarted()) {
-                LOG.error(this + " Caught exception in mainloop", e);
+                LOG.error("{} Caught exception in mainloop", this, e);
             }
         } finally {
             if (running.get()) {
                 try {
                     stop();
                 } catch (Exception e) {
-                    LOG.error("Failed to stop " + this);
+                    LOG.error("Failed to stop {}", this);
                 }
             }
         }
@@ -507,56 +688,55 @@ class JobSchedulerImpl extends ServiceSupport implements Runnable, JobScheduler
 
     @Override
     public String toString() {
-        return "JobScheduler:" + this.name;
+        return "JobScheduler: " + this.name;
     }
 
     protected void mainLoop() {
         while (this.running.get()) {
             this.scheduleTime.clearNewJob();
             try {
-                // peek the next job
                 long currentTime = System.currentTimeMillis();
 
-                // Read the list of scheduled events and fire the jobs.  Once done with the batch
-                // remove all that were fired, and reschedule as needed.
+                // Read the list of scheduled events and fire the jobs, reschedule repeating jobs as
+                // needed before firing the job event.
                 Map.Entry<Long, List<JobLocation>> first = getNextToSchedule();
                 if (first != null) {
                     List<JobLocation> list = new ArrayList<JobLocation>(first.getValue());
-                    List<JobLocation> fired = new ArrayList<JobLocation>(list.size());
+                    List<JobLocation> toRemove = new ArrayList<JobLocation>(list.size());
                     final long executionTime = first.getKey();
                     long nextExecutionTime = 0;
                     if (executionTime <= currentTime) {
                         for (final JobLocation job : list) {
+
+                            if (!running.get()) {
+                                break;
+                            }
+
                             int repeat = job.getRepeat();
                             nextExecutionTime = calculateNextExecutionTime(job, currentTime, repeat);
                             long waitTime = nextExecutionTime - currentTime;
                             this.scheduleTime.setWaitTime(waitTime);
-                            if (job.isCron() == false) {
+                            if (!job.isCron()) {
                                 fireJob(job);
                                 if (repeat != 0) {
-                                    repeat--;
-                                    job.setRepeat(repeat);
-                                    // remove this job from the index so it doesn't get destroyed
-                                    removeFromIndex(executionTime, job.getJobId());
-                                    // and re-store it
-                                    storeJob(job, nextExecutionTime);
+                                    // Reschedule for the next time, the scheduler will take care of
+                                    // updating the repeat counter on the update.
+                                    doReschedule(job.getJobId(), executionTime, nextExecutionTime, job.getRescheduledCount() + 1);
                                 } else {
-                                    fired.add(job);
+                                    toRemove.add(job);
                                 }
                             } else {
-                                // cron job will have a repeat time.
                                 if (repeat == 0) {
-                                    // we haven't got a separate scheduler to execute at
-                                    // this time - just a cron job - so fire it
+                                    // This is a non-repeating Cron entry so we can fire and forget it.
                                     fireJob(job);
                                 }
 
                                 if (nextExecutionTime > currentTime) {
-                                    // we will run again ...
-                                    // remove this job from the index - so it doesn't get destroyed
-                                    removeFromIndex(executionTime, job.getJobId());
-                                    // and re-store it
-                                    storeJob(job, nextExecutionTime);
+                                    // Reschedule the cron job as a new event, if the cron entry signals
+                                    // a repeat then it will be stored separately and fired as a normal
+                                    // event with decrementing repeat.
+                                    doReschedule(job.getJobId(), executionTime, nextExecutionTime, job.getRescheduledCount() + 1);
+
                                     if (repeat != 0) {
                                         // we have a separate schedule to run at this time
                                         // so the cron job is used to set of a separate schedule
@@ -569,14 +749,14 @@ class JobSchedulerImpl extends ServiceSupport implements Runnable, JobScheduler
                                         this.scheduleTime.setWaitTime(waitTime);
                                     }
                                 } else {
-                                    fired.add(job);
+                                    toRemove.add(job);
                                 }
                             }
                         }
 
                         // now remove all jobs that have not been rescheduled from this execution
                         // time, if there are no more entries in that time it will be removed.
-                        remove(executionTime, fired);
+                        doRemove(executionTime, toRemove);
 
                         // If there is a job that should fire before the currently set wait time
                         // we need to reset wait time otherwise we'll miss it.
@@ -588,25 +768,30 @@ class JobSchedulerImpl extends ServiceSupport implements Runnable, JobScheduler
                             }
                         }
                     } else {
-                        if (LOG.isDebugEnabled()) {
-                            LOG.debug("Not yet time to execute the job, waiting " + (executionTime - currentTime) + " ms");
-                        }
                         this.scheduleTime.setWaitTime(executionTime - currentTime);
                     }
                 }
 
                 this.scheduleTime.pause();
             } catch (Exception ioe) {
-                LOG.error(this.name + " Failed to schedule job", ioe);
+                LOG.error("{} Failed to schedule job", this.name, ioe);
                 try {
                     this.store.stop();
                 } catch (Exception e) {
-                    LOG.error(this.name + " Failed to shutdown JobSchedulerStore", e);
+                    LOG.error("{} Failed to shutdown JobSchedulerStore", this.name, e);
                 }
             }
         }
     }
 
+    void fireJob(JobLocation job) throws IllegalStateException, IOException {
+        LOG.debug("Firing: {}", job);
+        ByteSequence bs = this.store.getPayload(job.getLocation());
+        for (JobListener l : jobListeners) {
+            l.scheduledJob(job.getJobId(), bs);
+        }
+    }
+
     @Override
     public void startDispatching() throws Exception {
         if (!this.running.get()) {
@@ -627,7 +812,7 @@ class JobSchedulerImpl extends ServiceSupport implements Runnable, JobScheduler
             Thread t = this.thread;
             this.thread = null;
             if (t != null) {
-                t.join(1000);
+                t.join(3000);
             }
         }
     }
@@ -643,6 +828,10 @@ class JobSchedulerImpl extends ServiceSupport implements Runnable, JobScheduler
         stopDispatching();
     }
 
+    private ByteSequence getPayload(Location location) throws IllegalStateException, IOException {
+        return this.store.getPayload(location);
+    }
+
     long calculateNextExecutionTime(final JobLocation job, long currentTime, int repeat) throws MessageFormatException {
         long result = currentTime;
         String cron = job.getCronEntry();
@@ -660,7 +849,7 @@ class JobSchedulerImpl extends ServiceSupport implements Runnable, JobScheduler
 
     void load(Transaction tx) throws IOException {
         this.index.setKeyMarshaller(LongMarshaller.INSTANCE);
-        this.index.setValueMarshaller(ValueMarshaller.INSTANCE);
+        this.index.setValueMarshaller(JobLocationsMarshaller.INSTANCE);
         this.index.load(tx);
     }
 
@@ -668,7 +857,7 @@ class JobSchedulerImpl extends ServiceSupport implements Runnable, JobScheduler
         this.name = in.readUTF();
         this.index = new BTreeIndex<Long, List<JobLocation>>(this.store.getPageFile(), in.readLong());
         this.index.setKeyMarshaller(LongMarshaller.INSTANCE);
-        this.index.setValueMarshaller(ValueMarshaller.INSTANCE);
+        this.index.setValueMarshaller(JobLocationsMarshaller.INSTANCE);
     }
 
     public void write(DataOutput out) throws IOException {
@@ -676,30 +865,6 @@ class JobSchedulerImpl extends ServiceSupport implements Runnable, JobScheduler
         out.writeLong(this.index.getPageId());
     }
 
-    static class ValueMarshaller extends VariableMarshaller<List<JobLocation>> {
-        static ValueMarshaller INSTANCE = new ValueMarshaller();
-
-        @Override
-        public List<JobLocation> readPayload(DataInput dataIn) throws IOException {
-            List<JobLocation> result = new ArrayList<JobLocation>();
-            int size = dataIn.readInt();
-            for (int i = 0; i < size; i++) {
-                JobLocation jobLocation = new JobLocation();
-                jobLocation.readExternal(dataIn);
-                result.add(jobLocation);
-            }
-            return result;
-        }
-
-        @Override
-        public void writePayload(List<JobLocation> value, DataOutput dataOut) throws IOException {
-            dataOut.writeInt(value.size());
-            for (JobLocation jobLocation : value) {
-                jobLocation.writeExternal(dataOut);
-            }
-        }
-    }
-
     static class ScheduleTime {
         private final int DEFAULT_WAIT = 500;
         private final int DEFAULT_NEW_JOB_WAIT = 100;

http://git-wip-us.apache.org/repos/asf/activemq/blob/fc244f48/activemq-kahadb-store/src/main/java/org/apache/activemq/store/kahadb/scheduler/JobSchedulerKahaDBMetaData.java
----------------------------------------------------------------------
diff --git a/activemq-kahadb-store/src/main/java/org/apache/activemq/store/kahadb/scheduler/JobSchedulerKahaDBMetaData.java b/activemq-kahadb-store/src/main/java/org/apache/activemq/store/kahadb/scheduler/JobSchedulerKahaDBMetaData.java
new file mode 100644
index 0000000..c92dc7b
--- /dev/null
+++ b/activemq-kahadb-store/src/main/java/org/apache/activemq/store/kahadb/scheduler/JobSchedulerKahaDBMetaData.java
@@ -0,0 +1,246 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.activemq.store.kahadb.scheduler;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.UUID;
+
+import org.apache.activemq.store.kahadb.AbstractKahaDBMetaData;
+import org.apache.activemq.store.kahadb.disk.index.BTreeIndex;
+import org.apache.activemq.store.kahadb.disk.page.Transaction;
+import org.apache.activemq.store.kahadb.disk.util.IntegerMarshaller;
+import org.apache.activemq.store.kahadb.disk.util.LocationMarshaller;
+import org.apache.activemq.store.kahadb.disk.util.StringMarshaller;
+import org.apache.activemq.store.kahadb.disk.util.VariableMarshaller;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The KahaDB MetaData used to house the Index data for the KahaDB implementation
+ * of a JobSchedulerStore.
+ */
+public class JobSchedulerKahaDBMetaData extends AbstractKahaDBMetaData<JobSchedulerKahaDBMetaData> {
+
+    static final Logger LOG = LoggerFactory.getLogger(JobSchedulerKahaDBMetaData.class);
+
+    private final JobSchedulerStoreImpl store;
+
+    private UUID token = JobSchedulerStoreImpl.SCHEDULER_STORE_TOKEN;
+    private int version = JobSchedulerStoreImpl.CURRENT_VERSION;
+
+    private BTreeIndex<Integer, List<Integer>> removeLocationTracker;
+    private BTreeIndex<Integer, Integer> journalRC;
+    private BTreeIndex<String, JobSchedulerImpl> storedSchedulers;
+
+    /**
+     * Creates a new instance of this meta data object with the assigned
+     * parent JobSchedulerStore instance.
+     *
+     * @param store
+     *        the store instance that owns this meta data.
+     */
+    public JobSchedulerKahaDBMetaData(JobSchedulerStoreImpl store) {
+        this.store = store;
+    }
+
+    /**
+     * @return the current value of the Scheduler store identification token.
+     */
+    public UUID getToken() {
+        return this.token;
+    }
+
+    /**
+     * @return the current value of the version tag for this meta data instance.
+     */
+    public int getVersion() {
+        return this.version;
+    }
+
+    /**
+     * Gets the index that contains the location tracking information for Jobs
+     * that have been removed from the index but whose add operation has yet
+     * to be removed from the Journal.
+     *
+     * The Journal log file where a remove command is written cannot be released
+     * until the log file with the original add command has also been released,
+     * otherwise on a log replay the scheduled job could reappear in the scheduler
+     * since its corresponding remove might no longer be present.
+     *
+     * @return the remove command location tracker index.
+     */
+    public BTreeIndex<Integer, List<Integer>> getRemoveLocationTracker() {
+        return this.removeLocationTracker;
+    }
+
+    /**
+     * Gets the index used to track the number of reference to a Journal log file.
+     *
+     * A log file in the Journal can only be considered for removal after all the
+     * references to it have been released.
+     *
+     * @return the journal log file reference counter index.
+     */
+    public BTreeIndex<Integer, Integer> getJournalRC() {
+        return this.journalRC;
+    }
+
+    /**
+     * Gets the index of JobScheduler instances that have been created and stored
+     * in the JobSchedulerStore instance.
+     *
+     * @return the index of stored JobScheduler instances.
+     */
+    public BTreeIndex<String, JobSchedulerImpl> getJobSchedulers() {
+        return this.storedSchedulers;
+    }
+
+    @Override
+    public void initialize(Transaction tx) throws IOException {
+        this.storedSchedulers = new BTreeIndex<String, JobSchedulerImpl>(store.getPageFile(), tx.allocate().getPageId());
+        this.journalRC = new BTreeIndex<Integer, Integer>(store.getPageFile(), tx.allocate().getPageId());
+        this.removeLocationTracker = new BTreeIndex<Integer, List<Integer>>(store.getPageFile(), tx.allocate().getPageId());
+    }
+
+    @Override
+    public void load(Transaction tx) throws IOException {
+        this.storedSchedulers.setKeyMarshaller(StringMarshaller.INSTANCE);
+        this.storedSchedulers.setValueMarshaller(new JobSchedulerMarshaller(this.store));
+        this.storedSchedulers.load(tx);
+        this.journalRC.setKeyMarshaller(IntegerMarshaller.INSTANCE);
+        this.journalRC.setValueMarshaller(IntegerMarshaller.INSTANCE);
+        this.journalRC.load(tx);
+        this.removeLocationTracker.setKeyMarshaller(IntegerMarshaller.INSTANCE);
+        this.removeLocationTracker.setValueMarshaller(new IntegerListMarshaller());
+        this.removeLocationTracker.load(tx);
+    }
+
+    /**
+     * Loads all the stored JobScheduler instances into the provided map.
+     *
+     * @param tx
+     *        the Transaction under which the load operation should be executed.
+     * @param schedulers
+     *        a Map<String, JobSchedulerImpl> into which the loaded schedulers are stored.
+     *
+     * @throws IOException if an error occurs while performing the load operation.
+     */
+    public void loadScheduler(Transaction tx, Map<String, JobSchedulerImpl> schedulers) throws IOException {
+        for (Iterator<Entry<String, JobSchedulerImpl>> i = this.storedSchedulers.iterator(tx); i.hasNext();) {
+            Entry<String, JobSchedulerImpl> entry = i.next();
+            entry.getValue().load(tx);
+            schedulers.put(entry.getKey(), entry.getValue());
+        }
+    }
+
+    @Override
+    public void read(DataInput in) throws IOException {
+        try {
+            long msb = in.readLong();
+            long lsb = in.readLong();
+            this.token = new UUID(msb, lsb);
+        } catch (Exception e) {
+            throw new UnknownStoreVersionException(e);
+        }
+
+        if (!token.equals(JobSchedulerStoreImpl.SCHEDULER_STORE_TOKEN)) {
+            throw new UnknownStoreVersionException(token.toString());
+        }
+        this.version = in.readInt();
+        if (in.readBoolean()) {
+            setLastUpdateLocation(LocationMarshaller.INSTANCE.readPayload(in));
+        } else {
+            setLastUpdateLocation(null);
+        }
+        this.storedSchedulers = new BTreeIndex<String, JobSchedulerImpl>(store.getPageFile(), in.readLong());
+        this.storedSchedulers.setKeyMarshaller(StringMarshaller.INSTANCE);
+        this.storedSchedulers.setValueMarshaller(new JobSchedulerMarshaller(this.store));
+        this.journalRC = new BTreeIndex<Integer, Integer>(store.getPageFile(), in.readLong());
+        this.journalRC.setKeyMarshaller(IntegerMarshaller.INSTANCE);
+        this.journalRC.setValueMarshaller(IntegerMarshaller.INSTANCE);
+        this.removeLocationTracker = new BTreeIndex<Integer, List<Integer>>(store.getPageFile(), in.readLong());
+        this.removeLocationTracker.setKeyMarshaller(IntegerMarshaller.INSTANCE);
+        this.removeLocationTracker.setValueMarshaller(new IntegerListMarshaller());
+
+        LOG.info("Scheduler Store version {} loaded", this.version);
+    }
+
+    @Override
+    public void write(DataOutput out) throws IOException {
+        out.writeLong(this.token.getMostSignificantBits());
+        out.writeLong(this.token.getLeastSignificantBits());
+        out.writeInt(this.version);
+        if (getLastUpdateLocation() != null) {
+            out.writeBoolean(true);
+            LocationMarshaller.INSTANCE.writePayload(getLastUpdateLocation(), out);
+        } else {
+            out.writeBoolean(false);
+        }
+        out.writeLong(this.storedSchedulers.getPageId());
+        out.writeLong(this.journalRC.getPageId());
+        out.writeLong(this.removeLocationTracker.getPageId());
+    }
+
+    private class JobSchedulerMarshaller extends VariableMarshaller<JobSchedulerImpl> {
+        private final JobSchedulerStoreImpl store;
+
+        JobSchedulerMarshaller(JobSchedulerStoreImpl store) {
+            this.store = store;
+        }
+
+        @Override
+        public JobSchedulerImpl readPayload(DataInput dataIn) throws IOException {
+            JobSchedulerImpl result = new JobSchedulerImpl(this.store);
+            result.read(dataIn);
+            return result;
+        }
+
+        @Override
+        public void writePayload(JobSchedulerImpl js, DataOutput dataOut) throws IOException {
+            js.write(dataOut);
+        }
+    }
+
+    private class IntegerListMarshaller extends VariableMarshaller<List<Integer>> {
+
+        @Override
+        public List<Integer> readPayload(DataInput dataIn) throws IOException {
+            List<Integer> result = new ArrayList<Integer>();
+            int size = dataIn.readInt();
+            for (int i = 0; i < size; i++) {
+                result.add(IntegerMarshaller.INSTANCE.readPayload(dataIn));
+            }
+            return result;
+        }
+
+        @Override
+        public void writePayload(List<Integer> value, DataOutput dataOut) throws IOException {
+            dataOut.writeInt(value.size());
+            for (Integer integer : value) {
+                IntegerMarshaller.INSTANCE.writePayload(integer, dataOut);
+            }
+        }
+    }
+}


[11/16] activemq git commit: https://issues.apache.org/jira/browse/AMQ-5086 - init of broker got dropped after mutex wait - testWaitFor was failing

Posted by ha...@apache.org.
https://issues.apache.org/jira/browse/AMQ-5086 - init of broker got dropped after mutex wait - testWaitFor was failing


Project: http://git-wip-us.apache.org/repos/asf/activemq/repo
Commit: http://git-wip-us.apache.org/repos/asf/activemq/commit/db669e44
Tree: http://git-wip-us.apache.org/repos/asf/activemq/tree/db669e44
Diff: http://git-wip-us.apache.org/repos/asf/activemq/diff/db669e44

Branch: refs/heads/activemq-5.10.x
Commit: db669e44d852aa1ed3d6822c07bb92415a3f807f
Parents: 00426a9
Author: gtully <ga...@gmail.com>
Authored: Mon Jul 14 16:49:03 2014 +0100
Committer: Hadrian Zbarcea <ha...@apache.org>
Committed: Mon Dec 15 19:36:59 2014 -0500

----------------------------------------------------------------------
 .../transport/vm/VMTransportFactory.java        | 21 ++++++++++++++++----
 .../transport/vm/VMTransportWaitForTest.java    |  4 ++--
 2 files changed, 19 insertions(+), 6 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/activemq/blob/db669e44/activemq-broker/src/main/java/org/apache/activemq/transport/vm/VMTransportFactory.java
----------------------------------------------------------------------
diff --git a/activemq-broker/src/main/java/org/apache/activemq/transport/vm/VMTransportFactory.java b/activemq-broker/src/main/java/org/apache/activemq/transport/vm/VMTransportFactory.java
index e88faaf..40caa81 100755
--- a/activemq-broker/src/main/java/org/apache/activemq/transport/vm/VMTransportFactory.java
+++ b/activemq-broker/src/main/java/org/apache/activemq/transport/vm/VMTransportFactory.java
@@ -184,12 +184,25 @@ public class VMTransportFactory extends TransportFactory {
                 final long expiry = System.currentTimeMillis() + waitForStart;
                 while ((broker == null || !broker.isStarted()) && expiry > System.currentTimeMillis()) {
                     long timeout = Math.max(0, expiry - System.currentTimeMillis());
-                    try {
+                    if (broker == null) {
+                        try {
+                            LOG.debug("waiting for broker named: " + brokerName + " to enter registry");
+                            registry.getRegistryMutext().wait(timeout);
+                            broker = registry.lookup(brokerName);
+                        } catch (InterruptedException ignored) {
+                        }
+                    }
+                    if (broker != null && !broker.isStarted()) {
                         LOG.debug("waiting for broker named: " + brokerName + " to start");
-                        registry.getRegistryMutext().wait(timeout);
-                    } catch (InterruptedException ignored) {
+                        timeout = Math.max(0, expiry - System.currentTimeMillis());
+                        // Wait for however long we have left for broker to be started, if
+                        // it doesn't get started we need to clear broker so it doesn't get
+                        // returned.  A null return should throw an exception.
+                        if (!broker.waitUntilStarted(timeout)) {
+                            broker = null;
+                            break;
+                        }
                     }
-                    broker = registry.lookup(brokerName);
                 }
             }
         }

http://git-wip-us.apache.org/repos/asf/activemq/blob/db669e44/activemq-unit-tests/src/test/java/org/apache/activemq/transport/vm/VMTransportWaitForTest.java
----------------------------------------------------------------------
diff --git a/activemq-unit-tests/src/test/java/org/apache/activemq/transport/vm/VMTransportWaitForTest.java b/activemq-unit-tests/src/test/java/org/apache/activemq/transport/vm/VMTransportWaitForTest.java
index faa93e4..080d04d 100644
--- a/activemq-unit-tests/src/test/java/org/apache/activemq/transport/vm/VMTransportWaitForTest.java
+++ b/activemq-unit-tests/src/test/java/org/apache/activemq/transport/vm/VMTransportWaitForTest.java
@@ -52,7 +52,7 @@ public class VMTransportWaitForTest {
 
         // spawn a thread that will wait for an embedded broker to start via
         // vm://..
-        Thread t = new Thread() {
+        Thread t = new Thread("ClientConnectionThread") {
             @Override
             public void run() {
                 try {
@@ -74,7 +74,7 @@ public class VMTransportWaitForTest {
         BrokerService broker = new BrokerService();
         broker.setPersistent(false);
         broker.start();
-        assertTrue("has got connection", gotConnection.await(400, TimeUnit.MILLISECONDS));
+        assertTrue("has got connection", gotConnection.await(5, TimeUnit.SECONDS));
         broker.stop();
     }
 }


Re: [16/16] activemq git commit: https://issues.apache.org/jira/browse/AMQ-3758

Posted by Timothy Bish <ta...@gmail.com>.
-1

This is a huge change that completely alters the Job scheduler store to 
use a totally new store format and while the update does allow for the 
update from an older to a newer store this not something I would expect 
to see in a point release.

On 12/16/2014 05:10 PM, hadrian@apache.org wrote:
> https://issues.apache.org/jira/browse/AMQ-3758
>
> Refactor the scheduler store into a more KahaDB style store that can
> recover from various problems like missing journal files or corruption
> as well as rebuild its index when needed.  Move the scheduler store into
> a more configurable style that allows for users to plug in their own
> implementations.  Store update from legacy versions is automatic.
>
>
> Project: http://git-wip-us.apache.org/repos/asf/activemq/repo
> Commit: http://git-wip-us.apache.org/repos/asf/activemq/commit/fc244f48
> Tree: http://git-wip-us.apache.org/repos/asf/activemq/tree/fc244f48
> Diff: http://git-wip-us.apache.org/repos/asf/activemq/diff/fc244f48
>
> Branch: refs/heads/activemq-5.10.x
> Commit: fc244f48e48596c668a7d9dc3b84c26e60693823
> Parents: db669e4
> Author: Timothy Bish <ta...@gmail.com>
> Authored: Mon Jul 7 12:28:11 2014 -0400
> Committer: Hadrian Zbarcea <ha...@apache.org>
> Committed: Tue Dec 16 16:11:09 2014 -0500
>
> ----------------------------------------------------------------------
>   .../apache/activemq/broker/BrokerService.java   |   25 +-
>   .../activemq/broker/jmx/JobSchedulerView.java   |   56 +-
>   .../broker/jmx/JobSchedulerViewMBean.java       |  113 +-
>   .../apache/activemq/broker/scheduler/Job.java   |   23 +-
>   .../activemq/broker/scheduler/JobListener.java  |   16 +-
>   .../activemq/broker/scheduler/JobScheduler.java |   33 +-
>   .../broker/scheduler/JobSchedulerFacade.java    |    6 +
>   .../broker/scheduler/JobSchedulerStore.java     |   43 +
>   .../activemq/broker/scheduler/JobSupport.java   |    5 +-
>   .../activemq/store/PersistenceAdapter.java      |  119 +-
>   .../store/memory/MemoryPersistenceAdapter.java  |   36 +-
>   .../java/org/apache/activemq/util/IOHelper.java |   68 +-
>   .../store/jdbc/JDBCPersistenceAdapter.java      |    7 +
>   .../journal/JournalPersistenceAdapter.java      |   71 +-
>   .../store/kahadb/AbstractKahaDBMetaData.java    |   57 +
>   .../store/kahadb/AbstractKahaDBStore.java       |  745 ++++++++++++
>   .../activemq/store/kahadb/KahaDBMetaData.java   |  135 +++
>   .../store/kahadb/KahaDBPersistenceAdapter.java  |   15 +-
>   .../activemq/store/kahadb/KahaDBStore.java      |   55 +-
>   .../kahadb/MultiKahaDBPersistenceAdapter.java   |   56 +-
>   .../kahadb/MultiKahaDBTransactionStore.java     |   18 +-
>   .../activemq/store/kahadb/TempKahaDBStore.java  |  138 ++-
>   .../apache/activemq/store/kahadb/Visitor.java   |   20 +
>   .../store/kahadb/scheduler/JobImpl.java         |   21 +-
>   .../store/kahadb/scheduler/JobLocation.java     |   77 +-
>   .../scheduler/JobLocationsMarshaller.java       |   53 +
>   .../kahadb/scheduler/JobSchedulerImpl.java      |  837 ++++++++------
>   .../scheduler/JobSchedulerKahaDBMetaData.java   |  246 ++++
>   .../kahadb/scheduler/JobSchedulerStoreImpl.java | 1076 +++++++++++++-----
>   .../scheduler/UnknownStoreVersionException.java |   24 +
>   .../kahadb/scheduler/legacy/LegacyJobImpl.java  |   72 ++
>   .../scheduler/legacy/LegacyJobLocation.java     |  296 +++++
>   .../legacy/LegacyJobSchedulerImpl.java          |  222 ++++
>   .../legacy/LegacyJobSchedulerStoreImpl.java     |  378 ++++++
>   .../scheduler/legacy/LegacyStoreReplayer.java   |  155 +++
>   .../src/main/proto/journal-data.proto           |   61 +
>   .../apache/activemq/leveldb/LevelDBStore.scala  |    5 +
>   .../leveldb/replicated/ProxyLevelDBStore.scala  |    5 +
>   .../JobSchedulerBrokerShutdownTest.java         |    1 +
>   .../JobSchedulerJmxManagementTests.java         |  155 +++
>   .../scheduler/JobSchedulerManagementTest.java   |   84 +-
>   .../JobSchedulerStoreCheckpointTest.java        |  125 ++
>   .../broker/scheduler/JobSchedulerStoreTest.java |   46 +-
>   .../broker/scheduler/JobSchedulerTest.java      |   36 +
>   .../scheduler/JobSchedulerTestSupport.java      |  112 ++
>   .../KahaDBSchedulerIndexRebuildTest.java        |  179 +++
>   .../KahaDBSchedulerMissingJournalLogsTest.java  |  204 ++++
>   .../scheduler/SchedulerDBVersionTest.java       |  164 +++
>   .../src/test/resources/log4j.properties         |    1 +
>   .../activemq/store/schedulerDB/legacy/db-1.log  |  Bin 0 -> 524288 bytes
>   .../store/schedulerDB/legacy/scheduleDB.data    |  Bin 0 -> 20480 bytes
>   .../store/schedulerDB/legacy/scheduleDB.redo    |  Bin 0 -> 16408 bytes
>   52 files changed, 5584 insertions(+), 911 deletions(-)
> ----------------------------------------------------------------------
>
>
> http://git-wip-us.apache.org/repos/asf/activemq/blob/fc244f48/activemq-broker/src/main/java/org/apache/activemq/broker/BrokerService.java
> ----------------------------------------------------------------------
> diff --git a/activemq-broker/src/main/java/org/apache/activemq/broker/BrokerService.java b/activemq-broker/src/main/java/org/apache/activemq/broker/BrokerService.java
> index 00d4abd..5becec2 100644
> --- a/activemq-broker/src/main/java/org/apache/activemq/broker/BrokerService.java
> +++ b/activemq-broker/src/main/java/org/apache/activemq/broker/BrokerService.java
> @@ -1861,6 +1861,23 @@ public class BrokerService implements Service {
>   
>               try {
>                   PersistenceAdapter pa = getPersistenceAdapter();
> +                if (pa != null) {
> +                    this.jobSchedulerStore = pa.createJobSchedulerStore();
> +                    jobSchedulerStore.setDirectory(getSchedulerDirectoryFile());
> +                    configureService(jobSchedulerStore);
> +                    jobSchedulerStore.start();
> +                    return this.jobSchedulerStore;
> +                }
> +            } catch (IOException e) {
> +                throw new RuntimeException(e);
> +            } catch (UnsupportedOperationException ex) {
> +                // It's ok if the store doesn't implement a scheduler.
> +            } catch (Exception e) {
> +                throw new RuntimeException(e);
> +            }
> +
> +            try {
> +                PersistenceAdapter pa = getPersistenceAdapter();
>                   if (pa != null && pa instanceof JobSchedulerStore) {
>                       this.jobSchedulerStore = (JobSchedulerStore) pa;
>                       configureService(jobSchedulerStore);
> @@ -1870,9 +1887,13 @@ public class BrokerService implements Service {
>                   throw new RuntimeException(e);
>               }
>   
> +            // Load the KahaDB store as a last resort, this only works if KahaDB is
> +            // included at runtime, otherwise this will fail.  User should disable
> +            // scheduler support if this fails.
>               try {
> -                String clazz = "org.apache.activemq.store.kahadb.scheduler.JobSchedulerStoreImpl";
> -                jobSchedulerStore = (JobSchedulerStore) getClass().getClassLoader().loadClass(clazz).newInstance();
> +                String clazz = "org.apache.activemq.store.kahadb.KahaDBPersistenceAdapter";
> +                PersistenceAdapter adaptor = (PersistenceAdapter)getClass().getClassLoader().loadClass(clazz).newInstance();
> +                jobSchedulerStore = adaptor.createJobSchedulerStore();
>                   jobSchedulerStore.setDirectory(getSchedulerDirectoryFile());
>                   configureService(jobSchedulerStore);
>                   jobSchedulerStore.start();
>
> http://git-wip-us.apache.org/repos/asf/activemq/blob/fc244f48/activemq-broker/src/main/java/org/apache/activemq/broker/jmx/JobSchedulerView.java
> ----------------------------------------------------------------------
> diff --git a/activemq-broker/src/main/java/org/apache/activemq/broker/jmx/JobSchedulerView.java b/activemq-broker/src/main/java/org/apache/activemq/broker/jmx/JobSchedulerView.java
> index 9e5a1fb..2118a96 100644
> --- a/activemq-broker/src/main/java/org/apache/activemq/broker/jmx/JobSchedulerView.java
> +++ b/activemq-broker/src/main/java/org/apache/activemq/broker/jmx/JobSchedulerView.java
> @@ -16,23 +16,39 @@
>    */
>   package org.apache.activemq.broker.jmx;
>   
> +import java.util.List;
> +
> +import javax.management.openmbean.CompositeDataSupport;
> +import javax.management.openmbean.CompositeType;
> +import javax.management.openmbean.TabularData;
> +import javax.management.openmbean.TabularDataSupport;
> +import javax.management.openmbean.TabularType;
> +
>   import org.apache.activemq.broker.jmx.OpenTypeSupport.OpenTypeFactory;
>   import org.apache.activemq.broker.scheduler.Job;
>   import org.apache.activemq.broker.scheduler.JobScheduler;
>   import org.apache.activemq.broker.scheduler.JobSupport;
>   
> -import javax.management.openmbean.*;
> -import java.io.IOException;
> -import java.util.List;
> -
> +/**
> + * MBean object that can be used to manage a single instance of a JobScheduler.  The object
> + * provides methods for querying for jobs and removing some or all of the jobs that are
> + * scheduled in the managed store.
> + */
>   public class JobSchedulerView implements JobSchedulerViewMBean {
>   
>       private final JobScheduler jobScheduler;
>   
> +    /**
> +     * Creates a new instance of the JobScheduler management MBean.
> +     *
> +     * @param jobScheduler
> +     *        The scheduler instance to manage.
> +     */
>       public JobSchedulerView(JobScheduler jobScheduler) {
>           this.jobScheduler = jobScheduler;
>       }
>   
> +    @Override
>       public TabularData getAllJobs() throws Exception {
>           OpenTypeFactory factory = OpenTypeSupport.getFactory(Job.class);
>           CompositeType ct = factory.getCompositeType();
> @@ -45,6 +61,7 @@ public class JobSchedulerView implements JobSchedulerViewMBean {
>           return rc;
>       }
>   
> +    @Override
>       public TabularData getAllJobs(String startTime, String finishTime) throws Exception {
>           OpenTypeFactory factory = OpenTypeSupport.getFactory(Job.class);
>           CompositeType ct = factory.getCompositeType();
> @@ -59,6 +76,7 @@ public class JobSchedulerView implements JobSchedulerViewMBean {
>           return rc;
>       }
>   
> +    @Override
>       public TabularData getNextScheduleJobs() throws Exception {
>           OpenTypeFactory factory = OpenTypeSupport.getFactory(Job.class);
>           CompositeType ct = factory.getCompositeType();
> @@ -71,31 +89,51 @@ public class JobSchedulerView implements JobSchedulerViewMBean {
>           return rc;
>       }
>   
> +    @Override
>       public String getNextScheduleTime() throws Exception {
>           long time = this.jobScheduler.getNextScheduleTime();
>           return JobSupport.getDateTime(time);
>       }
>   
> +    @Override
>       public void removeAllJobs() throws Exception {
>           this.jobScheduler.removeAllJobs();
> -
>       }
>   
> +    @Override
>       public void removeAllJobs(String startTime, String finishTime) throws Exception {
>           long start = JobSupport.getDataTime(startTime);
>           long finish = JobSupport.getDataTime(finishTime);
>           this.jobScheduler.removeAllJobs(start, finish);
> +    }
>   
> +    @Override
> +    public void removeAllJobsAtScheduledTime(String time) throws Exception {
> +        long removeAtTime = JobSupport.getDataTime(time);
> +        this.jobScheduler.remove(removeAtTime);
>       }
>   
> +    @Override
> +    public void removeJobAtScheduledTime(String time) throws Exception {
> +        removeAllJobsAtScheduledTime(time);
> +    }
> +
> +    @Override
>       public void removeJob(String jobId) throws Exception {
>           this.jobScheduler.remove(jobId);
> -
>       }
>   
> -    public void removeJobAtScheduledTime(String time) throws IOException {
> -        // TODO Auto-generated method stub
> +    @Override
> +    public int getExecutionCount(String jobId) throws Exception {
> +        int result = 0;
>   
> -    }
> +        List<Job> jobs = this.jobScheduler.getAllJobs();
> +        for (Job job : jobs) {
> +            if (job.getJobId().equals(jobId)) {
> +                result = job.getExecutionCount();
> +            }
> +        }
>   
> +        return result;
> +    }
>   }
>
> http://git-wip-us.apache.org/repos/asf/activemq/blob/fc244f48/activemq-broker/src/main/java/org/apache/activemq/broker/jmx/JobSchedulerViewMBean.java
> ----------------------------------------------------------------------
> diff --git a/activemq-broker/src/main/java/org/apache/activemq/broker/jmx/JobSchedulerViewMBean.java b/activemq-broker/src/main/java/org/apache/activemq/broker/jmx/JobSchedulerViewMBean.java
> index f5745ea..76a7926 100644
> --- a/activemq-broker/src/main/java/org/apache/activemq/broker/jmx/JobSchedulerViewMBean.java
> +++ b/activemq-broker/src/main/java/org/apache/activemq/broker/jmx/JobSchedulerViewMBean.java
> @@ -18,76 +18,125 @@ package org.apache.activemq.broker.jmx;
>   
>   import javax.management.openmbean.TabularData;
>   
> -
> -
>   public interface JobSchedulerViewMBean {
> +
>       /**
> -     * remove all jobs scheduled to run at this time
> +     * Remove all jobs scheduled to run at this time.  If there are no jobs scheduled
> +     * at the given time this methods returns without making any modifications to the
> +     * scheduler store.
> +     *
>        * @param time
> -     * @throws Exception
> +     *        the string formated time that should be used to remove jobs.
> +     *
> +     * @throws Exception if an error occurs while performing the remove.
> +     *
> +     * @deprecated use removeAllJobsAtScheduledTime instead as it is more explicit about what
> +     *             the method is actually doing.
>        */
> +    @Deprecated
>       @MBeanInfo("remove jobs with matching execution time")
>       public abstract void removeJobAtScheduledTime(@MBeanInfo("time: yyyy-MM-dd hh:mm:ss")String time) throws Exception;
>   
>       /**
> -     * remove a job with the matching jobId
> +     * Remove all jobs scheduled to run at this time.  If there are no jobs scheduled
> +     * at the given time this methods returns without making any modifications to the
> +     * scheduler store.
> +     *
> +     * @param time
> +     *        the string formated time that should be used to remove jobs.
> +     *
> +     * @throws Exception if an error occurs while performing the remove.
> +     */
> +    @MBeanInfo("remove jobs with matching execution time")
> +    public abstract void removeAllJobsAtScheduledTime(@MBeanInfo("time: yyyy-MM-dd hh:mm:ss")String time) throws Exception;
> +
> +    /**
> +     * Remove a job with the matching jobId.  If the method does not find a matching job
> +     * then it returns without throwing an error or making any modifications to the job
> +     * scheduler store.
> +     *
>        * @param jobId
> -     * @throws Exception
> +     *        the Job Id to remove from the scheduler store.
> +     *
> +     * @throws Exception if an error occurs while attempting to remove the Job.
>        */
>       @MBeanInfo("remove jobs with matching jobId")
>       public abstract void removeJob(@MBeanInfo("jobId")String jobId) throws Exception;
> -
> +
>       /**
> -     * remove all the Jobs from the scheduler
> -     * @throws Exception
> +     * Remove all the Jobs from the scheduler,
> +     *
> +     * @throws Exception if an error occurs while purging the store.
>        */
>       @MBeanInfo("remove all scheduled jobs")
>       public abstract void removeAllJobs() throws Exception;
> -
> +
>       /**
> -     * remove all the Jobs from the scheduler that are due between the start and finish times
> -     * @param start time
> -     * @param finish time
> -     * @throws Exception
> +     * Remove all the Jobs from the scheduler that are due between the start and finish times.
> +     *
> +     * @param start
> +     *        the starting time to remove jobs from.
> +     * @param finish
> +     *        the finish time for the remove operation.
> +     *
> +     * @throws Exception if an error occurs while attempting to remove the jobs.
>        */
>       @MBeanInfo("remove all scheduled jobs between time ranges ")
>       public abstract void removeAllJobs(@MBeanInfo("start: yyyy-MM-dd hh:mm:ss")String start,@MBeanInfo("finish: yyyy-MM-dd hh:mm:ss")String finish) throws Exception;
> -
>   
> -
>       /**
> -     * Get the next time jobs will be fired
> -     * @return the time in milliseconds
> -     * @throws Exception
> +     * Get the next time jobs will be fired from this scheduler store.
> +     *
> +     * @return the time in milliseconds of the next job to execute.
> +     *
> +     * @throws Exception if an error occurs while accessing the store.
>        */
>       @MBeanInfo("get the next time a job is due to be scheduled ")
>       public abstract String getNextScheduleTime() throws Exception;
> -
> +
> +    /**
> +     * Gets the number of times a scheduled Job has been executed.
> +     *
> +     * @return the total number of time a scheduled job has executed.
> +     *
> +     * @throws Exception if an error occurs while querying for the Job.
> +     */
> +    @MBeanInfo("get the next time a job is due to be scheduled ")
> +    public abstract int getExecutionCount(@MBeanInfo("jobId")String jobId) throws Exception;
> +
>       /**
> -     * Get all the jobs scheduled to run next
> +     * Get all the jobs scheduled to run next.
> +     *
>        * @return a list of jobs that will be scheduled next
> -     * @throws Exception
> +     *
> +     * @throws Exception if an error occurs while reading the scheduler store.
>        */
>       @MBeanInfo("get the next job(s) to be scheduled. Not HTML friendly ")
>       public abstract TabularData getNextScheduleJobs() throws Exception;
> -
> -    /**
> -     * Get all the outstanding Jobs
> -     * @return a  table of all jobs
> -     * @throws Exception
>   
> +    /**
> +     * Get all the outstanding Jobs that are scheduled in this scheduler store.
> +     *
> +     * @return a table of all jobs in this scheduler store.
> +     *
> +     * @throws Exception if an error occurs while reading the store.
>        */
>       @MBeanInfo("get the scheduled Jobs in the Store. Not HTML friendly ")
>       public abstract TabularData getAllJobs() throws Exception;
> -
> +
>       /**
> -     * Get all outstanding jobs due to run between start and finish
> +     * Get all outstanding jobs due to run between start and finish time range.
> +     *
>        * @param start
> +     *        the starting time range to query the store for jobs.
>        * @param finish
> -     * @return a table of jobs in the range
> -     * @throws Exception
> -
> +     *        the ending time of this query for scheduled jobs.
> +     *
> +     * @return a table of jobs in the range given.
> +     *
> +     * @throws Exception if an error occurs while querying the scheduler store.
>        */
>       @MBeanInfo("get the scheduled Jobs in the Store within the time range. Not HTML friendly ")
>       public abstract TabularData getAllJobs(@MBeanInfo("start: yyyy-MM-dd hh:mm:ss")String start,@MBeanInfo("finish: yyyy-MM-dd hh:mm:ss")String finish)throws Exception;
> +
>   }
>
> http://git-wip-us.apache.org/repos/asf/activemq/blob/fc244f48/activemq-broker/src/main/java/org/apache/activemq/broker/scheduler/Job.java
> ----------------------------------------------------------------------
> diff --git a/activemq-broker/src/main/java/org/apache/activemq/broker/scheduler/Job.java b/activemq-broker/src/main/java/org/apache/activemq/broker/scheduler/Job.java
> index 7b28a5b..047fe23 100644
> --- a/activemq-broker/src/main/java/org/apache/activemq/broker/scheduler/Job.java
> +++ b/activemq-broker/src/main/java/org/apache/activemq/broker/scheduler/Job.java
> @@ -16,7 +16,12 @@
>    */
>   package org.apache.activemq.broker.scheduler;
>   
> -
> +/**
> + * Interface for a scheduled Job object.
> + *
> + * Each Job is identified by a unique Job Id which can be used to reference the Job
> + * in the Job Scheduler store for updates or removal.
> + */
>   public interface Job {
>   
>       /**
> @@ -38,11 +43,12 @@ public interface Job {
>        * @return the Delay
>        */
>       public abstract long getDelay();
> +
>       /**
>        * @return the period
>        */
>       public abstract long getPeriod();
> -
> +
>       /**
>        * @return the cron entry
>        */
> @@ -52,17 +58,24 @@ public interface Job {
>        * @return the payload
>        */
>       public abstract byte[] getPayload();
> -
> +
>       /**
>        * Get the start time as a Date time string
>        * @return the date time
>        */
>       public String getStartTime();
> -
> +
>       /**
> -     * Get the time the job is next due to execute
> +     * Get the time the job is next due to execute
>        * @return the date time
>        */
>       public String getNextExecutionTime();
>   
> +    /**
> +     * Gets the total number of times this job has executed.
> +     *
> +     * @returns the number of times this job has been executed.
> +     */
> +    public int getExecutionCount();
> +
>   }
> \ No newline at end of file
>
> http://git-wip-us.apache.org/repos/asf/activemq/blob/fc244f48/activemq-broker/src/main/java/org/apache/activemq/broker/scheduler/JobListener.java
> ----------------------------------------------------------------------
> diff --git a/activemq-broker/src/main/java/org/apache/activemq/broker/scheduler/JobListener.java b/activemq-broker/src/main/java/org/apache/activemq/broker/scheduler/JobListener.java
> index c53d9c6..a453595 100644
> --- a/activemq-broker/src/main/java/org/apache/activemq/broker/scheduler/JobListener.java
> +++ b/activemq-broker/src/main/java/org/apache/activemq/broker/scheduler/JobListener.java
> @@ -18,13 +18,21 @@ package org.apache.activemq.broker.scheduler;
>   
>   import org.apache.activemq.util.ByteSequence;
>   
> +/**
> + * Job event listener interface. Provides event points for Job related events
> + * such as job ready events.
> + */
>   public interface JobListener {
> -
> +
>       /**
> -     * A Job that has been scheduled is now ready
> -     * @param id
> +     * A Job that has been scheduled is now ready to be fired.  The Job is passed
> +     * in its raw byte form and must be un-marshaled before being delivered.
> +     *
> +     * @param jobId
> +     *        The unique Job Id of the Job that is ready to fire.
>        * @param job
> +     *        The job that is now ready, delivered in byte form.
>        */
> -    public void scheduledJob(String id,ByteSequence job);
> +    public void scheduledJob(String id, ByteSequence job);
>   
>   }
>
> http://git-wip-us.apache.org/repos/asf/activemq/blob/fc244f48/activemq-broker/src/main/java/org/apache/activemq/broker/scheduler/JobScheduler.java
> ----------------------------------------------------------------------
> diff --git a/activemq-broker/src/main/java/org/apache/activemq/broker/scheduler/JobScheduler.java b/activemq-broker/src/main/java/org/apache/activemq/broker/scheduler/JobScheduler.java
> index 2e96eae..e951861 100644
> --- a/activemq-broker/src/main/java/org/apache/activemq/broker/scheduler/JobScheduler.java
> +++ b/activemq-broker/src/main/java/org/apache/activemq/broker/scheduler/JobScheduler.java
> @@ -46,20 +46,25 @@ public interface JobScheduler {
>       void stopDispatching() throws Exception;
>   
>       /**
> -     * Add a Job listener
> +     * Add a Job listener which will receive events related to scheduled jobs.
> +     *
> +     * @param listener
> +     *      The job listener to add.
>        *
> -     * @param l
>        * @throws Exception
>        */
> -    void addListener(JobListener l) throws Exception;
> +    void addListener(JobListener listener) throws Exception;
>   
>       /**
> -     * remove a JobListener
> +     * remove a JobListener that was previously registered.  If the given listener is not in
> +     * the registry this method has no effect.
> +     *
> +     * @param listener
> +     *      The listener that should be removed from the listener registry.
>        *
> -     * @param l
>        * @throws Exception
>        */
> -    void removeListener(JobListener l) throws Exception;
> +    void removeListener(JobListener listener) throws Exception;
>   
>       /**
>        * Add a job to be scheduled
> @@ -70,7 +75,8 @@ public interface JobScheduler {
>        *            the message to be sent when the job is scheduled
>        * @param delay
>        *            the time in milliseconds before the job will be run
> -     * @throws Exception
> +     *
> +     * @throws Exception if an error occurs while scheduling the Job.
>        */
>       void schedule(String jobId, ByteSequence payload, long delay) throws Exception;
>   
> @@ -82,8 +88,9 @@ public interface JobScheduler {
>        * @param payload
>        *            the message to be sent when the job is scheduled
>        * @param cronEntry
> -     *            - cron entry
> -     * @throws Exception
> +     *            The cron entry to use to schedule this job.
> +     *
> +     * @throws Exception if an error occurs while scheduling the Job.
>        */
>       void schedule(String jobId, ByteSequence payload, String cronEntry) throws Exception;
>   
> @@ -95,7 +102,7 @@ public interface JobScheduler {
>        * @param payload
>        *            the message to be sent when the job is scheduled
>        * @param cronEntry
> -     *            - cron entry
> +     *            cron entry
>        * @param delay
>        *            time in ms to wait before scheduling
>        * @param period
> @@ -110,6 +117,8 @@ public interface JobScheduler {
>        * remove all jobs scheduled to run at this time
>        *
>        * @param time
> +     *      The UTC time to use to remove a batch of scheduled Jobs.
> +     *
>        * @throws Exception
>        */
>       void remove(long time) throws Exception;
> @@ -118,7 +127,9 @@ public interface JobScheduler {
>        * remove a job with the matching jobId
>        *
>        * @param jobId
> -     * @throws Exception
> +     *      The unique Job Id to search for and remove from the scheduled set of jobs.
> +     *
> +     * @throws Exception if an error occurs while removing the Job.
>        */
>       void remove(String jobId) throws Exception;
>   
>
> http://git-wip-us.apache.org/repos/asf/activemq/blob/fc244f48/activemq-broker/src/main/java/org/apache/activemq/broker/scheduler/JobSchedulerFacade.java
> ----------------------------------------------------------------------
> diff --git a/activemq-broker/src/main/java/org/apache/activemq/broker/scheduler/JobSchedulerFacade.java b/activemq-broker/src/main/java/org/apache/activemq/broker/scheduler/JobSchedulerFacade.java
> index d46d04a..24a216a 100644
> --- a/activemq-broker/src/main/java/org/apache/activemq/broker/scheduler/JobSchedulerFacade.java
> +++ b/activemq-broker/src/main/java/org/apache/activemq/broker/scheduler/JobSchedulerFacade.java
> @@ -21,6 +21,12 @@ import java.util.List;
>   
>   import org.apache.activemq.util.ByteSequence;
>   
> +/**
> + * A wrapper for instances of the JobScheduler interface that ensures that methods
> + * provides safe and sane return values and can deal with null values being passed
> + * in etc.  Provides a measure of safety when using unknown implementations of the
> + * JobSchedulerStore which might not always do the right thing.
> + */
>   public class JobSchedulerFacade implements JobScheduler {
>   
>       private final SchedulerBroker broker;
>
> http://git-wip-us.apache.org/repos/asf/activemq/blob/fc244f48/activemq-broker/src/main/java/org/apache/activemq/broker/scheduler/JobSchedulerStore.java
> ----------------------------------------------------------------------
> diff --git a/activemq-broker/src/main/java/org/apache/activemq/broker/scheduler/JobSchedulerStore.java b/activemq-broker/src/main/java/org/apache/activemq/broker/scheduler/JobSchedulerStore.java
> index 3cbc367..c6863c7 100644
> --- a/activemq-broker/src/main/java/org/apache/activemq/broker/scheduler/JobSchedulerStore.java
> +++ b/activemq-broker/src/main/java/org/apache/activemq/broker/scheduler/JobSchedulerStore.java
> @@ -26,13 +26,56 @@ import org.apache.activemq.Service;
>    */
>   public interface JobSchedulerStore extends Service {
>   
> +    /**
> +     * Gets the location where the Job Scheduler will write the persistent data used
> +     * to preserve and recover scheduled Jobs.
> +     *
> +     * If the scheduler implementation does not utilize a file system based store this
> +     * method returns null.
> +     *
> +     * @return the directory where persistent store data is written.
> +     */
>       File getDirectory();
>   
> +    /**
> +     * Sets the directory where persistent store data will be written.  This method
> +     * must be called before the scheduler store is started to have any effect.
> +     *
> +     * @param directory
> +     *      The directory where the job scheduler store is to be located.
> +     */
>       void setDirectory(File directory);
>   
> +    /**
> +     * The size of the current store on disk if the store utilizes a disk based store
> +     * mechanism.
> +     *
> +     * @return the current store size on disk.
> +     */
>       long size();
>   
> +    /**
> +     * Returns the JobScheduler instance identified by the given name.
> +     *
> +     * @param name
> +     *        the name of the JobScheduler instance to lookup.
> +     *
> +     * @return the named JobScheduler or null if none exists with the given name.
> +     *
> +     * @throws Exception if an error occurs while loading the named scheduler.
> +     */
>       JobScheduler getJobScheduler(String name) throws Exception;
>   
> +    /**
> +     * Removes the named JobScheduler if it exists, purging all scheduled messages
> +     * assigned to it.
> +     *
> +     * @param name
> +     *        the name of the scheduler instance to remove.
> +     *
> +     * @return true if there was a scheduler with the given name to remove.
> +     *
> +     * @throws Exception if an error occurs while removing the scheduler.
> +     */
>       boolean removeJobScheduler(String name) throws Exception;
>   }
>
> http://git-wip-us.apache.org/repos/asf/activemq/blob/fc244f48/activemq-broker/src/main/java/org/apache/activemq/broker/scheduler/JobSupport.java
> ----------------------------------------------------------------------
> diff --git a/activemq-broker/src/main/java/org/apache/activemq/broker/scheduler/JobSupport.java b/activemq-broker/src/main/java/org/apache/activemq/broker/scheduler/JobSupport.java
> index 6b78d77..fc5b8dd 100644
> --- a/activemq-broker/src/main/java/org/apache/activemq/broker/scheduler/JobSupport.java
> +++ b/activemq-broker/src/main/java/org/apache/activemq/broker/scheduler/JobSupport.java
> @@ -20,7 +20,11 @@ import java.text.DateFormat;
>   import java.text.SimpleDateFormat;
>   import java.util.Date;
>   
> +/**
> + * A class to provide common Job Scheduler related methods.
> + */
>   public class JobSupport {
> +
>       public static String getDateTime(long value) {
>           DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
>           Date date = new Date(value);
> @@ -32,5 +36,4 @@ public class JobSupport {
>            Date date = dfm.parse(value);
>            return date.getTime();
>        }
> -
>   }
>
> http://git-wip-us.apache.org/repos/asf/activemq/blob/fc244f48/activemq-broker/src/main/java/org/apache/activemq/store/PersistenceAdapter.java
> ----------------------------------------------------------------------
> diff --git a/activemq-broker/src/main/java/org/apache/activemq/store/PersistenceAdapter.java b/activemq-broker/src/main/java/org/apache/activemq/store/PersistenceAdapter.java
> index 31efd32..01a9634 100755
> --- a/activemq-broker/src/main/java/org/apache/activemq/store/PersistenceAdapter.java
> +++ b/activemq-broker/src/main/java/org/apache/activemq/store/PersistenceAdapter.java
> @@ -22,6 +22,7 @@ import java.util.Set;
>   
>   import org.apache.activemq.Service;
>   import org.apache.activemq.broker.ConnectionContext;
> +import org.apache.activemq.broker.scheduler.JobSchedulerStore;
>   import org.apache.activemq.command.ActiveMQDestination;
>   import org.apache.activemq.command.ActiveMQQueue;
>   import org.apache.activemq.command.ActiveMQTopic;
> @@ -31,74 +32,99 @@ import org.apache.activemq.usage.SystemUsage;
>   /**
>    * Adapter to the actual persistence mechanism used with ActiveMQ
>    *
> - *
> + *
>    */
>   public interface PersistenceAdapter extends Service {
>   
>       /**
> -     * Returns a set of all the {@link org.apache.activemq.command.ActiveMQDestination}
> -     * objects that the persistence store is aware exist.
> +     * Returns a set of all the
> +     * {@link org.apache.activemq.command.ActiveMQDestination} objects that the
> +     * persistence store is aware exist.
>        *
>        * @return active destinations
>        */
>       Set<ActiveMQDestination> getDestinations();
>   
>       /**
> -     * Factory method to create a new queue message store with the given destination name
> +     * Factory method to create a new queue message store with the given
> +     * destination name
> +     *
>        * @param destination
>        * @return the message store
> -     * @throws IOException
> +     * @throws IOException
>        */
>       MessageStore createQueueMessageStore(ActiveMQQueue destination) throws IOException;
>   
>       /**
> -     * Factory method to create a new topic message store with the given destination name
> -     * @param destination
> +     * Factory method to create a new topic message store with the given
> +     * destination name
> +     *
> +     * @param destination
>        * @return the topic message store
> -     * @throws IOException
> +     * @throws IOException
>        */
>       TopicMessageStore createTopicMessageStore(ActiveMQTopic destination) throws IOException;
>   
>       /**
> +     * Creates and returns a new Job Scheduler store instance.
> +     *
> +     * @return a new JobSchedulerStore instance if this Persistence adapter provides its own.
> +     *
> +     * @throws IOException If an error occurs while creating the new JobSchedulerStore.
> +     * @throws UnsupportedOperationException If this adapter does not provide its own
> +     *                                       scheduler store implementation.
> +     */
> +    JobSchedulerStore createJobSchedulerStore() throws IOException, UnsupportedOperationException;
> +
> +    /**
>        * Cleanup method to remove any state associated with the given destination.
>        * This method does not stop the message store (it might not be cached).
> -     * @param destination Destination to forget
> +     *
> +     * @param destination
> +     *            Destination to forget
>        */
>       void removeQueueMessageStore(ActiveMQQueue destination);
>   
>       /**
>        * Cleanup method to remove any state associated with the given destination
>        * This method does not stop the message store (it might not be cached).
> -     * @param destination Destination to forget
> +     *
> +     * @param destination
> +     *            Destination to forget
>        */
>       void removeTopicMessageStore(ActiveMQTopic destination);
>   
>       /**
> -     * Factory method to create a new persistent prepared transaction store for XA recovery
> +     * Factory method to create a new persistent prepared transaction store for
> +     * XA recovery
> +     *
>        * @return transaction store
> -     * @throws IOException
> +     * @throws IOException
>        */
>       TransactionStore createTransactionStore() throws IOException;
>   
>       /**
> -     * This method starts a transaction on the persistent storage - which is nothing to
> -     * do with JMS or XA transactions - its purely a mechanism to perform multiple writes
> -     * to a persistent store in 1 transaction as a performance optimization.
> +     * This method starts a transaction on the persistent storage - which is
> +     * nothing to do with JMS or XA transactions - its purely a mechanism to
> +     * perform multiple writes to a persistent store in 1 transaction as a
> +     * performance optimization.
>        * <p/>
> -     * Typically one transaction will require one disk synchronization point and so for
> -     * real high performance its usually faster to perform many writes within the same
> -     * transaction to minimize latency caused by disk synchronization. This is especially
> -     * true when using tools like Berkeley Db or embedded JDBC servers.
> -     * @param context
> -     * @throws IOException
> +     * Typically one transaction will require one disk synchronization point and
> +     * so for real high performance its usually faster to perform many writes
> +     * within the same transaction to minimize latency caused by disk
> +     * synchronization. This is especially true when using tools like Berkeley
> +     * Db or embedded JDBC servers.
> +     *
> +     * @param context
> +     * @throws IOException
>        */
>       void beginTransaction(ConnectionContext context) throws IOException;
>   
> -
>       /**
>        * Commit a persistence transaction
> -     * @param context
> -     * @throws IOException
> +     *
> +     * @param context
> +     * @throws IOException
>        *
>        * @see PersistenceAdapter#beginTransaction(ConnectionContext context)
>        */
> @@ -106,40 +132,45 @@ public interface PersistenceAdapter extends Service {
>   
>       /**
>        * Rollback a persistence transaction
> -     * @param context
> -     * @throws IOException
> +     *
> +     * @param context
> +     * @throws IOException
>        *
>        * @see PersistenceAdapter#beginTransaction(ConnectionContext context)
>        */
>       void rollbackTransaction(ConnectionContext context) throws IOException;
> -
> +
>       /**
> -     *
> +     *
>        * @return last broker sequence
>        * @throws IOException
>        */
>       long getLastMessageBrokerSequenceId() throws IOException;
> -
> +
>       /**
>        * Delete's all the messages in the persistent store.
> -     *
> +     *
>        * @throws IOException
>        */
>       void deleteAllMessages() throws IOException;
> -
> +
>       /**
> -     * @param usageManager The UsageManager that is controlling the broker's memory usage.
> +     * @param usageManager
> +     *            The UsageManager that is controlling the broker's memory
> +     *            usage.
>        */
>       void setUsageManager(SystemUsage usageManager);
> -
> +
>       /**
>        * Set the name of the broker using the adapter
> +     *
>        * @param brokerName
>        */
>       void setBrokerName(String brokerName);
> -
> +
>       /**
>        * Set the directory where any data files should be created
> +     *
>        * @param dir
>        */
>       void setDirectory(File dir);
> @@ -148,26 +179,30 @@ public interface PersistenceAdapter extends Service {
>        * @return the directory used by the persistence adaptor
>        */
>       File getDirectory();
> -
> +
>       /**
>        * checkpoint any
> -     * @param sync
> -     * @throws IOException
> +     *
> +     * @param sync
> +     * @throws IOException
>        *
>        */
>       void checkpoint(boolean sync) throws IOException;
> -
> +
>       /**
>        * A hint to return the size of the store on disk
> +     *
>        * @return disk space used in bytes of 0 if not implemented
>        */
>       long size();
>   
>       /**
> -     * return the last stored producer sequenceId for this producer Id
> -     * used to suppress duplicate sends on failover reconnect at the transport
> -     * when a reconnect occurs
> -     * @param id the producerId to find a sequenceId for
> +     * return the last stored producer sequenceId for this producer Id used to
> +     * suppress duplicate sends on failover reconnect at the transport when a
> +     * reconnect occurs
> +     *
> +     * @param id
> +     *            the producerId to find a sequenceId for
>        * @return the last stored sequence id or -1 if no suppression needed
>        */
>       long getLastProducerSequenceId(ProducerId id) throws IOException;
>
> http://git-wip-us.apache.org/repos/asf/activemq/blob/fc244f48/activemq-broker/src/main/java/org/apache/activemq/store/memory/MemoryPersistenceAdapter.java
> ----------------------------------------------------------------------
> diff --git a/activemq-broker/src/main/java/org/apache/activemq/store/memory/MemoryPersistenceAdapter.java b/activemq-broker/src/main/java/org/apache/activemq/store/memory/MemoryPersistenceAdapter.java
> index 0fd6bfc..73ea104 100755
> --- a/activemq-broker/src/main/java/org/apache/activemq/store/memory/MemoryPersistenceAdapter.java
> +++ b/activemq-broker/src/main/java/org/apache/activemq/store/memory/MemoryPersistenceAdapter.java
> @@ -24,6 +24,7 @@ import java.util.Set;
>   import java.util.concurrent.ConcurrentHashMap;
>   
>   import org.apache.activemq.broker.ConnectionContext;
> +import org.apache.activemq.broker.scheduler.JobSchedulerStore;
>   import org.apache.activemq.command.ActiveMQDestination;
>   import org.apache.activemq.command.ActiveMQQueue;
>   import org.apache.activemq.command.ActiveMQTopic;
> @@ -39,7 +40,7 @@ import org.slf4j.LoggerFactory;
>   
>   /**
>    * @org.apache.xbean.XBean
> - *
> + *
>    */
>   public class MemoryPersistenceAdapter implements PersistenceAdapter {
>       private static final Logger LOG = LoggerFactory.getLogger(MemoryPersistenceAdapter.class);
> @@ -49,6 +50,7 @@ public class MemoryPersistenceAdapter implements PersistenceAdapter {
>       ConcurrentHashMap<ActiveMQDestination, MessageStore> queues = new ConcurrentHashMap<ActiveMQDestination, MessageStore>();
>       private boolean useExternalMessageReferences;
>   
> +    @Override
>       public Set<ActiveMQDestination> getDestinations() {
>           Set<ActiveMQDestination> rc = new HashSet<ActiveMQDestination>(queues.size() + topics.size());
>           for (Iterator<ActiveMQDestination> iter = queues.keySet().iterator(); iter.hasNext();) {
> @@ -64,6 +66,7 @@ public class MemoryPersistenceAdapter implements PersistenceAdapter {
>           return new MemoryPersistenceAdapter();
>       }
>   
> +    @Override
>       public MessageStore createQueueMessageStore(ActiveMQQueue destination) throws IOException {
>           MessageStore rc = queues.get(destination);
>           if (rc == null) {
> @@ -76,6 +79,7 @@ public class MemoryPersistenceAdapter implements PersistenceAdapter {
>           return rc;
>       }
>   
> +    @Override
>       public TopicMessageStore createTopicMessageStore(ActiveMQTopic destination) throws IOException {
>           TopicMessageStore rc = topics.get(destination);
>           if (rc == null) {
> @@ -93,6 +97,7 @@ public class MemoryPersistenceAdapter implements PersistenceAdapter {
>        *
>        * @param destination Destination to forget
>        */
> +    @Override
>       public void removeQueueMessageStore(ActiveMQQueue destination) {
>           queues.remove(destination);
>       }
> @@ -102,10 +107,12 @@ public class MemoryPersistenceAdapter implements PersistenceAdapter {
>        *
>        * @param destination Destination to forget
>        */
> +    @Override
>       public void removeTopicMessageStore(ActiveMQTopic destination) {
>           topics.remove(destination);
>       }
>   
> +    @Override
>       public TransactionStore createTransactionStore() throws IOException {
>           if (transactionStore == null) {
>               transactionStore = new MemoryTransactionStore(this);
> @@ -113,25 +120,32 @@ public class MemoryPersistenceAdapter implements PersistenceAdapter {
>           return transactionStore;
>       }
>   
> +    @Override
>       public void beginTransaction(ConnectionContext context) {
>       }
>   
> +    @Override
>       public void commitTransaction(ConnectionContext context) {
>       }
>   
> +    @Override
>       public void rollbackTransaction(ConnectionContext context) {
>       }
>   
> +    @Override
>       public void start() throws Exception {
>       }
>   
> +    @Override
>       public void stop() throws Exception {
>       }
>   
> +    @Override
>       public long getLastMessageBrokerSequenceId() throws IOException {
>           return 0;
>       }
>   
> +    @Override
>       public void deleteAllMessages() throws IOException {
>           for (Iterator<TopicMessageStore> iter = topics.values().iterator(); iter.hasNext();) {
>               MemoryMessageStore store = asMemoryMessageStore(iter.next());
> @@ -177,38 +191,52 @@ public class MemoryPersistenceAdapter implements PersistenceAdapter {
>        * @param usageManager The UsageManager that is controlling the broker's
>        *                memory usage.
>        */
> +    @Override
>       public void setUsageManager(SystemUsage usageManager) {
>       }
>   
> +    @Override
>       public String toString() {
>           return "MemoryPersistenceAdapter";
>       }
>   
> +    @Override
>       public void setBrokerName(String brokerName) {
>       }
>   
> +    @Override
>       public void setDirectory(File dir) {
>       }
> -
> +
> +    @Override
>       public File getDirectory(){
>           return null;
>       }
>   
> +    @Override
>       public void checkpoint(boolean sync) throws IOException {
>       }
> -
> +
> +    @Override
>       public long size(){
>           return 0;
>       }
> -
> +
>       public void setCreateTransactionStore(boolean create) throws IOException {
>           if (create) {
>               createTransactionStore();
>           }
>       }
>   
> +    @Override
>       public long getLastProducerSequenceId(ProducerId id) {
>           // memory map does duplicate suppression
>           return -1;
>       }
> +
> +    @Override
> +    public JobSchedulerStore createJobSchedulerStore() throws IOException, UnsupportedOperationException {
> +        // We could eventuall implement an in memory scheduler.
> +        throw new UnsupportedOperationException();
> +    }
>   }
>
> http://git-wip-us.apache.org/repos/asf/activemq/blob/fc244f48/activemq-broker/src/main/java/org/apache/activemq/util/IOHelper.java
> ----------------------------------------------------------------------
> diff --git a/activemq-broker/src/main/java/org/apache/activemq/util/IOHelper.java b/activemq-broker/src/main/java/org/apache/activemq/util/IOHelper.java
> index a623de9..2a70194 100644
> --- a/activemq-broker/src/main/java/org/apache/activemq/util/IOHelper.java
> +++ b/activemq-broker/src/main/java/org/apache/activemq/util/IOHelper.java
> @@ -61,8 +61,9 @@ public final class IOHelper {
>       }
>   
>       /**
> -     * Converts any string into a string that is safe to use as a file name.
> -     * The result will only include ascii characters and numbers, and the "-","_", and "." characters.
> +     * Converts any string into a string that is safe to use as a file name. The
> +     * result will only include ascii characters and numbers, and the "-","_",
> +     * and "." characters.
>        *
>        * @param name
>        * @return
> @@ -76,15 +77,16 @@ public final class IOHelper {
>       }
>   
>       /**
> -     * Converts any string into a string that is safe to use as a file name.
> -     * The result will only include ascii characters and numbers, and the "-","_", and "." characters.
> +     * Converts any string into a string that is safe to use as a file name. The
> +     * result will only include ascii characters and numbers, and the "-","_",
> +     * and "." characters.
>        *
>        * @param name
>        * @param dirSeparators
>        * @param maxFileLength
>        * @return
>        */
> -    public static String toFileSystemSafeName(String name,boolean dirSeparators,int maxFileLength) {
> +    public static String toFileSystemSafeName(String name, boolean dirSeparators, int maxFileLength) {
>           int size = name.length();
>           StringBuffer rc = new StringBuffer(size * 2);
>           for (int i = 0; i < size; i++) {
> @@ -92,8 +94,7 @@ public final class IOHelper {
>               boolean valid = c >= 'a' && c <= 'z';
>               valid = valid || (c >= 'A' && c <= 'Z');
>               valid = valid || (c >= '0' && c <= '9');
> -            valid = valid || (c == '_') || (c == '-') || (c == '.') || (c=='#')
> -                    ||(dirSeparators && ( (c == '/') || (c == '\\')));
> +            valid = valid || (c == '_') || (c == '-') || (c == '.') || (c == '#') || (dirSeparators && ((c == '/') || (c == '\\')));
>   
>               if (valid) {
>                   rc.append(c);
> @@ -105,7 +106,7 @@ public final class IOHelper {
>           }
>           String result = rc.toString();
>           if (result.length() > maxFileLength) {
> -            result = result.substring(result.length()-maxFileLength,result.length());
> +            result = result.substring(result.length() - maxFileLength, result.length());
>           }
>           return result;
>       }
> @@ -168,8 +169,7 @@ public final class IOHelper {
>               } else {
>                   for (int i = 0; i < files.length; i++) {
>                       File file = files[i];
> -                    if (file.getName().equals(".")
> -                            || file.getName().equals("..")) {
> +                    if (file.getName().equals(".") || file.getName().equals("..")) {
>                           continue;
>                       }
>                       if (file.isDirectory()) {
> @@ -190,6 +190,27 @@ public final class IOHelper {
>           }
>       }
>   
> +    public static void moveFiles(File srcDirectory, File targetDirectory, FilenameFilter filter) throws IOException {
> +        if (!srcDirectory.isDirectory()) {
> +            throw new IOException("source is not a directory");
> +        }
> +
> +        if (targetDirectory.exists() && !targetDirectory.isDirectory()) {
> +            throw new IOException("target exists and is not a directory");
> +        } else {
> +            mkdirs(targetDirectory);
> +        }
> +
> +        List<File> filesToMove = new ArrayList<File>();
> +        getFiles(srcDirectory, filesToMove, filter);
> +
> +        for (File file : filesToMove) {
> +            if (!file.isDirectory()) {
> +                moveFile(file, targetDirectory);
> +            }
> +        }
> +    }
> +
>       public static void copyFile(File src, File dest) throws IOException {
>           copyFile(src, dest, null);
>       }
> @@ -222,32 +243,32 @@ public final class IOHelper {
>           File parent = src.getParentFile();
>           String fromPath = from.getAbsolutePath();
>           if (parent.getAbsolutePath().equals(fromPath)) {
> -            //one level down
> +            // one level down
>               result = to;
> -        }else {
> +        } else {
>               String parentPath = parent.getAbsolutePath();
>               String path = parentPath.substring(fromPath.length());
> -            result = new File(to.getAbsolutePath()+File.separator+path);
> +            result = new File(to.getAbsolutePath() + File.separator + path);
>           }
>           return result;
>       }
>   
> -    static List<File> getFiles(File dir,FilenameFilter filter){
> +    static List<File> getFiles(File dir, FilenameFilter filter) {
>           List<File> result = new ArrayList<File>();
> -        getFiles(dir,result,filter);
> +        getFiles(dir, result, filter);
>           return result;
>       }
>   
> -    static void getFiles(File dir,List<File> list,FilenameFilter filter) {
> +    static void getFiles(File dir, List<File> list, FilenameFilter filter) {
>           if (!list.contains(dir)) {
>               list.add(dir);
> -            String[] fileNames=dir.list(filter);
> -            for (int i =0; i < fileNames.length;i++) {
> -                File f = new File(dir,fileNames[i]);
> +            String[] fileNames = dir.list(filter);
> +            for (int i = 0; i < fileNames.length; i++) {
> +                File f = new File(dir, fileNames[i]);
>                   if (f.isFile()) {
>                       list.add(f);
> -                }else {
> -                    getFiles(dir,list,filter);
> +                } else {
> +                    getFiles(dir, list, filter);
>                   }
>               }
>           }
> @@ -286,12 +307,13 @@ public final class IOHelper {
>       public static void mkdirs(File dir) throws IOException {
>           if (dir.exists()) {
>               if (!dir.isDirectory()) {
> -                throw new IOException("Failed to create directory '" + dir +"', regular file already existed with that name");
> +                throw new IOException("Failed to create directory '" + dir +
> +                                      "', regular file already existed with that name");
>               }
>   
>           } else {
>               if (!dir.mkdirs()) {
> -                throw new IOException("Failed to create directory '" + dir+"'");
> +                throw new IOException("Failed to create directory '" + dir + "'");
>               }
>           }
>       }
>
> http://git-wip-us.apache.org/repos/asf/activemq/blob/fc244f48/activemq-jdbc-store/src/main/java/org/apache/activemq/store/jdbc/JDBCPersistenceAdapter.java
> ----------------------------------------------------------------------
> diff --git a/activemq-jdbc-store/src/main/java/org/apache/activemq/store/jdbc/JDBCPersistenceAdapter.java b/activemq-jdbc-store/src/main/java/org/apache/activemq/store/jdbc/JDBCPersistenceAdapter.java
> index 7ff4ae0..a3a8250 100755
> --- a/activemq-jdbc-store/src/main/java/org/apache/activemq/store/jdbc/JDBCPersistenceAdapter.java
> +++ b/activemq-jdbc-store/src/main/java/org/apache/activemq/store/jdbc/JDBCPersistenceAdapter.java
> @@ -34,6 +34,7 @@ import org.apache.activemq.ActiveMQMessageAudit;
>   import org.apache.activemq.broker.BrokerService;
>   import org.apache.activemq.broker.ConnectionContext;
>   import org.apache.activemq.broker.Locker;
> +import org.apache.activemq.broker.scheduler.JobSchedulerStore;
>   import org.apache.activemq.command.ActiveMQDestination;
>   import org.apache.activemq.command.ActiveMQQueue;
>   import org.apache.activemq.command.ActiveMQTopic;
> @@ -422,6 +423,7 @@ public class JDBCPersistenceAdapter extends DataSourceServiceSupport implements
>           this.lockDataSource = dataSource;
>       }
>   
> +    @Override
>       public BrokerService getBrokerService() {
>           return brokerService;
>       }
> @@ -846,4 +848,9 @@ public class JDBCPersistenceAdapter extends DataSourceServiceSupport implements
>           }
>           return result;
>       }
> +
> +    @Override
> +    public JobSchedulerStore createJobSchedulerStore() throws IOException, UnsupportedOperationException {
> +        throw new UnsupportedOperationException();
> +    }
>   }
>
> http://git-wip-us.apache.org/repos/asf/activemq/blob/fc244f48/activemq-jdbc-store/src/main/java/org/apache/activemq/store/journal/JournalPersistenceAdapter.java
> ----------------------------------------------------------------------
> diff --git a/activemq-jdbc-store/src/main/java/org/apache/activemq/store/journal/JournalPersistenceAdapter.java b/activemq-jdbc-store/src/main/java/org/apache/activemq/store/journal/JournalPersistenceAdapter.java
> index 565fc9f..cc5282f 100755
> --- a/activemq-jdbc-store/src/main/java/org/apache/activemq/store/journal/JournalPersistenceAdapter.java
> +++ b/activemq-jdbc-store/src/main/java/org/apache/activemq/store/journal/JournalPersistenceAdapter.java
> @@ -31,6 +31,7 @@ import java.util.concurrent.ThreadFactory;
>   import java.util.concurrent.ThreadPoolExecutor;
>   import java.util.concurrent.TimeUnit;
>   import java.util.concurrent.atomic.AtomicBoolean;
> +
>   import org.apache.activeio.journal.InvalidRecordLocationException;
>   import org.apache.activeio.journal.Journal;
>   import org.apache.activeio.journal.JournalEventListener;
> @@ -40,6 +41,7 @@ import org.apache.activeio.packet.Packet;
>   import org.apache.activemq.broker.BrokerService;
>   import org.apache.activemq.broker.BrokerServiceAware;
>   import org.apache.activemq.broker.ConnectionContext;
> +import org.apache.activemq.broker.scheduler.JobSchedulerStore;
>   import org.apache.activemq.command.ActiveMQDestination;
>   import org.apache.activemq.command.ActiveMQQueue;
>   import org.apache.activemq.command.ActiveMQTopic;
> @@ -78,14 +80,14 @@ import org.slf4j.LoggerFactory;
>    * An implementation of {@link PersistenceAdapter} designed for use with a
>    * {@link Journal} and then check pointing asynchronously on a timeout with some
>    * other long term persistent storage.
> - *
> + *
>    * @org.apache.xbean.XBean
> - *
> + *
>    */
>   public class JournalPersistenceAdapter implements PersistenceAdapter, JournalEventListener, UsageListener, BrokerServiceAware {
>   
>       private BrokerService brokerService;
> -	
> +
>       protected Scheduler scheduler;
>       private static final Logger LOG = LoggerFactory.getLogger(JournalPersistenceAdapter.class);
>   
> @@ -118,9 +120,9 @@ public class JournalPersistenceAdapter implements PersistenceAdapter, JournalEve
>       private TaskRunnerFactory taskRunnerFactory;
>       private File directory;
>   
> -    public JournalPersistenceAdapter() {
> +    public JournalPersistenceAdapter() {
>       }
> -
> +
>       public JournalPersistenceAdapter(Journal journal, PersistenceAdapter longTermPersistence, TaskRunnerFactory taskRunnerFactory) throws IOException {
>           setJournal(journal);
>           setTaskRunnerFactory(taskRunnerFactory);
> @@ -135,13 +137,14 @@ public class JournalPersistenceAdapter implements PersistenceAdapter, JournalEve
>           this.journal = journal;
>           journal.setJournalEventListener(this);
>       }
> -
> +
>       public void setPersistenceAdapter(PersistenceAdapter longTermPersistence) {
>           this.longTermPersistence = longTermPersistence;
>       }
> -
> +
>       final Runnable createPeriodicCheckpointTask() {
>           return new Runnable() {
> +            @Override
>               public void run() {
>                   long lastTime = 0;
>                   synchronized (this) {
> @@ -158,11 +161,13 @@ public class JournalPersistenceAdapter implements PersistenceAdapter, JournalEve
>        * @param usageManager The UsageManager that is controlling the
>        *                destination's memory usage.
>        */
> +    @Override
>       public void setUsageManager(SystemUsage usageManager) {
>           this.usageManager = usageManager;
>           longTermPersistence.setUsageManager(usageManager);
>       }
>   
> +    @Override
>       public Set<ActiveMQDestination> getDestinations() {
>           Set<ActiveMQDestination> destinations = new HashSet<ActiveMQDestination>(longTermPersistence.getDestinations());
>           destinations.addAll(queues.keySet());
> @@ -178,6 +183,7 @@ public class JournalPersistenceAdapter implements PersistenceAdapter, JournalEve
>           }
>       }
>   
> +    @Override
>       public MessageStore createQueueMessageStore(ActiveMQQueue destination) throws IOException {
>           JournalMessageStore store = queues.get(destination);
>           if (store == null) {
> @@ -188,6 +194,7 @@ public class JournalPersistenceAdapter implements PersistenceAdapter, JournalEve
>           return store;
>       }
>   
> +    @Override
>       public TopicMessageStore createTopicMessageStore(ActiveMQTopic destinationName) throws IOException {
>           JournalTopicMessageStore store = topics.get(destinationName);
>           if (store == null) {
> @@ -203,6 +210,7 @@ public class JournalPersistenceAdapter implements PersistenceAdapter, JournalEve
>        *
>        * @param destination Destination to forget
>        */
> +    @Override
>       public void removeQueueMessageStore(ActiveMQQueue destination) {
>           queues.remove(destination);
>       }
> @@ -212,30 +220,37 @@ public class JournalPersistenceAdapter implements PersistenceAdapter, JournalEve
>        *
>        * @param destination Destination to forget
>        */
> +    @Override
>       public void removeTopicMessageStore(ActiveMQTopic destination) {
>           topics.remove(destination);
>       }
>   
> +    @Override
>       public TransactionStore createTransactionStore() throws IOException {
>           return transactionStore;
>       }
>   
> +    @Override
>       public long getLastMessageBrokerSequenceId() throws IOException {
>           return longTermPersistence.getLastMessageBrokerSequenceId();
>       }
>   
> +    @Override
>       public void beginTransaction(ConnectionContext context) throws IOException {
>           longTermPersistence.beginTransaction(context);
>       }
>   
> +    @Override
>       public void commitTransaction(ConnectionContext context) throws IOException {
>           longTermPersistence.commitTransaction(context);
>       }
>   
> +    @Override
>       public void rollbackTransaction(ConnectionContext context) throws IOException {
>           longTermPersistence.rollbackTransaction(context);
>       }
>   
> +    @Override
>       public synchronized void start() throws Exception {
>           if (!started.compareAndSet(false, true)) {
>               return;
> @@ -246,12 +261,14 @@ public class JournalPersistenceAdapter implements PersistenceAdapter, JournalEve
>           }
>   
>           checkpointTask = taskRunnerFactory.createTaskRunner(new Task() {
> +            @Override
>               public boolean iterate() {
>                   return doCheckpoint();
>               }
>           }, "ActiveMQ Journal Checkpoint Worker");
>   
>           checkpointExecutor = new ThreadPoolExecutor(maxCheckpointWorkers, maxCheckpointWorkers, 30, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(), new ThreadFactory() {
> +            @Override
>               public Thread newThread(Runnable runable) {
>                   Thread t = new Thread(runable, "Journal checkpoint worker");
>                   t.setPriority(7);
> @@ -279,6 +296,7 @@ public class JournalPersistenceAdapter implements PersistenceAdapter, JournalEve
>   
>       }
>   
> +    @Override
>       public void stop() throws Exception {
>   
>           this.usageManager.getMemoryUsage().removeUsageListener(this);
> @@ -330,16 +348,17 @@ public class JournalPersistenceAdapter implements PersistenceAdapter, JournalEve
>       /**
>        * The Journal give us a call back so that we can move old data out of the
>        * journal. Taking a checkpoint does this for us.
> -     *
> +     *
>        * @see org.apache.activemq.journal.JournalEventListener#overflowNotification(org.apache.activemq.journal.RecordLocation)
>        */
> +    @Override
>       public void overflowNotification(RecordLocation safeLocation) {
>           checkpoint(false, true);
>       }
>   
>       /**
>        * When we checkpoint we move all the journalled data to long term storage.
> -     *
> +     *
>        */
>       public void checkpoint(boolean sync, boolean fullCheckpoint) {
>           try {
> @@ -369,13 +388,14 @@ public class JournalPersistenceAdapter implements PersistenceAdapter, JournalEve
>           }
>       }
>   
> +    @Override
>       public void checkpoint(boolean sync) {
>           checkpoint(sync, sync);
>       }
>   
>       /**
>        * This does the actual checkpoint.
> -     *
> +     *
>        * @return
>        */
>       public boolean doCheckpoint() {
> @@ -398,7 +418,7 @@ public class JournalPersistenceAdapter implements PersistenceAdapter, JournalEve
>               // We do many partial checkpoints (fullCheckpoint==false) to move
>               // topic messages
>               // to long term store as soon as possible.
> -            //
> +            //
>               // We want to avoid doing that for queue messages since removes the
>               // come in the same
>               // checkpoint cycle will nullify the previous message add.
> @@ -411,6 +431,7 @@ public class JournalPersistenceAdapter implements PersistenceAdapter, JournalEve
>                       try {
>                           final JournalMessageStore ms = iterator.next();
>                           FutureTask<RecordLocation> task = new FutureTask<RecordLocation>(new Callable<RecordLocation>() {
> +                            @Override
>                               public RecordLocation call() throws Exception {
>                                   return ms.checkpoint();
>                               }
> @@ -428,6 +449,7 @@ public class JournalPersistenceAdapter implements PersistenceAdapter, JournalEve
>                   try {
>                       final JournalTopicMessageStore ms = iterator.next();
>                       FutureTask<RecordLocation> task = new FutureTask<RecordLocation>(new Callable<RecordLocation>() {
> +                        @Override
>                           public RecordLocation call() throws Exception {
>                               return ms.checkpoint();
>                           }
> @@ -505,7 +527,7 @@ public class JournalPersistenceAdapter implements PersistenceAdapter, JournalEve
>       /**
>        * Move all the messages that were in the journal into long term storage. We
>        * just replay and do a checkpoint.
> -     *
> +     *
>        * @throws IOException
>        * @throws IOException
>        * @throws InvalidRecordLocationException
> @@ -644,11 +666,11 @@ public class JournalPersistenceAdapter implements PersistenceAdapter, JournalEve
>       public RecordLocation writeCommand(DataStructure command, boolean sync) throws IOException {
>           if (started.get()) {
>               try {
> -        	    return journal.write(toPacket(wireFormat.marshal(command)), sync);
> +                return journal.write(toPacket(wireFormat.marshal(command)), sync);
>               } catch (IOException ioe) {
> -        	    LOG.error("Cannot write to the journal", ioe);
> -        	    brokerService.handleIOException(ioe);
> -        	    throw ioe;
> +                LOG.error("Cannot write to the journal", ioe);
> +                brokerService.handleIOException(ioe);
> +                throw ioe;
>               }
>           }
>           throw new IOException("closed");
> @@ -660,6 +682,7 @@ public class JournalPersistenceAdapter implements PersistenceAdapter, JournalEve
>           return writeCommand(trace, sync);
>       }
>   
> +    @Override
>       public void onUsageChanged(Usage usage, int oldPercentUsage, int newPercentUsage) {
>           newPercentUsage = (newPercentUsage / 10) * 10;
>           oldPercentUsage = (oldPercentUsage / 10) * 10;
> @@ -673,6 +696,7 @@ public class JournalPersistenceAdapter implements PersistenceAdapter, JournalEve
>           return transactionStore;
>       }
>   
> +    @Override
>       public void deleteAllMessages() throws IOException {
>           try {
>               JournalTrace trace = new JournalTrace();
> @@ -735,6 +759,7 @@ public class JournalPersistenceAdapter implements PersistenceAdapter, JournalEve
>           return new ByteSequence(sequence.getData(), sequence.getOffset(), sequence.getLength());
>       }
>   
> +    @Override
>       public void setBrokerName(String brokerName) {
>           longTermPersistence.setBrokerName(brokerName);
>       }
> @@ -744,18 +769,22 @@ public class JournalPersistenceAdapter implements PersistenceAdapter, JournalEve
>           return "JournalPersistenceAdapter(" + longTermPersistence + ")";
>       }
>   
> +    @Override
>       public void setDirectory(File dir) {
>           this.directory=dir;
>       }
> -
> +
> +    @Override
>       public File getDirectory(){
>           return directory;
>       }
> -
> +
> +    @Override
>       public long size(){
>           return 0;
>       }
>   
> +    @Override
>       public void setBrokerService(BrokerService brokerService) {
>           this.brokerService = brokerService;
>           PersistenceAdapter pa = getLongTermPersistence();
> @@ -764,8 +793,14 @@ public class JournalPersistenceAdapter implements PersistenceAdapter, JournalEve
>           }
>       }
>   
> +    @Override
>       public long getLastProducerSequenceId(ProducerId id) {
>           return -1;
>       }
>   
> +    @Override
> +    public JobSchedulerStore createJobSchedulerStore() throws IOException, UnsupportedOperationException {
> +        return longTermPersistence.createJobSchedulerStore();
> +    }
> +
>   }
>
> http://git-wip-us.apache.org/repos/asf/activemq/blob/fc244f48/activemq-kahadb-store/src/main/java/org/apache/activemq/store/kahadb/AbstractKahaDBMetaData.java
> ----------------------------------------------------------------------
> diff --git a/activemq-kahadb-store/src/main/java/org/apache/activemq/store/kahadb/AbstractKahaDBMetaData.java b/activemq-kahadb-store/src/main/java/org/apache/activemq/store/kahadb/AbstractKahaDBMetaData.java
> new file mode 100644
> index 0000000..edb2750
> --- /dev/null
> +++ b/activemq-kahadb-store/src/main/java/org/apache/activemq/store/kahadb/AbstractKahaDBMetaData.java
> @@ -0,0 +1,57 @@
> +/**
> + * Licensed to the Apache Software Foundation (ASF) under one or more
> + * contributor license agreements.  See the NOTICE file distributed with
> + * this work for additional information regarding copyright ownership.
> + * The ASF licenses this file to You under the Apache License, Version 2.0
> + * (the "License"); you may not use this file except in compliance with
> + * the License.  You may obtain a copy of the License at
> + *
> + *      http://www.apache.org/licenses/LICENSE-2.0
> + *
> + * Unless required by applicable law or agreed to in writing, software
> + * distributed under the License is distributed on an "AS IS" BASIS,
> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
> + * See the License for the specific language governing permissions and
> + * limitations under the License.
> + */
> +package org.apache.activemq.store.kahadb;
> +
> +import org.apache.activemq.store.kahadb.disk.journal.Location;
> +import org.apache.activemq.store.kahadb.disk.page.Page;
> +
> +public abstract class AbstractKahaDBMetaData<T> implements KahaDBMetaData<T> {
> +
> +    private int state;
> +    private Location lastUpdateLocation;
> +    private Page<T> page;
> +
> +    @Override
> +    public Page<T> getPage() {
> +        return page;
> +    }
> +
> +    @Override
> +    public int getState() {
> +        return state;
> +    }
> +
> +    @Override
> +    public Location getLastUpdateLocation() {
> +        return lastUpdateLocation;
> +    }
> +
> +    @Override
> +    public void setPage(Page<T> page) {
> +        this.page = page;
> +    }
> +
> +    @Override
> +    public void setState(int value) {
> +        this.state = value;
> +    }
> +
> +    @Override
> +    public void setLastUpdateLocation(Location location) {
> +        this.lastUpdateLocation = location;
> +    }
> +}
>
> .
>


-- 
Tim Bish
Sr Software Engineer | RedHat Inc.
tim.bish@redhat.com | www.redhat.com
skype: tabish121 | twitter: @tabish121
blog: http://timbish.blogspot.com/


[16/16] activemq git commit: https://issues.apache.org/jira/browse/AMQ-3758

Posted by ha...@apache.org.
https://issues.apache.org/jira/browse/AMQ-3758

Refactor the scheduler store into a more KahaDB style store that can
recover from various problems like missing journal files or corruption
as well as rebuild its index when needed.  Move the scheduler store into
a more configurable style that allows for users to plug in their own
implementations.  Store update from legacy versions is automatic.


Project: http://git-wip-us.apache.org/repos/asf/activemq/repo
Commit: http://git-wip-us.apache.org/repos/asf/activemq/commit/fc244f48
Tree: http://git-wip-us.apache.org/repos/asf/activemq/tree/fc244f48
Diff: http://git-wip-us.apache.org/repos/asf/activemq/diff/fc244f48

Branch: refs/heads/activemq-5.10.x
Commit: fc244f48e48596c668a7d9dc3b84c26e60693823
Parents: db669e4
Author: Timothy Bish <ta...@gmail.com>
Authored: Mon Jul 7 12:28:11 2014 -0400
Committer: Hadrian Zbarcea <ha...@apache.org>
Committed: Tue Dec 16 16:11:09 2014 -0500

----------------------------------------------------------------------
 .../apache/activemq/broker/BrokerService.java   |   25 +-
 .../activemq/broker/jmx/JobSchedulerView.java   |   56 +-
 .../broker/jmx/JobSchedulerViewMBean.java       |  113 +-
 .../apache/activemq/broker/scheduler/Job.java   |   23 +-
 .../activemq/broker/scheduler/JobListener.java  |   16 +-
 .../activemq/broker/scheduler/JobScheduler.java |   33 +-
 .../broker/scheduler/JobSchedulerFacade.java    |    6 +
 .../broker/scheduler/JobSchedulerStore.java     |   43 +
 .../activemq/broker/scheduler/JobSupport.java   |    5 +-
 .../activemq/store/PersistenceAdapter.java      |  119 +-
 .../store/memory/MemoryPersistenceAdapter.java  |   36 +-
 .../java/org/apache/activemq/util/IOHelper.java |   68 +-
 .../store/jdbc/JDBCPersistenceAdapter.java      |    7 +
 .../journal/JournalPersistenceAdapter.java      |   71 +-
 .../store/kahadb/AbstractKahaDBMetaData.java    |   57 +
 .../store/kahadb/AbstractKahaDBStore.java       |  745 ++++++++++++
 .../activemq/store/kahadb/KahaDBMetaData.java   |  135 +++
 .../store/kahadb/KahaDBPersistenceAdapter.java  |   15 +-
 .../activemq/store/kahadb/KahaDBStore.java      |   55 +-
 .../kahadb/MultiKahaDBPersistenceAdapter.java   |   56 +-
 .../kahadb/MultiKahaDBTransactionStore.java     |   18 +-
 .../activemq/store/kahadb/TempKahaDBStore.java  |  138 ++-
 .../apache/activemq/store/kahadb/Visitor.java   |   20 +
 .../store/kahadb/scheduler/JobImpl.java         |   21 +-
 .../store/kahadb/scheduler/JobLocation.java     |   77 +-
 .../scheduler/JobLocationsMarshaller.java       |   53 +
 .../kahadb/scheduler/JobSchedulerImpl.java      |  837 ++++++++------
 .../scheduler/JobSchedulerKahaDBMetaData.java   |  246 ++++
 .../kahadb/scheduler/JobSchedulerStoreImpl.java | 1076 +++++++++++++-----
 .../scheduler/UnknownStoreVersionException.java |   24 +
 .../kahadb/scheduler/legacy/LegacyJobImpl.java  |   72 ++
 .../scheduler/legacy/LegacyJobLocation.java     |  296 +++++
 .../legacy/LegacyJobSchedulerImpl.java          |  222 ++++
 .../legacy/LegacyJobSchedulerStoreImpl.java     |  378 ++++++
 .../scheduler/legacy/LegacyStoreReplayer.java   |  155 +++
 .../src/main/proto/journal-data.proto           |   61 +
 .../apache/activemq/leveldb/LevelDBStore.scala  |    5 +
 .../leveldb/replicated/ProxyLevelDBStore.scala  |    5 +
 .../JobSchedulerBrokerShutdownTest.java         |    1 +
 .../JobSchedulerJmxManagementTests.java         |  155 +++
 .../scheduler/JobSchedulerManagementTest.java   |   84 +-
 .../JobSchedulerStoreCheckpointTest.java        |  125 ++
 .../broker/scheduler/JobSchedulerStoreTest.java |   46 +-
 .../broker/scheduler/JobSchedulerTest.java      |   36 +
 .../scheduler/JobSchedulerTestSupport.java      |  112 ++
 .../KahaDBSchedulerIndexRebuildTest.java        |  179 +++
 .../KahaDBSchedulerMissingJournalLogsTest.java  |  204 ++++
 .../scheduler/SchedulerDBVersionTest.java       |  164 +++
 .../src/test/resources/log4j.properties         |    1 +
 .../activemq/store/schedulerDB/legacy/db-1.log  |  Bin 0 -> 524288 bytes
 .../store/schedulerDB/legacy/scheduleDB.data    |  Bin 0 -> 20480 bytes
 .../store/schedulerDB/legacy/scheduleDB.redo    |  Bin 0 -> 16408 bytes
 52 files changed, 5584 insertions(+), 911 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/activemq/blob/fc244f48/activemq-broker/src/main/java/org/apache/activemq/broker/BrokerService.java
----------------------------------------------------------------------
diff --git a/activemq-broker/src/main/java/org/apache/activemq/broker/BrokerService.java b/activemq-broker/src/main/java/org/apache/activemq/broker/BrokerService.java
index 00d4abd..5becec2 100644
--- a/activemq-broker/src/main/java/org/apache/activemq/broker/BrokerService.java
+++ b/activemq-broker/src/main/java/org/apache/activemq/broker/BrokerService.java
@@ -1861,6 +1861,23 @@ public class BrokerService implements Service {
 
             try {
                 PersistenceAdapter pa = getPersistenceAdapter();
+                if (pa != null) {
+                    this.jobSchedulerStore = pa.createJobSchedulerStore();
+                    jobSchedulerStore.setDirectory(getSchedulerDirectoryFile());
+                    configureService(jobSchedulerStore);
+                    jobSchedulerStore.start();
+                    return this.jobSchedulerStore;
+                }
+            } catch (IOException e) {
+                throw new RuntimeException(e);
+            } catch (UnsupportedOperationException ex) {
+                // It's ok if the store doesn't implement a scheduler.
+            } catch (Exception e) {
+                throw new RuntimeException(e);
+            }
+
+            try {
+                PersistenceAdapter pa = getPersistenceAdapter();
                 if (pa != null && pa instanceof JobSchedulerStore) {
                     this.jobSchedulerStore = (JobSchedulerStore) pa;
                     configureService(jobSchedulerStore);
@@ -1870,9 +1887,13 @@ public class BrokerService implements Service {
                 throw new RuntimeException(e);
             }
 
+            // Load the KahaDB store as a last resort, this only works if KahaDB is
+            // included at runtime, otherwise this will fail.  User should disable
+            // scheduler support if this fails.
             try {
-                String clazz = "org.apache.activemq.store.kahadb.scheduler.JobSchedulerStoreImpl";
-                jobSchedulerStore = (JobSchedulerStore) getClass().getClassLoader().loadClass(clazz).newInstance();
+                String clazz = "org.apache.activemq.store.kahadb.KahaDBPersistenceAdapter";
+                PersistenceAdapter adaptor = (PersistenceAdapter)getClass().getClassLoader().loadClass(clazz).newInstance();
+                jobSchedulerStore = adaptor.createJobSchedulerStore();
                 jobSchedulerStore.setDirectory(getSchedulerDirectoryFile());
                 configureService(jobSchedulerStore);
                 jobSchedulerStore.start();

http://git-wip-us.apache.org/repos/asf/activemq/blob/fc244f48/activemq-broker/src/main/java/org/apache/activemq/broker/jmx/JobSchedulerView.java
----------------------------------------------------------------------
diff --git a/activemq-broker/src/main/java/org/apache/activemq/broker/jmx/JobSchedulerView.java b/activemq-broker/src/main/java/org/apache/activemq/broker/jmx/JobSchedulerView.java
index 9e5a1fb..2118a96 100644
--- a/activemq-broker/src/main/java/org/apache/activemq/broker/jmx/JobSchedulerView.java
+++ b/activemq-broker/src/main/java/org/apache/activemq/broker/jmx/JobSchedulerView.java
@@ -16,23 +16,39 @@
  */
 package org.apache.activemq.broker.jmx;
 
+import java.util.List;
+
+import javax.management.openmbean.CompositeDataSupport;
+import javax.management.openmbean.CompositeType;
+import javax.management.openmbean.TabularData;
+import javax.management.openmbean.TabularDataSupport;
+import javax.management.openmbean.TabularType;
+
 import org.apache.activemq.broker.jmx.OpenTypeSupport.OpenTypeFactory;
 import org.apache.activemq.broker.scheduler.Job;
 import org.apache.activemq.broker.scheduler.JobScheduler;
 import org.apache.activemq.broker.scheduler.JobSupport;
 
-import javax.management.openmbean.*;
-import java.io.IOException;
-import java.util.List;
-
+/**
+ * MBean object that can be used to manage a single instance of a JobScheduler.  The object
+ * provides methods for querying for jobs and removing some or all of the jobs that are
+ * scheduled in the managed store.
+ */
 public class JobSchedulerView implements JobSchedulerViewMBean {
 
     private final JobScheduler jobScheduler;
 
+    /**
+     * Creates a new instance of the JobScheduler management MBean.
+     *
+     * @param jobScheduler
+     *        The scheduler instance to manage.
+     */
     public JobSchedulerView(JobScheduler jobScheduler) {
         this.jobScheduler = jobScheduler;
     }
 
+    @Override
     public TabularData getAllJobs() throws Exception {
         OpenTypeFactory factory = OpenTypeSupport.getFactory(Job.class);
         CompositeType ct = factory.getCompositeType();
@@ -45,6 +61,7 @@ public class JobSchedulerView implements JobSchedulerViewMBean {
         return rc;
     }
 
+    @Override
     public TabularData getAllJobs(String startTime, String finishTime) throws Exception {
         OpenTypeFactory factory = OpenTypeSupport.getFactory(Job.class);
         CompositeType ct = factory.getCompositeType();
@@ -59,6 +76,7 @@ public class JobSchedulerView implements JobSchedulerViewMBean {
         return rc;
     }
 
+    @Override
     public TabularData getNextScheduleJobs() throws Exception {
         OpenTypeFactory factory = OpenTypeSupport.getFactory(Job.class);
         CompositeType ct = factory.getCompositeType();
@@ -71,31 +89,51 @@ public class JobSchedulerView implements JobSchedulerViewMBean {
         return rc;
     }
 
+    @Override
     public String getNextScheduleTime() throws Exception {
         long time = this.jobScheduler.getNextScheduleTime();
         return JobSupport.getDateTime(time);
     }
 
+    @Override
     public void removeAllJobs() throws Exception {
         this.jobScheduler.removeAllJobs();
-
     }
 
+    @Override
     public void removeAllJobs(String startTime, String finishTime) throws Exception {
         long start = JobSupport.getDataTime(startTime);
         long finish = JobSupport.getDataTime(finishTime);
         this.jobScheduler.removeAllJobs(start, finish);
+    }
 
+    @Override
+    public void removeAllJobsAtScheduledTime(String time) throws Exception {
+        long removeAtTime = JobSupport.getDataTime(time);
+        this.jobScheduler.remove(removeAtTime);
     }
 
+    @Override
+    public void removeJobAtScheduledTime(String time) throws Exception {
+        removeAllJobsAtScheduledTime(time);
+    }
+
+    @Override
     public void removeJob(String jobId) throws Exception {
         this.jobScheduler.remove(jobId);
-
     }
 
-    public void removeJobAtScheduledTime(String time) throws IOException {
-        // TODO Auto-generated method stub
+    @Override
+    public int getExecutionCount(String jobId) throws Exception {
+        int result = 0;
 
-    }
+        List<Job> jobs = this.jobScheduler.getAllJobs();
+        for (Job job : jobs) {
+            if (job.getJobId().equals(jobId)) {
+                result = job.getExecutionCount();
+            }
+        }
 
+        return result;
+    }
 }

http://git-wip-us.apache.org/repos/asf/activemq/blob/fc244f48/activemq-broker/src/main/java/org/apache/activemq/broker/jmx/JobSchedulerViewMBean.java
----------------------------------------------------------------------
diff --git a/activemq-broker/src/main/java/org/apache/activemq/broker/jmx/JobSchedulerViewMBean.java b/activemq-broker/src/main/java/org/apache/activemq/broker/jmx/JobSchedulerViewMBean.java
index f5745ea..76a7926 100644
--- a/activemq-broker/src/main/java/org/apache/activemq/broker/jmx/JobSchedulerViewMBean.java
+++ b/activemq-broker/src/main/java/org/apache/activemq/broker/jmx/JobSchedulerViewMBean.java
@@ -18,76 +18,125 @@ package org.apache.activemq.broker.jmx;
 
 import javax.management.openmbean.TabularData;
 
-
-
 public interface JobSchedulerViewMBean {
+
     /**
-     * remove all jobs scheduled to run at this time
+     * Remove all jobs scheduled to run at this time.  If there are no jobs scheduled
+     * at the given time this methods returns without making any modifications to the
+     * scheduler store.
+     *
      * @param time
-     * @throws Exception
+     *        the string formated time that should be used to remove jobs.
+     *
+     * @throws Exception if an error occurs while performing the remove.
+     *
+     * @deprecated use removeAllJobsAtScheduledTime instead as it is more explicit about what
+     *             the method is actually doing.
      */
+    @Deprecated
     @MBeanInfo("remove jobs with matching execution time")
     public abstract void removeJobAtScheduledTime(@MBeanInfo("time: yyyy-MM-dd hh:mm:ss")String time) throws Exception;
 
     /**
-     * remove a job with the matching jobId
+     * Remove all jobs scheduled to run at this time.  If there are no jobs scheduled
+     * at the given time this methods returns without making any modifications to the
+     * scheduler store.
+     *
+     * @param time
+     *        the string formated time that should be used to remove jobs.
+     *
+     * @throws Exception if an error occurs while performing the remove.
+     */
+    @MBeanInfo("remove jobs with matching execution time")
+    public abstract void removeAllJobsAtScheduledTime(@MBeanInfo("time: yyyy-MM-dd hh:mm:ss")String time) throws Exception;
+
+    /**
+     * Remove a job with the matching jobId.  If the method does not find a matching job
+     * then it returns without throwing an error or making any modifications to the job
+     * scheduler store.
+     *
      * @param jobId
-     * @throws Exception
+     *        the Job Id to remove from the scheduler store.
+     *
+     * @throws Exception if an error occurs while attempting to remove the Job.
      */
     @MBeanInfo("remove jobs with matching jobId")
     public abstract void removeJob(@MBeanInfo("jobId")String jobId) throws Exception;
-    
+
     /**
-     * remove all the Jobs from the scheduler
-     * @throws Exception
+     * Remove all the Jobs from the scheduler,
+     *
+     * @throws Exception if an error occurs while purging the store.
      */
     @MBeanInfo("remove all scheduled jobs")
     public abstract void removeAllJobs() throws Exception;
-    
+
     /**
-     * remove all the Jobs from the scheduler that are due between the start and finish times
-     * @param start time 
-     * @param finish time
-     * @throws Exception
+     * Remove all the Jobs from the scheduler that are due between the start and finish times.
+     *
+     * @param start
+     *        the starting time to remove jobs from.
+     * @param finish
+     *        the finish time for the remove operation.
+     *
+     * @throws Exception if an error occurs while attempting to remove the jobs.
      */
     @MBeanInfo("remove all scheduled jobs between time ranges ")
     public abstract void removeAllJobs(@MBeanInfo("start: yyyy-MM-dd hh:mm:ss")String start,@MBeanInfo("finish: yyyy-MM-dd hh:mm:ss")String finish) throws Exception;
-    
 
-    
     /**
-     * Get the next time jobs will be fired
-     * @return the time in milliseconds
-     * @throws Exception 
+     * Get the next time jobs will be fired from this scheduler store.
+     *
+     * @return the time in milliseconds of the next job to execute.
+     *
+     * @throws Exception if an error occurs while accessing the store.
      */
     @MBeanInfo("get the next time a job is due to be scheduled ")
     public abstract String getNextScheduleTime() throws Exception;
-    
+
+    /**
+     * Gets the number of times a scheduled Job has been executed.
+     *
+     * @return the total number of time a scheduled job has executed.
+     *
+     * @throws Exception if an error occurs while querying for the Job.
+     */
+    @MBeanInfo("get the next time a job is due to be scheduled ")
+    public abstract int getExecutionCount(@MBeanInfo("jobId")String jobId) throws Exception;
+
     /**
-     * Get all the jobs scheduled to run next
+     * Get all the jobs scheduled to run next.
+     *
      * @return a list of jobs that will be scheduled next
-     * @throws Exception
+     *
+     * @throws Exception if an error occurs while reading the scheduler store.
      */
     @MBeanInfo("get the next job(s) to be scheduled. Not HTML friendly ")
     public abstract TabularData getNextScheduleJobs() throws Exception;
-    
-    /**
-     * Get all the outstanding Jobs
-     * @return a  table of all jobs
-     * @throws Exception
 
+    /**
+     * Get all the outstanding Jobs that are scheduled in this scheduler store.
+     *
+     * @return a table of all jobs in this scheduler store.
+     *
+     * @throws Exception if an error occurs while reading the store.
      */
     @MBeanInfo("get the scheduled Jobs in the Store. Not HTML friendly ")
     public abstract TabularData getAllJobs() throws Exception;
-    
+
     /**
-     * Get all outstanding jobs due to run between start and finish
+     * Get all outstanding jobs due to run between start and finish time range.
+     *
      * @param start
+     *        the starting time range to query the store for jobs.
      * @param finish
-     * @return a table of jobs in the range
-     * @throws Exception
-
+     *        the ending time of this query for scheduled jobs.
+     *
+     * @return a table of jobs in the range given.
+     *
+     * @throws Exception if an error occurs while querying the scheduler store.
      */
     @MBeanInfo("get the scheduled Jobs in the Store within the time range. Not HTML friendly ")
     public abstract TabularData getAllJobs(@MBeanInfo("start: yyyy-MM-dd hh:mm:ss")String start,@MBeanInfo("finish: yyyy-MM-dd hh:mm:ss")String finish)throws Exception;
+
 }

http://git-wip-us.apache.org/repos/asf/activemq/blob/fc244f48/activemq-broker/src/main/java/org/apache/activemq/broker/scheduler/Job.java
----------------------------------------------------------------------
diff --git a/activemq-broker/src/main/java/org/apache/activemq/broker/scheduler/Job.java b/activemq-broker/src/main/java/org/apache/activemq/broker/scheduler/Job.java
index 7b28a5b..047fe23 100644
--- a/activemq-broker/src/main/java/org/apache/activemq/broker/scheduler/Job.java
+++ b/activemq-broker/src/main/java/org/apache/activemq/broker/scheduler/Job.java
@@ -16,7 +16,12 @@
  */
 package org.apache.activemq.broker.scheduler;
 
-
+/**
+ * Interface for a scheduled Job object.
+ *
+ * Each Job is identified by a unique Job Id which can be used to reference the Job
+ * in the Job Scheduler store for updates or removal.
+ */
 public interface Job {
 
     /**
@@ -38,11 +43,12 @@ public interface Job {
      * @return the Delay
      */
     public abstract long getDelay();
+
     /**
      * @return the period
      */
     public abstract long getPeriod();
-    
+
     /**
      * @return the cron entry
      */
@@ -52,17 +58,24 @@ public interface Job {
      * @return the payload
      */
     public abstract byte[] getPayload();
-    
+
     /**
      * Get the start time as a Date time string
      * @return the date time
      */
     public String getStartTime();
-    
+
     /**
-     * Get the time the job is next due to execute 
+     * Get the time the job is next due to execute
      * @return the date time
      */
     public String getNextExecutionTime();
 
+    /**
+     * Gets the total number of times this job has executed.
+     *
+     * @returns the number of times this job has been executed.
+     */
+    public int getExecutionCount();
+
 }
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/activemq/blob/fc244f48/activemq-broker/src/main/java/org/apache/activemq/broker/scheduler/JobListener.java
----------------------------------------------------------------------
diff --git a/activemq-broker/src/main/java/org/apache/activemq/broker/scheduler/JobListener.java b/activemq-broker/src/main/java/org/apache/activemq/broker/scheduler/JobListener.java
index c53d9c6..a453595 100644
--- a/activemq-broker/src/main/java/org/apache/activemq/broker/scheduler/JobListener.java
+++ b/activemq-broker/src/main/java/org/apache/activemq/broker/scheduler/JobListener.java
@@ -18,13 +18,21 @@ package org.apache.activemq.broker.scheduler;
 
 import org.apache.activemq.util.ByteSequence;
 
+/**
+ * Job event listener interface. Provides event points for Job related events
+ * such as job ready events.
+ */
 public interface JobListener {
-    
+
     /**
-     * A Job that has been scheduled is now ready 
-     * @param id
+     * A Job that has been scheduled is now ready to be fired.  The Job is passed
+     * in its raw byte form and must be un-marshaled before being delivered.
+     *
+     * @param jobId
+     *        The unique Job Id of the Job that is ready to fire.
      * @param job
+     *        The job that is now ready, delivered in byte form.
      */
-    public void scheduledJob(String id,ByteSequence job);
+    public void scheduledJob(String id, ByteSequence job);
 
 }

http://git-wip-us.apache.org/repos/asf/activemq/blob/fc244f48/activemq-broker/src/main/java/org/apache/activemq/broker/scheduler/JobScheduler.java
----------------------------------------------------------------------
diff --git a/activemq-broker/src/main/java/org/apache/activemq/broker/scheduler/JobScheduler.java b/activemq-broker/src/main/java/org/apache/activemq/broker/scheduler/JobScheduler.java
index 2e96eae..e951861 100644
--- a/activemq-broker/src/main/java/org/apache/activemq/broker/scheduler/JobScheduler.java
+++ b/activemq-broker/src/main/java/org/apache/activemq/broker/scheduler/JobScheduler.java
@@ -46,20 +46,25 @@ public interface JobScheduler {
     void stopDispatching() throws Exception;
 
     /**
-     * Add a Job listener
+     * Add a Job listener which will receive events related to scheduled jobs.
+     *
+     * @param listener
+     *      The job listener to add.
      *
-     * @param l
      * @throws Exception
      */
-    void addListener(JobListener l) throws Exception;
+    void addListener(JobListener listener) throws Exception;
 
     /**
-     * remove a JobListener
+     * remove a JobListener that was previously registered.  If the given listener is not in
+     * the registry this method has no effect.
+     *
+     * @param listener
+     *      The listener that should be removed from the listener registry.
      *
-     * @param l
      * @throws Exception
      */
-    void removeListener(JobListener l) throws Exception;
+    void removeListener(JobListener listener) throws Exception;
 
     /**
      * Add a job to be scheduled
@@ -70,7 +75,8 @@ public interface JobScheduler {
      *            the message to be sent when the job is scheduled
      * @param delay
      *            the time in milliseconds before the job will be run
-     * @throws Exception
+     *
+     * @throws Exception if an error occurs while scheduling the Job.
      */
     void schedule(String jobId, ByteSequence payload, long delay) throws Exception;
 
@@ -82,8 +88,9 @@ public interface JobScheduler {
      * @param payload
      *            the message to be sent when the job is scheduled
      * @param cronEntry
-     *            - cron entry
-     * @throws Exception
+     *            The cron entry to use to schedule this job.
+     *
+     * @throws Exception if an error occurs while scheduling the Job.
      */
     void schedule(String jobId, ByteSequence payload, String cronEntry) throws Exception;
 
@@ -95,7 +102,7 @@ public interface JobScheduler {
      * @param payload
      *            the message to be sent when the job is scheduled
      * @param cronEntry
-     *            - cron entry
+     *            cron entry
      * @param delay
      *            time in ms to wait before scheduling
      * @param period
@@ -110,6 +117,8 @@ public interface JobScheduler {
      * remove all jobs scheduled to run at this time
      *
      * @param time
+     *      The UTC time to use to remove a batch of scheduled Jobs.
+     *
      * @throws Exception
      */
     void remove(long time) throws Exception;
@@ -118,7 +127,9 @@ public interface JobScheduler {
      * remove a job with the matching jobId
      *
      * @param jobId
-     * @throws Exception
+     *      The unique Job Id to search for and remove from the scheduled set of jobs.
+     *
+     * @throws Exception if an error occurs while removing the Job.
      */
     void remove(String jobId) throws Exception;
 

http://git-wip-us.apache.org/repos/asf/activemq/blob/fc244f48/activemq-broker/src/main/java/org/apache/activemq/broker/scheduler/JobSchedulerFacade.java
----------------------------------------------------------------------
diff --git a/activemq-broker/src/main/java/org/apache/activemq/broker/scheduler/JobSchedulerFacade.java b/activemq-broker/src/main/java/org/apache/activemq/broker/scheduler/JobSchedulerFacade.java
index d46d04a..24a216a 100644
--- a/activemq-broker/src/main/java/org/apache/activemq/broker/scheduler/JobSchedulerFacade.java
+++ b/activemq-broker/src/main/java/org/apache/activemq/broker/scheduler/JobSchedulerFacade.java
@@ -21,6 +21,12 @@ import java.util.List;
 
 import org.apache.activemq.util.ByteSequence;
 
+/**
+ * A wrapper for instances of the JobScheduler interface that ensures that methods
+ * provides safe and sane return values and can deal with null values being passed
+ * in etc.  Provides a measure of safety when using unknown implementations of the
+ * JobSchedulerStore which might not always do the right thing.
+ */
 public class JobSchedulerFacade implements JobScheduler {
 
     private final SchedulerBroker broker;

http://git-wip-us.apache.org/repos/asf/activemq/blob/fc244f48/activemq-broker/src/main/java/org/apache/activemq/broker/scheduler/JobSchedulerStore.java
----------------------------------------------------------------------
diff --git a/activemq-broker/src/main/java/org/apache/activemq/broker/scheduler/JobSchedulerStore.java b/activemq-broker/src/main/java/org/apache/activemq/broker/scheduler/JobSchedulerStore.java
index 3cbc367..c6863c7 100644
--- a/activemq-broker/src/main/java/org/apache/activemq/broker/scheduler/JobSchedulerStore.java
+++ b/activemq-broker/src/main/java/org/apache/activemq/broker/scheduler/JobSchedulerStore.java
@@ -26,13 +26,56 @@ import org.apache.activemq.Service;
  */
 public interface JobSchedulerStore extends Service {
 
+    /**
+     * Gets the location where the Job Scheduler will write the persistent data used
+     * to preserve and recover scheduled Jobs.
+     *
+     * If the scheduler implementation does not utilize a file system based store this
+     * method returns null.
+     *
+     * @return the directory where persistent store data is written.
+     */
     File getDirectory();
 
+    /**
+     * Sets the directory where persistent store data will be written.  This method
+     * must be called before the scheduler store is started to have any effect.
+     *
+     * @param directory
+     *      The directory where the job scheduler store is to be located.
+     */
     void setDirectory(File directory);
 
+    /**
+     * The size of the current store on disk if the store utilizes a disk based store
+     * mechanism.
+     *
+     * @return the current store size on disk.
+     */
     long size();
 
+    /**
+     * Returns the JobScheduler instance identified by the given name.
+     *
+     * @param name
+     *        the name of the JobScheduler instance to lookup.
+     *
+     * @return the named JobScheduler or null if none exists with the given name.
+     *
+     * @throws Exception if an error occurs while loading the named scheduler.
+     */
     JobScheduler getJobScheduler(String name) throws Exception;
 
+    /**
+     * Removes the named JobScheduler if it exists, purging all scheduled messages
+     * assigned to it.
+     *
+     * @param name
+     *        the name of the scheduler instance to remove.
+     *
+     * @return true if there was a scheduler with the given name to remove.
+     *
+     * @throws Exception if an error occurs while removing the scheduler.
+     */
     boolean removeJobScheduler(String name) throws Exception;
 }

http://git-wip-us.apache.org/repos/asf/activemq/blob/fc244f48/activemq-broker/src/main/java/org/apache/activemq/broker/scheduler/JobSupport.java
----------------------------------------------------------------------
diff --git a/activemq-broker/src/main/java/org/apache/activemq/broker/scheduler/JobSupport.java b/activemq-broker/src/main/java/org/apache/activemq/broker/scheduler/JobSupport.java
index 6b78d77..fc5b8dd 100644
--- a/activemq-broker/src/main/java/org/apache/activemq/broker/scheduler/JobSupport.java
+++ b/activemq-broker/src/main/java/org/apache/activemq/broker/scheduler/JobSupport.java
@@ -20,7 +20,11 @@ import java.text.DateFormat;
 import java.text.SimpleDateFormat;
 import java.util.Date;
 
+/**
+ * A class to provide common Job Scheduler related methods.
+ */
 public class JobSupport {
+
     public static String getDateTime(long value) {
         DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
         Date date = new Date(value);
@@ -32,5 +36,4 @@ public class JobSupport {
          Date date = dfm.parse(value);
          return date.getTime();
      }
-
 }

http://git-wip-us.apache.org/repos/asf/activemq/blob/fc244f48/activemq-broker/src/main/java/org/apache/activemq/store/PersistenceAdapter.java
----------------------------------------------------------------------
diff --git a/activemq-broker/src/main/java/org/apache/activemq/store/PersistenceAdapter.java b/activemq-broker/src/main/java/org/apache/activemq/store/PersistenceAdapter.java
index 31efd32..01a9634 100755
--- a/activemq-broker/src/main/java/org/apache/activemq/store/PersistenceAdapter.java
+++ b/activemq-broker/src/main/java/org/apache/activemq/store/PersistenceAdapter.java
@@ -22,6 +22,7 @@ import java.util.Set;
 
 import org.apache.activemq.Service;
 import org.apache.activemq.broker.ConnectionContext;
+import org.apache.activemq.broker.scheduler.JobSchedulerStore;
 import org.apache.activemq.command.ActiveMQDestination;
 import org.apache.activemq.command.ActiveMQQueue;
 import org.apache.activemq.command.ActiveMQTopic;
@@ -31,74 +32,99 @@ import org.apache.activemq.usage.SystemUsage;
 /**
  * Adapter to the actual persistence mechanism used with ActiveMQ
  *
- * 
+ *
  */
 public interface PersistenceAdapter extends Service {
 
     /**
-     * Returns a set of all the {@link org.apache.activemq.command.ActiveMQDestination}
-     * objects that the persistence store is aware exist.
+     * Returns a set of all the
+     * {@link org.apache.activemq.command.ActiveMQDestination} objects that the
+     * persistence store is aware exist.
      *
      * @return active destinations
      */
     Set<ActiveMQDestination> getDestinations();
 
     /**
-     * Factory method to create a new queue message store with the given destination name
+     * Factory method to create a new queue message store with the given
+     * destination name
+     *
      * @param destination
      * @return the message store
-     * @throws IOException 
+     * @throws IOException
      */
     MessageStore createQueueMessageStore(ActiveMQQueue destination) throws IOException;
 
     /**
-     * Factory method to create a new topic message store with the given destination name
-     * @param destination 
+     * Factory method to create a new topic message store with the given
+     * destination name
+     *
+     * @param destination
      * @return the topic message store
-     * @throws IOException 
+     * @throws IOException
      */
     TopicMessageStore createTopicMessageStore(ActiveMQTopic destination) throws IOException;
 
     /**
+     * Creates and returns a new Job Scheduler store instance.
+     *
+     * @return a new JobSchedulerStore instance if this Persistence adapter provides its own.
+     *
+     * @throws IOException If an error occurs while creating the new JobSchedulerStore.
+     * @throws UnsupportedOperationException If this adapter does not provide its own
+     *                                       scheduler store implementation.
+     */
+    JobSchedulerStore createJobSchedulerStore() throws IOException, UnsupportedOperationException;
+
+    /**
      * Cleanup method to remove any state associated with the given destination.
      * This method does not stop the message store (it might not be cached).
-     * @param destination Destination to forget
+     *
+     * @param destination
+     *            Destination to forget
      */
     void removeQueueMessageStore(ActiveMQQueue destination);
 
     /**
      * Cleanup method to remove any state associated with the given destination
      * This method does not stop the message store (it might not be cached).
-     * @param destination Destination to forget
+     *
+     * @param destination
+     *            Destination to forget
      */
     void removeTopicMessageStore(ActiveMQTopic destination);
 
     /**
-     * Factory method to create a new persistent prepared transaction store for XA recovery
+     * Factory method to create a new persistent prepared transaction store for
+     * XA recovery
+     *
      * @return transaction store
-     * @throws IOException 
+     * @throws IOException
      */
     TransactionStore createTransactionStore() throws IOException;
 
     /**
-     * This method starts a transaction on the persistent storage - which is nothing to
-     * do with JMS or XA transactions - its purely a mechanism to perform multiple writes
-     * to a persistent store in 1 transaction as a performance optimization.
+     * This method starts a transaction on the persistent storage - which is
+     * nothing to do with JMS or XA transactions - its purely a mechanism to
+     * perform multiple writes to a persistent store in 1 transaction as a
+     * performance optimization.
      * <p/>
-     * Typically one transaction will require one disk synchronization point and so for
-     * real high performance its usually faster to perform many writes within the same
-     * transaction to minimize latency caused by disk synchronization. This is especially
-     * true when using tools like Berkeley Db or embedded JDBC servers.
-     * @param context 
-     * @throws IOException 
+     * Typically one transaction will require one disk synchronization point and
+     * so for real high performance its usually faster to perform many writes
+     * within the same transaction to minimize latency caused by disk
+     * synchronization. This is especially true when using tools like Berkeley
+     * Db or embedded JDBC servers.
+     *
+     * @param context
+     * @throws IOException
      */
     void beginTransaction(ConnectionContext context) throws IOException;
 
-
     /**
      * Commit a persistence transaction
-     * @param context 
-     * @throws IOException 
+     *
+     * @param context
+     * @throws IOException
      *
      * @see PersistenceAdapter#beginTransaction(ConnectionContext context)
      */
@@ -106,40 +132,45 @@ public interface PersistenceAdapter extends Service {
 
     /**
      * Rollback a persistence transaction
-     * @param context 
-     * @throws IOException 
+     *
+     * @param context
+     * @throws IOException
      *
      * @see PersistenceAdapter#beginTransaction(ConnectionContext context)
      */
     void rollbackTransaction(ConnectionContext context) throws IOException;
-    
+
     /**
-     * 
+     *
      * @return last broker sequence
      * @throws IOException
      */
     long getLastMessageBrokerSequenceId() throws IOException;
-    
+
     /**
      * Delete's all the messages in the persistent store.
-     * 
+     *
      * @throws IOException
      */
     void deleteAllMessages() throws IOException;
-        
+
     /**
-     * @param usageManager The UsageManager that is controlling the broker's memory usage.
+     * @param usageManager
+     *            The UsageManager that is controlling the broker's memory
+     *            usage.
      */
     void setUsageManager(SystemUsage usageManager);
-    
+
     /**
      * Set the name of the broker using the adapter
+     *
      * @param brokerName
      */
     void setBrokerName(String brokerName);
-    
+
     /**
      * Set the directory where any data files should be created
+     *
      * @param dir
      */
     void setDirectory(File dir);
@@ -148,26 +179,30 @@ public interface PersistenceAdapter extends Service {
      * @return the directory used by the persistence adaptor
      */
     File getDirectory();
-    
+
     /**
      * checkpoint any
-     * @param sync 
-     * @throws IOException 
+     *
+     * @param sync
+     * @throws IOException
      *
      */
     void checkpoint(boolean sync) throws IOException;
-    
+
     /**
      * A hint to return the size of the store on disk
+     *
      * @return disk space used in bytes of 0 if not implemented
      */
     long size();
 
     /**
-     * return the last stored producer sequenceId for this producer Id
-     * used to suppress duplicate sends on failover reconnect at the transport
-     * when a reconnect occurs
-     * @param id the producerId to find a sequenceId for
+     * return the last stored producer sequenceId for this producer Id used to
+     * suppress duplicate sends on failover reconnect at the transport when a
+     * reconnect occurs
+     *
+     * @param id
+     *            the producerId to find a sequenceId for
      * @return the last stored sequence id or -1 if no suppression needed
      */
     long getLastProducerSequenceId(ProducerId id) throws IOException;

http://git-wip-us.apache.org/repos/asf/activemq/blob/fc244f48/activemq-broker/src/main/java/org/apache/activemq/store/memory/MemoryPersistenceAdapter.java
----------------------------------------------------------------------
diff --git a/activemq-broker/src/main/java/org/apache/activemq/store/memory/MemoryPersistenceAdapter.java b/activemq-broker/src/main/java/org/apache/activemq/store/memory/MemoryPersistenceAdapter.java
index 0fd6bfc..73ea104 100755
--- a/activemq-broker/src/main/java/org/apache/activemq/store/memory/MemoryPersistenceAdapter.java
+++ b/activemq-broker/src/main/java/org/apache/activemq/store/memory/MemoryPersistenceAdapter.java
@@ -24,6 +24,7 @@ import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 
 import org.apache.activemq.broker.ConnectionContext;
+import org.apache.activemq.broker.scheduler.JobSchedulerStore;
 import org.apache.activemq.command.ActiveMQDestination;
 import org.apache.activemq.command.ActiveMQQueue;
 import org.apache.activemq.command.ActiveMQTopic;
@@ -39,7 +40,7 @@ import org.slf4j.LoggerFactory;
 
 /**
  * @org.apache.xbean.XBean
- * 
+ *
  */
 public class MemoryPersistenceAdapter implements PersistenceAdapter {
     private static final Logger LOG = LoggerFactory.getLogger(MemoryPersistenceAdapter.class);
@@ -49,6 +50,7 @@ public class MemoryPersistenceAdapter implements PersistenceAdapter {
     ConcurrentHashMap<ActiveMQDestination, MessageStore> queues = new ConcurrentHashMap<ActiveMQDestination, MessageStore>();
     private boolean useExternalMessageReferences;
 
+    @Override
     public Set<ActiveMQDestination> getDestinations() {
         Set<ActiveMQDestination> rc = new HashSet<ActiveMQDestination>(queues.size() + topics.size());
         for (Iterator<ActiveMQDestination> iter = queues.keySet().iterator(); iter.hasNext();) {
@@ -64,6 +66,7 @@ public class MemoryPersistenceAdapter implements PersistenceAdapter {
         return new MemoryPersistenceAdapter();
     }
 
+    @Override
     public MessageStore createQueueMessageStore(ActiveMQQueue destination) throws IOException {
         MessageStore rc = queues.get(destination);
         if (rc == null) {
@@ -76,6 +79,7 @@ public class MemoryPersistenceAdapter implements PersistenceAdapter {
         return rc;
     }
 
+    @Override
     public TopicMessageStore createTopicMessageStore(ActiveMQTopic destination) throws IOException {
         TopicMessageStore rc = topics.get(destination);
         if (rc == null) {
@@ -93,6 +97,7 @@ public class MemoryPersistenceAdapter implements PersistenceAdapter {
      *
      * @param destination Destination to forget
      */
+    @Override
     public void removeQueueMessageStore(ActiveMQQueue destination) {
         queues.remove(destination);
     }
@@ -102,10 +107,12 @@ public class MemoryPersistenceAdapter implements PersistenceAdapter {
      *
      * @param destination Destination to forget
      */
+    @Override
     public void removeTopicMessageStore(ActiveMQTopic destination) {
         topics.remove(destination);
     }
 
+    @Override
     public TransactionStore createTransactionStore() throws IOException {
         if (transactionStore == null) {
             transactionStore = new MemoryTransactionStore(this);
@@ -113,25 +120,32 @@ public class MemoryPersistenceAdapter implements PersistenceAdapter {
         return transactionStore;
     }
 
+    @Override
     public void beginTransaction(ConnectionContext context) {
     }
 
+    @Override
     public void commitTransaction(ConnectionContext context) {
     }
 
+    @Override
     public void rollbackTransaction(ConnectionContext context) {
     }
 
+    @Override
     public void start() throws Exception {
     }
 
+    @Override
     public void stop() throws Exception {
     }
 
+    @Override
     public long getLastMessageBrokerSequenceId() throws IOException {
         return 0;
     }
 
+    @Override
     public void deleteAllMessages() throws IOException {
         for (Iterator<TopicMessageStore> iter = topics.values().iterator(); iter.hasNext();) {
             MemoryMessageStore store = asMemoryMessageStore(iter.next());
@@ -177,38 +191,52 @@ public class MemoryPersistenceAdapter implements PersistenceAdapter {
      * @param usageManager The UsageManager that is controlling the broker's
      *                memory usage.
      */
+    @Override
     public void setUsageManager(SystemUsage usageManager) {
     }
 
+    @Override
     public String toString() {
         return "MemoryPersistenceAdapter";
     }
 
+    @Override
     public void setBrokerName(String brokerName) {
     }
 
+    @Override
     public void setDirectory(File dir) {
     }
-    
+
+    @Override
     public File getDirectory(){
         return null;
     }
 
+    @Override
     public void checkpoint(boolean sync) throws IOException {
     }
-    
+
+    @Override
     public long size(){
         return 0;
     }
-    
+
     public void setCreateTransactionStore(boolean create) throws IOException {
         if (create) {
             createTransactionStore();
         }
     }
 
+    @Override
     public long getLastProducerSequenceId(ProducerId id) {
         // memory map does duplicate suppression
         return -1;
     }
+
+    @Override
+    public JobSchedulerStore createJobSchedulerStore() throws IOException, UnsupportedOperationException {
+        // We could eventuall implement an in memory scheduler.
+        throw new UnsupportedOperationException();
+    }
 }

http://git-wip-us.apache.org/repos/asf/activemq/blob/fc244f48/activemq-broker/src/main/java/org/apache/activemq/util/IOHelper.java
----------------------------------------------------------------------
diff --git a/activemq-broker/src/main/java/org/apache/activemq/util/IOHelper.java b/activemq-broker/src/main/java/org/apache/activemq/util/IOHelper.java
index a623de9..2a70194 100644
--- a/activemq-broker/src/main/java/org/apache/activemq/util/IOHelper.java
+++ b/activemq-broker/src/main/java/org/apache/activemq/util/IOHelper.java
@@ -61,8 +61,9 @@ public final class IOHelper {
     }
 
     /**
-     * Converts any string into a string that is safe to use as a file name.
-     * The result will only include ascii characters and numbers, and the "-","_", and "." characters.
+     * Converts any string into a string that is safe to use as a file name. The
+     * result will only include ascii characters and numbers, and the "-","_",
+     * and "." characters.
      *
      * @param name
      * @return
@@ -76,15 +77,16 @@ public final class IOHelper {
     }
 
     /**
-     * Converts any string into a string that is safe to use as a file name.
-     * The result will only include ascii characters and numbers, and the "-","_", and "." characters.
+     * Converts any string into a string that is safe to use as a file name. The
+     * result will only include ascii characters and numbers, and the "-","_",
+     * and "." characters.
      *
      * @param name
      * @param dirSeparators
      * @param maxFileLength
      * @return
      */
-    public static String toFileSystemSafeName(String name,boolean dirSeparators,int maxFileLength) {
+    public static String toFileSystemSafeName(String name, boolean dirSeparators, int maxFileLength) {
         int size = name.length();
         StringBuffer rc = new StringBuffer(size * 2);
         for (int i = 0; i < size; i++) {
@@ -92,8 +94,7 @@ public final class IOHelper {
             boolean valid = c >= 'a' && c <= 'z';
             valid = valid || (c >= 'A' && c <= 'Z');
             valid = valid || (c >= '0' && c <= '9');
-            valid = valid || (c == '_') || (c == '-') || (c == '.') || (c=='#')
-                    ||(dirSeparators && ( (c == '/') || (c == '\\')));
+            valid = valid || (c == '_') || (c == '-') || (c == '.') || (c == '#') || (dirSeparators && ((c == '/') || (c == '\\')));
 
             if (valid) {
                 rc.append(c);
@@ -105,7 +106,7 @@ public final class IOHelper {
         }
         String result = rc.toString();
         if (result.length() > maxFileLength) {
-            result = result.substring(result.length()-maxFileLength,result.length());
+            result = result.substring(result.length() - maxFileLength, result.length());
         }
         return result;
     }
@@ -168,8 +169,7 @@ public final class IOHelper {
             } else {
                 for (int i = 0; i < files.length; i++) {
                     File file = files[i];
-                    if (file.getName().equals(".")
-                            || file.getName().equals("..")) {
+                    if (file.getName().equals(".") || file.getName().equals("..")) {
                         continue;
                     }
                     if (file.isDirectory()) {
@@ -190,6 +190,27 @@ public final class IOHelper {
         }
     }
 
+    public static void moveFiles(File srcDirectory, File targetDirectory, FilenameFilter filter) throws IOException {
+        if (!srcDirectory.isDirectory()) {
+            throw new IOException("source is not a directory");
+        }
+
+        if (targetDirectory.exists() && !targetDirectory.isDirectory()) {
+            throw new IOException("target exists and is not a directory");
+        } else {
+            mkdirs(targetDirectory);
+        }
+
+        List<File> filesToMove = new ArrayList<File>();
+        getFiles(srcDirectory, filesToMove, filter);
+
+        for (File file : filesToMove) {
+            if (!file.isDirectory()) {
+                moveFile(file, targetDirectory);
+            }
+        }
+    }
+
     public static void copyFile(File src, File dest) throws IOException {
         copyFile(src, dest, null);
     }
@@ -222,32 +243,32 @@ public final class IOHelper {
         File parent = src.getParentFile();
         String fromPath = from.getAbsolutePath();
         if (parent.getAbsolutePath().equals(fromPath)) {
-            //one level down
+            // one level down
             result = to;
-        }else {
+        } else {
             String parentPath = parent.getAbsolutePath();
             String path = parentPath.substring(fromPath.length());
-            result = new File(to.getAbsolutePath()+File.separator+path);
+            result = new File(to.getAbsolutePath() + File.separator + path);
         }
         return result;
     }
 
-    static List<File> getFiles(File dir,FilenameFilter filter){
+    static List<File> getFiles(File dir, FilenameFilter filter) {
         List<File> result = new ArrayList<File>();
-        getFiles(dir,result,filter);
+        getFiles(dir, result, filter);
         return result;
     }
 
-    static void getFiles(File dir,List<File> list,FilenameFilter filter) {
+    static void getFiles(File dir, List<File> list, FilenameFilter filter) {
         if (!list.contains(dir)) {
             list.add(dir);
-            String[] fileNames=dir.list(filter);
-            for (int i =0; i < fileNames.length;i++) {
-                File f = new File(dir,fileNames[i]);
+            String[] fileNames = dir.list(filter);
+            for (int i = 0; i < fileNames.length; i++) {
+                File f = new File(dir, fileNames[i]);
                 if (f.isFile()) {
                     list.add(f);
-                }else {
-                    getFiles(dir,list,filter);
+                } else {
+                    getFiles(dir, list, filter);
                 }
             }
         }
@@ -286,12 +307,13 @@ public final class IOHelper {
     public static void mkdirs(File dir) throws IOException {
         if (dir.exists()) {
             if (!dir.isDirectory()) {
-                throw new IOException("Failed to create directory '" + dir +"', regular file already existed with that name");
+                throw new IOException("Failed to create directory '" + dir +
+                                      "', regular file already existed with that name");
             }
 
         } else {
             if (!dir.mkdirs()) {
-                throw new IOException("Failed to create directory '" + dir+"'");
+                throw new IOException("Failed to create directory '" + dir + "'");
             }
         }
     }

http://git-wip-us.apache.org/repos/asf/activemq/blob/fc244f48/activemq-jdbc-store/src/main/java/org/apache/activemq/store/jdbc/JDBCPersistenceAdapter.java
----------------------------------------------------------------------
diff --git a/activemq-jdbc-store/src/main/java/org/apache/activemq/store/jdbc/JDBCPersistenceAdapter.java b/activemq-jdbc-store/src/main/java/org/apache/activemq/store/jdbc/JDBCPersistenceAdapter.java
index 7ff4ae0..a3a8250 100755
--- a/activemq-jdbc-store/src/main/java/org/apache/activemq/store/jdbc/JDBCPersistenceAdapter.java
+++ b/activemq-jdbc-store/src/main/java/org/apache/activemq/store/jdbc/JDBCPersistenceAdapter.java
@@ -34,6 +34,7 @@ import org.apache.activemq.ActiveMQMessageAudit;
 import org.apache.activemq.broker.BrokerService;
 import org.apache.activemq.broker.ConnectionContext;
 import org.apache.activemq.broker.Locker;
+import org.apache.activemq.broker.scheduler.JobSchedulerStore;
 import org.apache.activemq.command.ActiveMQDestination;
 import org.apache.activemq.command.ActiveMQQueue;
 import org.apache.activemq.command.ActiveMQTopic;
@@ -422,6 +423,7 @@ public class JDBCPersistenceAdapter extends DataSourceServiceSupport implements
         this.lockDataSource = dataSource;
     }
 
+    @Override
     public BrokerService getBrokerService() {
         return brokerService;
     }
@@ -846,4 +848,9 @@ public class JDBCPersistenceAdapter extends DataSourceServiceSupport implements
         }
         return result;
     }
+
+    @Override
+    public JobSchedulerStore createJobSchedulerStore() throws IOException, UnsupportedOperationException {
+        throw new UnsupportedOperationException();
+    }
 }

http://git-wip-us.apache.org/repos/asf/activemq/blob/fc244f48/activemq-jdbc-store/src/main/java/org/apache/activemq/store/journal/JournalPersistenceAdapter.java
----------------------------------------------------------------------
diff --git a/activemq-jdbc-store/src/main/java/org/apache/activemq/store/journal/JournalPersistenceAdapter.java b/activemq-jdbc-store/src/main/java/org/apache/activemq/store/journal/JournalPersistenceAdapter.java
index 565fc9f..cc5282f 100755
--- a/activemq-jdbc-store/src/main/java/org/apache/activemq/store/journal/JournalPersistenceAdapter.java
+++ b/activemq-jdbc-store/src/main/java/org/apache/activemq/store/journal/JournalPersistenceAdapter.java
@@ -31,6 +31,7 @@ import java.util.concurrent.ThreadFactory;
 import java.util.concurrent.ThreadPoolExecutor;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicBoolean;
+
 import org.apache.activeio.journal.InvalidRecordLocationException;
 import org.apache.activeio.journal.Journal;
 import org.apache.activeio.journal.JournalEventListener;
@@ -40,6 +41,7 @@ import org.apache.activeio.packet.Packet;
 import org.apache.activemq.broker.BrokerService;
 import org.apache.activemq.broker.BrokerServiceAware;
 import org.apache.activemq.broker.ConnectionContext;
+import org.apache.activemq.broker.scheduler.JobSchedulerStore;
 import org.apache.activemq.command.ActiveMQDestination;
 import org.apache.activemq.command.ActiveMQQueue;
 import org.apache.activemq.command.ActiveMQTopic;
@@ -78,14 +80,14 @@ import org.slf4j.LoggerFactory;
  * An implementation of {@link PersistenceAdapter} designed for use with a
  * {@link Journal} and then check pointing asynchronously on a timeout with some
  * other long term persistent storage.
- * 
+ *
  * @org.apache.xbean.XBean
- * 
+ *
  */
 public class JournalPersistenceAdapter implements PersistenceAdapter, JournalEventListener, UsageListener, BrokerServiceAware {
 
     private BrokerService brokerService;
-	
+
     protected Scheduler scheduler;
     private static final Logger LOG = LoggerFactory.getLogger(JournalPersistenceAdapter.class);
 
@@ -118,9 +120,9 @@ public class JournalPersistenceAdapter implements PersistenceAdapter, JournalEve
     private TaskRunnerFactory taskRunnerFactory;
     private File directory;
 
-    public JournalPersistenceAdapter() {        
+    public JournalPersistenceAdapter() {
     }
-    
+
     public JournalPersistenceAdapter(Journal journal, PersistenceAdapter longTermPersistence, TaskRunnerFactory taskRunnerFactory) throws IOException {
         setJournal(journal);
         setTaskRunnerFactory(taskRunnerFactory);
@@ -135,13 +137,14 @@ public class JournalPersistenceAdapter implements PersistenceAdapter, JournalEve
         this.journal = journal;
         journal.setJournalEventListener(this);
     }
-    
+
     public void setPersistenceAdapter(PersistenceAdapter longTermPersistence) {
         this.longTermPersistence = longTermPersistence;
     }
-    
+
     final Runnable createPeriodicCheckpointTask() {
         return new Runnable() {
+            @Override
             public void run() {
                 long lastTime = 0;
                 synchronized (this) {
@@ -158,11 +161,13 @@ public class JournalPersistenceAdapter implements PersistenceAdapter, JournalEve
      * @param usageManager The UsageManager that is controlling the
      *                destination's memory usage.
      */
+    @Override
     public void setUsageManager(SystemUsage usageManager) {
         this.usageManager = usageManager;
         longTermPersistence.setUsageManager(usageManager);
     }
 
+    @Override
     public Set<ActiveMQDestination> getDestinations() {
         Set<ActiveMQDestination> destinations = new HashSet<ActiveMQDestination>(longTermPersistence.getDestinations());
         destinations.addAll(queues.keySet());
@@ -178,6 +183,7 @@ public class JournalPersistenceAdapter implements PersistenceAdapter, JournalEve
         }
     }
 
+    @Override
     public MessageStore createQueueMessageStore(ActiveMQQueue destination) throws IOException {
         JournalMessageStore store = queues.get(destination);
         if (store == null) {
@@ -188,6 +194,7 @@ public class JournalPersistenceAdapter implements PersistenceAdapter, JournalEve
         return store;
     }
 
+    @Override
     public TopicMessageStore createTopicMessageStore(ActiveMQTopic destinationName) throws IOException {
         JournalTopicMessageStore store = topics.get(destinationName);
         if (store == null) {
@@ -203,6 +210,7 @@ public class JournalPersistenceAdapter implements PersistenceAdapter, JournalEve
      *
      * @param destination Destination to forget
      */
+    @Override
     public void removeQueueMessageStore(ActiveMQQueue destination) {
         queues.remove(destination);
     }
@@ -212,30 +220,37 @@ public class JournalPersistenceAdapter implements PersistenceAdapter, JournalEve
      *
      * @param destination Destination to forget
      */
+    @Override
     public void removeTopicMessageStore(ActiveMQTopic destination) {
         topics.remove(destination);
     }
 
+    @Override
     public TransactionStore createTransactionStore() throws IOException {
         return transactionStore;
     }
 
+    @Override
     public long getLastMessageBrokerSequenceId() throws IOException {
         return longTermPersistence.getLastMessageBrokerSequenceId();
     }
 
+    @Override
     public void beginTransaction(ConnectionContext context) throws IOException {
         longTermPersistence.beginTransaction(context);
     }
 
+    @Override
     public void commitTransaction(ConnectionContext context) throws IOException {
         longTermPersistence.commitTransaction(context);
     }
 
+    @Override
     public void rollbackTransaction(ConnectionContext context) throws IOException {
         longTermPersistence.rollbackTransaction(context);
     }
 
+    @Override
     public synchronized void start() throws Exception {
         if (!started.compareAndSet(false, true)) {
             return;
@@ -246,12 +261,14 @@ public class JournalPersistenceAdapter implements PersistenceAdapter, JournalEve
         }
 
         checkpointTask = taskRunnerFactory.createTaskRunner(new Task() {
+            @Override
             public boolean iterate() {
                 return doCheckpoint();
             }
         }, "ActiveMQ Journal Checkpoint Worker");
 
         checkpointExecutor = new ThreadPoolExecutor(maxCheckpointWorkers, maxCheckpointWorkers, 30, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(), new ThreadFactory() {
+            @Override
             public Thread newThread(Runnable runable) {
                 Thread t = new Thread(runable, "Journal checkpoint worker");
                 t.setPriority(7);
@@ -279,6 +296,7 @@ public class JournalPersistenceAdapter implements PersistenceAdapter, JournalEve
 
     }
 
+    @Override
     public void stop() throws Exception {
 
         this.usageManager.getMemoryUsage().removeUsageListener(this);
@@ -330,16 +348,17 @@ public class JournalPersistenceAdapter implements PersistenceAdapter, JournalEve
     /**
      * The Journal give us a call back so that we can move old data out of the
      * journal. Taking a checkpoint does this for us.
-     * 
+     *
      * @see org.apache.activemq.journal.JournalEventListener#overflowNotification(org.apache.activemq.journal.RecordLocation)
      */
+    @Override
     public void overflowNotification(RecordLocation safeLocation) {
         checkpoint(false, true);
     }
 
     /**
      * When we checkpoint we move all the journalled data to long term storage.
-     * 
+     *
      */
     public void checkpoint(boolean sync, boolean fullCheckpoint) {
         try {
@@ -369,13 +388,14 @@ public class JournalPersistenceAdapter implements PersistenceAdapter, JournalEve
         }
     }
 
+    @Override
     public void checkpoint(boolean sync) {
         checkpoint(sync, sync);
     }
 
     /**
      * This does the actual checkpoint.
-     * 
+     *
      * @return
      */
     public boolean doCheckpoint() {
@@ -398,7 +418,7 @@ public class JournalPersistenceAdapter implements PersistenceAdapter, JournalEve
             // We do many partial checkpoints (fullCheckpoint==false) to move
             // topic messages
             // to long term store as soon as possible.
-            // 
+            //
             // We want to avoid doing that for queue messages since removes the
             // come in the same
             // checkpoint cycle will nullify the previous message add.
@@ -411,6 +431,7 @@ public class JournalPersistenceAdapter implements PersistenceAdapter, JournalEve
                     try {
                         final JournalMessageStore ms = iterator.next();
                         FutureTask<RecordLocation> task = new FutureTask<RecordLocation>(new Callable<RecordLocation>() {
+                            @Override
                             public RecordLocation call() throws Exception {
                                 return ms.checkpoint();
                             }
@@ -428,6 +449,7 @@ public class JournalPersistenceAdapter implements PersistenceAdapter, JournalEve
                 try {
                     final JournalTopicMessageStore ms = iterator.next();
                     FutureTask<RecordLocation> task = new FutureTask<RecordLocation>(new Callable<RecordLocation>() {
+                        @Override
                         public RecordLocation call() throws Exception {
                             return ms.checkpoint();
                         }
@@ -505,7 +527,7 @@ public class JournalPersistenceAdapter implements PersistenceAdapter, JournalEve
     /**
      * Move all the messages that were in the journal into long term storage. We
      * just replay and do a checkpoint.
-     * 
+     *
      * @throws IOException
      * @throws IOException
      * @throws InvalidRecordLocationException
@@ -644,11 +666,11 @@ public class JournalPersistenceAdapter implements PersistenceAdapter, JournalEve
     public RecordLocation writeCommand(DataStructure command, boolean sync) throws IOException {
         if (started.get()) {
             try {
-        	    return journal.write(toPacket(wireFormat.marshal(command)), sync);
+                return journal.write(toPacket(wireFormat.marshal(command)), sync);
             } catch (IOException ioe) {
-        	    LOG.error("Cannot write to the journal", ioe);
-        	    brokerService.handleIOException(ioe);
-        	    throw ioe;
+                LOG.error("Cannot write to the journal", ioe);
+                brokerService.handleIOException(ioe);
+                throw ioe;
             }
         }
         throw new IOException("closed");
@@ -660,6 +682,7 @@ public class JournalPersistenceAdapter implements PersistenceAdapter, JournalEve
         return writeCommand(trace, sync);
     }
 
+    @Override
     public void onUsageChanged(Usage usage, int oldPercentUsage, int newPercentUsage) {
         newPercentUsage = (newPercentUsage / 10) * 10;
         oldPercentUsage = (oldPercentUsage / 10) * 10;
@@ -673,6 +696,7 @@ public class JournalPersistenceAdapter implements PersistenceAdapter, JournalEve
         return transactionStore;
     }
 
+    @Override
     public void deleteAllMessages() throws IOException {
         try {
             JournalTrace trace = new JournalTrace();
@@ -735,6 +759,7 @@ public class JournalPersistenceAdapter implements PersistenceAdapter, JournalEve
         return new ByteSequence(sequence.getData(), sequence.getOffset(), sequence.getLength());
     }
 
+    @Override
     public void setBrokerName(String brokerName) {
         longTermPersistence.setBrokerName(brokerName);
     }
@@ -744,18 +769,22 @@ public class JournalPersistenceAdapter implements PersistenceAdapter, JournalEve
         return "JournalPersistenceAdapter(" + longTermPersistence + ")";
     }
 
+    @Override
     public void setDirectory(File dir) {
         this.directory=dir;
     }
-    
+
+    @Override
     public File getDirectory(){
         return directory;
     }
-    
+
+    @Override
     public long size(){
         return 0;
     }
 
+    @Override
     public void setBrokerService(BrokerService brokerService) {
         this.brokerService = brokerService;
         PersistenceAdapter pa = getLongTermPersistence();
@@ -764,8 +793,14 @@ public class JournalPersistenceAdapter implements PersistenceAdapter, JournalEve
         }
     }
 
+    @Override
     public long getLastProducerSequenceId(ProducerId id) {
         return -1;
     }
 
+    @Override
+    public JobSchedulerStore createJobSchedulerStore() throws IOException, UnsupportedOperationException {
+        return longTermPersistence.createJobSchedulerStore();
+    }
+
 }

http://git-wip-us.apache.org/repos/asf/activemq/blob/fc244f48/activemq-kahadb-store/src/main/java/org/apache/activemq/store/kahadb/AbstractKahaDBMetaData.java
----------------------------------------------------------------------
diff --git a/activemq-kahadb-store/src/main/java/org/apache/activemq/store/kahadb/AbstractKahaDBMetaData.java b/activemq-kahadb-store/src/main/java/org/apache/activemq/store/kahadb/AbstractKahaDBMetaData.java
new file mode 100644
index 0000000..edb2750
--- /dev/null
+++ b/activemq-kahadb-store/src/main/java/org/apache/activemq/store/kahadb/AbstractKahaDBMetaData.java
@@ -0,0 +1,57 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.activemq.store.kahadb;
+
+import org.apache.activemq.store.kahadb.disk.journal.Location;
+import org.apache.activemq.store.kahadb.disk.page.Page;
+
+public abstract class AbstractKahaDBMetaData<T> implements KahaDBMetaData<T> {
+
+    private int state;
+    private Location lastUpdateLocation;
+    private Page<T> page;
+
+    @Override
+    public Page<T> getPage() {
+        return page;
+    }
+
+    @Override
+    public int getState() {
+        return state;
+    }
+
+    @Override
+    public Location getLastUpdateLocation() {
+        return lastUpdateLocation;
+    }
+
+    @Override
+    public void setPage(Page<T> page) {
+        this.page = page;
+    }
+
+    @Override
+    public void setState(int value) {
+        this.state = value;
+    }
+
+    @Override
+    public void setLastUpdateLocation(Location location) {
+        this.lastUpdateLocation = location;
+    }
+}


[07/16] activemq git commit: https://issues.apache.org/jira/browse/AMQ-5262

Posted by ha...@apache.org.
https://issues.apache.org/jira/browse/AMQ-5262

close connections when the connector is stopped.


Project: http://git-wip-us.apache.org/repos/asf/activemq/repo
Commit: http://git-wip-us.apache.org/repos/asf/activemq/commit/73cb029c
Tree: http://git-wip-us.apache.org/repos/asf/activemq/tree/73cb029c
Diff: http://git-wip-us.apache.org/repos/asf/activemq/diff/73cb029c

Branch: refs/heads/activemq-5.10.x
Commit: 73cb029c1b2f8c1f113b26e76ff5af9e98007726
Parents: 8f43810
Author: Timothy Bish <ta...@gmail.com>
Authored: Tue Jul 8 16:20:21 2014 -0400
Committer: Hadrian Zbarcea <ha...@apache.org>
Committed: Mon Dec 15 19:08:06 2014 -0500

----------------------------------------------------------------------
 .../activemq/network/jms/JmsConnector.java      | 31 +++++++++++++++-----
 1 file changed, 24 insertions(+), 7 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/activemq/blob/73cb029c/activemq-broker/src/main/java/org/apache/activemq/network/jms/JmsConnector.java
----------------------------------------------------------------------
diff --git a/activemq-broker/src/main/java/org/apache/activemq/network/jms/JmsConnector.java b/activemq-broker/src/main/java/org/apache/activemq/network/jms/JmsConnector.java
index afeb88a..6ddc6c5 100755
--- a/activemq-broker/src/main/java/org/apache/activemq/network/jms/JmsConnector.java
+++ b/activemq-broker/src/main/java/org/apache/activemq/network/jms/JmsConnector.java
@@ -29,7 +29,6 @@ import java.util.concurrent.atomic.AtomicReference;
 
 import javax.jms.Connection;
 import javax.jms.Destination;
-import javax.jms.QueueConnection;
 
 import org.apache.activemq.ActiveMQConnectionFactory;
 import org.apache.activemq.Service;
@@ -73,20 +72,21 @@ public abstract class JmsConnector implements Service {
 
     private ReconnectionPolicy policy = new ReconnectionPolicy();
     protected ThreadPoolExecutor connectionSerivce;
-    private List<DestinationBridge> inboundBridges = new CopyOnWriteArrayList<DestinationBridge>();
-    private List<DestinationBridge> outboundBridges = new CopyOnWriteArrayList<DestinationBridge>();
+    private final List<DestinationBridge> inboundBridges = new CopyOnWriteArrayList<DestinationBridge>();
+    private final List<DestinationBridge> outboundBridges = new CopyOnWriteArrayList<DestinationBridge>();
     private String name;
 
     private static LRUCache<Destination, DestinationBridge> createLRUCache() {
         return new LRUCache<Destination, DestinationBridge>() {
             private static final long serialVersionUID = -7446792754185879286L;
 
+            @Override
             protected boolean removeEldestEntry(Map.Entry<Destination, DestinationBridge> enty) {
                 if (size() > maxCacheSize) {
                     Iterator<Map.Entry<Destination, DestinationBridge>> iter = entrySet().iterator();
                     Map.Entry<Destination, DestinationBridge> lru = iter.next();
                     remove(lru.getKey());
-                    DestinationBridge bridge = (DestinationBridge)lru.getValue();
+                    DestinationBridge bridge = lru.getValue();
                     try {
                         bridge.stop();
                         LOG.info("Expired bridge: {}", bridge);
@@ -151,6 +151,7 @@ public abstract class JmsConnector implements Service {
         return true;
     }
 
+    @Override
     public void start() throws Exception {
         if (started.compareAndSet(false, true)) {
             init();
@@ -164,12 +165,27 @@ public abstract class JmsConnector implements Service {
         }
     }
 
+    @Override
     public void stop() throws Exception {
         if (started.compareAndSet(true, false)) {
 
             ThreadPoolUtils.shutdown(connectionSerivce);
             connectionSerivce = null;
 
+            if (foreignConnection.get() != null) {
+                try {
+                    foreignConnection.get().close();
+                } catch (Exception e) {
+                }
+            }
+
+            if (localConnection.get() != null) {
+                try {
+                    localConnection.get().close();
+                } catch (Exception e) {
+                }
+            }
+
             for (DestinationBridge bridge : inboundBridges) {
                 bridge.stop();
             }
@@ -480,7 +496,7 @@ public abstract class JmsConnector implements Service {
         // TODO - How do we handle the re-wiring of replyToBridges in this case.
         replyToBridges.clear();
 
-        if (this.foreignConnection.compareAndSet((QueueConnection)connection, null)) {
+        if (this.foreignConnection.compareAndSet(connection, null)) {
 
             // Stop the inbound bridges when the foreign connection is dropped since
             // the bridge has no consumer and needs to be restarted once a new connection
@@ -505,7 +521,7 @@ public abstract class JmsConnector implements Service {
                 }
             });
 
-        } else if (this.localConnection.compareAndSet((QueueConnection)connection, null)) {
+        } else if (this.localConnection.compareAndSet(connection, null)) {
 
             // Stop the outbound bridges when the local connection is dropped since
             // the bridge has no consumer and needs to be restarted once a new connection
@@ -614,7 +630,8 @@ public abstract class JmsConnector implements Service {
         this.failed.set(true);
     }
 
-    private ThreadFactory factory = new ThreadFactory() {
+    private final ThreadFactory factory = new ThreadFactory() {
+        @Override
         public Thread newThread(Runnable runnable) {
             Thread thread = new Thread(runnable, "JmsConnector Async Connection Task: ");
             thread.setDaemon(true);


[06/16] activemq git commit: https://issues.apache.org/jira/browse/AMQ-5125

Posted by ha...@apache.org.
https://issues.apache.org/jira/browse/AMQ-5125

Fix for potential deadlock when external classes synchronize on the
LevelDBStore instance which can deadlock the hawtDispatch runner thread
if a task also attempts to take the lock to protect some mutable state
values.


Project: http://git-wip-us.apache.org/repos/asf/activemq/repo
Commit: http://git-wip-us.apache.org/repos/asf/activemq/commit/8f438106
Tree: http://git-wip-us.apache.org/repos/asf/activemq/tree/8f438106
Diff: http://git-wip-us.apache.org/repos/asf/activemq/diff/8f438106

Branch: refs/heads/activemq-5.10.x
Commit: 8f43810679ad9a1d3ab4a28397ad0229fc8ffe31
Parents: 8ae10b9
Author: Timothy Bish <ta...@gmail.com>
Authored: Mon Jul 7 17:53:46 2014 -0400
Committer: Hadrian Zbarcea <ha...@apache.org>
Committed: Mon Dec 15 19:06:48 2014 -0500

----------------------------------------------------------------------
 .../apache/activemq/leveldb/LevelDBStore.scala  | 20 +++++++++++---------
 1 file changed, 11 insertions(+), 9 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/activemq/blob/8f438106/activemq-leveldb-store/src/main/scala/org/apache/activemq/leveldb/LevelDBStore.scala
----------------------------------------------------------------------
diff --git a/activemq-leveldb-store/src/main/scala/org/apache/activemq/leveldb/LevelDBStore.scala b/activemq-leveldb-store/src/main/scala/org/apache/activemq/leveldb/LevelDBStore.scala
index 146269d..42b25c6 100644
--- a/activemq-leveldb-store/src/main/scala/org/apache/activemq/leveldb/LevelDBStore.scala
+++ b/activemq-leveldb-store/src/main/scala/org/apache/activemq/leveldb/LevelDBStore.scala
@@ -183,6 +183,8 @@ class LevelDBStore extends LockableServiceSupport with BrokerServiceAware with P
   val topicsById = collection.mutable.HashMap[Long, LevelDBStore#LevelDBTopicMessageStore]()
   val plists = collection.mutable.HashMap[String, LevelDBStore#LevelDBPList]()
 
+  private val lock = new Object();
+
   def check_running = {
     if( this.isStopped ) {
       throw new SuppressReplyException("Store has been stopped")
@@ -539,12 +541,12 @@ class LevelDBStore extends LockableServiceSupport with BrokerServiceAware with P
 
 
   def getPList(name: String): PList = {
-    this.synchronized(plists.get(name)).getOrElse(db.createPList(name))
+    lock.synchronized(plists.get(name)).getOrElse(db.createPList(name))
   }
 
   def createPList(name: String, key: Long):LevelDBStore#LevelDBPList = {
     var rc = new LevelDBPList(name, key)
-    this.synchronized {
+    lock.synchronized {
       plists.put(name, rc)
     }
     rc
@@ -572,30 +574,30 @@ class LevelDBStore extends LockableServiceSupport with BrokerServiceAware with P
   }
 
   def createQueueMessageStore(destination: ActiveMQQueue):LevelDBStore#LevelDBMessageStore = {
-    this.synchronized(queues.get(destination)).getOrElse(db.createQueueStore(destination))
+    lock.synchronized(queues.get(destination)).getOrElse(db.createQueueStore(destination))
   }
 
   def createQueueMessageStore(destination: ActiveMQQueue, key: Long):LevelDBStore#LevelDBMessageStore = {
     var rc = new LevelDBMessageStore(destination, key)
-    this.synchronized {
+    lock.synchronized {
       queues.put(destination, rc)
     }
     rc
   }
 
-  def removeQueueMessageStore(destination: ActiveMQQueue): Unit = this synchronized {
+  def removeQueueMessageStore(destination: ActiveMQQueue): Unit = lock synchronized {
     queues.remove(destination).foreach { store=>
       db.destroyQueueStore(store.key)
     }
   }
 
   def createTopicMessageStore(destination: ActiveMQTopic):LevelDBStore#LevelDBTopicMessageStore = {
-    this.synchronized(topics.get(destination)).getOrElse(db.createTopicStore(destination))
+    lock.synchronized(topics.get(destination)).getOrElse(db.createTopicStore(destination))
   }
 
   def createTopicMessageStore(destination: ActiveMQTopic, key: Long):LevelDBStore#LevelDBTopicMessageStore = {
     var rc = new LevelDBTopicMessageStore(destination, key)
-    this synchronized {
+    lock synchronized {
       topics.put(destination, rc)
       topicsById.put(key, rc)
     }
@@ -772,7 +774,7 @@ class LevelDBStore extends LockableServiceSupport with BrokerServiceAware with P
   // This gts called when the store is first loading up, it restores
   // the existing durable subs..
   def createSubscription(sub:DurableSubscription) = {
-    this.synchronized(topicsById.get(sub.topicKey)) match {
+    lock.synchronized(topicsById.get(sub.topicKey)) match {
       case Some(topic) =>
         topic.synchronized {
           topic.subscriptions.put((sub.info.getClientId, sub.info.getSubcriptionName), sub)
@@ -785,7 +787,7 @@ class LevelDBStore extends LockableServiceSupport with BrokerServiceAware with P
 
   def getTopicGCPositions = {
     import collection.JavaConversions._
-    val topics = this.synchronized {
+    val topics = lock.synchronized {
       new ArrayList(topicsById.values())
     }
     topics.flatMap(_.gcPosition).toSeq


[10/16] activemq git commit: https://issues.apache.org/jira/browse/AMQ-5268

Posted by ha...@apache.org.
https://issues.apache.org/jira/browse/AMQ-5268

Explicity set the properties from the generic JMS pooled
connectionfactory as the introspection based tools can easily get stuck
on getters that cause recursion or on inner types that have methods
which allow chaining.


Project: http://git-wip-us.apache.org/repos/asf/activemq/repo
Commit: http://git-wip-us.apache.org/repos/asf/activemq/commit/00426a9a
Tree: http://git-wip-us.apache.org/repos/asf/activemq/tree/00426a9a
Diff: http://git-wip-us.apache.org/repos/asf/activemq/diff/00426a9a

Branch: refs/heads/activemq-5.10.x
Commit: 00426a9ae138df4d10b1e3beb72b2f60c9b786f9
Parents: f8ccdfb
Author: Timothy Bish <ta...@gmail.com>
Authored: Thu Jul 10 19:03:31 2014 -0400
Committer: Hadrian Zbarcea <ha...@apache.org>
Committed: Mon Dec 15 19:27:25 2014 -0500

----------------------------------------------------------------------
 .../jms/pool/PooledConnectionFactory.java       | 21 +++++++++
 .../activemq/pool/PooledConnectionFactory.java  |  8 ++--
 .../pool/PooledConnectionFactoryTest.java       | 47 ++++++++++++++++++++
 3 files changed, 73 insertions(+), 3 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/activemq/blob/00426a9a/activemq-jms-pool/src/main/java/org/apache/activemq/jms/pool/PooledConnectionFactory.java
----------------------------------------------------------------------
diff --git a/activemq-jms-pool/src/main/java/org/apache/activemq/jms/pool/PooledConnectionFactory.java b/activemq-jms-pool/src/main/java/org/apache/activemq/jms/pool/PooledConnectionFactory.java
index 12852ce..35e460a 100644
--- a/activemq-jms-pool/src/main/java/org/apache/activemq/jms/pool/PooledConnectionFactory.java
+++ b/activemq-jms-pool/src/main/java/org/apache/activemq/jms/pool/PooledConnectionFactory.java
@@ -16,6 +16,7 @@
  */
 package org.apache.activemq.jms.pool;
 
+import java.util.Properties;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicReference;
 
@@ -539,4 +540,24 @@ public class PooledConnectionFactory implements ConnectionFactory {
     public void setBlockIfSessionPoolIsFullTimeout(long blockIfSessionPoolIsFullTimeout) {
         this.blockIfSessionPoolIsFullTimeout = blockIfSessionPoolIsFullTimeout;
     }
+
+    /**
+     * Called by any superclass that implements a JNDIReferencable or similar that needs to collect
+     * the properties of this class for storage etc.
+     *
+     * This method should be updated any time there is a new property added.
+     *
+     * @param props
+     *        a properties object that should be filled in with this objects property values.
+     */
+    protected void populateProperties(Properties props) {
+        props.setProperty("maximumActiveSessionPerConnection", Integer.toString(getMaximumActiveSessionPerConnection()));
+        props.setProperty("maxConnections", Integer.toString(getMaxConnections()));
+        props.setProperty("idleTimeout", Integer.toString(getIdleTimeout()));
+        props.setProperty("expiryTimeout", Long.toString(getExpiryTimeout()));
+        props.setProperty("timeBetweenExpirationCheckMillis", Long.toString(getTimeBetweenExpirationCheckMillis()));
+        props.setProperty("createConnectionOnStartup", Boolean.toString(isCreateConnectionOnStartup()));
+        props.setProperty("useAnonymousProducers", Boolean.toString(isUseAnonymousProducers()));
+        props.setProperty("blockIfSessionPoolIsFullTimeout", Long.toString(getBlockIfSessionPoolIsFullTimeout()));
+    }
 }

http://git-wip-us.apache.org/repos/asf/activemq/blob/00426a9a/activemq-pool/src/main/java/org/apache/activemq/pool/PooledConnectionFactory.java
----------------------------------------------------------------------
diff --git a/activemq-pool/src/main/java/org/apache/activemq/pool/PooledConnectionFactory.java b/activemq-pool/src/main/java/org/apache/activemq/pool/PooledConnectionFactory.java
index 62b97f9..1cd5130 100644
--- a/activemq-pool/src/main/java/org/apache/activemq/pool/PooledConnectionFactory.java
+++ b/activemq-pool/src/main/java/org/apache/activemq/pool/PooledConnectionFactory.java
@@ -19,9 +19,11 @@ package org.apache.activemq.pool;
 import java.io.IOException;
 import java.util.HashMap;
 import java.util.Properties;
+
 import javax.jms.Connection;
 import javax.naming.NamingException;
 import javax.naming.Reference;
+
 import org.apache.activemq.ActiveMQConnection;
 import org.apache.activemq.ActiveMQConnectionFactory;
 import org.apache.activemq.Service;
@@ -55,6 +57,7 @@ public class PooledConnectionFactory extends org.apache.activemq.jms.pool.Pooled
         setConnectionFactory(new ActiveMQConnectionFactory(brokerURL));
     }
 
+    @SuppressWarnings({ "unchecked", "rawtypes" })
     protected void buildFromProperties(Properties props) {
         ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory();
         activeMQConnectionFactory.buildFromProperties(props);
@@ -62,9 +65,10 @@ public class PooledConnectionFactory extends org.apache.activemq.jms.pool.Pooled
         IntrospectionSupport.setProperties(this, new HashMap(props), POOL_PROPS_PREFIX);
     }
 
+    @Override
     protected void populateProperties(Properties props) {
         ((ActiveMQConnectionFactory)getConnectionFactory()).populateProperties(props);
-        IntrospectionSupport.getProperties(this, props, POOL_PROPS_PREFIX);
+        super.populateProperties(props);
     }
 
     @Override
@@ -79,7 +83,6 @@ public class PooledConnectionFactory extends org.apache.activemq.jms.pool.Pooled
         return properties;
     }
 
-
     @Override
     public Reference getReference() throws NamingException {
         return JNDIReferenceFactory.createReference(this.getClass().getName(), this);
@@ -137,5 +140,4 @@ public class PooledConnectionFactory extends org.apache.activemq.jms.pool.Pooled
             }
         };
     }
-
 }

http://git-wip-us.apache.org/repos/asf/activemq/blob/00426a9a/activemq-pool/src/test/java/org/apache/activemq/pool/PooledConnectionFactoryTest.java
----------------------------------------------------------------------
diff --git a/activemq-pool/src/test/java/org/apache/activemq/pool/PooledConnectionFactoryTest.java b/activemq-pool/src/test/java/org/apache/activemq/pool/PooledConnectionFactoryTest.java
new file mode 100644
index 0000000..e2e33cd
--- /dev/null
+++ b/activemq-pool/src/test/java/org/apache/activemq/pool/PooledConnectionFactoryTest.java
@@ -0,0 +1,47 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.activemq.pool;
+
+import static org.junit.Assert.assertNotNull;
+
+import javax.naming.Reference;
+
+import org.junit.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Test JNDI
+ */
+public class PooledConnectionFactoryTest {
+
+    private final Logger LOG = LoggerFactory.getLogger(PooledConnectionFactoryTest.class);
+
+    @Test(timeout=240000)
+    public void testGetReference() throws Exception {
+        PooledConnectionFactory factory = createPooledConnectionFactory();
+        Reference ref = factory.getReference();
+        assertNotNull(ref);
+    }
+
+    protected PooledConnectionFactory createPooledConnectionFactory() {
+        PooledConnectionFactory cf = new PooledConnectionFactory(
+            "vm://localhost?broker.persistent=false");
+        LOG.debug("ConnectionFactory initialized.");
+        return cf;
+    }
+}


[12/16] activemq git commit: https://issues.apache.org/jira/browse/AMQ-3758

Posted by ha...@apache.org.
http://git-wip-us.apache.org/repos/asf/activemq/blob/fc244f48/activemq-kahadb-store/src/main/java/org/apache/activemq/store/kahadb/scheduler/legacy/LegacyStoreReplayer.java
----------------------------------------------------------------------
diff --git a/activemq-kahadb-store/src/main/java/org/apache/activemq/store/kahadb/scheduler/legacy/LegacyStoreReplayer.java b/activemq-kahadb-store/src/main/java/org/apache/activemq/store/kahadb/scheduler/legacy/LegacyStoreReplayer.java
new file mode 100644
index 0000000..92563f4
--- /dev/null
+++ b/activemq-kahadb-store/src/main/java/org/apache/activemq/store/kahadb/scheduler/legacy/LegacyStoreReplayer.java
@@ -0,0 +1,155 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.activemq.store.kahadb.scheduler.legacy;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.List;
+import java.util.Set;
+
+import org.apache.activemq.store.kahadb.data.KahaAddScheduledJobCommand;
+import org.apache.activemq.store.kahadb.scheduler.JobSchedulerStoreImpl;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Used to upgrade a Legacy Job Scheduler store to the latest version this class
+ * loads a found legacy scheduler store and generates new add commands for all
+ * jobs currently in the store.
+ */
+public class LegacyStoreReplayer {
+
+    static final Logger LOG = LoggerFactory.getLogger(LegacyStoreReplayer.class);
+
+    private LegacyJobSchedulerStoreImpl store;
+    private final File legacyStoreDirectory;
+
+    /**
+     * Creates a new Legacy Store Replayer with the given target store
+     * @param targetStore
+     * @param directory
+     */
+    public LegacyStoreReplayer(File directory) {
+        this.legacyStoreDirectory = directory;
+    }
+
+    /**
+     * Loads the legacy store and prepares it for replay into a newer Store instance.
+     *
+     * @throws IOException if an error occurs while reading in the legacy store.
+     */
+    public void load() throws IOException {
+
+        store = new LegacyJobSchedulerStoreImpl();
+        store.setDirectory(legacyStoreDirectory);
+        store.setFailIfDatabaseIsLocked(true);
+
+        try {
+            store.start();
+        } catch (IOException ioe) {
+            LOG.warn("Legacy store load failed: ", ioe);
+            throw ioe;
+        } catch (Exception e) {
+            LOG.warn("Legacy store load failed: ", e);
+            throw new IOException(e);
+        }
+    }
+
+    /**
+     * Unloads a previously loaded legacy store to release any resources associated with it.
+     *
+     * Once a store is unloaded it cannot be replayed again until it has been reloaded.
+     * @throws IOException
+     */
+    public void unload() throws IOException {
+
+        if (store != null) {
+            try {
+                store.stop();
+            } catch (Exception e) {
+                LOG.warn("Legacy store unload failed: ", e);
+                throw new IOException(e);
+            } finally {
+                store = null;
+            }
+        }
+    }
+
+    /**
+     * Performs a replay of scheduled jobs into the target JobSchedulerStore.
+     *
+     * @param targetStore
+     *      The JobSchedulerStore that will receive the replay events from the legacy store.
+     *
+     * @throws IOException if an error occurs during replay of the legacy store.
+     */
+    public void startReplay(JobSchedulerStoreImpl targetStore) throws IOException {
+        checkLoaded();
+
+        if (targetStore == null) {
+            throw new IOException("Cannot replay to a null store");
+        }
+
+        try {
+            Set<String> schedulers = store.getJobSchedulerNames();
+            if (!schedulers.isEmpty()) {
+
+                for (String name : schedulers) {
+                    LegacyJobSchedulerImpl scheduler = store.getJobScheduler(name);
+                    LOG.info("Replay of legacy store {} starting.", name);
+                    replayScheduler(scheduler, targetStore);
+                }
+            }
+
+            LOG.info("Replay of legacy store complate.");
+        } catch (IOException ioe) {
+            LOG.warn("Failed during replay of legacy store: ", ioe);
+            throw ioe;
+        } catch (Exception e) {
+            LOG.warn("Failed during replay of legacy store: ", e);
+            throw new IOException(e);
+        }
+    }
+
+    private final void replayScheduler(LegacyJobSchedulerImpl legacy, JobSchedulerStoreImpl target) throws Exception {
+        List<LegacyJobImpl> jobs = legacy.getAllJobs();
+
+        String schedulerName = legacy.getName();
+
+        for (LegacyJobImpl job : jobs) {
+            LOG.trace("Storing job from legacy store to new store: {}", job);
+            KahaAddScheduledJobCommand newJob = new KahaAddScheduledJobCommand();
+            newJob.setScheduler(schedulerName);
+            newJob.setJobId(job.getJobId());
+            newJob.setStartTime(job.getStartTime());
+            newJob.setCronEntry(job.getCronEntry());
+            newJob.setDelay(job.getDelay());
+            newJob.setPeriod(job.getPeriod());
+            newJob.setRepeat(job.getRepeat());
+            newJob.setNextExecutionTime(job.getNextExecutionTime());
+            newJob.setPayload(job.getPayload());
+
+            target.store(newJob);
+        }
+    }
+
+    private final void checkLoaded() throws IOException {
+        if (this.store == null) {
+            throw new IOException("Cannot replay until legacy store is loaded.");
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/activemq/blob/fc244f48/activemq-kahadb-store/src/main/proto/journal-data.proto
----------------------------------------------------------------------
diff --git a/activemq-kahadb-store/src/main/proto/journal-data.proto b/activemq-kahadb-store/src/main/proto/journal-data.proto
index 8290c4c..01607a5 100644
--- a/activemq-kahadb-store/src/main/proto/journal-data.proto
+++ b/activemq-kahadb-store/src/main/proto/journal-data.proto
@@ -32,6 +32,11 @@ enum KahaEntryType {
   KAHA_PRODUCER_AUDIT_COMMAND = 8;
   KAHA_ACK_MESSAGE_FILE_MAP_COMMAND = 9;
   KAHA_UPDATE_MESSAGE_COMMAND = 10;
+  KAHA_ADD_SCHEDULED_JOB_COMMAND = 11;
+  KAHA_RESCHEDULE_JOB_COMMAND = 12;
+  KAHA_REMOVE_SCHEDULED_JOB_COMMAND = 13;
+  KAHA_REMOVE_SCHEDULED_JOBS_COMMAND = 14;
+  KAHA_DESTROY_SCHEDULER_COMMAND = 15;
 }
 
 message KahaTraceCommand {
@@ -179,6 +184,62 @@ message KahaLocation {
   required int32 offset = 2;
 }
 
+message KahaAddScheduledJobCommand {
+  //| option java_implments = "org.apache.activemq.store.kahadb.JournalCommand<KahaAddScheduledJobCommand>";
+  //| option java_visitor = "org.apache.activemq.store.kahadb.Visitor:void:java.io.IOException";
+  //| option java_type_method = "KahaEntryType";
+
+  required string scheduler=1;
+  required string job_id=2;
+  required int64 start_time=3;
+  required string cron_entry=4;
+  required int64 delay=5;
+  required int64 period=6;
+  required int32 repeat=7;
+  required bytes payload=8;
+  required int64 next_execution_time=9;
+}
+
+message KahaRescheduleJobCommand {
+  //| option java_implments = "org.apache.activemq.store.kahadb.JournalCommand<KahaRescheduleJobCommand>";
+  //| option java_visitor = "org.apache.activemq.store.kahadb.Visitor:void:java.io.IOException";
+  //| option java_type_method = "KahaEntryType";
+
+  required string scheduler=1;
+  required string job_id=2;
+  required int64 execution_time=3;
+  required int64 next_execution_time=4;
+  required int32 rescheduled_count=5;
+}
+
+message KahaRemoveScheduledJobCommand {
+  //| option java_implments = "org.apache.activemq.store.kahadb.JournalCommand<KahaRemoveScheduledJobCommand>";
+  //| option java_visitor = "org.apache.activemq.store.kahadb.Visitor:void:java.io.IOException";
+  //| option java_type_method = "KahaEntryType";
+
+  required string scheduler=1;
+  required string job_id=2;
+  required int64 next_execution_time=3;
+}
+
+message KahaRemoveScheduledJobsCommand {
+  //| option java_implments = "org.apache.activemq.store.kahadb.JournalCommand<KahaRemoveScheduledJobsCommand>";
+  //| option java_visitor = "org.apache.activemq.store.kahadb.Visitor:void:java.io.IOException";
+  //| option java_type_method = "KahaEntryType";
+
+  required string scheduler=1;
+  required int64 start_time=2;
+  required int64 end_time=3;
+}
+
+message KahaDestroySchedulerCommand {
+  //| option java_implments = "org.apache.activemq.store.kahadb.JournalCommand<KahaDestroySchedulerCommand>";
+  //| option java_visitor = "org.apache.activemq.store.kahadb.Visitor:void:java.io.IOException";
+  //| option java_type_method = "KahaEntryType";
+
+  required string scheduler=1;
+}
+
 // TODO things to ponder
 // should we move more message fields
 // that are set by the sender (and rarely required by the broker

http://git-wip-us.apache.org/repos/asf/activemq/blob/fc244f48/activemq-leveldb-store/src/main/scala/org/apache/activemq/leveldb/LevelDBStore.scala
----------------------------------------------------------------------
diff --git a/activemq-leveldb-store/src/main/scala/org/apache/activemq/leveldb/LevelDBStore.scala b/activemq-leveldb-store/src/main/scala/org/apache/activemq/leveldb/LevelDBStore.scala
index 42b25c6..01d5170 100644
--- a/activemq-leveldb-store/src/main/scala/org/apache/activemq/leveldb/LevelDBStore.scala
+++ b/activemq-leveldb-store/src/main/scala/org/apache/activemq/leveldb/LevelDBStore.scala
@@ -35,6 +35,7 @@ import org.apache.activemq.leveldb.util.Log
 import org.apache.activemq.store.PList.PListIterator
 import org.fusesource.hawtbuf.{UTF8Buffer, DataByteArrayOutputStream}
 import org.fusesource.hawtdispatch;
+import org.apache.activemq.broker.scheduler.JobSchedulerStore;
 
 object LevelDBStore extends Log {
   val DEFAULT_DIRECTORY = new File("LevelDB");
@@ -604,6 +605,10 @@ class LevelDBStore extends LockableServiceSupport with BrokerServiceAware with P
     rc
   }
 
+  def createJobSchedulerStore():JobSchedulerStore = {
+    throw new UnsupportedOperationException();
+  }
+
   def removeTopicMessageStore(destination: ActiveMQTopic): Unit = {
     topics.remove(destination).foreach { store=>
       store.subscriptions.values.foreach { sub =>

http://git-wip-us.apache.org/repos/asf/activemq/blob/fc244f48/activemq-leveldb-store/src/main/scala/org/apache/activemq/leveldb/replicated/ProxyLevelDBStore.scala
----------------------------------------------------------------------
diff --git a/activemq-leveldb-store/src/main/scala/org/apache/activemq/leveldb/replicated/ProxyLevelDBStore.scala b/activemq-leveldb-store/src/main/scala/org/apache/activemq/leveldb/replicated/ProxyLevelDBStore.scala
index bd3904f..efd55f3 100644
--- a/activemq-leveldb-store/src/main/scala/org/apache/activemq/leveldb/replicated/ProxyLevelDBStore.scala
+++ b/activemq-leveldb-store/src/main/scala/org/apache/activemq/leveldb/replicated/ProxyLevelDBStore.scala
@@ -25,6 +25,7 @@ import java.io.File
 import java.io.IOException
 import java.util.Set
 import org.apache.activemq.util.{ServiceStopper, ServiceSupport}
+import org.apache.activemq.broker.scheduler.JobSchedulerStore
 
 /**
  */
@@ -44,6 +45,10 @@ abstract class ProxyLevelDBStore extends LockableServiceSupport with BrokerServi
     return proxy_target.createTopicMessageStore(destination)
   }
 
+  def createJobSchedulerStore():JobSchedulerStore = {
+    return proxy_target.createJobSchedulerStore()
+  }
+
   def setDirectory(dir: File) {
     proxy_target.setDirectory(dir)
   }

http://git-wip-us.apache.org/repos/asf/activemq/blob/fc244f48/activemq-unit-tests/src/test/java/org/apache/activemq/broker/scheduler/JobSchedulerBrokerShutdownTest.java
----------------------------------------------------------------------
diff --git a/activemq-unit-tests/src/test/java/org/apache/activemq/broker/scheduler/JobSchedulerBrokerShutdownTest.java b/activemq-unit-tests/src/test/java/org/apache/activemq/broker/scheduler/JobSchedulerBrokerShutdownTest.java
index 04277bf..641ee97 100644
--- a/activemq-unit-tests/src/test/java/org/apache/activemq/broker/scheduler/JobSchedulerBrokerShutdownTest.java
+++ b/activemq-unit-tests/src/test/java/org/apache/activemq/broker/scheduler/JobSchedulerBrokerShutdownTest.java
@@ -39,6 +39,7 @@ public class JobSchedulerBrokerShutdownTest extends EmbeddedBrokerTestSupport {
 
         BrokerService broker = super.createBroker();
         broker.setSchedulerSupport(true);
+        broker.setDataDirectory("target");
         broker.setSchedulerDirectoryFile(schedulerDirectory);
         broker.getSystemUsage().getStoreUsage().setLimit(1 * 512);
         broker.deleteAllMessages();

http://git-wip-us.apache.org/repos/asf/activemq/blob/fc244f48/activemq-unit-tests/src/test/java/org/apache/activemq/broker/scheduler/JobSchedulerJmxManagementTests.java
----------------------------------------------------------------------
diff --git a/activemq-unit-tests/src/test/java/org/apache/activemq/broker/scheduler/JobSchedulerJmxManagementTests.java b/activemq-unit-tests/src/test/java/org/apache/activemq/broker/scheduler/JobSchedulerJmxManagementTests.java
new file mode 100644
index 0000000..8adb980
--- /dev/null
+++ b/activemq-unit-tests/src/test/java/org/apache/activemq/broker/scheduler/JobSchedulerJmxManagementTests.java
@@ -0,0 +1,155 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.activemq.broker.scheduler;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import java.util.List;
+
+import javax.jms.Connection;
+import javax.jms.MessageProducer;
+import javax.jms.Session;
+import javax.jms.TextMessage;
+import javax.management.openmbean.TabularData;
+
+import org.apache.activemq.ScheduledMessage;
+import org.apache.activemq.broker.jmx.JobSchedulerViewMBean;
+import org.apache.activemq.util.Wait;
+import org.junit.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Tests of the JMX JobSchedulerStore management MBean.
+ */
+public class JobSchedulerJmxManagementTests extends JobSchedulerTestSupport {
+
+    private static final Logger LOG = LoggerFactory.getLogger(JobSchedulerJmxManagementTests.class);
+
+    @Test
+    public void testJobSchedulerMBeanIsRegistered() throws Exception {
+        JobSchedulerViewMBean view = getJobSchedulerMBean();
+        assertNotNull(view);
+        assertTrue(view.getAllJobs().isEmpty());
+    }
+
+    @Test
+    public void testGetNumberOfJobs() throws Exception {
+        JobSchedulerViewMBean view = getJobSchedulerMBean();
+        assertNotNull(view);
+        assertTrue(view.getAllJobs().isEmpty());
+        scheduleMessage(60000, -1, -1);
+        assertFalse(view.getAllJobs().isEmpty());
+        assertEquals(1, view.getAllJobs().size());
+        scheduleMessage(60000, -1, -1);
+        assertEquals(2, view.getAllJobs().size());
+    }
+
+    @Test
+    public void testRemvoeJob() throws Exception {
+        JobSchedulerViewMBean view = getJobSchedulerMBean();
+        assertNotNull(view);
+        assertTrue(view.getAllJobs().isEmpty());
+        scheduleMessage(60000, -1, -1);
+        assertFalse(view.getAllJobs().isEmpty());
+        TabularData jobs = view.getAllJobs();
+        assertEquals(1, jobs.size());
+        for (Object key : jobs.keySet()) {
+            String jobId = ((List<?>)key).get(0).toString();
+            LOG.info("Attempting to remove Job: {}", jobId);
+            view.removeJob(jobId);
+        }
+        assertTrue(view.getAllJobs().isEmpty());
+    }
+
+    @Test
+    public void testRemvoeJobInRange() throws Exception {
+        JobSchedulerViewMBean view = getJobSchedulerMBean();
+        assertNotNull(view);
+        assertTrue(view.getAllJobs().isEmpty());
+        scheduleMessage(60000, -1, -1);
+        assertFalse(view.getAllJobs().isEmpty());
+        String now = JobSupport.getDateTime(System.currentTimeMillis());
+        String later = JobSupport.getDateTime(System.currentTimeMillis() + 120 * 1000);
+        view.removeAllJobs(now, later);
+        assertTrue(view.getAllJobs().isEmpty());
+    }
+
+    @Test
+    public void testGetNextScheduledJob() throws Exception {
+        JobSchedulerViewMBean view = getJobSchedulerMBean();
+        assertNotNull(view);
+        assertTrue(view.getAllJobs().isEmpty());
+        scheduleMessage(60000, -1, -1);
+        assertFalse(view.getAllJobs().isEmpty());
+        long before = System.currentTimeMillis() + 57 * 1000;
+        long toLate = System.currentTimeMillis() + 63 * 1000;
+        String next = view.getNextScheduleTime();
+        long nextTime = JobSupport.getDataTime(next);
+        LOG.info("Next Scheduled Time: {}", next);
+        assertTrue(nextTime > before);
+        assertTrue(nextTime < toLate);
+    }
+
+    @Test
+    public void testGetExecutionCount() throws Exception {
+        final JobSchedulerViewMBean view = getJobSchedulerMBean();
+        assertNotNull(view);
+        assertTrue(view.getAllJobs().isEmpty());
+        scheduleMessage(10000, 1000, 10);
+        assertFalse(view.getAllJobs().isEmpty());
+        TabularData jobs = view.getAllJobs();
+        assertEquals(1, jobs.size());
+        String jobId = null;
+        for (Object key : jobs.keySet()) {
+            jobId = ((List<?>)key).get(0).toString();
+        }
+
+        final String fixedJobId = jobId;
+        LOG.info("Attempting to get execution count for Job: {}", jobId);
+        assertEquals(0, view.getExecutionCount(jobId));
+
+        assertTrue("Should execute again", Wait.waitFor(new Wait.Condition() {
+
+            @Override
+            public boolean isSatisified() throws Exception {
+                return view.getExecutionCount(fixedJobId) > 0;
+            }
+        }));
+    }
+
+    @Override
+    protected boolean isUseJmx() {
+        return true;
+    }
+
+    protected void scheduleMessage(int time, int period, int repeat) throws Exception {
+        Connection connection = createConnection();
+        Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
+        MessageProducer producer = session.createProducer(destination);
+        TextMessage message = session.createTextMessage("test msg");
+        message.setLongProperty(ScheduledMessage.AMQ_SCHEDULED_DELAY, time);
+        message.setLongProperty(ScheduledMessage.AMQ_SCHEDULED_PERIOD, period);
+        message.setIntProperty(ScheduledMessage.AMQ_SCHEDULED_REPEAT, repeat);
+        producer.send(message);
+        connection.close();
+    }
+}

http://git-wip-us.apache.org/repos/asf/activemq/blob/fc244f48/activemq-unit-tests/src/test/java/org/apache/activemq/broker/scheduler/JobSchedulerManagementTest.java
----------------------------------------------------------------------
diff --git a/activemq-unit-tests/src/test/java/org/apache/activemq/broker/scheduler/JobSchedulerManagementTest.java b/activemq-unit-tests/src/test/java/org/apache/activemq/broker/scheduler/JobSchedulerManagementTest.java
index bc89d9e..c82f8ef 100644
--- a/activemq-unit-tests/src/test/java/org/apache/activemq/broker/scheduler/JobSchedulerManagementTest.java
+++ b/activemq-unit-tests/src/test/java/org/apache/activemq/broker/scheduler/JobSchedulerManagementTest.java
@@ -16,7 +16,11 @@
  */
 package org.apache.activemq.broker.scheduler;
 
-import java.io.File;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.fail;
+
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 
@@ -29,18 +33,17 @@ import javax.jms.MessageProducer;
 import javax.jms.Session;
 import javax.jms.TextMessage;
 
-import org.apache.activemq.EmbeddedBrokerTestSupport;
 import org.apache.activemq.ScheduledMessage;
-import org.apache.activemq.broker.BrokerService;
-import org.apache.activemq.util.IOHelper;
 import org.apache.activemq.util.IdGenerator;
+import org.junit.Test;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-public class JobSchedulerManagementTest extends EmbeddedBrokerTestSupport {
+public class JobSchedulerManagementTest extends JobSchedulerTestSupport {
 
     private static final transient Logger LOG = LoggerFactory.getLogger(JobSchedulerManagementTest.class);
 
+    @Test
     public void testRemoveAllScheduled() throws Exception {
         final int COUNT = 5;
         Connection connection = createConnection();
@@ -77,6 +80,7 @@ public class JobSchedulerManagementTest extends EmbeddedBrokerTestSupport {
         assertEquals(latch.getCount(), COUNT);
     }
 
+    @Test
     public void testRemoveAllScheduledAtTime() throws Exception {
         final int COUNT = 3;
         Connection connection = createConnection();
@@ -122,8 +126,7 @@ public class JobSchedulerManagementTest extends EmbeddedBrokerTestSupport {
         // Send the remove request
         MessageProducer producer = session.createProducer(management);
         Message request = session.createMessage();
-        request.setStringProperty(ScheduledMessage.AMQ_SCHEDULER_ACTION,
-                                  ScheduledMessage.AMQ_SCHEDULER_ACTION_REMOVEALL);
+        request.setStringProperty(ScheduledMessage.AMQ_SCHEDULER_ACTION, ScheduledMessage.AMQ_SCHEDULER_ACTION_REMOVEALL);
         request.setStringProperty(ScheduledMessage.AMQ_SCHEDULER_ACTION_START_TIME, Long.toString(start));
         request.setStringProperty(ScheduledMessage.AMQ_SCHEDULER_ACTION_END_TIME, Long.toString(end));
         producer.send(request);
@@ -143,6 +146,7 @@ public class JobSchedulerManagementTest extends EmbeddedBrokerTestSupport {
         assertEquals(2, latch.getCount());
     }
 
+    @Test
     public void testBrowseAllScheduled() throws Exception {
         final int COUNT = 10;
         Connection connection = createConnection();
@@ -191,7 +195,8 @@ public class JobSchedulerManagementTest extends EmbeddedBrokerTestSupport {
         Thread.sleep(2000);
         assertEquals(latch.getCount(), COUNT);
 
-        // now see if we got all the scheduled messages on the browse destination.
+        // now see if we got all the scheduled messages on the browse
+        // destination.
         latch.await(10, TimeUnit.SECONDS);
         assertEquals(browsedLatch.getCount(), 0);
 
@@ -200,6 +205,7 @@ public class JobSchedulerManagementTest extends EmbeddedBrokerTestSupport {
         assertEquals(latch.getCount(), 0);
     }
 
+    @Test
     public void testBrowseWindowlScheduled() throws Exception {
         final int COUNT = 10;
         Connection connection = createConnection();
@@ -255,15 +261,18 @@ public class JobSchedulerManagementTest extends EmbeddedBrokerTestSupport {
         Thread.sleep(2000);
         assertEquals(COUNT + 2, latch.getCount());
 
-        // now see if we got all the scheduled messages on the browse destination.
+        // now see if we got all the scheduled messages on the browse
+        // destination.
         latch.await(15, TimeUnit.SECONDS);
         assertEquals(0, browsedLatch.getCount());
 
-        // now see if we got all the scheduled messages on the browse destination.
+        // now see if we got all the scheduled messages on the browse
+        // destination.
         latch.await(20, TimeUnit.SECONDS);
         assertEquals(0, latch.getCount());
     }
 
+    @Test
     public void testRemoveScheduled() throws Exception {
         final int COUNT = 10;
         Connection connection = createConnection();
@@ -297,8 +306,7 @@ public class JobSchedulerManagementTest extends EmbeddedBrokerTestSupport {
 
         // Send the browse request
         Message request = session.createMessage();
-        request.setStringProperty(ScheduledMessage.AMQ_SCHEDULER_ACTION,
-                                  ScheduledMessage.AMQ_SCHEDULER_ACTION_BROWSE);
+        request.setStringProperty(ScheduledMessage.AMQ_SCHEDULER_ACTION, ScheduledMessage.AMQ_SCHEDULER_ACTION_BROWSE);
         request.setJMSReplyTo(browseDest);
         producer.send(request);
 
@@ -307,14 +315,12 @@ public class JobSchedulerManagementTest extends EmbeddedBrokerTestSupport {
             Message message = browser.receive(2000);
             assertNotNull(message);
 
-            try{
+            try {
                 Message remove = session.createMessage();
-                remove.setStringProperty(ScheduledMessage.AMQ_SCHEDULER_ACTION,
-                        ScheduledMessage.AMQ_SCHEDULER_ACTION_REMOVE);
-                remove.setStringProperty(ScheduledMessage.AMQ_SCHEDULED_ID,
-                        message.getStringProperty(ScheduledMessage.AMQ_SCHEDULED_ID));
+                remove.setStringProperty(ScheduledMessage.AMQ_SCHEDULER_ACTION, ScheduledMessage.AMQ_SCHEDULER_ACTION_REMOVE);
+                remove.setStringProperty(ScheduledMessage.AMQ_SCHEDULED_ID, message.getStringProperty(ScheduledMessage.AMQ_SCHEDULED_ID));
                 producer.send(remove);
-            } catch(Exception e) {
+            } catch (Exception e) {
             }
         }
 
@@ -323,6 +329,7 @@ public class JobSchedulerManagementTest extends EmbeddedBrokerTestSupport {
         assertEquals(COUNT, latch.getCount());
     }
 
+    @Test
     public void testRemoveNotScheduled() throws Exception {
         Connection connection = createConnection();
 
@@ -333,19 +340,19 @@ public class JobSchedulerManagementTest extends EmbeddedBrokerTestSupport {
 
         MessageProducer producer = session.createProducer(management);
 
-        try{
+        try {
 
             // Send the remove request
             Message remove = session.createMessage();
-            remove.setStringProperty(ScheduledMessage.AMQ_SCHEDULER_ACTION,
-                    ScheduledMessage.AMQ_SCHEDULER_ACTION_REMOVEALL);
+            remove.setStringProperty(ScheduledMessage.AMQ_SCHEDULER_ACTION, ScheduledMessage.AMQ_SCHEDULER_ACTION_REMOVEALL);
             remove.setStringProperty(ScheduledMessage.AMQ_SCHEDULED_ID, new IdGenerator().generateId());
             producer.send(remove);
-        } catch(Exception e) {
+        } catch (Exception e) {
             fail("Caught unexpected exception during remove of unscheduled message.");
         }
     }
 
+    @Test
     public void testBrowseWithSelector() throws Exception {
         Connection connection = createConnection();
 
@@ -362,7 +369,7 @@ public class JobSchedulerManagementTest extends EmbeddedBrokerTestSupport {
         Destination browseDest = session.createTemporaryTopic();
 
         // Create the "Browser"
-        MessageConsumer browser = session.createConsumer(browseDest, ScheduledMessage.AMQ_SCHEDULED_DELAY + " = 45000" );
+        MessageConsumer browser = session.createConsumer(browseDest, ScheduledMessage.AMQ_SCHEDULED_DELAY + " = 45000");
 
         connection.start();
 
@@ -383,7 +390,6 @@ public class JobSchedulerManagementTest extends EmbeddedBrokerTestSupport {
         assertNull(message);
     }
 
-
     protected void scheduleMessage(Connection connection, long delay) throws Exception {
         scheduleMessage(connection, delay, 1);
     }
@@ -394,38 +400,10 @@ public class JobSchedulerManagementTest extends EmbeddedBrokerTestSupport {
         TextMessage message = session.createTextMessage("test msg");
         message.setLongProperty(ScheduledMessage.AMQ_SCHEDULED_DELAY, delay);
 
-        for(int i = 0; i < count; ++i ) {
+        for (int i = 0; i < count; ++i) {
             producer.send(message);
         }
 
         producer.close();
     }
-
-    @Override
-    protected void setUp() throws Exception {
-        bindAddress = "vm://localhost";
-        super.setUp();
-    }
-
-    @Override
-    protected BrokerService createBroker() throws Exception {
-        return createBroker(true);
-    }
-
-    protected BrokerService createBroker(boolean delete) throws Exception {
-        File schedulerDirectory = new File("target/scheduler");
-        if (delete) {
-            IOHelper.mkdirs(schedulerDirectory);
-            IOHelper.deleteChildren(schedulerDirectory);
-        }
-        BrokerService answer = new BrokerService();
-        answer.setPersistent(true);
-        answer.setDeleteAllMessagesOnStartup(true);
-        answer.setDataDirectory("target");
-        answer.setSchedulerDirectoryFile(schedulerDirectory);
-        answer.setSchedulerSupport(true);
-        answer.setUseJmx(false);
-        answer.addConnector(bindAddress);
-        return answer;
-    }
 }

http://git-wip-us.apache.org/repos/asf/activemq/blob/fc244f48/activemq-unit-tests/src/test/java/org/apache/activemq/broker/scheduler/JobSchedulerStoreCheckpointTest.java
----------------------------------------------------------------------
diff --git a/activemq-unit-tests/src/test/java/org/apache/activemq/broker/scheduler/JobSchedulerStoreCheckpointTest.java b/activemq-unit-tests/src/test/java/org/apache/activemq/broker/scheduler/JobSchedulerStoreCheckpointTest.java
new file mode 100644
index 0000000..c013a4c
--- /dev/null
+++ b/activemq-unit-tests/src/test/java/org/apache/activemq/broker/scheduler/JobSchedulerStoreCheckpointTest.java
@@ -0,0 +1,125 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.activemq.broker.scheduler;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.activemq.store.kahadb.scheduler.JobSchedulerStoreImpl;
+import org.apache.activemq.util.ByteSequence;
+import org.apache.activemq.util.IOHelper;
+import org.apache.activemq.util.Wait;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class JobSchedulerStoreCheckpointTest {
+
+    static final Logger LOG = LoggerFactory.getLogger(JobSchedulerStoreCheckpointTest.class);
+
+    private JobSchedulerStoreImpl store;
+    private JobScheduler scheduler;
+    private ByteSequence payload;
+
+    @Before
+    public void setUp() throws Exception {
+        File directory = new File("target/test/ScheduledJobsDB");
+        IOHelper.mkdirs(directory);
+        IOHelper.deleteChildren(directory);
+        startStore(directory);
+
+        byte[] data = new byte[8192];
+        for (int i = 0; i < data.length; ++i) {
+            data[i] = (byte) (i % 256);
+        }
+
+        payload = new ByteSequence(data);
+    }
+
+    protected void startStore(File directory) throws Exception {
+        store = new JobSchedulerStoreImpl();
+        store.setDirectory(directory);
+        store.setCheckpointInterval(5000);
+        store.setCleanupInterval(10000);
+        store.setJournalMaxFileLength(10 * 1024);
+        store.start();
+        scheduler = store.getJobScheduler("test");
+        scheduler.startDispatching();
+    }
+
+    private int getNumJournalFiles() throws IOException {
+        return store.getJournal().getFileMap().size();
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        scheduler.stopDispatching();
+        store.stop();
+    }
+
+    @Test
+    public void test() throws Exception {
+        final int COUNT = 10;
+        final CountDownLatch latch = new CountDownLatch(COUNT);
+        scheduler.addListener(new JobListener() {
+            @Override
+            public void scheduledJob(String id, ByteSequence job) {
+                latch.countDown();
+            }
+        });
+
+        long time = TimeUnit.SECONDS.toMillis(30);
+        for (int i = 0; i < COUNT; i++) {
+            scheduler.schedule("id" + i, payload, "", time, 0, 0);
+        }
+
+        int size = scheduler.getAllJobs().size();
+        assertEquals(size, COUNT);
+
+        LOG.info("Number of journal log files: {}", getNumJournalFiles());
+        // need a little slack so go over 60 seconds
+        assertTrue(latch.await(70, TimeUnit.SECONDS));
+        assertEquals(0, latch.getCount());
+
+        for (int i = 0; i < COUNT; i++) {
+            scheduler.schedule("id" + i, payload, "", time, 0, 0);
+        }
+
+        LOG.info("Number of journal log files: {}", getNumJournalFiles());
+        // need a little slack so go over 60 seconds
+        assertTrue(latch.await(70, TimeUnit.SECONDS));
+        assertEquals(0, latch.getCount());
+
+        assertTrue("Should be only one log left: " + getNumJournalFiles(), Wait.waitFor(new Wait.Condition() {
+
+            @Override
+            public boolean isSatisified() throws Exception {
+                return getNumJournalFiles() == 1;
+            }
+        }, TimeUnit.MINUTES.toMillis(2)));
+
+        LOG.info("Number of journal log files: {}", getNumJournalFiles());
+    }
+}

http://git-wip-us.apache.org/repos/asf/activemq/blob/fc244f48/activemq-unit-tests/src/test/java/org/apache/activemq/broker/scheduler/JobSchedulerStoreTest.java
----------------------------------------------------------------------
diff --git a/activemq-unit-tests/src/test/java/org/apache/activemq/broker/scheduler/JobSchedulerStoreTest.java b/activemq-unit-tests/src/test/java/org/apache/activemq/broker/scheduler/JobSchedulerStoreTest.java
index 0e0c1d7..df1e7ff 100644
--- a/activemq-unit-tests/src/test/java/org/apache/activemq/broker/scheduler/JobSchedulerStoreTest.java
+++ b/activemq-unit-tests/src/test/java/org/apache/activemq/broker/scheduler/JobSchedulerStoreTest.java
@@ -16,50 +16,62 @@
  */
 package org.apache.activemq.broker.scheduler;
 
+import static org.junit.Assert.assertEquals;
+
 import java.io.File;
 import java.util.ArrayList;
 import java.util.List;
 
-import junit.framework.TestCase;
-
 import org.apache.activemq.store.kahadb.scheduler.JobSchedulerStoreImpl;
 import org.apache.activemq.util.ByteSequence;
 import org.apache.activemq.util.IOHelper;
+import org.junit.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class JobSchedulerStoreTest {
 
-public class JobSchedulerStoreTest extends TestCase {
+    private static final Logger LOG = LoggerFactory.getLogger(JobSchedulerStoreTest.class);
 
+    @Test(timeout = 120 * 1000)
     public void testRestart() throws Exception {
         JobSchedulerStore store = new JobSchedulerStoreImpl();
         File directory = new File("target/test/ScheduledDB");
-          IOHelper.mkdirs(directory);
-          IOHelper.deleteChildren(directory);
-          store.setDirectory(directory);
+        IOHelper.mkdirs(directory);
+        IOHelper.deleteChildren(directory);
+        store.setDirectory(directory);
         final int NUMBER = 1000;
         store.start();
-        List<ByteSequence>list = new ArrayList<ByteSequence>();
-        for (int i = 0; i < NUMBER;i++ ) {
-            ByteSequence buff = new ByteSequence(new String("testjob"+i).getBytes());
+        List<ByteSequence> list = new ArrayList<ByteSequence>();
+        for (int i = 0; i < NUMBER; i++) {
+            ByteSequence buff = new ByteSequence(new String("testjob" + i).getBytes());
             list.add(buff);
         }
+
         JobScheduler js = store.getJobScheduler("test");
         js.startDispatching();
         int count = 0;
-        long startTime = 10 * 60 * 1000; long period = startTime;
-        for (ByteSequence job:list) {
-            js.schedule("id:"+(count++), job, "", startTime, period, -1);
+        long startTime = 10 * 60 * 1000;
+        long period = startTime;
+        for (ByteSequence job : list) {
+            js.schedule("id:" + (count++), job, "", startTime, period, -1);
         }
-        List<Job>test = js.getAllJobs();
-        assertEquals(list.size(),test.size());
+
+        List<Job> test = js.getAllJobs();
+        LOG.debug("Found {} jobs in the store before restart", test.size());
+        assertEquals(list.size(), test.size());
         store.stop();
 
         store.start();
         js = store.getJobScheduler("test");
         test = js.getAllJobs();
-        assertEquals(list.size(),test.size());
-        for (int i = 0; i < list.size();i++) {
+        LOG.debug("Found {} jobs in the store after restart", test.size());
+        assertEquals(list.size(), test.size());
+
+        for (int i = 0; i < list.size(); i++) {
             String orig = new String(list.get(i).getData());
             String payload = new String(test.get(i).getPayload());
-            assertEquals(orig,payload);
+            assertEquals(orig, payload);
         }
     }
 }

http://git-wip-us.apache.org/repos/asf/activemq/blob/fc244f48/activemq-unit-tests/src/test/java/org/apache/activemq/broker/scheduler/JobSchedulerTest.java
----------------------------------------------------------------------
diff --git a/activemq-unit-tests/src/test/java/org/apache/activemq/broker/scheduler/JobSchedulerTest.java b/activemq-unit-tests/src/test/java/org/apache/activemq/broker/scheduler/JobSchedulerTest.java
index 5126970..2210eba 100644
--- a/activemq-unit-tests/src/test/java/org/apache/activemq/broker/scheduler/JobSchedulerTest.java
+++ b/activemq-unit-tests/src/test/java/org/apache/activemq/broker/scheduler/JobSchedulerTest.java
@@ -31,8 +31,13 @@ import org.apache.activemq.util.IOHelper;
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 public class JobSchedulerTest {
+
+    private static final Logger LOG = LoggerFactory.getLogger(JobSchedulerTest.class);
+
     private JobSchedulerStore store;
     private JobScheduler scheduler;
 
@@ -173,6 +178,37 @@ public class JobSchedulerTest {
     }
 
     @Test
+    public void testGetExecutionCount() throws Exception {
+        final String jobId = "Job-1";
+        long time = 10000;
+        final CountDownLatch done = new CountDownLatch(10);
+
+        String str = new String("test");
+        scheduler.schedule(jobId, new ByteSequence(str.getBytes()), "", time, 1000, 10);
+
+        int size = scheduler.getAllJobs().size();
+        assertEquals(size, 1);
+
+        scheduler.addListener(new JobListener() {
+            @Override
+            public void scheduledJob(String id, ByteSequence job) {
+                LOG.info("Job exectued: {}", 11 - done.getCount());
+                done.countDown();
+            }
+        });
+
+        List<Job> jobs = scheduler.getNextScheduleJobs();
+        assertEquals(1, jobs.size());
+        Job job = jobs.get(0);
+        assertEquals(jobId, job.getJobId());
+        assertEquals(0, job.getExecutionCount());
+        assertTrue("Should have fired ten times.", done.await(60, TimeUnit.SECONDS));
+        // The job is not updated on the last firing as it is removed from the store following
+        // it's last execution so the count will always be one less than the max firings.
+        assertTrue(job.getExecutionCount() >= 9);
+    }
+
+    @Test
     public void testgetAllJobs() throws Exception {
         final int COUNT = 10;
         final String ID = "id:";

http://git-wip-us.apache.org/repos/asf/activemq/blob/fc244f48/activemq-unit-tests/src/test/java/org/apache/activemq/broker/scheduler/JobSchedulerTestSupport.java
----------------------------------------------------------------------
diff --git a/activemq-unit-tests/src/test/java/org/apache/activemq/broker/scheduler/JobSchedulerTestSupport.java b/activemq-unit-tests/src/test/java/org/apache/activemq/broker/scheduler/JobSchedulerTestSupport.java
new file mode 100644
index 0000000..2b25797
--- /dev/null
+++ b/activemq-unit-tests/src/test/java/org/apache/activemq/broker/scheduler/JobSchedulerTestSupport.java
@@ -0,0 +1,112 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.activemq.broker.scheduler;
+
+import java.io.File;
+
+import javax.jms.Connection;
+import javax.jms.ConnectionFactory;
+import javax.jms.Queue;
+import javax.management.ObjectName;
+
+import org.apache.activemq.ActiveMQConnectionFactory;
+import org.apache.activemq.broker.BrokerService;
+import org.apache.activemq.broker.jmx.JobSchedulerViewMBean;
+import org.apache.activemq.command.ActiveMQQueue;
+import org.apache.activemq.util.IOHelper;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.rules.TestName;
+
+/**
+ * Base class for tests of the Broker's JobSchedulerStore.
+ */
+public class JobSchedulerTestSupport {
+
+    @Rule public TestName name = new TestName();
+
+    protected String connectionUri;
+    protected BrokerService broker;
+    protected JobScheduler jobScheduler;
+    protected Queue destination;
+
+    @Before
+    public void setUp() throws Exception {
+        connectionUri = "vm://localhost";
+        destination = new ActiveMQQueue(name.getMethodName());
+
+        broker = createBroker();
+        broker.start();
+        broker.waitUntilStarted();
+
+        jobScheduler = broker.getJobSchedulerStore().getJobScheduler("JMS");
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        if (broker != null) {
+            broker.stop();
+            broker.waitUntilStopped();
+        }
+    }
+
+    protected Connection createConnection() throws Exception {
+        return createConnectionFactory().createConnection();
+    }
+
+    protected ConnectionFactory createConnectionFactory() throws Exception {
+        return new ActiveMQConnectionFactory(connectionUri);
+    }
+
+    protected BrokerService createBroker() throws Exception {
+        return createBroker(true);
+    }
+
+    protected boolean isUseJmx() {
+        return false;
+    }
+
+    protected JobSchedulerViewMBean getJobSchedulerMBean() throws Exception {
+        ObjectName objectName = broker.getAdminView().getJMSJobScheduler();
+        JobSchedulerViewMBean scheduler = null;
+        if (objectName != null) {
+            scheduler = (JobSchedulerViewMBean) broker.getManagementContext()
+                .newProxyInstance(objectName, JobSchedulerViewMBean.class, true);
+        }
+
+        return scheduler;
+    }
+
+    protected BrokerService createBroker(boolean delete) throws Exception {
+        File schedulerDirectory = new File("target/scheduler");
+        if (delete) {
+            IOHelper.mkdirs(schedulerDirectory);
+            IOHelper.deleteChildren(schedulerDirectory);
+        }
+
+        BrokerService answer = new BrokerService();
+        answer.setPersistent(true);
+        answer.setDeleteAllMessagesOnStartup(true);
+        answer.setDataDirectory("target");
+        answer.setSchedulerDirectoryFile(schedulerDirectory);
+        answer.setSchedulerSupport(true);
+        answer.setUseJmx(isUseJmx());
+        return answer;
+    }
+}

http://git-wip-us.apache.org/repos/asf/activemq/blob/fc244f48/activemq-unit-tests/src/test/java/org/apache/activemq/broker/scheduler/KahaDBSchedulerIndexRebuildTest.java
----------------------------------------------------------------------
diff --git a/activemq-unit-tests/src/test/java/org/apache/activemq/broker/scheduler/KahaDBSchedulerIndexRebuildTest.java b/activemq-unit-tests/src/test/java/org/apache/activemq/broker/scheduler/KahaDBSchedulerIndexRebuildTest.java
new file mode 100644
index 0000000..9d0fef2
--- /dev/null
+++ b/activemq-unit-tests/src/test/java/org/apache/activemq/broker/scheduler/KahaDBSchedulerIndexRebuildTest.java
@@ -0,0 +1,179 @@
+package org.apache.activemq.broker.scheduler;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import java.io.File;
+import java.io.IOException;
+import java.security.ProtectionDomain;
+import java.util.concurrent.TimeUnit;
+
+import javax.jms.Connection;
+import javax.jms.MessageProducer;
+import javax.jms.Queue;
+import javax.jms.Session;
+import javax.jms.TextMessage;
+
+import org.apache.activemq.ActiveMQConnectionFactory;
+import org.apache.activemq.ScheduledMessage;
+import org.apache.activemq.broker.BrokerService;
+import org.apache.activemq.store.kahadb.scheduler.JobSchedulerStoreImpl;
+import org.apache.activemq.util.IOHelper;
+import org.apache.activemq.util.Wait;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class KahaDBSchedulerIndexRebuildTest {
+
+    static final Logger LOG = LoggerFactory.getLogger(KahaDBSchedulerIndexRebuildTest.class);
+
+    private BrokerService broker = null;
+    private final int NUM_JOBS = 50;
+
+    static String basedir;
+    static {
+        try {
+            ProtectionDomain protectionDomain = SchedulerDBVersionTest.class.getProtectionDomain();
+            basedir = new File(new File(protectionDomain.getCodeSource().getLocation().getPath()), "../.").getCanonicalPath();
+        } catch (IOException e) {
+            basedir = ".";
+        }
+    }
+
+    private final File schedulerStoreDir = new File(basedir, "activemq-data/store/scheduler");
+    private final File storeDir = new File(basedir, "activemq-data/store/");
+
+    @Before
+    public void setUp() throws Exception {
+        LOG.info("Test Dir = {}", schedulerStoreDir);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        if (broker != null) {
+            broker.stop();
+        }
+    }
+
+    @Test
+    public void testIndexRebuilds() throws Exception {
+        IOHelper.deleteFile(schedulerStoreDir);
+
+        JobSchedulerStoreImpl schedulerStore = createScheduler();
+        broker = createBroker(schedulerStore);
+        broker.start();
+        ActiveMQConnectionFactory cf = new ActiveMQConnectionFactory("vm://localhost");
+        Connection connection = cf.createConnection();
+        connection.start();
+        for (int i = 0; i < NUM_JOBS; ++i) {
+            scheduleRepeating(connection);
+        }
+        connection.close();
+
+        JobScheduler scheduler = schedulerStore.getJobScheduler("JMS");
+        assertNotNull(scheduler);
+        assertEquals(NUM_JOBS, scheduler.getAllJobs().size());
+
+        broker.stop();
+
+        IOHelper.delete(new File(schedulerStoreDir, "scheduleDB.data"));
+
+        schedulerStore = createScheduler();
+        broker = createBroker(schedulerStore);
+        broker.start();
+
+        scheduler = schedulerStore.getJobScheduler("JMS");
+        assertNotNull(scheduler);
+        assertEquals(NUM_JOBS, scheduler.getAllJobs().size());
+    }
+
+    @Test
+    public void testIndexRebuildsAfterSomeJobsExpire() throws Exception {
+        IOHelper.deleteFile(schedulerStoreDir);
+
+        JobSchedulerStoreImpl schedulerStore = createScheduler();
+        broker = createBroker(schedulerStore);
+        broker.start();
+        ActiveMQConnectionFactory cf = new ActiveMQConnectionFactory("vm://localhost");
+        Connection connection = cf.createConnection();
+        connection.start();
+        for (int i = 0; i < NUM_JOBS; ++i) {
+            scheduleRepeating(connection);
+            scheduleOneShot(connection);
+        }
+        connection.close();
+
+        JobScheduler scheduler = schedulerStore.getJobScheduler("JMS");
+        assertNotNull(scheduler);
+        assertEquals(NUM_JOBS * 2, scheduler.getAllJobs().size());
+
+        final JobScheduler awaitingOneShotTimeout = scheduler;
+        assertTrue("One shot jobs should time out", Wait.waitFor(new Wait.Condition() {
+
+            @Override
+            public boolean isSatisified() throws Exception {
+                return awaitingOneShotTimeout.getAllJobs().size() == NUM_JOBS;
+            }
+        }, TimeUnit.MINUTES.toMillis(2)));
+
+        broker.stop();
+
+        IOHelper.delete(new File(schedulerStoreDir, "scheduleDB.data"));
+
+        schedulerStore = createScheduler();
+        broker = createBroker(schedulerStore);
+        broker.start();
+
+        scheduler = schedulerStore.getJobScheduler("JMS");
+        assertNotNull(scheduler);
+        assertEquals(NUM_JOBS, scheduler.getAllJobs().size());
+    }
+
+    private void scheduleRepeating(Connection connection) throws Exception {
+        Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
+        Queue queue = session.createQueue("test.queue");
+        MessageProducer producer = session.createProducer(queue);
+
+        TextMessage message = session.createTextMessage("test msg");
+        long time = 360 * 1000;
+        message.setLongProperty(ScheduledMessage.AMQ_SCHEDULED_DELAY, time);
+        message.setLongProperty(ScheduledMessage.AMQ_SCHEDULED_PERIOD, 500);
+        message.setIntProperty(ScheduledMessage.AMQ_SCHEDULED_REPEAT, -1);
+        producer.send(message);
+        producer.close();
+    }
+
+    private void scheduleOneShot(Connection connection) throws Exception {
+        Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
+        Queue queue = session.createQueue("test.queue");
+        MessageProducer producer = session.createProducer(queue);
+
+        TextMessage message = session.createTextMessage("test msg");
+        long time = TimeUnit.SECONDS.toMillis(30);
+        message.setLongProperty(ScheduledMessage.AMQ_SCHEDULED_DELAY, time);
+        message.setIntProperty(ScheduledMessage.AMQ_SCHEDULED_REPEAT, 0);
+        producer.send(message);
+        producer.close();
+    }
+
+    protected JobSchedulerStoreImpl createScheduler() {
+        JobSchedulerStoreImpl scheduler = new JobSchedulerStoreImpl();
+        scheduler.setDirectory(schedulerStoreDir);
+        scheduler.setJournalMaxFileLength(10 * 1024);
+        return scheduler;
+    }
+
+    protected BrokerService createBroker(JobSchedulerStoreImpl scheduler) throws Exception {
+        BrokerService answer = new BrokerService();
+        answer.setJobSchedulerStore(scheduler);
+        answer.setPersistent(true);
+        answer.setDataDirectory(storeDir.getAbsolutePath());
+        answer.setSchedulerSupport(true);
+        answer.setUseJmx(false);
+        return answer;
+    }
+}

http://git-wip-us.apache.org/repos/asf/activemq/blob/fc244f48/activemq-unit-tests/src/test/java/org/apache/activemq/broker/scheduler/KahaDBSchedulerMissingJournalLogsTest.java
----------------------------------------------------------------------
diff --git a/activemq-unit-tests/src/test/java/org/apache/activemq/broker/scheduler/KahaDBSchedulerMissingJournalLogsTest.java b/activemq-unit-tests/src/test/java/org/apache/activemq/broker/scheduler/KahaDBSchedulerMissingJournalLogsTest.java
new file mode 100644
index 0000000..30da10d
--- /dev/null
+++ b/activemq-unit-tests/src/test/java/org/apache/activemq/broker/scheduler/KahaDBSchedulerMissingJournalLogsTest.java
@@ -0,0 +1,204 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.activemq.broker.scheduler;
+
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.io.File;
+import java.io.IOException;
+import java.security.ProtectionDomain;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import javax.jms.Connection;
+import javax.jms.MessageProducer;
+import javax.jms.Queue;
+import javax.jms.Session;
+import javax.jms.TextMessage;
+
+import org.apache.activemq.ActiveMQConnectionFactory;
+import org.apache.activemq.ScheduledMessage;
+import org.apache.activemq.broker.BrokerService;
+import org.apache.activemq.store.kahadb.disk.journal.DataFile;
+import org.apache.activemq.store.kahadb.scheduler.JobSchedulerStoreImpl;
+import org.apache.activemq.util.IOHelper;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ *Test that the store recovers even if some log files are missing.
+ */
+public class KahaDBSchedulerMissingJournalLogsTest {
+
+    static final Logger LOG = LoggerFactory.getLogger(KahaDBSchedulerIndexRebuildTest.class);
+
+    private BrokerService broker = null;
+    private JobSchedulerStoreImpl schedulerStore = null;
+
+    private final int NUM_LOGS = 6;
+
+    static String basedir;
+    static {
+        try {
+            ProtectionDomain protectionDomain = SchedulerDBVersionTest.class.getProtectionDomain();
+            basedir = new File(new File(protectionDomain.getCodeSource().getLocation().getPath()), "../.").getCanonicalPath();
+        } catch (IOException e) {
+            basedir = ".";
+        }
+    }
+
+    private final File schedulerStoreDir = new File(basedir, "activemq-data/store/scheduler");
+    private final File storeDir = new File(basedir, "activemq-data/store/");
+
+    /**
+     * @throws java.lang.Exception
+     */
+    @Before
+    public void setUp() throws Exception {
+        IOHelper.deleteFile(schedulerStoreDir);
+        LOG.info("Test Dir = {}", schedulerStoreDir);
+
+        createBroker();
+        broker.start();
+        broker.waitUntilStarted();
+
+        schedulerStore = (JobSchedulerStoreImpl) broker.getJobSchedulerStore();
+    }
+
+    /**
+     * @throws java.lang.Exception
+     */
+    @After
+    public void tearDown() throws Exception {
+        if (broker != null) {
+            broker.stop();
+            broker.waitUntilStopped();
+        }
+    }
+
+    @Test(timeout=120 * 1000)
+    public void testMissingLogsCausesBrokerToFail() throws Exception {
+        fillUpSomeLogFiles();
+
+        int jobCount = schedulerStore.getJobScheduler("JMS").getAllJobs().size();
+        LOG.info("There are {} jobs in the store.", jobCount);
+
+        List<File> toDelete = new ArrayList<File>();
+        Map<Integer, DataFile> files = schedulerStore.getJournal().getFileMap();
+        for (int i = files.size(); i > files.size() / 2; i--) {
+            toDelete.add(files.get(i).getFile());
+        }
+
+        broker.stop();
+        broker.waitUntilStopped();
+
+        for (File file : toDelete) {
+            LOG.info("File to delete: {}", file);
+            IOHelper.delete(file);
+        }
+
+        try {
+            createBroker();
+            fail("Should not start when logs are missing.");
+        } catch (Exception e) {
+        }
+    }
+
+    @Test(timeout=120 * 1000)
+    public void testRecoverWhenSomeLogsAreMissing() throws Exception {
+        fillUpSomeLogFiles();
+
+        int jobCount = schedulerStore.getJobScheduler("JMS").getAllJobs().size();
+        LOG.info("There are {} jobs in the store.", jobCount);
+
+        List<File> toDelete = new ArrayList<File>();
+        Map<Integer, DataFile> files = schedulerStore.getJournal().getFileMap();
+        for (int i = files.size() - 1; i > files.size() / 2; i--) {
+            toDelete.add(files.get(i).getFile());
+        }
+
+        broker.stop();
+        broker.waitUntilStopped();
+
+        for (File file : toDelete) {
+            LOG.info("File to delete: {}", file);
+            IOHelper.delete(file);
+        }
+
+        schedulerStore = createScheduler();
+        schedulerStore.setIgnoreMissingJournalfiles(true);
+
+        createBroker(schedulerStore);
+        broker.start();
+        broker.waitUntilStarted();
+
+        int postRecoverJobCount = schedulerStore.getJobScheduler("JMS").getAllJobs().size();
+        assertTrue(postRecoverJobCount > 0);
+        assertTrue(postRecoverJobCount < jobCount);
+    }
+
+    private void fillUpSomeLogFiles() throws Exception {
+        ActiveMQConnectionFactory cf = new ActiveMQConnectionFactory("vm://localhost");
+        Connection connection = cf.createConnection();
+        Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
+        Queue queue = session.createQueue("test.queue");
+        MessageProducer producer = session.createProducer(queue);
+        connection.start();
+        while (true) {
+            scheduleRepeating(session, producer);
+            if (schedulerStore.getJournal().getFileMap().size() == NUM_LOGS) {
+                break;
+            }
+        }
+        connection.close();
+    }
+
+    private void scheduleRepeating(Session session, MessageProducer producer) throws Exception {
+        TextMessage message = session.createTextMessage("test msg");
+        long time = 360 * 1000;
+        message.setLongProperty(ScheduledMessage.AMQ_SCHEDULED_DELAY, time);
+        message.setLongProperty(ScheduledMessage.AMQ_SCHEDULED_PERIOD, 500);
+        message.setIntProperty(ScheduledMessage.AMQ_SCHEDULED_REPEAT, -1);
+        producer.send(message);
+    }
+
+    protected JobSchedulerStoreImpl createScheduler() {
+        JobSchedulerStoreImpl scheduler = new JobSchedulerStoreImpl();
+        scheduler.setDirectory(schedulerStoreDir);
+        scheduler.setJournalMaxFileLength(10 * 1024);
+        return scheduler;
+    }
+
+    protected void createBroker() throws Exception {
+        createBroker(createScheduler());
+    }
+
+    protected void createBroker(JobSchedulerStoreImpl scheduler) throws Exception {
+        broker = new BrokerService();
+        broker.setJobSchedulerStore(scheduler);
+        broker.setPersistent(true);
+        broker.setDataDirectory(storeDir.getAbsolutePath());
+        broker.setSchedulerSupport(true);
+        broker.setUseJmx(false);
+    }
+}

http://git-wip-us.apache.org/repos/asf/activemq/blob/fc244f48/activemq-unit-tests/src/test/java/org/apache/activemq/broker/scheduler/SchedulerDBVersionTest.java
----------------------------------------------------------------------
diff --git a/activemq-unit-tests/src/test/java/org/apache/activemq/broker/scheduler/SchedulerDBVersionTest.java b/activemq-unit-tests/src/test/java/org/apache/activemq/broker/scheduler/SchedulerDBVersionTest.java
new file mode 100644
index 0000000..721f417
--- /dev/null
+++ b/activemq-unit-tests/src/test/java/org/apache/activemq/broker/scheduler/SchedulerDBVersionTest.java
@@ -0,0 +1,164 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.activemq.broker.scheduler;
+
+import static org.junit.Assert.assertEquals;
+
+import java.io.File;
+import java.io.IOException;
+import java.security.ProtectionDomain;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import javax.jms.Connection;
+import javax.jms.Message;
+import javax.jms.MessageConsumer;
+import javax.jms.MessageListener;
+import javax.jms.MessageProducer;
+import javax.jms.Queue;
+import javax.jms.Session;
+import javax.jms.TextMessage;
+
+import org.apache.activemq.ActiveMQConnectionFactory;
+import org.apache.activemq.ScheduledMessage;
+import org.apache.activemq.broker.BrokerService;
+import org.apache.activemq.store.kahadb.scheduler.JobSchedulerStoreImpl;
+import org.apache.activemq.util.IOHelper;
+import org.junit.After;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class SchedulerDBVersionTest {
+    static String basedir;
+    static {
+        try {
+            ProtectionDomain protectionDomain = SchedulerDBVersionTest.class.getProtectionDomain();
+            basedir = new File(new File(protectionDomain.getCodeSource().getLocation().getPath()), "../..").getCanonicalPath();
+        } catch (IOException e) {
+            basedir = ".";
+        }
+    }
+
+    static final Logger LOG = LoggerFactory.getLogger(SchedulerDBVersionTest.class);
+    final static File VERSION_LEGACY_JMS =
+        new File(basedir + "/src/test/resources/org/apache/activemq/store/schedulerDB/legacy");
+
+    BrokerService broker = null;
+
+    protected BrokerService createBroker(JobSchedulerStoreImpl scheduler) throws Exception {
+        BrokerService answer = new BrokerService();
+        answer.setJobSchedulerStore(scheduler);
+        answer.setPersistent(true);
+        answer.setDataDirectory("target");
+        answer.setSchedulerSupport(true);
+        answer.setUseJmx(false);
+        return answer;
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        if (broker != null) {
+            broker.stop();
+        }
+    }
+
+    @Ignore("Used only when a new version of the store needs to archive it's test data.")
+    @Test
+    public void testCreateStore() throws Exception {
+        JobSchedulerStoreImpl scheduler = new JobSchedulerStoreImpl();
+        File dir = new File("src/test/resources/org/apache/activemq/store/schedulerDB/legacy");
+        IOHelper.deleteFile(dir);
+        scheduler.setDirectory(dir);
+        scheduler.setJournalMaxFileLength(1024 * 1024);
+        broker = createBroker(scheduler);
+        broker.start();
+        ActiveMQConnectionFactory cf = new ActiveMQConnectionFactory("vm://localhost");
+        Connection connection = cf.createConnection();
+        connection.start();
+        scheduleRepeating(connection);
+        connection.close();
+        broker.stop();
+    }
+
+    private void scheduleRepeating(Connection connection) throws Exception {
+        Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
+        Queue queue = session.createQueue("test.queue");
+        MessageProducer producer = session.createProducer(queue);
+
+        TextMessage message = session.createTextMessage("test msg");
+        long time = 1000;
+        message.setLongProperty(ScheduledMessage.AMQ_SCHEDULED_DELAY, time);
+        message.setLongProperty(ScheduledMessage.AMQ_SCHEDULED_PERIOD, 500);
+        message.setIntProperty(ScheduledMessage.AMQ_SCHEDULED_REPEAT, -1);
+        producer.send(message);
+        producer.close();
+    }
+
+    @Test
+    public void testLegacyStoreConversion() throws Exception {
+        doTestScheduleRepeated(VERSION_LEGACY_JMS);
+    }
+
+    public void doTestScheduleRepeated(File existingStore) throws Exception {
+        File testDir = new File("target/activemq-data/store/scheduler/versionDB");
+        IOHelper.deleteFile(testDir);
+        IOHelper.copyFile(existingStore, testDir);
+
+        final int NUMBER = 10;
+        ActiveMQConnectionFactory cf = new ActiveMQConnectionFactory("vm://localhost");
+
+        for (int i = 0; i < 3; ++i) {
+            JobSchedulerStoreImpl scheduler = new JobSchedulerStoreImpl();
+            scheduler.setDirectory(testDir);
+            scheduler.setJournalMaxFileLength(1024 * 1024);
+            BrokerService broker = createBroker(scheduler);
+            broker.start();
+            broker.waitUntilStarted();
+
+            final AtomicInteger count = new AtomicInteger();
+            Connection connection = cf.createConnection();
+
+            Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
+            Queue queue = session.createQueue("test.queue");
+
+            MessageConsumer consumer = session.createConsumer(queue);
+
+            final CountDownLatch latch = new CountDownLatch(NUMBER);
+            consumer.setMessageListener(new MessageListener() {
+                @Override
+                public void onMessage(Message message) {
+                    LOG.info("Received scheduled message: {}", message);
+                    latch.countDown();
+                    count.incrementAndGet();
+                }
+            });
+
+            connection.start();
+            assertEquals(latch.getCount(), NUMBER);
+            latch.await(30, TimeUnit.SECONDS);
+
+            connection.close();
+            broker.stop();
+            broker.waitUntilStopped();
+
+            assertEquals(0, latch.getCount());
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/activemq/blob/fc244f48/activemq-unit-tests/src/test/resources/log4j.properties
----------------------------------------------------------------------
diff --git a/activemq-unit-tests/src/test/resources/log4j.properties b/activemq-unit-tests/src/test/resources/log4j.properties
index 564ed9e..85516aa 100755
--- a/activemq-unit-tests/src/test/resources/log4j.properties
+++ b/activemq-unit-tests/src/test/resources/log4j.properties
@@ -21,6 +21,7 @@
 log4j.rootLogger=INFO, out, stdout
 
 #log4j.logger.org.apache.activemq.broker.scheduler=DEBUG
+#log4j.logger.org.apache.activemq.store.kahadb.scheduler=DEBUG
 #log4j.logger.org.apache.activemq.network.DemandForwardingBridgeSupport=DEBUG
 #log4j.logger.org.apache.activemq.transport.failover=TRACE
 #log4j.logger.org.apache.activemq.store.jdbc=TRACE

http://git-wip-us.apache.org/repos/asf/activemq/blob/fc244f48/activemq-unit-tests/src/test/resources/org/apache/activemq/store/schedulerDB/legacy/db-1.log
----------------------------------------------------------------------
diff --git a/activemq-unit-tests/src/test/resources/org/apache/activemq/store/schedulerDB/legacy/db-1.log b/activemq-unit-tests/src/test/resources/org/apache/activemq/store/schedulerDB/legacy/db-1.log
new file mode 100644
index 0000000..342f8c7
Binary files /dev/null and b/activemq-unit-tests/src/test/resources/org/apache/activemq/store/schedulerDB/legacy/db-1.log differ

http://git-wip-us.apache.org/repos/asf/activemq/blob/fc244f48/activemq-unit-tests/src/test/resources/org/apache/activemq/store/schedulerDB/legacy/scheduleDB.data
----------------------------------------------------------------------
diff --git a/activemq-unit-tests/src/test/resources/org/apache/activemq/store/schedulerDB/legacy/scheduleDB.data b/activemq-unit-tests/src/test/resources/org/apache/activemq/store/schedulerDB/legacy/scheduleDB.data
new file mode 100644
index 0000000..30c937d
Binary files /dev/null and b/activemq-unit-tests/src/test/resources/org/apache/activemq/store/schedulerDB/legacy/scheduleDB.data differ

http://git-wip-us.apache.org/repos/asf/activemq/blob/fc244f48/activemq-unit-tests/src/test/resources/org/apache/activemq/store/schedulerDB/legacy/scheduleDB.redo
----------------------------------------------------------------------
diff --git a/activemq-unit-tests/src/test/resources/org/apache/activemq/store/schedulerDB/legacy/scheduleDB.redo b/activemq-unit-tests/src/test/resources/org/apache/activemq/store/schedulerDB/legacy/scheduleDB.redo
new file mode 100644
index 0000000..b06e549
Binary files /dev/null and b/activemq-unit-tests/src/test/resources/org/apache/activemq/store/schedulerDB/legacy/scheduleDB.redo differ


[02/16] activemq git commit: https://issues.apache.org/jira/browse/AMQ-5251

Posted by ha...@apache.org.
https://issues.apache.org/jira/browse/AMQ-5251

Synchronize method and remove old deprecated schedule method.


Project: http://git-wip-us.apache.org/repos/asf/activemq/repo
Commit: http://git-wip-us.apache.org/repos/asf/activemq/commit/709204d8
Tree: http://git-wip-us.apache.org/repos/asf/activemq/tree/709204d8
Diff: http://git-wip-us.apache.org/repos/asf/activemq/diff/709204d8

Branch: refs/heads/activemq-5.10.x
Commit: 709204d8ed1c0a637fb1024aa1394cc1fcc10b7c
Parents: 327e19e
Author: Timothy Bish <ta...@gmail.com>
Authored: Sat Jun 28 10:21:34 2014 -0400
Committer: Hadrian Zbarcea <ha...@apache.org>
Committed: Mon Dec 15 18:51:41 2014 -0500

----------------------------------------------------------------------
 .../apache/activemq/broker/region/Topic.java    |  2 +-
 .../org/apache/activemq/thread/Scheduler.java   | 24 +++++---------------
 2 files changed, 7 insertions(+), 19 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/activemq/blob/709204d8/activemq-broker/src/main/java/org/apache/activemq/broker/region/Topic.java
----------------------------------------------------------------------
diff --git a/activemq-broker/src/main/java/org/apache/activemq/broker/region/Topic.java b/activemq-broker/src/main/java/org/apache/activemq/broker/region/Topic.java
index 0186b42..cd144c3 100755
--- a/activemq-broker/src/main/java/org/apache/activemq/broker/region/Topic.java
+++ b/activemq-broker/src/main/java/org/apache/activemq/broker/region/Topic.java
@@ -560,7 +560,7 @@ public class Topic extends BaseDestination implements Task {
         }
 
         if (getExpireMessagesPeriod() > 0) {
-            scheduler.schedualPeriodically(expireMessagesTask, getExpireMessagesPeriod());
+            scheduler.executePeriodically(expireMessagesTask, getExpireMessagesPeriod());
         }
     }
 

http://git-wip-us.apache.org/repos/asf/activemq/blob/709204d8/activemq-client/src/main/java/org/apache/activemq/thread/Scheduler.java
----------------------------------------------------------------------
diff --git a/activemq-client/src/main/java/org/apache/activemq/thread/Scheduler.java b/activemq-client/src/main/java/org/apache/activemq/thread/Scheduler.java
index d6dc372..2fdb11a 100755
--- a/activemq-client/src/main/java/org/apache/activemq/thread/Scheduler.java
+++ b/activemq-client/src/main/java/org/apache/activemq/thread/Scheduler.java
@@ -27,28 +27,16 @@ import org.apache.activemq.util.ServiceSupport;
  *
  */
 public final class Scheduler extends ServiceSupport {
+
     private final String name;
     private Timer timer;
     private final HashMap<Runnable, TimerTask> timerTasks = new HashMap<Runnable, TimerTask>();
 
-    public Scheduler (String name) {
+    public Scheduler(String name) {
         this.name = name;
     }
 
-    public void executePeriodically(final Runnable task, long period) {
-        TimerTask timerTask = new SchedulerTimerTask(task);
-        timer.schedule(timerTask, period, period);
-        timerTasks.put(task, timerTask);
-    }
-
-    /*
-     * execute on rough schedule based on termination of last execution. There is no
-     * compensation (two runs in quick succession) for delays
-     *
-     * @deprecated use {@link #executePeriodically}
-     */
-    @Deprecated
-    public synchronized void schedualPeriodically(final Runnable task, long period) {
+    public synchronized void executePeriodically(final Runnable task, long period) {
         TimerTask timerTask = new SchedulerTimerTask(task);
         timer.schedule(timerTask, period, period);
         timerTasks.put(task, timerTask);
@@ -78,9 +66,9 @@ public final class Scheduler extends ServiceSupport {
 
     @Override
     protected synchronized void doStop(ServiceStopper stopper) throws Exception {
-       if (this.timer != null) {
-           this.timer.cancel();
-       }
+        if (this.timer != null) {
+            this.timer.cancel();
+        }
     }
 
     public String getName() {


[08/16] activemq git commit: AMQ-5265 - fix race condition for task

Posted by ha...@apache.org.
AMQ-5265 - fix race condition for task


Project: http://git-wip-us.apache.org/repos/asf/activemq/repo
Commit: http://git-wip-us.apache.org/repos/asf/activemq/commit/569a6771
Tree: http://git-wip-us.apache.org/repos/asf/activemq/tree/569a6771
Diff: http://git-wip-us.apache.org/repos/asf/activemq/diff/569a6771

Branch: refs/heads/activemq-5.10.x
Commit: 569a67714d3d1c4e63553c19a2d92f9d2413360b
Parents: 73cb029
Author: Jeff Genender <jg...@savoirtech.com>
Authored: Tue Jul 8 15:05:41 2014 -0600
Committer: Hadrian Zbarcea <ha...@apache.org>
Committed: Mon Dec 15 19:09:58 2014 -0500

----------------------------------------------------------------------
 .../network/MBeanBridgeDestination.java         | 32 ++++++++------------
 1 file changed, 12 insertions(+), 20 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/activemq/blob/569a6771/activemq-broker/src/main/java/org/apache/activemq/network/MBeanBridgeDestination.java
----------------------------------------------------------------------
diff --git a/activemq-broker/src/main/java/org/apache/activemq/network/MBeanBridgeDestination.java b/activemq-broker/src/main/java/org/apache/activemq/network/MBeanBridgeDestination.java
index 583fab7..bab5574 100644
--- a/activemq-broker/src/main/java/org/apache/activemq/network/MBeanBridgeDestination.java
+++ b/activemq-broker/src/main/java/org/apache/activemq/network/MBeanBridgeDestination.java
@@ -21,6 +21,7 @@ import java.util.Map;
 import java.util.concurrent.ConcurrentHashMap;
 
 import javax.management.ObjectName;
+
 import org.apache.activemq.broker.BrokerService;
 import org.apache.activemq.broker.jmx.AnnotatedMBean;
 import org.apache.activemq.broker.jmx.BrokerMBeanSupport;
@@ -144,32 +145,23 @@ public class MBeanBridgeDestination {
 
     private void purgeInactiveDestinationView(Map<ActiveMQDestination, NetworkDestinationView> map) {
         long time = System.currentTimeMillis() - networkBridgeConfiguration.getGcSweepTime();
-        Map<ActiveMQDestination, NetworkDestinationView> gc = null;
         for (Map.Entry<ActiveMQDestination, NetworkDestinationView> entry : map.entrySet()) {
             if (entry.getValue().getLastAccessTime() <= time) {
-                if (gc == null) {
-                    gc = new HashMap<ActiveMQDestination, NetworkDestinationView>();
-                }
-                gc.put(entry.getKey(), entry.getValue());
-            }
-        }
-
-        if (gc != null) {
-            for (Map.Entry<ActiveMQDestination, NetworkDestinationView> entry : gc.entrySet()) {
-                map.remove(entry.getKey());
-                ObjectName objectName = destinationObjectNameMap.get(entry.getKey());
-                if (objectName != null) {
-                    try {
-                        if (objectName != null) {
-                            brokerService.getManagementContext().unregisterMBean(objectName);
+                synchronized (destinationObjectNameMap) {
+                    map.remove(entry.getKey());
+                    ObjectName objectName = destinationObjectNameMap.remove(entry.getKey());
+                    if (objectName != null) {
+                        try {
+                            if (objectName != null) {
+                                brokerService.getManagementContext().unregisterMBean(objectName);
+                            }
+                        } catch (Throwable e) {
+                            LOG.debug("Network bridge could not be unregistered in JMX: {}", e.getMessage(), e);
                         }
-                    } catch (Throwable e) {
-                        LOG.debug("Network bridge could not be unregistered in JMX: {}", e.getMessage(), e);
                     }
+                    entry.getValue().close();
                 }
-                entry.getValue().close();
             }
         }
     }
-
 }


[13/16] activemq git commit: https://issues.apache.org/jira/browse/AMQ-3758

Posted by ha...@apache.org.
http://git-wip-us.apache.org/repos/asf/activemq/blob/fc244f48/activemq-kahadb-store/src/main/java/org/apache/activemq/store/kahadb/scheduler/JobSchedulerStoreImpl.java
----------------------------------------------------------------------
diff --git a/activemq-kahadb-store/src/main/java/org/apache/activemq/store/kahadb/scheduler/JobSchedulerStoreImpl.java b/activemq-kahadb-store/src/main/java/org/apache/activemq/store/kahadb/scheduler/JobSchedulerStoreImpl.java
index 5934914..1a08931 100644
--- a/activemq-kahadb-store/src/main/java/org/apache/activemq/store/kahadb/scheduler/JobSchedulerStoreImpl.java
+++ b/activemq-kahadb-store/src/main/java/org/apache/activemq/store/kahadb/scheduler/JobSchedulerStoreImpl.java
@@ -19,8 +19,10 @@ package org.apache.activemq.store.kahadb.scheduler;
 import java.io.DataInput;
 import java.io.DataOutput;
 import java.io.File;
+import java.io.FilenameFilter;
 import java.io.IOException;
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
@@ -28,363 +30,917 @@ import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
 import java.util.Set;
-import java.util.concurrent.atomic.AtomicLong;
+import java.util.TreeSet;
+import java.util.UUID;
 
-import org.apache.activemq.broker.LockableServiceSupport;
-import org.apache.activemq.broker.Locker;
 import org.apache.activemq.broker.scheduler.JobScheduler;
 import org.apache.activemq.broker.scheduler.JobSchedulerStore;
-import org.apache.activemq.store.SharedFileLocker;
-import org.apache.activemq.store.kahadb.disk.index.BTreeIndex;
-import org.apache.activemq.store.kahadb.disk.journal.Journal;
+import org.apache.activemq.protobuf.Buffer;
+import org.apache.activemq.store.kahadb.AbstractKahaDBStore;
+import org.apache.activemq.store.kahadb.JournalCommand;
+import org.apache.activemq.store.kahadb.KahaDBMetaData;
+import org.apache.activemq.store.kahadb.Visitor;
+import org.apache.activemq.store.kahadb.data.KahaAddScheduledJobCommand;
+import org.apache.activemq.store.kahadb.data.KahaDestroySchedulerCommand;
+import org.apache.activemq.store.kahadb.data.KahaRemoveScheduledJobCommand;
+import org.apache.activemq.store.kahadb.data.KahaRemoveScheduledJobsCommand;
+import org.apache.activemq.store.kahadb.data.KahaRescheduleJobCommand;
+import org.apache.activemq.store.kahadb.data.KahaTraceCommand;
+import org.apache.activemq.store.kahadb.disk.index.BTreeVisitor;
+import org.apache.activemq.store.kahadb.disk.journal.DataFile;
 import org.apache.activemq.store.kahadb.disk.journal.Location;
 import org.apache.activemq.store.kahadb.disk.page.Page;
 import org.apache.activemq.store.kahadb.disk.page.PageFile;
 import org.apache.activemq.store.kahadb.disk.page.Transaction;
-import org.apache.activemq.store.kahadb.disk.util.IntegerMarshaller;
-import org.apache.activemq.store.kahadb.disk.util.StringMarshaller;
 import org.apache.activemq.store.kahadb.disk.util.VariableMarshaller;
+import org.apache.activemq.store.kahadb.scheduler.legacy.LegacyStoreReplayer;
 import org.apache.activemq.util.ByteSequence;
 import org.apache.activemq.util.IOHelper;
-import org.apache.activemq.util.ServiceStopper;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-public class JobSchedulerStoreImpl extends LockableServiceSupport implements JobSchedulerStore {
-    static final Logger LOG = LoggerFactory.getLogger(JobSchedulerStoreImpl.class);
-    private static final int DATABASE_LOCKED_WAIT_DELAY = 10 * 1000;
-
-    public static final int CLOSED_STATE = 1;
-    public static final int OPEN_STATE = 2;
-
-    private File directory;
-    PageFile pageFile;
-    private Journal journal;
-    protected AtomicLong journalSize = new AtomicLong(0);
-    private boolean failIfDatabaseIsLocked;
-    private int journalMaxFileLength = Journal.DEFAULT_MAX_FILE_LENGTH;
-    private int journalMaxWriteBatchSize = Journal.DEFAULT_MAX_WRITE_BATCH_SIZE;
-    private boolean enableIndexWriteAsync = false;
-    MetaData metaData = new MetaData(this);
-    final MetaDataMarshaller metaDataMarshaller = new MetaDataMarshaller(this);
-    Map<String, JobSchedulerImpl> schedulers = new HashMap<String, JobSchedulerImpl>();
-
-    protected class MetaData {
-        protected MetaData(JobSchedulerStoreImpl store) {
-            this.store = store;
-        }
+public class JobSchedulerStoreImpl extends AbstractKahaDBStore implements JobSchedulerStore {
 
-        private final JobSchedulerStoreImpl store;
-        Page<MetaData> page;
-        BTreeIndex<Integer, Integer> journalRC;
-        BTreeIndex<String, JobSchedulerImpl> storedSchedulers;
+    private static final Logger LOG = LoggerFactory.getLogger(JobSchedulerStoreImpl.class);
 
-        void createIndexes(Transaction tx) throws IOException {
-            this.storedSchedulers = new BTreeIndex<String, JobSchedulerImpl>(pageFile, tx.allocate().getPageId());
-            this.journalRC = new BTreeIndex<Integer, Integer>(pageFile, tx.allocate().getPageId());
-        }
+    private JobSchedulerKahaDBMetaData metaData = new JobSchedulerKahaDBMetaData(this);
+    private final MetaDataMarshaller metaDataMarshaller = new MetaDataMarshaller(this);
+    private final Map<String, JobSchedulerImpl> schedulers = new HashMap<String, JobSchedulerImpl>();
+    private File legacyStoreArchiveDirectory;
+
+    /**
+     * The Scheduler Token is used to identify base revisions of the Scheduler store.  A store
+     * based on the initial scheduler design will not have this tag in it's meta-data and will
+     * indicate an update is needed.  Later versions of the scheduler can also change this value
+     * to indicate incompatible store bases which require complete meta-data and journal rewrites
+     * instead of simpler meta-data updates.
+     */
+    static final UUID SCHEDULER_STORE_TOKEN = UUID.fromString("57ed642b-1ee3-47b3-be6d-b7297d500409");
 
-        void load(Transaction tx) throws IOException {
-            this.storedSchedulers.setKeyMarshaller(StringMarshaller.INSTANCE);
-            this.storedSchedulers.setValueMarshaller(new JobSchedulerMarshaller(this.store));
-            this.storedSchedulers.load(tx);
-            this.journalRC.setKeyMarshaller(IntegerMarshaller.INSTANCE);
-            this.journalRC.setValueMarshaller(IntegerMarshaller.INSTANCE);
-            this.journalRC.load(tx);
+    /**
+     * The default scheduler store version.  All new store instance will be given this version and
+     * earlier versions will be updated to this version.
+     */
+    static final int CURRENT_VERSION = 1;
+
+    @Override
+    public JobScheduler getJobScheduler(final String name) throws Exception {
+        this.indexLock.writeLock().lock();
+        try {
+            JobSchedulerImpl result = this.schedulers.get(name);
+            if (result == null) {
+                final JobSchedulerImpl js = new JobSchedulerImpl(this);
+                js.setName(name);
+                getPageFile().tx().execute(new Transaction.Closure<IOException>() {
+                    @Override
+                    public void execute(Transaction tx) throws IOException {
+                        js.createIndexes(tx);
+                        js.load(tx);
+                        metaData.getJobSchedulers().put(tx, name, js);
+                    }
+                });
+                result = js;
+                this.schedulers.put(name, js);
+                if (isStarted()) {
+                    result.start();
+                }
+                this.pageFile.flush();
+            }
+            return result;
+        } finally {
+            this.indexLock.writeLock().unlock();
         }
+    }
+
+    @Override
+    public boolean removeJobScheduler(final String name) throws Exception {
+        boolean result = false;
 
-        void loadScheduler(Transaction tx, Map<String, JobSchedulerImpl> schedulers) throws IOException {
-            for (Iterator<Entry<String, JobSchedulerImpl>> i = this.storedSchedulers.iterator(tx); i.hasNext();) {
-                Entry<String, JobSchedulerImpl> entry = i.next();
-                entry.getValue().load(tx);
-                schedulers.put(entry.getKey(), entry.getValue());
+        this.indexLock.writeLock().lock();
+        try {
+            final JobSchedulerImpl js = this.schedulers.remove(name);
+            result = js != null;
+            if (result) {
+                js.stop();
+                getPageFile().tx().execute(new Transaction.Closure<IOException>() {
+                    @Override
+                    public void execute(Transaction tx) throws IOException {
+                        metaData.getJobSchedulers().remove(tx, name);
+                        js.removeAll(tx);
+                    }
+                });
             }
+        } finally {
+            this.indexLock.writeLock().unlock();
         }
+        return result;
+    }
 
-        public void read(DataInput is) throws IOException {
-            this.storedSchedulers = new BTreeIndex<String, JobSchedulerImpl>(pageFile, is.readLong());
-            this.storedSchedulers.setKeyMarshaller(StringMarshaller.INSTANCE);
-            this.storedSchedulers.setValueMarshaller(new JobSchedulerMarshaller(this.store));
-            this.journalRC = new BTreeIndex<Integer, Integer>(pageFile, is.readLong());
-            this.journalRC.setKeyMarshaller(IntegerMarshaller.INSTANCE);
-            this.journalRC.setValueMarshaller(IntegerMarshaller.INSTANCE);
+    /**
+     * Sets the directory where the legacy scheduler store files are archived before an
+     * update attempt is made.  Both the legacy index files and the journal files are moved
+     * to this folder prior to an upgrade attempt.
+     *
+     * @param directory
+     *      The directory to move the legacy Scheduler Store files to.
+     */
+    public void setLegacyStoreArchiveDirectory(File directory) {
+        this.legacyStoreArchiveDirectory = directory;
+    }
+
+    /**
+     * Gets the directory where the legacy Scheduler Store files will be archived if the
+     * broker is started and an existing Job Scheduler Store from an old version is detected.
+     *
+     * @return the directory where scheduler store legacy files are archived on upgrade.
+     */
+    public File getLegacyStoreArchiveDirectory() {
+        if (this.legacyStoreArchiveDirectory == null) {
+            this.legacyStoreArchiveDirectory = new File(getDirectory(), "legacySchedulerStore");
         }
 
-        public void write(DataOutput os) throws IOException {
-            os.writeLong(this.storedSchedulers.getPageId());
-            os.writeLong(this.journalRC.getPageId());
+        return this.legacyStoreArchiveDirectory.getAbsoluteFile();
+    }
+
+    @Override
+    public void load() throws IOException {
+        if (opened.compareAndSet(false, true)) {
+            getJournal().start();
+            try {
+                loadPageFile();
+            } catch (UnknownStoreVersionException ex) {
+                LOG.info("Can't start until store update is performed.");
+                upgradeFromLegacy();
+                // Restart with the updated store
+                getJournal().start();
+                loadPageFile();
+                LOG.info("Update from legacy Scheduler store completed successfully.");
+            } catch (Throwable t) {
+                LOG.warn("Index corrupted. Recovering the index through journal replay. Cause: {}", t.toString());
+                LOG.debug("Index load failure", t);
+
+                // try to recover index
+                try {
+                    pageFile.unload();
+                } catch (Exception ignore) {
+                }
+                if (isArchiveCorruptedIndex()) {
+                    pageFile.archive();
+                } else {
+                    pageFile.delete();
+                }
+                metaData = new JobSchedulerKahaDBMetaData(this);
+                pageFile = null;
+                loadPageFile();
+            }
+            startCheckpoint();
+            recover();
         }
+        LOG.info("{} started.", this);
     }
 
-    class MetaDataMarshaller extends VariableMarshaller<MetaData> {
-        private final JobSchedulerStoreImpl store;
+    @Override
+    public void unload() throws IOException {
+        if (opened.compareAndSet(true, false)) {
+            for (JobSchedulerImpl js : this.schedulers.values()) {
+                try {
+                    js.stop();
+                } catch (Exception e) {
+                    throw new IOException(e);
+                }
+            }
+            this.indexLock.writeLock().lock();
+            try {
+                if (pageFile != null && pageFile.isLoaded()) {
+                    metaData.setState(KahaDBMetaData.CLOSED_STATE);
+
+                    if (metaData.getPage() != null) {
+                        pageFile.tx().execute(new Transaction.Closure<IOException>() {
+                            @Override
+                            public void execute(Transaction tx) throws IOException {
+                                tx.store(metaData.getPage(), metaDataMarshaller, true);
+                            }
+                        });
+                    }
+                }
+            } finally {
+                this.indexLock.writeLock().unlock();
+            }
 
-        MetaDataMarshaller(JobSchedulerStoreImpl store) {
-            this.store = store;
-        }
+            checkpointLock.writeLock().lock();
+            try {
+                if (metaData.getPage() != null) {
+                    checkpointUpdate(true);
+                }
+            } finally {
+                checkpointLock.writeLock().unlock();
+            }
+            synchronized (checkpointThreadLock) {
+                if (checkpointThread != null) {
+                    try {
+                        checkpointThread.join();
+                        checkpointThread = null;
+                    } catch (InterruptedException e) {
+                    }
+                }
+            }
 
-        @Override
-        public MetaData readPayload(DataInput dataIn) throws IOException {
-            MetaData rc = new MetaData(this.store);
-            rc.read(dataIn);
-            return rc;
+            if (pageFile != null) {
+                pageFile.unload();
+                pageFile = null;
+            }
+            if (this.journal != null) {
+                journal.close();
+                journal = null;
+            }
+
+            metaData = new JobSchedulerKahaDBMetaData(this);
         }
+        LOG.info("{} stopped.", this);
+    }
 
-        @Override
-        public void writePayload(MetaData object, DataOutput dataOut) throws IOException {
-            object.write(dataOut);
+    private void loadPageFile() throws IOException {
+        this.indexLock.writeLock().lock();
+        try {
+            final PageFile pageFile = getPageFile();
+            pageFile.load();
+            pageFile.tx().execute(new Transaction.Closure<IOException>() {
+                @Override
+                public void execute(Transaction tx) throws IOException {
+                    if (pageFile.getPageCount() == 0) {
+                        Page<JobSchedulerKahaDBMetaData> page = tx.allocate();
+                        assert page.getPageId() == 0;
+                        page.set(metaData);
+                        metaData.setPage(page);
+                        metaData.setState(KahaDBMetaData.CLOSED_STATE);
+                        metaData.initialize(tx);
+                        tx.store(metaData.getPage(), metaDataMarshaller, true);
+                    } else {
+                        Page<JobSchedulerKahaDBMetaData> page = null;
+                        page = tx.load(0, metaDataMarshaller);
+                        metaData = page.get();
+                        metaData.setPage(page);
+                    }
+                    metaData.load(tx);
+                    metaData.loadScheduler(tx, schedulers);
+                    for (JobSchedulerImpl js : schedulers.values()) {
+                        try {
+                            js.start();
+                        } catch (Exception e) {
+                            JobSchedulerStoreImpl.LOG.error("Failed to load " + js.getName(), e);
+                        }
+                    }
+                }
+            });
+
+            pageFile.flush();
+        } finally {
+            this.indexLock.writeLock().unlock();
         }
     }
 
-    class ValueMarshaller extends VariableMarshaller<List<JobLocation>> {
-        @Override
-        public List<JobLocation> readPayload(DataInput dataIn) throws IOException {
-            List<JobLocation> result = new ArrayList<JobLocation>();
-            int size = dataIn.readInt();
-            for (int i = 0; i < size; i++) {
-                JobLocation jobLocation = new JobLocation();
-                jobLocation.readExternal(dataIn);
-                result.add(jobLocation);
+    private void upgradeFromLegacy() throws IOException {
+
+        journal.close();
+        journal = null;
+        try {
+            pageFile.unload();
+            pageFile = null;
+        } catch (Exception ignore) {}
+
+        File storeDir = getDirectory().getAbsoluteFile();
+        File storeArchiveDir = getLegacyStoreArchiveDirectory();
+
+        LOG.info("Attempting to move old store files from {} to {}", storeDir, storeArchiveDir);
+
+        // Move only the known store files, locks and other items left in place.
+        IOHelper.moveFiles(storeDir, storeArchiveDir, new FilenameFilter() {
+
+            @Override
+            public boolean accept(File dir, String name) {
+                if (name.endsWith(".data") || name.endsWith(".redo") || name.endsWith(".log")) {
+                    return true;
+                }
+                return false;
+            }
+        });
+
+        // We reset everything to clean state, then we can read from the old
+        // scheduler store and replay the scheduled jobs into this one as adds.
+        getJournal().start();
+        metaData = new JobSchedulerKahaDBMetaData(this);
+        pageFile = null;
+        loadPageFile();
+
+        LegacyStoreReplayer replayer = new LegacyStoreReplayer(getLegacyStoreArchiveDirectory());
+        replayer.load();
+        replayer.startReplay(this);
+
+        // Cleanup after replay and store what we've done.
+        pageFile.tx().execute(new Transaction.Closure<IOException>() {
+            @Override
+            public void execute(Transaction tx) throws IOException {
+                tx.store(metaData.getPage(), metaDataMarshaller, true);
+            }
+        });
+
+        checkpointUpdate(true);
+        getJournal().close();
+        getPageFile().unload();
+    }
+
+    @Override
+    protected void checkpointUpdate(Transaction tx, boolean cleanup) throws IOException {
+        LOG.debug("Job Scheduler Store Checkpoint started.");
+
+        // reflect last update exclusive of current checkpoint
+        Location lastUpdate = metaData.getLastUpdateLocation();
+        metaData.setState(KahaDBMetaData.OPEN_STATE);
+        tx.store(metaData.getPage(), metaDataMarshaller, true);
+        pageFile.flush();
+
+        if (cleanup) {
+            final TreeSet<Integer> completeFileSet = new TreeSet<Integer>(journal.getFileMap().keySet());
+            final TreeSet<Integer> gcCandidateSet = new TreeSet<Integer>(completeFileSet);
+
+            LOG.trace("Last update: {}, full gc candidates set: {}", lastUpdate, gcCandidateSet);
+
+            if (lastUpdate != null) {
+                gcCandidateSet.remove(lastUpdate.getDataFileId());
+            }
+
+            this.metaData.getJournalRC().visit(tx, new BTreeVisitor<Integer, Integer>() {
+
+                @Override
+                public void visit(List<Integer> keys, List<Integer> values) {
+                    for (Integer key : keys) {
+                        if (gcCandidateSet.remove(key)) {
+                            LOG.trace("Removed referenced file: {} from GC set", key);
+                        }
+                    }
+                }
+
+                @Override
+                public boolean isInterestedInKeysBetween(Integer first, Integer second) {
+                    return true;
+                }
+            });
+
+            LOG.trace("gc candidates after reference check: {}", gcCandidateSet);
+
+            // If there are GC candidates then check the remove command location to see
+            // if any of them can go or if they must stay in order to ensure proper recover.
+            //
+            // A log containing any remove commands must be kept until all the logs with the
+            // add commands for all the removed jobs have been dropped.
+            if (!gcCandidateSet.isEmpty()) {
+                Iterator<Entry<Integer, List<Integer>>> removals = metaData.getRemoveLocationTracker().iterator(tx);
+                List<Integer> orphans = new ArrayList<Integer>();
+                while (removals.hasNext()) {
+                    boolean orphanedRemve = true;
+                    Entry<Integer, List<Integer>> entry = removals.next();
+
+                    // If this log is not a GC candidate then there's no need to do a check to rule it out
+                    if (gcCandidateSet.contains(entry.getKey())) {
+                        for (Integer addLocation : entry.getValue()) {
+                            if (completeFileSet.contains(addLocation)) {
+                                orphanedRemve = false;
+                                break;
+                            }
+                        }
+
+                        // If it's not orphaned than we can't remove it, otherwise we
+                        // stop tracking it it's log will get deleted on the next check.
+                        if (!orphanedRemve) {
+                            LOG.trace("A remove in log {} has an add still in existance.", entry.getKey());
+                            gcCandidateSet.remove(entry.getKey());
+                        } else {
+                            LOG.trace("All removes in log {} are orphaned, file can be GC'd", entry.getKey());
+                            orphans.add(entry.getKey());
+                        }
+                    }
+                }
+
+                // Drop all orphaned removes from the tracker.
+                for (Integer orphan : orphans) {
+                    metaData.getRemoveLocationTracker().remove(tx, orphan);
+                }
+            }
+
+            LOG.trace("gc candidates after removals check: {}", gcCandidateSet);
+            if (!gcCandidateSet.isEmpty()) {
+                if (LOG.isDebugEnabled()) {
+                    LOG.debug("Cleanup removing the data files: " + gcCandidateSet);
+                }
+                journal.removeDataFiles(gcCandidateSet);
             }
-            return result;
         }
 
-        @Override
-        public void writePayload(List<JobLocation> value, DataOutput dataOut) throws IOException {
-            dataOut.writeInt(value.size());
-            for (JobLocation jobLocation : value) {
-                jobLocation.writeExternal(dataOut);
+        LOG.debug("Job Scheduler Store Checkpoint complete.");
+    }
+
+    /**
+     * Adds a reference for the journal log file pointed to by the given Location value.
+     *
+     * To prevent log files in the journal that still contain valid data that needs to be
+     * kept in order to allow for recovery the logs must have active references.  Each Job
+     * scheduler should ensure that the logs are accurately referenced.
+     *
+     * @param tx
+     *      The TX under which the update is to be performed.
+     * @param location
+     *      The location value to update the reference count of.
+     *
+     * @throws IOException if an error occurs while updating the journal references table.
+     */
+    protected void incrementJournalCount(Transaction tx, Location location) throws IOException {
+        int logId = location.getDataFileId();
+        Integer val = metaData.getJournalRC().get(tx, logId);
+        int refCount = val != null ? val.intValue() + 1 : 1;
+        metaData.getJournalRC().put(tx, logId, refCount);
+    }
+
+    /**
+     * Removes one reference for the Journal log file indicated in the given Location value.
+     *
+     * The references are used to track which log files cannot be GC'd.  When the reference count
+     * on a log file reaches zero the file id is removed from the tracker and the log will be
+     * removed on the next check point update.
+     *
+     * @param tx
+     *      The TX under which the update is to be performed.
+     * @param location
+     *      The location value to update the reference count of.
+     *
+     * @throws IOException if an error occurs while updating the journal references table.
+     */
+    protected void decrementJournalCount(Transaction tx, Location location) throws IOException {
+        int logId = location.getDataFileId();
+        Integer refCount = metaData.getJournalRC().get(tx, logId);
+        if (refCount != null) {
+            int refCountValue = refCount;
+            refCountValue--;
+            if (refCountValue <= 0) {
+                metaData.getJournalRC().remove(tx, logId);
+            } else {
+                metaData.getJournalRC().put(tx, logId, refCountValue);
             }
         }
     }
 
-    class JobSchedulerMarshaller extends VariableMarshaller<JobSchedulerImpl> {
+    /**
+     * Updates the Job removal tracking index with the location of a remove command and the
+     * original JobLocation entry.
+     *
+     * The JobLocation holds the locations in the logs where the add and update commands for
+     * a job stored.  The log file containing the remove command can only be discarded after
+     * both the add and latest update log files have also been discarded.
+     *
+     * @param tx
+     *      The TX under which the update is to be performed.
+     * @param location
+     *      The location value to reference a remove command.
+     * @param removedJob
+     *      The original JobLocation instance that holds the add and update locations
+     *
+     * @throws IOException if an error occurs while updating the remove location tracker.
+     */
+    protected void referenceRemovedLocation(Transaction tx, Location location, JobLocation removedJob) throws IOException {
+        int logId = location.getDataFileId();
+        List<Integer> removed = this.metaData.getRemoveLocationTracker().get(tx, logId);
+        if (removed == null) {
+            removed = new ArrayList<Integer>();
+        }
+        removed.add(removedJob.getLocation().getDataFileId());
+        this.metaData.getRemoveLocationTracker().put(tx, logId, removed);
+    }
+
+    /**
+     * Retrieve the scheduled Job's byte blob from the journal.
+     *
+     * @param location
+     *      The location of the KahaAddScheduledJobCommand that originated the Job.
+     *
+     * @return a ByteSequence containing the payload of the scheduled Job.
+     *
+     * @throws IOException if an error occurs while reading the payload value.
+     */
+    protected ByteSequence getPayload(Location location) throws IOException {
+        KahaAddScheduledJobCommand job = (KahaAddScheduledJobCommand) this.load(location);
+        Buffer payload = job.getPayload();
+        return new ByteSequence(payload.getData(), payload.getOffset(), payload.getLength());
+    }
+
+    public void readLockIndex() {
+        this.indexLock.readLock().lock();
+    }
+
+    public void readUnlockIndex() {
+        this.indexLock.readLock().unlock();
+    }
+
+    public void writeLockIndex() {
+        this.indexLock.writeLock().lock();
+    }
+
+    public void writeUnlockIndex() {
+        this.indexLock.writeLock().unlock();
+    }
+
+    @Override
+    public String toString() {
+        return "JobSchedulerStore: " + getDirectory();
+    }
+
+    @Override
+    protected String getPageFileName() {
+        return "scheduleDB";
+    }
+
+    @Override
+    protected File getDefaultDataDirectory() {
+        return new File(IOHelper.getDefaultDataDirectory(), "delayedDB");
+    }
+
+    private class MetaDataMarshaller extends VariableMarshaller<JobSchedulerKahaDBMetaData> {
+
         private final JobSchedulerStoreImpl store;
 
-        JobSchedulerMarshaller(JobSchedulerStoreImpl store) {
+        MetaDataMarshaller(JobSchedulerStoreImpl store) {
             this.store = store;
         }
 
         @Override
-        public JobSchedulerImpl readPayload(DataInput dataIn) throws IOException {
-            JobSchedulerImpl result = new JobSchedulerImpl(this.store);
-            result.read(dataIn);
-            return result;
+        public JobSchedulerKahaDBMetaData readPayload(DataInput dataIn) throws IOException {
+            JobSchedulerKahaDBMetaData rc = new JobSchedulerKahaDBMetaData(store);
+            rc.read(dataIn);
+            return rc;
         }
 
         @Override
-        public void writePayload(JobSchedulerImpl js, DataOutput dataOut) throws IOException {
-            js.write(dataOut);
+        public void writePayload(JobSchedulerKahaDBMetaData object, DataOutput dataOut) throws IOException {
+            object.write(dataOut);
         }
     }
 
-    @Override
-    public File getDirectory() {
-        return directory;
+    /**
+     * Called during index recovery to rebuild the index from the last known good location.  For
+     * entries that occur before the last known good position we just ignore then and move on.
+     *
+     * @param command
+     *        the command read from the Journal which should be used to update the index.
+     * @param location
+     *        the location in the index where the command was read.
+     * @param inDoubtlocation
+     *        the location in the index known to be the last time the index was valid.
+     *
+     * @throws IOException if an error occurs while recovering the index.
+     */
+    protected void doRecover(JournalCommand<?> data, final Location location, final Location inDoubtlocation) throws IOException {
+        if (inDoubtlocation != null && location.compareTo(inDoubtlocation) >= 0) {
+            process(data, location);
+        }
     }
 
+    /**
+     * Called during recovery to allow the store to rebuild from scratch.
+     *
+     * @param data
+     *      The command to process, which was read from the Journal.
+     * @param location
+     *      The location of the command in the Journal.
+     *
+     * @throws IOException if an error occurs during command processing.
+     */
     @Override
-    public void setDirectory(File directory) {
-        this.directory = directory;
+    protected void process(JournalCommand<?> data, final Location location) throws IOException {
+        data.visit(new Visitor() {
+            @Override
+            public void visit(final KahaAddScheduledJobCommand command) throws IOException {
+                final JobSchedulerImpl scheduler;
+
+                indexLock.writeLock().lock();
+                try {
+                    try {
+                        scheduler = (JobSchedulerImpl) getJobScheduler(command.getScheduler());
+                    } catch (Exception e) {
+                        throw new IOException(e);
+                    }
+                    getPageFile().tx().execute(new Transaction.Closure<IOException>() {
+                        @Override
+                        public void execute(Transaction tx) throws IOException {
+                            scheduler.process(tx, command, location);
+                        }
+                    });
+
+                    processLocation(location);
+                } finally {
+                    indexLock.writeLock().unlock();
+                }
+            }
+
+            @Override
+            public void visit(final KahaRemoveScheduledJobCommand command) throws IOException {
+                final JobSchedulerImpl scheduler;
+
+                indexLock.writeLock().lock();
+                try {
+                    try {
+                        scheduler = (JobSchedulerImpl) getJobScheduler(command.getScheduler());
+                    } catch (Exception e) {
+                        throw new IOException(e);
+                    }
+                    getPageFile().tx().execute(new Transaction.Closure<IOException>() {
+                        @Override
+                        public void execute(Transaction tx) throws IOException {
+                            scheduler.process(tx, command, location);
+                        }
+                    });
+
+                    processLocation(location);
+                } finally {
+                    indexLock.writeLock().unlock();
+                }
+            }
+
+            @Override
+            public void visit(final KahaRemoveScheduledJobsCommand command) throws IOException {
+                final JobSchedulerImpl scheduler;
+
+                indexLock.writeLock().lock();
+                try {
+                    try {
+                        scheduler = (JobSchedulerImpl) getJobScheduler(command.getScheduler());
+                    } catch (Exception e) {
+                        throw new IOException(e);
+                    }
+                    getPageFile().tx().execute(new Transaction.Closure<IOException>() {
+                        @Override
+                        public void execute(Transaction tx) throws IOException {
+                            scheduler.process(tx, command, location);
+                        }
+                    });
+
+                    processLocation(location);
+                } finally {
+                    indexLock.writeLock().unlock();
+                }
+            }
+
+            @Override
+            public void visit(final KahaRescheduleJobCommand command) throws IOException {
+                final JobSchedulerImpl scheduler;
+
+                indexLock.writeLock().lock();
+                try {
+                    try {
+                        scheduler = (JobSchedulerImpl) getJobScheduler(command.getScheduler());
+                    } catch (Exception e) {
+                        throw new IOException(e);
+                    }
+                    getPageFile().tx().execute(new Transaction.Closure<IOException>() {
+                        @Override
+                        public void execute(Transaction tx) throws IOException {
+                            scheduler.process(tx, command, location);
+                        }
+                    });
+
+                    processLocation(location);
+                } finally {
+                    indexLock.writeLock().unlock();
+                }
+            }
+
+            @Override
+            public void visit(final KahaDestroySchedulerCommand command) {
+                try {
+                    removeJobScheduler(command.getScheduler());
+                } catch (Exception e) {
+                    LOG.warn("Failed to remove scheduler: {}", command.getScheduler());
+                }
+
+                processLocation(location);
+            }
+
+            @Override
+            public void visit(KahaTraceCommand command) {
+                processLocation(location);
+            }
+        });
     }
 
-    @Override
-    public long size() {
-        if (!isStarted()) {
-            return 0;
-        }
+    protected void processLocation(final Location location) {
+        indexLock.writeLock().lock();
         try {
-            return journalSize.get() + pageFile.getDiskSize();
-        } catch (IOException e) {
-            throw new RuntimeException(e);
+            this.metaData.setLastUpdateLocation(location);
+        } finally {
+            indexLock.writeLock().unlock();
         }
     }
 
-    @Override
-    public JobScheduler getJobScheduler(final String name) throws Exception {
-        JobSchedulerImpl result = this.schedulers.get(name);
-        if (result == null) {
-            final JobSchedulerImpl js = new JobSchedulerImpl(this);
-            js.setName(name);
-            getPageFile().tx().execute(new Transaction.Closure<IOException>() {
-                @Override
-                public void execute(Transaction tx) throws IOException {
-                    js.createIndexes(tx);
-                    js.load(tx);
-                    metaData.storedSchedulers.put(tx, name, js);
+    /**
+     * We recover from the Journal logs as needed to restore the index.
+     *
+     * @throws IllegalStateException
+     * @throws IOException
+     */
+    private void recover() throws IllegalStateException, IOException {
+        this.indexLock.writeLock().lock();
+        try {
+            long start = System.currentTimeMillis();
+            Location lastIndoubtPosition = getRecoveryPosition();
+            Location recoveryPosition = lastIndoubtPosition;
+
+            if (recoveryPosition != null) {
+                int redoCounter = 0;
+                LOG.info("Recovering from the journal ...");
+                while (recoveryPosition != null) {
+                    JournalCommand<?> message = load(recoveryPosition);
+                    metaData.setLastUpdateLocation(recoveryPosition);
+                    doRecover(message, recoveryPosition, lastIndoubtPosition);
+                    redoCounter++;
+                    recoveryPosition = journal.getNextLocation(recoveryPosition);
+                     if (LOG.isInfoEnabled() && redoCounter % 100000 == 0) {
+                         LOG.info("@ {}, {} entries recovered ..", recoveryPosition, redoCounter);
+                     }
                 }
-            });
-            result = js;
-            this.schedulers.put(name, js);
-            if (isStarted()) {
-                result.start();
+                long end = System.currentTimeMillis();
+                LOG.info("Recovery replayed {} operations from the journal in {} seconds.",
+                         redoCounter, ((end - start) / 1000.0f));
             }
-            this.pageFile.flush();
-        }
-        return result;
-    }
 
-    @Override
-    synchronized public boolean removeJobScheduler(final String name) throws Exception {
-        boolean result = false;
-        final JobSchedulerImpl js = this.schedulers.remove(name);
-        result = js != null;
-        if (result) {
-            js.stop();
-            getPageFile().tx().execute(new Transaction.Closure<IOException>() {
+            // We may have to undo some index updates.
+            pageFile.tx().execute(new Transaction.Closure<IOException>() {
                 @Override
                 public void execute(Transaction tx) throws IOException {
-                    metaData.storedSchedulers.remove(tx, name);
-                    js.destroy(tx);
+                    recoverIndex(tx);
                 }
             });
+
+        } finally {
+            this.indexLock.writeLock().unlock();
         }
-        return result;
     }
 
-    @Override
-    protected synchronized void doStart() throws Exception {
-        if (this.directory == null) {
-            this.directory = new File(IOHelper.getDefaultDataDirectory() + File.pathSeparator + "delayedDB");
+    private Location getRecoveryPosition() throws IOException {
+        // This loads the first position and we completely rebuild the index if we
+        // do not override it with some known recovery start location.
+        Location result = null;
+
+        if (!isForceRecoverIndex()) {
+            if (metaData.getLastUpdateLocation() != null) {
+                result = metaData.getLastUpdateLocation();
+            }
         }
-        IOHelper.mkdirs(this.directory);
-        this.journal = new Journal();
-        this.journal.setDirectory(directory);
-        this.journal.setMaxFileLength(getJournalMaxFileLength());
-        this.journal.setWriteBatchSize(getJournalMaxWriteBatchSize());
-        this.journal.setSizeAccumulator(this.journalSize);
-        this.journal.start();
-        this.pageFile = new PageFile(directory, "scheduleDB");
-        this.pageFile.setWriteBatchSize(1);
-        this.pageFile.load();
-
-        this.pageFile.tx().execute(new Transaction.Closure<IOException>() {
-            @Override
-            public void execute(Transaction tx) throws IOException {
-                if (pageFile.getPageCount() == 0) {
-                    Page<MetaData> page = tx.allocate();
-                    assert page.getPageId() == 0;
-                    page.set(metaData);
-                    metaData.page = page;
-                    metaData.createIndexes(tx);
-                    tx.store(metaData.page, metaDataMarshaller, true);
 
-                } else {
-                    Page<MetaData> page = tx.load(0, metaDataMarshaller);
-                    metaData = page.get();
-                    metaData.page = page;
-                }
-                metaData.load(tx);
-                metaData.loadScheduler(tx, schedulers);
-                for (JobSchedulerImpl js : schedulers.values()) {
-                    try {
-                        js.start();
-                    } catch (Exception e) {
-                        JobSchedulerStoreImpl.LOG.error("Failed to load " + js.getName(), e);
+        return journal.getNextLocation(result);
+    }
+
+    private void recoverIndex(Transaction tx) throws IOException {
+        long start = System.currentTimeMillis();
+
+        // It is possible index updates got applied before the journal updates..
+        // in that case we need to removed references to Jobs that are not in the journal
+        final Location lastAppendLocation = journal.getLastAppendLocation();
+        long undoCounter = 0;
+
+        // Go through all the jobs in each scheduler and check if any are added after
+        // the last appended location and remove those.  For now we ignore the update
+        // location since the scheduled job will update itself after the next fire and
+        // a new update will replace any existing update.
+        for (Iterator<Map.Entry<String, JobSchedulerImpl>> i = metaData.getJobSchedulers().iterator(tx); i.hasNext();) {
+            Map.Entry<String, JobSchedulerImpl> entry = i.next();
+            JobSchedulerImpl scheduler = entry.getValue();
+
+            List<JobLocation> jobs = scheduler.getAllScheduledJobs(tx);
+            for (JobLocation job : jobs) {
+                if (job.getLocation().compareTo(lastAppendLocation) >= 0) {
+                    if (scheduler.removeJobAtTime(tx, job.getJobId(), job.getNextTime())) {
+                        LOG.trace("Removed Job past last appened in the journal: {}", job.getJobId());
+                        undoCounter++;
                     }
                 }
             }
-        });
-
-        this.pageFile.flush();
-        LOG.info(this + " started");
-    }
+        }
 
-    @Override
-    protected synchronized void doStop(ServiceStopper stopper) throws Exception {
-        for (JobSchedulerImpl js : this.schedulers.values()) {
-            js.stop();
+        if (undoCounter > 0) {
+            // The rolled back operations are basically in flight journal writes.  To avoid getting
+            // these the end user should do sync writes to the journal.
+            long end = System.currentTimeMillis();
+            LOG.info("Rolled back {} messages from the index in {} seconds.", undoCounter, ((end - start) / 1000.0f));
+            undoCounter = 0;
         }
-        if (this.pageFile != null) {
-            this.pageFile.unload();
+
+        // Now we check for missing and corrupt journal files.
+
+        // 1. Collect the set of all referenced journal files based on the Location of the
+        //    the scheduled jobs and the marked last update field.
+        HashSet<Integer> missingJournalFiles = new HashSet<Integer>();
+        for (Iterator<Map.Entry<String, JobSchedulerImpl>> i = metaData.getJobSchedulers().iterator(tx); i.hasNext();) {
+            Map.Entry<String, JobSchedulerImpl> entry = i.next();
+            JobSchedulerImpl scheduler = entry.getValue();
+
+            List<JobLocation> jobs = scheduler.getAllScheduledJobs(tx);
+            for (JobLocation job : jobs) {
+                missingJournalFiles.add(job.getLocation().getDataFileId());
+                if (job.getLastUpdate() != null) {
+                    missingJournalFiles.add(job.getLastUpdate().getDataFileId());
+                }
+            }
         }
-        if (this.journal != null) {
-            journal.close();
+
+        // 2. Remove from that set all known data file Id's in the journal and what's left
+        //    is the missing set which will soon also contain the corrupted set.
+        missingJournalFiles.removeAll(journal.getFileMap().keySet());
+        if (!missingJournalFiles.isEmpty()) {
+            LOG.info("Some journal files are missing: {}", missingJournalFiles);
         }
-        LOG.info(this + " stopped");
-    }
 
-    synchronized void incrementJournalCount(Transaction tx, Location location) throws IOException {
-        int logId = location.getDataFileId();
-        Integer val = this.metaData.journalRC.get(tx, logId);
-        int refCount = val != null ? val.intValue() + 1 : 1;
-        this.metaData.journalRC.put(tx, logId, refCount);
-    }
+        // 3. Now check all references in the journal logs for corruption and add any
+        //    corrupt journal files to the missing set.
+        HashSet<Location> corruptedLocations = new HashSet<Location>();
 
-    synchronized void decrementJournalCount(Transaction tx, Location location) throws IOException {
-        int logId = location.getDataFileId();
-        int refCount = this.metaData.journalRC.get(tx, logId);
-        refCount--;
-        if (refCount <= 0) {
-            this.metaData.journalRC.remove(tx, logId);
-            Set<Integer> set = new HashSet<Integer>();
-            set.add(logId);
-            this.journal.removeDataFiles(set);
-        } else {
-            this.metaData.journalRC.put(tx, logId, refCount);
-        }
-    }
+        if (isCheckForCorruptJournalFiles()) {
+            Collection<DataFile> dataFiles = journal.getFileMap().values();
+            for (DataFile dataFile : dataFiles) {
+                int id = dataFile.getDataFileId();
+                for (long offset : dataFile.getCorruptedBlocks()) {
+                    corruptedLocations.add(new Location(id, (int) offset));
+                }
+            }
 
-    synchronized ByteSequence getPayload(Location location) throws IllegalStateException, IOException {
-        ByteSequence result = null;
-        result = this.journal.read(location);
-        return result;
-    }
+            if (!corruptedLocations.isEmpty()) {
+                LOG.debug("Found some corrupted data blocks in the journal: {}", corruptedLocations.size());
+            }
+        }
 
-    synchronized Location write(ByteSequence payload, boolean sync) throws IllegalStateException, IOException {
-        return this.journal.write(payload, sync);
-    }
+        // 4. Now we either fail or we remove all references to missing or corrupt journal
+        //    files from the various JobSchedulerImpl instances.  We only remove the Job if
+        //    the initial Add operation is missing when the ignore option is set, the updates
+        //    could be lost but that's price you pay when ignoring the missing logs.
+        if (!missingJournalFiles.isEmpty() || !corruptedLocations.isEmpty()) {
+            if (!isIgnoreMissingJournalfiles()) {
+                throw new IOException("Detected missing/corrupt journal files.");
+            }
 
-    PageFile getPageFile() {
-        this.pageFile.isLoaded();
-        return this.pageFile;
-    }
+            // Remove all Jobs that reference an Location that is either missing or corrupt.
+            undoCounter = removeJobsInMissingOrCorruptJounralFiles(tx, missingJournalFiles, corruptedLocations);
 
-    public boolean isFailIfDatabaseIsLocked() {
-        return failIfDatabaseIsLocked;
-    }
+            // Clean up the Journal Reference count Map.
+            removeJournalRCForMissingFiles(tx, missingJournalFiles);
+        }
 
-    public void setFailIfDatabaseIsLocked(boolean failIfDatabaseIsLocked) {
-        this.failIfDatabaseIsLocked = failIfDatabaseIsLocked;
+        if (undoCounter > 0) {
+            long end = System.currentTimeMillis();
+            LOG.info("Detected missing/corrupt journal files.  Dropped {} jobs from the " +
+                     "index in {} seconds.", undoCounter, ((end - start) / 1000.0f));
+        }
     }
 
-    public int getJournalMaxFileLength() {
-        return journalMaxFileLength;
-    }
+    private void removeJournalRCForMissingFiles(Transaction tx, Set<Integer> missing) throws IOException {
+        List<Integer> matches = new ArrayList<Integer>();
 
-    public void setJournalMaxFileLength(int journalMaxFileLength) {
-        this.journalMaxFileLength = journalMaxFileLength;
-    }
+        Iterator<Entry<Integer, Integer>> references = metaData.getJournalRC().iterator(tx);
+        while (references.hasNext()) {
+            int dataFileId = references.next().getKey();
+            if (missing.contains(dataFileId)) {
+                matches.add(dataFileId);
+            }
+        }
 
-    public int getJournalMaxWriteBatchSize() {
-        return journalMaxWriteBatchSize;
+        for (Integer match : matches) {
+            metaData.getJournalRC().remove(tx, match);
+        }
     }
 
-    public void setJournalMaxWriteBatchSize(int journalMaxWriteBatchSize) {
-        this.journalMaxWriteBatchSize = journalMaxWriteBatchSize;
-    }
+    private int removeJobsInMissingOrCorruptJounralFiles(Transaction tx, Set<Integer> missing, Set<Location> corrupted) throws IOException {
+        int removed = 0;
 
-    public boolean isEnableIndexWriteAsync() {
-        return enableIndexWriteAsync;
-    }
+        // Remove Jobs that reference missing or corrupt files.
+        // Remove Reference counts to missing or corrupt files.
+        // Remove and remove command markers to missing or corrupt files.
+        for (Iterator<Map.Entry<String, JobSchedulerImpl>> i = metaData.getJobSchedulers().iterator(tx); i.hasNext();) {
+            Map.Entry<String, JobSchedulerImpl> entry = i.next();
+            JobSchedulerImpl scheduler = entry.getValue();
 
-    public void setEnableIndexWriteAsync(boolean enableIndexWriteAsync) {
-        this.enableIndexWriteAsync = enableIndexWriteAsync;
-    }
+            List<JobLocation> jobs = scheduler.getAllScheduledJobs(tx);
+            for (JobLocation job : jobs) {
 
-    @Override
-    public String toString() {
-        return "JobSchedulerStore:" + this.directory;
-    }
+                // Remove all jobs in missing log files.
+                if (missing.contains(job.getLocation().getDataFileId())) {
+                    scheduler.removeJobAtTime(tx, job.getJobId(), job.getNextTime());
+                    removed++;
+                    continue;
+                }
 
-    @Override
-    public Locker createDefaultLocker() throws IOException {
-        SharedFileLocker locker = new SharedFileLocker();
-        locker.setDirectory(this.getDirectory());
-        return locker;
-    }
+                // Remove all jobs in corrupted parts of log files.
+                if (corrupted.contains(job.getLocation())) {
+                    scheduler.removeJobAtTime(tx, job.getJobId(), job.getNextTime());
+                    removed++;
+                }
+            }
+        }
 
-    @Override
-    public void init() throws Exception {
+        return removed;
     }
 }

http://git-wip-us.apache.org/repos/asf/activemq/blob/fc244f48/activemq-kahadb-store/src/main/java/org/apache/activemq/store/kahadb/scheduler/UnknownStoreVersionException.java
----------------------------------------------------------------------
diff --git a/activemq-kahadb-store/src/main/java/org/apache/activemq/store/kahadb/scheduler/UnknownStoreVersionException.java b/activemq-kahadb-store/src/main/java/org/apache/activemq/store/kahadb/scheduler/UnknownStoreVersionException.java
new file mode 100644
index 0000000..5146d84
--- /dev/null
+++ b/activemq-kahadb-store/src/main/java/org/apache/activemq/store/kahadb/scheduler/UnknownStoreVersionException.java
@@ -0,0 +1,24 @@
+package org.apache.activemq.store.kahadb.scheduler;
+
+import java.io.IOException;
+
+public class UnknownStoreVersionException extends IOException {
+
+    private static final long serialVersionUID = -8544753506151157145L;
+
+    private final String token;
+
+    public UnknownStoreVersionException(Throwable cause) {
+        super(cause);
+        this.token = "";
+    }
+
+    public UnknownStoreVersionException(String token) {
+        super("Failed to load Store, found unknown store token: " + token);
+        this.token = token;
+    }
+
+    public String getToken() {
+        return this.token;
+    }
+}

http://git-wip-us.apache.org/repos/asf/activemq/blob/fc244f48/activemq-kahadb-store/src/main/java/org/apache/activemq/store/kahadb/scheduler/legacy/LegacyJobImpl.java
----------------------------------------------------------------------
diff --git a/activemq-kahadb-store/src/main/java/org/apache/activemq/store/kahadb/scheduler/legacy/LegacyJobImpl.java b/activemq-kahadb-store/src/main/java/org/apache/activemq/store/kahadb/scheduler/legacy/LegacyJobImpl.java
new file mode 100644
index 0000000..2562f50
--- /dev/null
+++ b/activemq-kahadb-store/src/main/java/org/apache/activemq/store/kahadb/scheduler/legacy/LegacyJobImpl.java
@@ -0,0 +1,72 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.activemq.store.kahadb.scheduler.legacy;
+
+import org.apache.activemq.protobuf.Buffer;
+import org.apache.activemq.util.ByteSequence;
+
+/**
+ * Legacy version Job and Job payload wrapper.  Allows for easy replay of stored
+ * legacy jobs into a new JobSchedulerStoreImpl intsance.
+ */
+final class LegacyJobImpl {
+
+    private final LegacyJobLocation jobLocation;
+    private final Buffer payload;
+
+    protected LegacyJobImpl(LegacyJobLocation location, ByteSequence payload) {
+        this.jobLocation = location;
+        this.payload = new Buffer(payload.data, payload.offset, payload.length);
+    }
+
+    public String getJobId() {
+        return this.jobLocation.getJobId();
+    }
+
+    public Buffer getPayload() {
+       return this.payload;
+    }
+
+    public long getPeriod() {
+       return this.jobLocation.getPeriod();
+    }
+
+    public int getRepeat() {
+       return this.jobLocation.getRepeat();
+    }
+
+    public long getDelay() {
+        return this.jobLocation.getDelay();
+    }
+
+    public String getCronEntry() {
+        return this.jobLocation.getCronEntry();
+    }
+
+    public long getNextExecutionTime() {
+        return this.jobLocation.getNextTime();
+    }
+
+    public long getStartTime() {
+        return this.jobLocation.getStartTime();
+    }
+
+    @Override
+    public String toString() {
+        return this.jobLocation.toString();
+    }
+}

http://git-wip-us.apache.org/repos/asf/activemq/blob/fc244f48/activemq-kahadb-store/src/main/java/org/apache/activemq/store/kahadb/scheduler/legacy/LegacyJobLocation.java
----------------------------------------------------------------------
diff --git a/activemq-kahadb-store/src/main/java/org/apache/activemq/store/kahadb/scheduler/legacy/LegacyJobLocation.java b/activemq-kahadb-store/src/main/java/org/apache/activemq/store/kahadb/scheduler/legacy/LegacyJobLocation.java
new file mode 100644
index 0000000..8437064
--- /dev/null
+++ b/activemq-kahadb-store/src/main/java/org/apache/activemq/store/kahadb/scheduler/legacy/LegacyJobLocation.java
@@ -0,0 +1,296 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.activemq.store.kahadb.scheduler.legacy;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+import org.apache.activemq.store.kahadb.disk.journal.Location;
+import org.apache.activemq.store.kahadb.disk.util.VariableMarshaller;
+
+final class LegacyJobLocation {
+
+    private String jobId;
+    private int repeat;
+    private long startTime;
+    private long delay;
+    private long nextTime;
+    private long period;
+    private String cronEntry;
+    private final Location location;
+
+    public LegacyJobLocation(Location location) {
+        this.location = location;
+    }
+
+    public LegacyJobLocation() {
+        this(new Location());
+    }
+
+    public void readExternal(DataInput in) throws IOException {
+        this.jobId = in.readUTF();
+        this.repeat = in.readInt();
+        this.startTime = in.readLong();
+        this.delay = in.readLong();
+        this.nextTime = in.readLong();
+        this.period = in.readLong();
+        this.cronEntry = in.readUTF();
+        this.location.readExternal(in);
+    }
+
+    public void writeExternal(DataOutput out) throws IOException {
+        out.writeUTF(this.jobId);
+        out.writeInt(this.repeat);
+        out.writeLong(this.startTime);
+        out.writeLong(this.delay);
+        out.writeLong(this.nextTime);
+        out.writeLong(this.period);
+        if (this.cronEntry == null) {
+            this.cronEntry = "";
+        }
+        out.writeUTF(this.cronEntry);
+        this.location.writeExternal(out);
+    }
+
+    /**
+     * @return the jobId
+     */
+    public String getJobId() {
+        return this.jobId;
+    }
+
+    /**
+     * @param jobId
+     *            the jobId to set
+     */
+    public void setJobId(String jobId) {
+        this.jobId = jobId;
+    }
+
+    /**
+     * @return the repeat
+     */
+    public int getRepeat() {
+        return this.repeat;
+    }
+
+    /**
+     * @param repeat
+     *            the repeat to set
+     */
+    public void setRepeat(int repeat) {
+        this.repeat = repeat;
+    }
+
+    /**
+     * @return the start
+     */
+    public long getStartTime() {
+        return this.startTime;
+    }
+
+    /**
+     * @param start
+     *            the start to set
+     */
+    public void setStartTime(long start) {
+        this.startTime = start;
+    }
+
+    /**
+     * @return the nextTime
+     */
+    public synchronized long getNextTime() {
+        return this.nextTime;
+    }
+
+    /**
+     * @param nextTime
+     *            the nextTime to set
+     */
+    public synchronized void setNextTime(long nextTime) {
+        this.nextTime = nextTime;
+    }
+
+    /**
+     * @return the period
+     */
+    public long getPeriod() {
+        return this.period;
+    }
+
+    /**
+     * @param period
+     *            the period to set
+     */
+    public void setPeriod(long period) {
+        this.period = period;
+    }
+
+    /**
+     * @return the cronEntry
+     */
+    public synchronized String getCronEntry() {
+        return this.cronEntry;
+    }
+
+    /**
+     * @param cronEntry
+     *            the cronEntry to set
+     */
+    public synchronized void setCronEntry(String cronEntry) {
+        this.cronEntry = cronEntry;
+    }
+
+    /**
+     * @return if this JobLocation represents a cron entry.
+     */
+    public boolean isCron() {
+        return getCronEntry() != null && getCronEntry().length() > 0;
+    }
+
+    /**
+     * @return the delay
+     */
+    public long getDelay() {
+        return this.delay;
+    }
+
+    /**
+     * @param delay
+     *            the delay to set
+     */
+    public void setDelay(long delay) {
+        this.delay = delay;
+    }
+
+    /**
+     * @return the location
+     */
+    public Location getLocation() {
+        return this.location;
+    }
+
+    @Override
+    public String toString() {
+        return "Job [id=" + jobId + ", startTime=" + new Date(startTime) +
+               ", delay=" + delay + ", period=" + period +
+               ", repeat=" + repeat + ", nextTime=" + new Date(nextTime) + "]";
+    }
+
+    static class JobLocationMarshaller extends VariableMarshaller<List<LegacyJobLocation>> {
+        static final JobLocationMarshaller INSTANCE = new JobLocationMarshaller();
+
+        @Override
+        public List<LegacyJobLocation> readPayload(DataInput dataIn) throws IOException {
+            List<LegacyJobLocation> result = new ArrayList<LegacyJobLocation>();
+            int size = dataIn.readInt();
+            for (int i = 0; i < size; i++) {
+                LegacyJobLocation jobLocation = new LegacyJobLocation();
+                jobLocation.readExternal(dataIn);
+                result.add(jobLocation);
+            }
+            return result;
+        }
+
+        @Override
+        public void writePayload(List<LegacyJobLocation> value, DataOutput dataOut) throws IOException {
+            dataOut.writeInt(value.size());
+            for (LegacyJobLocation jobLocation : value) {
+                jobLocation.writeExternal(dataOut);
+            }
+        }
+    }
+
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + ((cronEntry == null) ? 0 : cronEntry.hashCode());
+        result = prime * result + (int) (delay ^ (delay >>> 32));
+        result = prime * result + ((jobId == null) ? 0 : jobId.hashCode());
+        result = prime * result + ((location == null) ? 0 : location.hashCode());
+        result = prime * result + (int) (nextTime ^ (nextTime >>> 32));
+        result = prime * result + (int) (period ^ (period >>> 32));
+        result = prime * result + repeat;
+        result = prime * result + (int) (startTime ^ (startTime >>> 32));
+        return result;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+
+        if (obj == null) {
+            return false;
+        }
+
+        if (getClass() != obj.getClass()) {
+            return false;
+        }
+
+        LegacyJobLocation other = (LegacyJobLocation) obj;
+
+        if (cronEntry == null) {
+            if (other.cronEntry != null) {
+                return false;
+            }
+        } else if (!cronEntry.equals(other.cronEntry)) {
+            return false;
+        }
+
+        if (delay != other.delay) {
+            return false;
+        }
+
+        if (jobId == null) {
+            if (other.jobId != null)
+                return false;
+        } else if (!jobId.equals(other.jobId)) {
+            return false;
+        }
+
+        if (location == null) {
+            if (other.location != null) {
+                return false;
+            }
+        } else if (!location.equals(other.location)) {
+            return false;
+        }
+
+        if (nextTime != other.nextTime) {
+            return false;
+        }
+        if (period != other.period) {
+            return false;
+        }
+        if (repeat != other.repeat) {
+            return false;
+        }
+        if (startTime != other.startTime) {
+            return false;
+        }
+
+        return true;
+    }
+}

http://git-wip-us.apache.org/repos/asf/activemq/blob/fc244f48/activemq-kahadb-store/src/main/java/org/apache/activemq/store/kahadb/scheduler/legacy/LegacyJobSchedulerImpl.java
----------------------------------------------------------------------
diff --git a/activemq-kahadb-store/src/main/java/org/apache/activemq/store/kahadb/scheduler/legacy/LegacyJobSchedulerImpl.java b/activemq-kahadb-store/src/main/java/org/apache/activemq/store/kahadb/scheduler/legacy/LegacyJobSchedulerImpl.java
new file mode 100644
index 0000000..687ffd7
--- /dev/null
+++ b/activemq-kahadb-store/src/main/java/org/apache/activemq/store/kahadb/scheduler/legacy/LegacyJobSchedulerImpl.java
@@ -0,0 +1,222 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.activemq.store.kahadb.scheduler.legacy;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.activemq.store.kahadb.disk.index.BTreeIndex;
+import org.apache.activemq.store.kahadb.disk.journal.Location;
+import org.apache.activemq.store.kahadb.disk.page.Transaction;
+import org.apache.activemq.store.kahadb.disk.util.LongMarshaller;
+import org.apache.activemq.store.kahadb.disk.util.VariableMarshaller;
+import org.apache.activemq.util.ByteSequence;
+import org.apache.activemq.util.ServiceStopper;
+import org.apache.activemq.util.ServiceSupport;
+
+/**
+ * Read-only view of a stored legacy JobScheduler instance.
+ */
+final class LegacyJobSchedulerImpl extends ServiceSupport {
+
+    private final LegacyJobSchedulerStoreImpl store;
+    private String name;
+    private BTreeIndex<Long, List<LegacyJobLocation>> index;
+
+    LegacyJobSchedulerImpl(LegacyJobSchedulerStoreImpl store) {
+        this.store = store;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public String getName() {
+        return this.name;
+    }
+
+    /**
+     * Returns the next time that a job would be scheduled to run.
+     *
+     * @return time of next scheduled job to run.
+     *
+     * @throws IOException if an error occurs while fetching the time.
+     */
+    public long getNextScheduleTime() throws IOException {
+        Map.Entry<Long, List<LegacyJobLocation>> first = this.index.getFirst(this.store.getPageFile().tx());
+        return first != null ? first.getKey() : -1l;
+    }
+
+    /**
+     * Gets the list of the next batch of scheduled jobs in the store.
+     *
+     * @return a list of the next jobs that will run.
+     *
+     * @throws IOException if an error occurs while fetching the jobs list.
+     */
+    public List<LegacyJobImpl> getNextScheduleJobs() throws IOException {
+        final List<LegacyJobImpl> result = new ArrayList<LegacyJobImpl>();
+
+        this.store.getPageFile().tx().execute(new Transaction.Closure<IOException>() {
+            @Override
+            public void execute(Transaction tx) throws IOException {
+                Map.Entry<Long, List<LegacyJobLocation>> first = index.getFirst(store.getPageFile().tx());
+                if (first != null) {
+                    for (LegacyJobLocation jl : first.getValue()) {
+                        ByteSequence bs = getPayload(jl.getLocation());
+                        LegacyJobImpl job = new LegacyJobImpl(jl, bs);
+                        result.add(job);
+                    }
+                }
+            }
+        });
+        return result;
+    }
+
+    /**
+     * Gets a list of all scheduled jobs in this store.
+     *
+     * @return a list of all the currently scheduled jobs in this store.
+     *
+     * @throws IOException if an error occurs while fetching the list of jobs.
+     */
+    public List<LegacyJobImpl> getAllJobs() throws IOException {
+        final List<LegacyJobImpl> result = new ArrayList<LegacyJobImpl>();
+        this.store.getPageFile().tx().execute(new Transaction.Closure<IOException>() {
+            @Override
+            public void execute(Transaction tx) throws IOException {
+                Iterator<Map.Entry<Long, List<LegacyJobLocation>>> iter = index.iterator(store.getPageFile().tx());
+                while (iter.hasNext()) {
+                    Map.Entry<Long, List<LegacyJobLocation>> next = iter.next();
+                    if (next != null) {
+                        for (LegacyJobLocation jl : next.getValue()) {
+                            ByteSequence bs = getPayload(jl.getLocation());
+                            LegacyJobImpl job = new LegacyJobImpl(jl, bs);
+                            result.add(job);
+                        }
+                    } else {
+                        break;
+                    }
+                }
+            }
+        });
+        return result;
+    }
+
+    /**
+     * Gets a list of all scheduled jobs that exist between the given start and end time.
+     *
+     * @param start
+     *      The start time to look for scheduled jobs.
+     * @param finish
+     *      The end time to stop looking for scheduled jobs.
+     *
+     * @return a list of all scheduled jobs that would run between the given start and end time.
+     *
+     * @throws IOException if an error occurs while fetching the list of jobs.
+     */
+    public List<LegacyJobImpl> getAllJobs(final long start, final long finish) throws IOException {
+        final List<LegacyJobImpl> result = new ArrayList<LegacyJobImpl>();
+        this.store.getPageFile().tx().execute(new Transaction.Closure<IOException>() {
+            @Override
+            public void execute(Transaction tx) throws IOException {
+                Iterator<Map.Entry<Long, List<LegacyJobLocation>>> iter = index.iterator(store.getPageFile().tx(), start);
+                while (iter.hasNext()) {
+                    Map.Entry<Long, List<LegacyJobLocation>> next = iter.next();
+                    if (next != null && next.getKey().longValue() <= finish) {
+                        for (LegacyJobLocation jl : next.getValue()) {
+                            ByteSequence bs = getPayload(jl.getLocation());
+                            LegacyJobImpl job = new LegacyJobImpl(jl, bs);
+                            result.add(job);
+                        }
+                    } else {
+                        break;
+                    }
+                }
+            }
+        });
+        return result;
+    }
+
+    ByteSequence getPayload(Location location) throws IllegalStateException, IOException {
+        return this.store.getPayload(location);
+    }
+
+    @Override
+    public String toString() {
+        return "LegacyJobScheduler: " + this.name;
+    }
+
+    @Override
+    protected void doStart() throws Exception {
+    }
+
+    @Override
+    protected void doStop(ServiceStopper stopper) throws Exception {
+    }
+
+    void createIndexes(Transaction tx) throws IOException {
+        this.index = new BTreeIndex<Long, List<LegacyJobLocation>>(this.store.getPageFile(), tx.allocate().getPageId());
+    }
+
+    void load(Transaction tx) throws IOException {
+        this.index.setKeyMarshaller(LongMarshaller.INSTANCE);
+        this.index.setValueMarshaller(ValueMarshaller.INSTANCE);
+        this.index.load(tx);
+    }
+
+    void read(DataInput in) throws IOException {
+        this.name = in.readUTF();
+        this.index = new BTreeIndex<Long, List<LegacyJobLocation>>(this.store.getPageFile(), in.readLong());
+        this.index.setKeyMarshaller(LongMarshaller.INSTANCE);
+        this.index.setValueMarshaller(ValueMarshaller.INSTANCE);
+    }
+
+    public void write(DataOutput out) throws IOException {
+        out.writeUTF(name);
+        out.writeLong(this.index.getPageId());
+    }
+
+    static class ValueMarshaller extends VariableMarshaller<List<LegacyJobLocation>> {
+        static ValueMarshaller INSTANCE = new ValueMarshaller();
+
+        @Override
+        public List<LegacyJobLocation> readPayload(DataInput dataIn) throws IOException {
+            List<LegacyJobLocation> result = new ArrayList<LegacyJobLocation>();
+            int size = dataIn.readInt();
+            for (int i = 0; i < size; i++) {
+                LegacyJobLocation jobLocation = new LegacyJobLocation();
+                jobLocation.readExternal(dataIn);
+                result.add(jobLocation);
+            }
+            return result;
+        }
+
+        @Override
+        public void writePayload(List<LegacyJobLocation> value, DataOutput dataOut) throws IOException {
+            dataOut.writeInt(value.size());
+            for (LegacyJobLocation jobLocation : value) {
+                jobLocation.writeExternal(dataOut);
+            }
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/activemq/blob/fc244f48/activemq-kahadb-store/src/main/java/org/apache/activemq/store/kahadb/scheduler/legacy/LegacyJobSchedulerStoreImpl.java
----------------------------------------------------------------------
diff --git a/activemq-kahadb-store/src/main/java/org/apache/activemq/store/kahadb/scheduler/legacy/LegacyJobSchedulerStoreImpl.java b/activemq-kahadb-store/src/main/java/org/apache/activemq/store/kahadb/scheduler/legacy/LegacyJobSchedulerStoreImpl.java
new file mode 100644
index 0000000..acbd4e7
--- /dev/null
+++ b/activemq-kahadb-store/src/main/java/org/apache/activemq/store/kahadb/scheduler/legacy/LegacyJobSchedulerStoreImpl.java
@@ -0,0 +1,378 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.activemq.store.kahadb.scheduler.legacy;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.NoSuchElementException;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicLong;
+
+import org.apache.activemq.store.kahadb.disk.index.BTreeIndex;
+import org.apache.activemq.store.kahadb.disk.journal.Journal;
+import org.apache.activemq.store.kahadb.disk.journal.Location;
+import org.apache.activemq.store.kahadb.disk.page.Page;
+import org.apache.activemq.store.kahadb.disk.page.PageFile;
+import org.apache.activemq.store.kahadb.disk.page.Transaction;
+import org.apache.activemq.store.kahadb.disk.util.IntegerMarshaller;
+import org.apache.activemq.store.kahadb.disk.util.StringMarshaller;
+import org.apache.activemq.store.kahadb.disk.util.VariableMarshaller;
+import org.apache.activemq.util.ByteSequence;
+import org.apache.activemq.util.IOHelper;
+import org.apache.activemq.util.LockFile;
+import org.apache.activemq.util.ServiceStopper;
+import org.apache.activemq.util.ServiceSupport;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Read-only view of a legacy JobSchedulerStore implementation.
+ */
+final class LegacyJobSchedulerStoreImpl extends ServiceSupport {
+
+    static final Logger LOG = LoggerFactory.getLogger(LegacyJobSchedulerStoreImpl.class);
+
+    private static final int DATABASE_LOCKED_WAIT_DELAY = 10 * 1000;
+
+    private File directory;
+    private PageFile pageFile;
+    private Journal journal;
+    private LockFile lockFile;
+    private final AtomicLong journalSize = new AtomicLong(0);
+    private boolean failIfDatabaseIsLocked;
+    private int journalMaxFileLength = Journal.DEFAULT_MAX_FILE_LENGTH;
+    private int journalMaxWriteBatchSize = Journal.DEFAULT_MAX_WRITE_BATCH_SIZE;
+    private boolean enableIndexWriteAsync = false;
+    private MetaData metaData = new MetaData(this);
+    private final MetaDataMarshaller metaDataMarshaller = new MetaDataMarshaller(this);
+    private final Map<String, LegacyJobSchedulerImpl> schedulers = new HashMap<String, LegacyJobSchedulerImpl>();
+
+    protected class MetaData {
+        protected MetaData(LegacyJobSchedulerStoreImpl store) {
+            this.store = store;
+        }
+
+        private final LegacyJobSchedulerStoreImpl store;
+        Page<MetaData> page;
+        BTreeIndex<Integer, Integer> journalRC;
+        BTreeIndex<String, LegacyJobSchedulerImpl> storedSchedulers;
+
+        void createIndexes(Transaction tx) throws IOException {
+            this.storedSchedulers = new BTreeIndex<String, LegacyJobSchedulerImpl>(pageFile, tx.allocate().getPageId());
+            this.journalRC = new BTreeIndex<Integer, Integer>(pageFile, tx.allocate().getPageId());
+        }
+
+        void load(Transaction tx) throws IOException {
+            this.storedSchedulers.setKeyMarshaller(StringMarshaller.INSTANCE);
+            this.storedSchedulers.setValueMarshaller(new JobSchedulerMarshaller(this.store));
+            this.storedSchedulers.load(tx);
+            this.journalRC.setKeyMarshaller(IntegerMarshaller.INSTANCE);
+            this.journalRC.setValueMarshaller(IntegerMarshaller.INSTANCE);
+            this.journalRC.load(tx);
+        }
+
+        void loadScheduler(Transaction tx, Map<String, LegacyJobSchedulerImpl> schedulers) throws IOException {
+            for (Iterator<Entry<String, LegacyJobSchedulerImpl>> i = this.storedSchedulers.iterator(tx); i.hasNext();) {
+                Entry<String, LegacyJobSchedulerImpl> entry = i.next();
+                entry.getValue().load(tx);
+                schedulers.put(entry.getKey(), entry.getValue());
+            }
+        }
+
+        public void read(DataInput is) throws IOException {
+            this.storedSchedulers = new BTreeIndex<String, LegacyJobSchedulerImpl>(pageFile, is.readLong());
+            this.storedSchedulers.setKeyMarshaller(StringMarshaller.INSTANCE);
+            this.storedSchedulers.setValueMarshaller(new JobSchedulerMarshaller(this.store));
+            this.journalRC = new BTreeIndex<Integer, Integer>(pageFile, is.readLong());
+            this.journalRC.setKeyMarshaller(IntegerMarshaller.INSTANCE);
+            this.journalRC.setValueMarshaller(IntegerMarshaller.INSTANCE);
+        }
+
+        public void write(DataOutput os) throws IOException {
+            os.writeLong(this.storedSchedulers.getPageId());
+            os.writeLong(this.journalRC.getPageId());
+        }
+    }
+
+    class MetaDataMarshaller extends VariableMarshaller<MetaData> {
+        private final LegacyJobSchedulerStoreImpl store;
+
+        MetaDataMarshaller(LegacyJobSchedulerStoreImpl store) {
+            this.store = store;
+        }
+
+        @Override
+        public MetaData readPayload(DataInput dataIn) throws IOException {
+            MetaData rc = new MetaData(this.store);
+            rc.read(dataIn);
+            return rc;
+        }
+
+        @Override
+        public void writePayload(MetaData object, DataOutput dataOut) throws IOException {
+            object.write(dataOut);
+        }
+    }
+
+    class ValueMarshaller extends VariableMarshaller<List<LegacyJobLocation>> {
+        @Override
+        public List<LegacyJobLocation> readPayload(DataInput dataIn) throws IOException {
+            List<LegacyJobLocation> result = new ArrayList<LegacyJobLocation>();
+            int size = dataIn.readInt();
+            for (int i = 0; i < size; i++) {
+                LegacyJobLocation jobLocation = new LegacyJobLocation();
+                jobLocation.readExternal(dataIn);
+                result.add(jobLocation);
+            }
+            return result;
+        }
+
+        @Override
+        public void writePayload(List<LegacyJobLocation> value, DataOutput dataOut) throws IOException {
+            dataOut.writeInt(value.size());
+            for (LegacyJobLocation jobLocation : value) {
+                jobLocation.writeExternal(dataOut);
+            }
+        }
+    }
+
+    class JobSchedulerMarshaller extends VariableMarshaller<LegacyJobSchedulerImpl> {
+        private final LegacyJobSchedulerStoreImpl store;
+
+        JobSchedulerMarshaller(LegacyJobSchedulerStoreImpl store) {
+            this.store = store;
+        }
+
+        @Override
+        public LegacyJobSchedulerImpl readPayload(DataInput dataIn) throws IOException {
+            LegacyJobSchedulerImpl result = new LegacyJobSchedulerImpl(this.store);
+            result.read(dataIn);
+            return result;
+        }
+
+        @Override
+        public void writePayload(LegacyJobSchedulerImpl js, DataOutput dataOut) throws IOException {
+            js.write(dataOut);
+        }
+    }
+
+    public File getDirectory() {
+        return directory;
+    }
+
+    public void setDirectory(File directory) {
+        this.directory = directory;
+    }
+
+    public long size() {
+        if (!isStarted()) {
+            return 0;
+        }
+        try {
+            return journalSize.get() + pageFile.getDiskSize();
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * Returns the named Job Scheduler if it exists, otherwise throws an exception.
+     *
+     * @param name
+     *     The name of the scheduler that is to be returned.
+     *
+     * @return the named scheduler if it exists.
+     *
+     * @throws Exception if the named scheduler does not exist in this store.
+     */
+    public LegacyJobSchedulerImpl getJobScheduler(final String name) throws Exception {
+        LegacyJobSchedulerImpl result = this.schedulers.get(name);
+        if (result == null) {
+            throw new NoSuchElementException("No such Job Scheduler in this store: " + name);
+        }
+        return result;
+    }
+
+    /**
+     * Returns the names of all the schedulers that exist in this scheduler store.
+     *
+     * @return a set of names of all scheduler instances in this store.
+     *
+     * @throws Exception if an error occurs while collecting the scheduler names.
+     */
+    public Set<String> getJobSchedulerNames() throws Exception {
+        Set<String> names = Collections.emptySet();
+
+        if (!schedulers.isEmpty()) {
+            return this.schedulers.keySet();
+        }
+
+        return names;
+    }
+
+    @Override
+    protected void doStart() throws Exception {
+        if (this.directory == null) {
+            this.directory = new File(IOHelper.getDefaultDataDirectory() + File.pathSeparator + "delayedDB");
+        }
+        IOHelper.mkdirs(this.directory);
+        lock();
+        this.journal = new Journal();
+        this.journal.setDirectory(directory);
+        this.journal.setMaxFileLength(getJournalMaxFileLength());
+        this.journal.setWriteBatchSize(getJournalMaxWriteBatchSize());
+        this.journal.setSizeAccumulator(this.journalSize);
+        this.journal.start();
+        this.pageFile = new PageFile(directory, "scheduleDB");
+        this.pageFile.setWriteBatchSize(1);
+        this.pageFile.load();
+
+        this.pageFile.tx().execute(new Transaction.Closure<IOException>() {
+            @Override
+            public void execute(Transaction tx) throws IOException {
+                if (pageFile.getPageCount() == 0) {
+                    Page<MetaData> page = tx.allocate();
+                    assert page.getPageId() == 0;
+                    page.set(metaData);
+                    metaData.page = page;
+                    metaData.createIndexes(tx);
+                    tx.store(metaData.page, metaDataMarshaller, true);
+
+                } else {
+                    Page<MetaData> page = tx.load(0, metaDataMarshaller);
+                    metaData = page.get();
+                    metaData.page = page;
+                }
+                metaData.load(tx);
+                metaData.loadScheduler(tx, schedulers);
+                for (LegacyJobSchedulerImpl js : schedulers.values()) {
+                    try {
+                        js.start();
+                    } catch (Exception e) {
+                        LegacyJobSchedulerStoreImpl.LOG.error("Failed to load " + js.getName(), e);
+                    }
+                }
+            }
+        });
+
+        this.pageFile.flush();
+        LOG.info(this + " started");
+    }
+
+    @Override
+    protected void doStop(ServiceStopper stopper) throws Exception {
+        for (LegacyJobSchedulerImpl js : this.schedulers.values()) {
+            js.stop();
+        }
+        if (this.pageFile != null) {
+            this.pageFile.unload();
+        }
+        if (this.journal != null) {
+            journal.close();
+        }
+        if (this.lockFile != null) {
+            this.lockFile.unlock();
+        }
+        this.lockFile = null;
+        LOG.info(this + " stopped");
+    }
+
+    ByteSequence getPayload(Location location) throws IllegalStateException, IOException {
+        ByteSequence result = null;
+        result = this.journal.read(location);
+        return result;
+    }
+
+    Location write(ByteSequence payload, boolean sync) throws IllegalStateException, IOException {
+        return this.journal.write(payload, sync);
+    }
+
+    private void lock() throws IOException {
+        if (lockFile == null) {
+            File lockFileName = new File(directory, "lock");
+            lockFile = new LockFile(lockFileName, true);
+            if (failIfDatabaseIsLocked) {
+                lockFile.lock();
+            } else {
+                while (true) {
+                    try {
+                        lockFile.lock();
+                        break;
+                    } catch (IOException e) {
+                        LOG.info("Database " + lockFileName + " is locked... waiting " + (DATABASE_LOCKED_WAIT_DELAY / 1000)
+                            + " seconds for the database to be unlocked. Reason: " + e);
+                        try {
+                            Thread.sleep(DATABASE_LOCKED_WAIT_DELAY);
+                        } catch (InterruptedException e1) {
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    PageFile getPageFile() {
+        this.pageFile.isLoaded();
+        return this.pageFile;
+    }
+
+    public boolean isFailIfDatabaseIsLocked() {
+        return failIfDatabaseIsLocked;
+    }
+
+    public void setFailIfDatabaseIsLocked(boolean failIfDatabaseIsLocked) {
+        this.failIfDatabaseIsLocked = failIfDatabaseIsLocked;
+    }
+
+    public int getJournalMaxFileLength() {
+        return journalMaxFileLength;
+    }
+
+    public void setJournalMaxFileLength(int journalMaxFileLength) {
+        this.journalMaxFileLength = journalMaxFileLength;
+    }
+
+    public int getJournalMaxWriteBatchSize() {
+        return journalMaxWriteBatchSize;
+    }
+
+    public void setJournalMaxWriteBatchSize(int journalMaxWriteBatchSize) {
+        this.journalMaxWriteBatchSize = journalMaxWriteBatchSize;
+    }
+
+    public boolean isEnableIndexWriteAsync() {
+        return enableIndexWriteAsync;
+    }
+
+    public void setEnableIndexWriteAsync(boolean enableIndexWriteAsync) {
+        this.enableIndexWriteAsync = enableIndexWriteAsync;
+    }
+
+    @Override
+    public String toString() {
+        return "LegacyJobSchedulerStore:" + this.directory;
+    }
+}


[03/16] activemq git commit: https://issues.apache.org/jira/browse/AMQ-5256 - fix spurious amqp ioexception on concurrent connection protocol discrimination, client would see a hang on open. additional test

Posted by ha...@apache.org.
https://issues.apache.org/jira/browse/AMQ-5256 - fix spurious amqp ioexception on concurrent connection protocol discrimination, client would see a hang on open. additional test


Project: http://git-wip-us.apache.org/repos/asf/activemq/repo
Commit: http://git-wip-us.apache.org/repos/asf/activemq/commit/42989666
Tree: http://git-wip-us.apache.org/repos/asf/activemq/tree/42989666
Diff: http://git-wip-us.apache.org/repos/asf/activemq/diff/42989666

Branch: refs/heads/activemq-5.10.x
Commit: 42989666f776e697b80dd44d808bd692ff82f9f3
Parents: 709204d
Author: gtully <ga...@gmail.com>
Authored: Tue Jul 1 22:33:42 2014 +0100
Committer: Hadrian Zbarcea <ha...@apache.org>
Committed: Mon Dec 15 18:53:23 2014 -0500

----------------------------------------------------------------------
 .../amqp/AMQPProtocolDiscriminator.java         |  2 +-
 .../transport/amqp/bugs/AMQ5256Test.java        | 68 ++++++++++++++++++++
 2 files changed, 69 insertions(+), 1 deletion(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/activemq/blob/42989666/activemq-amqp/src/main/java/org/apache/activemq/transport/amqp/AMQPProtocolDiscriminator.java
----------------------------------------------------------------------
diff --git a/activemq-amqp/src/main/java/org/apache/activemq/transport/amqp/AMQPProtocolDiscriminator.java b/activemq-amqp/src/main/java/org/apache/activemq/transport/amqp/AMQPProtocolDiscriminator.java
index bf0c655..58da746 100644
--- a/activemq-amqp/src/main/java/org/apache/activemq/transport/amqp/AMQPProtocolDiscriminator.java
+++ b/activemq-amqp/src/main/java/org/apache/activemq/transport/amqp/AMQPProtocolDiscriminator.java
@@ -59,7 +59,7 @@ public class AMQPProtocolDiscriminator implements IAmqpProtocolConverter {
 
     }
 
-    static final private ArrayList<Command> pendingCommands = new ArrayList<Command>();
+    final private ArrayList<Command> pendingCommands = new ArrayList<Command>();
 
     public AMQPProtocolDiscriminator(AmqpTransport transport) {
         this.transport = transport;

http://git-wip-us.apache.org/repos/asf/activemq/blob/42989666/activemq-amqp/src/test/java/org/apache/activemq/transport/amqp/bugs/AMQ5256Test.java
----------------------------------------------------------------------
diff --git a/activemq-amqp/src/test/java/org/apache/activemq/transport/amqp/bugs/AMQ5256Test.java b/activemq-amqp/src/test/java/org/apache/activemq/transport/amqp/bugs/AMQ5256Test.java
new file mode 100644
index 0000000..85acb64
--- /dev/null
+++ b/activemq-amqp/src/test/java/org/apache/activemq/transport/amqp/bugs/AMQ5256Test.java
@@ -0,0 +1,68 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.activemq.transport.amqp.bugs;
+
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+import javax.jms.Connection;
+import javax.jms.JMSException;
+import org.apache.activemq.transport.amqp.AmqpTestSupport;
+import org.apache.qpid.amqp_1_0.jms.impl.ConnectionFactoryImpl;
+import org.junit.Test;
+
+
+import static org.junit.Assert.assertTrue;
+
+public class AMQ5256Test extends AmqpTestSupport {
+
+    @Override
+    protected boolean isUseTcpConnector() {
+        return true;
+    }
+
+    @Override
+    protected boolean isUseNioPlusSslConnector() {
+        return false;
+    }
+
+    @Test(timeout = 40 * 1000)
+    public void testParallelConnect() throws Exception {
+        final int numThreads = 80;
+        ExecutorService executorService = Executors.newFixedThreadPool(numThreads);
+        for (int i = 0; i < numThreads; i++) {
+            executorService.execute(new Runnable() {
+                @Override
+                public void run() {
+
+                    try {
+                        final ConnectionFactoryImpl connectionFactory = new ConnectionFactoryImpl("localhost", port, "admin", "password", null, isUseSslConnector());
+                        Connection connection = connectionFactory.createConnection();
+                        connection.start();
+                        connection.close();
+                    } catch (JMSException e) {
+                        e.printStackTrace();
+                    }
+                }
+            });
+        }
+
+        executorService.shutdown();
+        assertTrue("executor done on time", executorService.awaitTermination(30, TimeUnit.SECONDS));
+
+    }
+}


[05/16] activemq git commit: https://issues.apache.org/jira/browse/AMQ-5258

Posted by ha...@apache.org.
https://issues.apache.org/jira/browse/AMQ-5258

Fixed, early created connection is closed so that it can expire or error
out as needed.


Project: http://git-wip-us.apache.org/repos/asf/activemq/repo
Commit: http://git-wip-us.apache.org/repos/asf/activemq/commit/8ae10b93
Tree: http://git-wip-us.apache.org/repos/asf/activemq/tree/8ae10b93
Diff: http://git-wip-us.apache.org/repos/asf/activemq/diff/8ae10b93

Branch: refs/heads/activemq-5.10.x
Commit: 8ae10b93958fa38e8e56e83e076936c0a010c8e3
Parents: 59109a6
Author: Timothy Bish <ta...@gmail.com>
Authored: Mon Jul 7 14:29:21 2014 -0400
Committer: Hadrian Zbarcea <ha...@apache.org>
Committed: Mon Dec 15 19:02:59 2014 -0500

----------------------------------------------------------------------
 .../java/org/apache/activemq/jms/pool/PooledConnectionFactory.java | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/activemq/blob/8ae10b93/activemq-jms-pool/src/main/java/org/apache/activemq/jms/pool/PooledConnectionFactory.java
----------------------------------------------------------------------
diff --git a/activemq-jms-pool/src/main/java/org/apache/activemq/jms/pool/PooledConnectionFactory.java b/activemq-jms-pool/src/main/java/org/apache/activemq/jms/pool/PooledConnectionFactory.java
index e60c52b..12852ce 100644
--- a/activemq-jms-pool/src/main/java/org/apache/activemq/jms/pool/PooledConnectionFactory.java
+++ b/activemq-jms-pool/src/main/java/org/apache/activemq/jms/pool/PooledConnectionFactory.java
@@ -267,7 +267,7 @@ public class PooledConnectionFactory implements ConnectionFactory {
         if (isCreateConnectionOnStartup()) {
             try {
                 // warm the pool by creating a connection during startup
-                createConnection();
+                createConnection().close();
             } catch (JMSException e) {
                 LOG.warn("Create pooled connection during start failed. This exception will be ignored.", e);
             }


[15/16] activemq git commit: https://issues.apache.org/jira/browse/AMQ-3758

Posted by ha...@apache.org.
http://git-wip-us.apache.org/repos/asf/activemq/blob/fc244f48/activemq-kahadb-store/src/main/java/org/apache/activemq/store/kahadb/AbstractKahaDBStore.java
----------------------------------------------------------------------
diff --git a/activemq-kahadb-store/src/main/java/org/apache/activemq/store/kahadb/AbstractKahaDBStore.java b/activemq-kahadb-store/src/main/java/org/apache/activemq/store/kahadb/AbstractKahaDBStore.java
new file mode 100644
index 0000000..6003c87
--- /dev/null
+++ b/activemq-kahadb-store/src/main/java/org/apache/activemq/store/kahadb/AbstractKahaDBStore.java
@@ -0,0 +1,745 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.activemq.store.kahadb;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Date;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+
+import org.apache.activemq.broker.LockableServiceSupport;
+import org.apache.activemq.broker.Locker;
+import org.apache.activemq.store.SharedFileLocker;
+import org.apache.activemq.store.kahadb.data.KahaEntryType;
+import org.apache.activemq.store.kahadb.data.KahaTraceCommand;
+import org.apache.activemq.store.kahadb.disk.journal.Journal;
+import org.apache.activemq.store.kahadb.disk.journal.Location;
+import org.apache.activemq.store.kahadb.disk.page.PageFile;
+import org.apache.activemq.store.kahadb.disk.page.Transaction;
+import org.apache.activemq.util.ByteSequence;
+import org.apache.activemq.util.DataByteArrayInputStream;
+import org.apache.activemq.util.DataByteArrayOutputStream;
+import org.apache.activemq.util.IOHelper;
+import org.apache.activemq.util.ServiceStopper;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public abstract class AbstractKahaDBStore extends LockableServiceSupport {
+
+    static final Logger LOG = LoggerFactory.getLogger(AbstractKahaDBStore.class);
+
+    public static final String PROPERTY_LOG_SLOW_ACCESS_TIME = "org.apache.activemq.store.kahadb.LOG_SLOW_ACCESS_TIME";
+    public static final int LOG_SLOW_ACCESS_TIME = Integer.getInteger(PROPERTY_LOG_SLOW_ACCESS_TIME, 0);
+
+    protected File directory;
+    protected PageFile pageFile;
+    protected Journal journal;
+    protected AtomicLong journalSize = new AtomicLong(0);
+    protected boolean failIfDatabaseIsLocked;
+    protected long checkpointInterval = 5*1000;
+    protected long cleanupInterval = 30*1000;
+    protected boolean checkForCorruptJournalFiles = false;
+    protected boolean checksumJournalFiles = true;
+    protected boolean forceRecoverIndex = false;
+    protected int journalMaxFileLength = Journal.DEFAULT_MAX_FILE_LENGTH;
+    protected int journalMaxWriteBatchSize = Journal.DEFAULT_MAX_WRITE_BATCH_SIZE;
+    protected boolean archiveCorruptedIndex = false;
+    protected boolean enableIndexWriteAsync = false;
+    protected boolean enableJournalDiskSyncs = false;
+    protected boolean deleteAllJobs = false;
+    protected int indexWriteBatchSize = PageFile.DEFAULT_WRITE_BATCH_SIZE;
+    protected boolean useIndexLFRUEviction = false;
+    protected float indexLFUEvictionFactor = 0.2f;
+    protected boolean ignoreMissingJournalfiles = false;
+    protected int indexCacheSize = 1000;
+    protected boolean enableIndexDiskSyncs = true;
+    protected boolean enableIndexRecoveryFile = true;
+    protected boolean enableIndexPageCaching = true;
+    protected boolean archiveDataLogs;
+    protected boolean purgeStoreOnStartup;
+    protected File directoryArchive;
+
+    protected AtomicBoolean opened = new AtomicBoolean();
+    protected Thread checkpointThread;
+    protected final Object checkpointThreadLock = new Object();
+    protected ReentrantReadWriteLock checkpointLock = new ReentrantReadWriteLock();
+    protected ReentrantReadWriteLock indexLock = new ReentrantReadWriteLock();
+
+    /**
+     * @return the name to give this store's PageFile instance.
+     */
+    protected abstract String getPageFileName();
+
+    /**
+     * @return the location of the data directory if no set by configuration.
+     */
+    protected abstract File getDefaultDataDirectory();
+
+    /**
+     * Loads the store from disk.
+     *
+     * Based on configuration this method can either load an existing store or it can purge
+     * an existing store and start in a clean state.
+     *
+     * @throws IOException if an error occurs during the load.
+     */
+    public abstract void load() throws IOException;
+
+    /**
+     * Unload the state of the Store to disk and shuts down all resources assigned to this
+     * KahaDB store implementation.
+     *
+     * @throws IOException if an error occurs during the store unload.
+     */
+    public abstract void unload() throws IOException;
+
+    @Override
+    protected void doStart() throws Exception {
+        this.indexLock.writeLock().lock();
+        if (getDirectory() == null) {
+            setDirectory(getDefaultDataDirectory());
+        }
+        IOHelper.mkdirs(getDirectory());
+        try {
+            if (isPurgeStoreOnStartup()) {
+                getJournal().start();
+                getJournal().delete();
+                getJournal().close();
+                journal = null;
+                getPageFile().delete();
+                LOG.info("{} Persistence store purged.", this);
+                setPurgeStoreOnStartup(false);
+            }
+
+            load();
+            store(new KahaTraceCommand().setMessage("LOADED " + new Date()));
+        } finally {
+            this.indexLock.writeLock().unlock();
+        }
+    }
+
+    @Override
+    protected void doStop(ServiceStopper stopper) throws Exception {
+        unload();
+    }
+
+    public PageFile getPageFile() {
+        if (pageFile == null) {
+            pageFile = createPageFile();
+        }
+        return pageFile;
+    }
+
+    public Journal getJournal() throws IOException {
+        if (journal == null) {
+            journal = createJournal();
+        }
+        return journal;
+    }
+
+    public File getDirectory() {
+        return directory;
+    }
+
+    public void setDirectory(File directory) {
+        this.directory = directory;
+    }
+
+    public boolean isArchiveCorruptedIndex() {
+        return archiveCorruptedIndex;
+    }
+
+    public void setArchiveCorruptedIndex(boolean archiveCorruptedIndex) {
+        this.archiveCorruptedIndex = archiveCorruptedIndex;
+    }
+
+    public boolean isFailIfDatabaseIsLocked() {
+        return failIfDatabaseIsLocked;
+    }
+
+    public void setFailIfDatabaseIsLocked(boolean failIfDatabaseIsLocked) {
+        this.failIfDatabaseIsLocked = failIfDatabaseIsLocked;
+    }
+
+    public boolean isCheckForCorruptJournalFiles() {
+        return checkForCorruptJournalFiles;
+    }
+
+    public void setCheckForCorruptJournalFiles(boolean checkForCorruptJournalFiles) {
+        this.checkForCorruptJournalFiles = checkForCorruptJournalFiles;
+    }
+
+    public long getCheckpointInterval() {
+        return checkpointInterval;
+    }
+
+    public void setCheckpointInterval(long checkpointInterval) {
+        this.checkpointInterval = checkpointInterval;
+    }
+
+    public long getCleanupInterval() {
+        return cleanupInterval;
+    }
+
+    public void setCleanupInterval(long cleanupInterval) {
+        this.cleanupInterval = cleanupInterval;
+    }
+
+    public boolean isChecksumJournalFiles() {
+        return checksumJournalFiles;
+    }
+
+    public void setChecksumJournalFiles(boolean checksumJournalFiles) {
+        this.checksumJournalFiles = checksumJournalFiles;
+    }
+
+    public boolean isForceRecoverIndex() {
+        return forceRecoverIndex;
+    }
+
+    public void setForceRecoverIndex(boolean forceRecoverIndex) {
+        this.forceRecoverIndex = forceRecoverIndex;
+    }
+
+    public int getJournalMaxFileLength() {
+        return journalMaxFileLength;
+    }
+
+    public void setJournalMaxFileLength(int journalMaxFileLength) {
+        this.journalMaxFileLength = journalMaxFileLength;
+    }
+
+    public int getJournalMaxWriteBatchSize() {
+        return journalMaxWriteBatchSize;
+    }
+
+    public void setJournalMaxWriteBatchSize(int journalMaxWriteBatchSize) {
+        this.journalMaxWriteBatchSize = journalMaxWriteBatchSize;
+    }
+
+    public boolean isEnableIndexWriteAsync() {
+        return enableIndexWriteAsync;
+    }
+
+    public void setEnableIndexWriteAsync(boolean enableIndexWriteAsync) {
+        this.enableIndexWriteAsync = enableIndexWriteAsync;
+    }
+
+    public boolean isEnableJournalDiskSyncs() {
+        return enableJournalDiskSyncs;
+    }
+
+    public void setEnableJournalDiskSyncs(boolean syncWrites) {
+        this.enableJournalDiskSyncs = syncWrites;
+    }
+
+    public boolean isDeleteAllJobs() {
+        return deleteAllJobs;
+    }
+
+    public void setDeleteAllJobs(boolean deleteAllJobs) {
+        this.deleteAllJobs = deleteAllJobs;
+    }
+
+    /**
+     * @return the archiveDataLogs
+     */
+    public boolean isArchiveDataLogs() {
+        return this.archiveDataLogs;
+    }
+
+    /**
+     * @param archiveDataLogs the archiveDataLogs to set
+     */
+    public void setArchiveDataLogs(boolean archiveDataLogs) {
+        this.archiveDataLogs = archiveDataLogs;
+    }
+
+    /**
+     * @return the directoryArchive
+     */
+    public File getDirectoryArchive() {
+        return this.directoryArchive;
+    }
+
+    /**
+     * @param directoryArchive the directoryArchive to set
+     */
+    public void setDirectoryArchive(File directoryArchive) {
+        this.directoryArchive = directoryArchive;
+    }
+
+    public int getIndexCacheSize() {
+        return indexCacheSize;
+    }
+
+    public void setIndexCacheSize(int indexCacheSize) {
+        this.indexCacheSize = indexCacheSize;
+    }
+
+    public int getIndexWriteBatchSize() {
+        return indexWriteBatchSize;
+    }
+
+    public void setIndexWriteBatchSize(int indexWriteBatchSize) {
+        this.indexWriteBatchSize = indexWriteBatchSize;
+    }
+
+    public boolean isUseIndexLFRUEviction() {
+        return useIndexLFRUEviction;
+    }
+
+    public void setUseIndexLFRUEviction(boolean useIndexLFRUEviction) {
+        this.useIndexLFRUEviction = useIndexLFRUEviction;
+    }
+
+    public float getIndexLFUEvictionFactor() {
+        return indexLFUEvictionFactor;
+    }
+
+    public void setIndexLFUEvictionFactor(float indexLFUEvictionFactor) {
+        this.indexLFUEvictionFactor = indexLFUEvictionFactor;
+    }
+
+    public boolean isEnableIndexDiskSyncs() {
+        return enableIndexDiskSyncs;
+    }
+
+    public void setEnableIndexDiskSyncs(boolean enableIndexDiskSyncs) {
+        this.enableIndexDiskSyncs = enableIndexDiskSyncs;
+    }
+
+    public boolean isEnableIndexRecoveryFile() {
+        return enableIndexRecoveryFile;
+    }
+
+    public void setEnableIndexRecoveryFile(boolean enableIndexRecoveryFile) {
+        this.enableIndexRecoveryFile = enableIndexRecoveryFile;
+    }
+
+    public boolean isEnableIndexPageCaching() {
+        return enableIndexPageCaching;
+    }
+
+    public void setEnableIndexPageCaching(boolean enableIndexPageCaching) {
+        this.enableIndexPageCaching = enableIndexPageCaching;
+    }
+
+    public boolean isPurgeStoreOnStartup() {
+        return this.purgeStoreOnStartup;
+    }
+
+    public void setPurgeStoreOnStartup(boolean purge) {
+        this.purgeStoreOnStartup = purge;
+    }
+
+    public boolean isIgnoreMissingJournalfiles() {
+        return ignoreMissingJournalfiles;
+    }
+
+    public void setIgnoreMissingJournalfiles(boolean ignoreMissingJournalfiles) {
+        this.ignoreMissingJournalfiles = ignoreMissingJournalfiles;
+    }
+
+    public long size() {
+        if (!isStarted()) {
+            return 0;
+        }
+        try {
+            return journalSize.get() + pageFile.getDiskSize();
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @Override
+    public Locker createDefaultLocker() throws IOException {
+        SharedFileLocker locker = new SharedFileLocker();
+        locker.setDirectory(this.getDirectory());
+        return locker;
+    }
+
+    @Override
+    public void init() throws Exception {
+    }
+
+    /**
+     * Store a command in the Journal and process to update the Store index.
+     *
+     * @param command
+     *      The specific JournalCommand to store and process.
+     *
+     * @returns the Location where the data was written in the Journal.
+     *
+     * @throws IOException if an error occurs storing or processing the command.
+     */
+    public Location store(JournalCommand<?> command) throws IOException {
+        return store(command, isEnableIndexDiskSyncs(), null, null, null);
+    }
+
+    /**
+     * Store a command in the Journal and process to update the Store index.
+     *
+     * @param command
+     *      The specific JournalCommand to store and process.
+     * @param sync
+     *      Should the store operation be done synchronously. (ignored if completion passed).
+     *
+     * @returns the Location where the data was written in the Journal.
+     *
+     * @throws IOException if an error occurs storing or processing the command.
+     */
+    public Location store(JournalCommand<?> command, boolean sync) throws IOException {
+        return store(command, sync, null, null, null);
+    }
+
+    /**
+     * Store a command in the Journal and process to update the Store index.
+     *
+     * @param command
+     *      The specific JournalCommand to store and process.
+     * @param onJournalStoreComplete
+     *      The Runnable to call when the Journal write operation completes.
+     *
+     * @returns the Location where the data was written in the Journal.
+     *
+     * @throws IOException if an error occurs storing or processing the command.
+     */
+    public Location store(JournalCommand<?> command, Runnable onJournalStoreComplete) throws IOException {
+        return store(command, isEnableIndexDiskSyncs(), null, null, onJournalStoreComplete);
+    }
+
+    /**
+     * Store a command in the Journal and process to update the Store index.
+     *
+     * @param command
+     *      The specific JournalCommand to store and process.
+     * @param sync
+     *      Should the store operation be done synchronously. (ignored if completion passed).
+     * @param before
+     *      The Runnable instance to execute before performing the store and process operation.
+     * @param after
+     *      The Runnable instance to execute after performing the store and process operation.
+     *
+     * @returns the Location where the data was written in the Journal.
+     *
+     * @throws IOException if an error occurs storing or processing the command.
+     */
+    public Location store(JournalCommand<?> command, boolean sync, Runnable before, Runnable after) throws IOException {
+        return store(command, sync, before, after, null);
+    }
+
+    /**
+     * All updated are are funneled through this method. The updates are converted to a
+     * JournalMessage which is logged to the journal and then the data from the JournalMessage
+     * is used to update the index just like it would be done during a recovery process.
+     *
+     * @param command
+     *      The specific JournalCommand to store and process.
+     * @param sync
+     *      Should the store operation be done synchronously. (ignored if completion passed).
+     * @param before
+     *      The Runnable instance to execute before performing the store and process operation.
+     * @param after
+     *      The Runnable instance to execute after performing the store and process operation.
+     * @param onJournalStoreComplete
+     *      Callback to be run when the journal write operation is complete.
+     *
+     * @returns the Location where the data was written in the Journal.
+     *
+     * @throws IOException if an error occurs storing or processing the command.
+     */
+    public Location store(JournalCommand<?> command, boolean sync, Runnable before, Runnable after, Runnable onJournalStoreComplete) throws IOException {
+        try {
+
+            if (before != null) {
+                before.run();
+            }
+
+            ByteSequence sequence = toByteSequence(command);
+            Location location;
+            checkpointLock.readLock().lock();
+            try {
+
+                long start = System.currentTimeMillis();
+                location = onJournalStoreComplete == null ? journal.write(sequence, sync) :
+                                                            journal.write(sequence, onJournalStoreComplete);
+                long start2 = System.currentTimeMillis();
+
+                process(command, location);
+
+                long end = System.currentTimeMillis();
+                if (LOG_SLOW_ACCESS_TIME > 0 && end - start > LOG_SLOW_ACCESS_TIME) {
+                    LOG.info("Slow KahaDB access: Journal append took: {} ms, Index update took {} ms",
+                             (start2-start), (end-start2));
+                }
+            } finally {
+                checkpointLock.readLock().unlock();
+            }
+
+            if (after != null) {
+                after.run();
+            }
+
+            if (checkpointThread != null && !checkpointThread.isAlive()) {
+                startCheckpoint();
+            }
+            return location;
+        } catch (IOException ioe) {
+            LOG.error("KahaDB failed to store to Journal", ioe);
+            if (brokerService != null) {
+                brokerService.handleIOException(ioe);
+            }
+            throw ioe;
+        }
+    }
+
+    /**
+     * Loads a previously stored JournalMessage
+     *
+     * @param location
+     *      The location of the journal command to read.
+     *
+     * @return a new un-marshaled JournalCommand instance.
+     *
+     * @throws IOException if an error occurs reading the stored command.
+     */
+    protected JournalCommand<?> load(Location location) throws IOException {
+        ByteSequence data = journal.read(location);
+        DataByteArrayInputStream is = new DataByteArrayInputStream(data);
+        byte readByte = is.readByte();
+        KahaEntryType type = KahaEntryType.valueOf(readByte);
+        if (type == null) {
+            try {
+                is.close();
+            } catch (IOException e) {
+            }
+            throw new IOException("Could not load journal record. Invalid location: " + location);
+        }
+        JournalCommand<?> message = (JournalCommand<?>)type.createMessage();
+        message.mergeFramed(is);
+        return message;
+    }
+
+    /**
+     * Process a stored or recovered JournalCommand instance and update the DB Index with the
+     * state changes that this command produces.  This can be called either as a new DB operation
+     * or as a replay during recovery operations.
+     *
+     * @param command
+     *      The JournalCommand to process.
+     * @param location
+     *      The location in the Journal where the command was written or read from.
+     */
+    protected abstract void process(JournalCommand<?> command, Location location) throws IOException;
+
+    /**
+     * Perform a checkpoint operation with optional cleanup.
+     *
+     * Called by the checkpoint background thread periodically to initiate a checkpoint operation
+     * and if the cleanup flag is set a cleanup sweep should be done to allow for release of no
+     * longer needed journal log files etc.
+     *
+     * @param cleanup
+     *      Should the method do a simple checkpoint or also perform a journal cleanup.
+     *
+     * @throws IOException if an error occurs during the checkpoint operation.
+     */
+    protected void checkpointUpdate(final boolean cleanup) throws IOException {
+        checkpointLock.writeLock().lock();
+        try {
+            this.indexLock.writeLock().lock();
+            try {
+                pageFile.tx().execute(new Transaction.Closure<IOException>() {
+                    @Override
+                    public void execute(Transaction tx) throws IOException {
+                        checkpointUpdate(tx, cleanup);
+                    }
+                });
+            } finally {
+                this.indexLock.writeLock().unlock();
+            }
+
+        } finally {
+            checkpointLock.writeLock().unlock();
+        }
+    }
+
+    /**
+     * Perform the checkpoint update operation.  If the cleanup flag is true then the
+     * operation should also purge any unused Journal log files.
+     *
+     * This method must always be called with the checkpoint and index write locks held.
+     *
+     * @param tx
+     *      The TX under which to perform the checkpoint update.
+     * @param cleanup
+     *      Should the checkpoint also do unused Journal file cleanup.
+     *
+     * @throws IOException if an error occurs while performing the checkpoint.
+     */
+    protected abstract void checkpointUpdate(Transaction tx, boolean cleanup) throws IOException;
+
+    /**
+     * Creates a new ByteSequence that represents the marshaled form of the given Journal Command.
+     *
+     * @param command
+     *      The Journal Command that should be marshaled to bytes for writing.
+     *
+     * @return the byte representation of the given journal command.
+     *
+     * @throws IOException if an error occurs while serializing the command.
+     */
+    protected ByteSequence toByteSequence(JournalCommand<?> data) throws IOException {
+        int size = data.serializedSizeFramed();
+        DataByteArrayOutputStream os = new DataByteArrayOutputStream(size + 1);
+        os.writeByte(data.type().getNumber());
+        data.writeFramed(os);
+        return os.toByteSequence();
+    }
+
+    /**
+     * Create the PageFile instance and configure it using the configuration options
+     * currently set.
+     *
+     * @return the newly created and configured PageFile instance.
+     */
+    protected PageFile createPageFile() {
+        PageFile index = new PageFile(getDirectory(), getPageFileName());
+        index.setEnableWriteThread(isEnableIndexWriteAsync());
+        index.setWriteBatchSize(getIndexWriteBatchSize());
+        index.setPageCacheSize(getIndexCacheSize());
+        index.setUseLFRUEviction(isUseIndexLFRUEviction());
+        index.setLFUEvictionFactor(getIndexLFUEvictionFactor());
+        index.setEnableDiskSyncs(isEnableIndexDiskSyncs());
+        index.setEnableRecoveryFile(isEnableIndexRecoveryFile());
+        index.setEnablePageCaching(isEnableIndexPageCaching());
+        return index;
+    }
+
+    /**
+     * Create a new Journal instance and configure it using the currently set configuration
+     * options.  If an archive directory is configured than this method will attempt to create
+     * that directory if it does not already exist.
+     *
+     * @return the newly created an configured Journal instance.
+     *
+     * @throws IOException if an error occurs while creating the Journal object.
+     */
+    protected Journal createJournal() throws IOException {
+        Journal manager = new Journal();
+        manager.setDirectory(getDirectory());
+        manager.setMaxFileLength(getJournalMaxFileLength());
+        manager.setCheckForCorruptionOnStartup(isCheckForCorruptJournalFiles());
+        manager.setChecksum(isChecksumJournalFiles() || isCheckForCorruptJournalFiles());
+        manager.setWriteBatchSize(getJournalMaxWriteBatchSize());
+        manager.setArchiveDataLogs(isArchiveDataLogs());
+        manager.setSizeAccumulator(journalSize);
+        manager.setEnableAsyncDiskSync(isEnableJournalDiskSyncs());
+        if (getDirectoryArchive() != null) {
+            IOHelper.mkdirs(getDirectoryArchive());
+            manager.setDirectoryArchive(getDirectoryArchive());
+        }
+        return manager;
+    }
+
+    /**
+     * Starts the checkpoint Thread instance if not already running and not disabled
+     * by configuration.
+     */
+    protected void startCheckpoint() {
+        if (checkpointInterval == 0 && cleanupInterval == 0) {
+            LOG.info("periodic checkpoint/cleanup disabled, will ocurr on clean shutdown/restart");
+            return;
+        }
+        synchronized (checkpointThreadLock) {
+            boolean start = false;
+            if (checkpointThread == null) {
+                start = true;
+            } else if (!checkpointThread.isAlive()) {
+                start = true;
+                LOG.info("KahaDB: Recovering checkpoint thread after death");
+            }
+            if (start) {
+                checkpointThread = new Thread("ActiveMQ Journal Checkpoint Worker") {
+                    @Override
+                    public void run() {
+                        try {
+                            long lastCleanup = System.currentTimeMillis();
+                            long lastCheckpoint = System.currentTimeMillis();
+                            // Sleep for a short time so we can periodically check
+                            // to see if we need to exit this thread.
+                            long sleepTime = Math.min(checkpointInterval > 0 ? checkpointInterval : cleanupInterval, 500);
+                            while (opened.get()) {
+                                Thread.sleep(sleepTime);
+                                long now = System.currentTimeMillis();
+                                if( cleanupInterval > 0 && (now - lastCleanup >= cleanupInterval) ) {
+                                    checkpointCleanup(true);
+                                    lastCleanup = now;
+                                    lastCheckpoint = now;
+                                } else if( checkpointInterval > 0 && (now - lastCheckpoint >= checkpointInterval )) {
+                                    checkpointCleanup(false);
+                                    lastCheckpoint = now;
+                                }
+                            }
+                        } catch (InterruptedException e) {
+                            // Looks like someone really wants us to exit this thread...
+                        } catch (IOException ioe) {
+                            LOG.error("Checkpoint failed", ioe);
+                            brokerService.handleIOException(ioe);
+                        }
+                    }
+                };
+
+                checkpointThread.setDaemon(true);
+                checkpointThread.start();
+            }
+        }
+    }
+
+    /**
+     * Called from the worker thread to start a checkpoint.
+     *
+     * This method ensure that the store is in an opened state and optionaly logs information
+     * related to slow store access times.
+     *
+     * @param cleanup
+     *      Should a cleanup of the journal occur during the checkpoint operation.
+     *
+     * @throws IOException if an error occurs during the checkpoint operation.
+     */
+    protected void checkpointCleanup(final boolean cleanup) throws IOException {
+        long start;
+        this.indexLock.writeLock().lock();
+        try {
+            start = System.currentTimeMillis();
+            if (!opened.get()) {
+                return;
+            }
+        } finally {
+            this.indexLock.writeLock().unlock();
+        }
+        checkpointUpdate(cleanup);
+        long end = System.currentTimeMillis();
+        if (LOG_SLOW_ACCESS_TIME > 0 && end - start > LOG_SLOW_ACCESS_TIME) {
+            LOG.info("Slow KahaDB access: cleanup took {}", (end - start));
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/activemq/blob/fc244f48/activemq-kahadb-store/src/main/java/org/apache/activemq/store/kahadb/KahaDBMetaData.java
----------------------------------------------------------------------
diff --git a/activemq-kahadb-store/src/main/java/org/apache/activemq/store/kahadb/KahaDBMetaData.java b/activemq-kahadb-store/src/main/java/org/apache/activemq/store/kahadb/KahaDBMetaData.java
new file mode 100644
index 0000000..defb238
--- /dev/null
+++ b/activemq-kahadb-store/src/main/java/org/apache/activemq/store/kahadb/KahaDBMetaData.java
@@ -0,0 +1,135 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.activemq.store.kahadb;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+
+import org.apache.activemq.store.kahadb.disk.journal.Location;
+import org.apache.activemq.store.kahadb.disk.page.Page;
+import org.apache.activemq.store.kahadb.disk.page.Transaction;
+
+/**
+ * Interface for the store meta data used to hold the index value and other needed
+ * information to manage a KahaDB store instance.
+ */
+public interface KahaDBMetaData<T> {
+
+    /**
+     * Indicates that this meta data instance has been opened and is active.
+     */
+    public static final int OPEN_STATE = 2;
+
+    /**
+     * Indicates that this meta data instance has been closed and is no longer active.
+     */
+    public static final int CLOSED_STATE = 1;
+
+    /**
+     * Gets the Page in the store PageFile where the KahaDBMetaData instance is stored.
+     *
+     * @return the Page to use to start access the KahaDBMetaData instance.
+     */
+    Page<T> getPage();
+
+    /**
+     * Sets the Page instance used to load and store the KahaDBMetaData instance.
+     *
+     * @param page
+     *        the new Page value to use.
+     */
+    void setPage(Page<T> page);
+
+    /**
+     * Gets the state flag of this meta data instance.
+     *
+     *  @return the current state value for this instance.
+     */
+    int getState();
+
+    /**
+     * Sets the current value of the state flag.
+     *
+     * @param value
+     *        the new value to assign to the state flag.
+     */
+    void setState(int value);
+
+    /**
+     * Returns the Journal Location value that indicates that last recorded update
+     * that was successfully performed for this KahaDB store implementation.
+     *
+     * @return the location of the last successful update location.
+     */
+    Location getLastUpdateLocation();
+
+    /**
+     * Updates the value of the last successful update.
+     *
+     * @param location
+     *        the new value to assign the last update location field.
+     */
+    void setLastUpdateLocation(Location location);
+
+    /**
+     * For a newly created KahaDBMetaData instance this method is called to allow
+     * the instance to create all of it's internal indices and other state data.
+     *
+     * @param tx
+     *        the Transaction instance under which the operation is executed.
+     *
+     * @throws IOException if an error occurs while creating the meta data structures.
+     */
+    void initialize(Transaction tx) throws IOException;
+
+    /**
+     * Instructs this object to load its internal data structures from the KahaDB PageFile
+     * and prepare itself for use.
+     *
+     * @param tx
+     *        the Transaction instance under which the operation is executed.
+     *
+     * @throws IOException if an error occurs while creating the meta data structures.
+     */
+    void load(Transaction tx) throws IOException;
+
+    /**
+     * Reads the serialized for of this object from the KadaDB PageFile and prepares it
+     * for use.  This method does not need to perform a full load of the meta data structures
+     * only read in the information necessary to load them from the PageFile on a call to the
+     * load method.
+     *
+     * @param in
+     *        the DataInput instance used to read this objects serialized form.
+     *
+     * @throws IOException if an error occurs while reading the serialized form.
+     */
+    void read(DataInput in) throws IOException;
+
+    /**
+     * Writes the object into a serialized form which can be read back in again using the
+     * read method.
+     *
+     * @param out
+     *        the DataOutput instance to use to write the current state to a serialized form.
+     *
+     * @throws IOException if an error occurs while serializing this instance.
+     */
+    void write(DataOutput out) throws IOException;
+
+}

http://git-wip-us.apache.org/repos/asf/activemq/blob/fc244f48/activemq-kahadb-store/src/main/java/org/apache/activemq/store/kahadb/KahaDBPersistenceAdapter.java
----------------------------------------------------------------------
diff --git a/activemq-kahadb-store/src/main/java/org/apache/activemq/store/kahadb/KahaDBPersistenceAdapter.java b/activemq-kahadb-store/src/main/java/org/apache/activemq/store/kahadb/KahaDBPersistenceAdapter.java
index d8b986e..8ca8ca4 100644
--- a/activemq-kahadb-store/src/main/java/org/apache/activemq/store/kahadb/KahaDBPersistenceAdapter.java
+++ b/activemq-kahadb-store/src/main/java/org/apache/activemq/store/kahadb/KahaDBPersistenceAdapter.java
@@ -31,6 +31,7 @@ import org.apache.activemq.broker.LockableServiceSupport;
 import org.apache.activemq.broker.Locker;
 import org.apache.activemq.broker.jmx.AnnotatedMBean;
 import org.apache.activemq.broker.jmx.PersistenceAdapterView;
+import org.apache.activemq.broker.scheduler.JobSchedulerStore;
 import org.apache.activemq.command.ActiveMQDestination;
 import org.apache.activemq.command.ActiveMQQueue;
 import org.apache.activemq.command.ActiveMQTopic;
@@ -39,7 +40,14 @@ import org.apache.activemq.command.ProducerId;
 import org.apache.activemq.command.TransactionId;
 import org.apache.activemq.command.XATransactionId;
 import org.apache.activemq.protobuf.Buffer;
-import org.apache.activemq.store.*;
+import org.apache.activemq.store.JournaledStore;
+import org.apache.activemq.store.MessageStore;
+import org.apache.activemq.store.PersistenceAdapter;
+import org.apache.activemq.store.SharedFileLocker;
+import org.apache.activemq.store.TopicMessageStore;
+import org.apache.activemq.store.TransactionIdTransformer;
+import org.apache.activemq.store.TransactionIdTransformerAware;
+import org.apache.activemq.store.TransactionStore;
 import org.apache.activemq.store.kahadb.data.KahaLocalTransactionId;
 import org.apache.activemq.store.kahadb.data.KahaTransactionInfo;
 import org.apache.activemq.store.kahadb.data.KahaXATransactionId;
@@ -642,4 +650,9 @@ public class KahaDBPersistenceAdapter extends LockableServiceSupport implements
     public void setTransactionIdTransformer(TransactionIdTransformer transactionIdTransformer) {
         getStore().setTransactionIdTransformer(transactionIdTransformer);
     }
+
+    @Override
+    public JobSchedulerStore createJobSchedulerStore() throws IOException, UnsupportedOperationException {
+        return this.letter.createJobSchedulerStore();
+    }
 }

http://git-wip-us.apache.org/repos/asf/activemq/blob/fc244f48/activemq-kahadb-store/src/main/java/org/apache/activemq/store/kahadb/KahaDBStore.java
----------------------------------------------------------------------
diff --git a/activemq-kahadb-store/src/main/java/org/apache/activemq/store/kahadb/KahaDBStore.java b/activemq-kahadb-store/src/main/java/org/apache/activemq/store/kahadb/KahaDBStore.java
index 60c0738..975cd05 100644
--- a/activemq-kahadb-store/src/main/java/org/apache/activemq/store/kahadb/KahaDBStore.java
+++ b/activemq-kahadb-store/src/main/java/org/apache/activemq/store/kahadb/KahaDBStore.java
@@ -42,6 +42,7 @@ import java.util.concurrent.atomic.AtomicInteger;
 import org.apache.activemq.broker.ConnectionContext;
 import org.apache.activemq.broker.region.Destination;
 import org.apache.activemq.broker.region.RegionBroker;
+import org.apache.activemq.broker.scheduler.JobSchedulerStore;
 import org.apache.activemq.command.ActiveMQDestination;
 import org.apache.activemq.command.ActiveMQQueue;
 import org.apache.activemq.command.ActiveMQTempQueue;
@@ -55,7 +56,14 @@ import org.apache.activemq.command.SubscriptionInfo;
 import org.apache.activemq.command.TransactionId;
 import org.apache.activemq.openwire.OpenWireFormat;
 import org.apache.activemq.protobuf.Buffer;
-import org.apache.activemq.store.*;
+import org.apache.activemq.store.AbstractMessageStore;
+import org.apache.activemq.store.ListenableFuture;
+import org.apache.activemq.store.MessageRecoveryListener;
+import org.apache.activemq.store.MessageStore;
+import org.apache.activemq.store.PersistenceAdapter;
+import org.apache.activemq.store.TopicMessageStore;
+import org.apache.activemq.store.TransactionIdTransformer;
+import org.apache.activemq.store.TransactionStore;
 import org.apache.activemq.store.kahadb.data.KahaAddMessageCommand;
 import org.apache.activemq.store.kahadb.data.KahaDestination;
 import org.apache.activemq.store.kahadb.data.KahaDestination.DestinationType;
@@ -66,6 +74,7 @@ import org.apache.activemq.store.kahadb.data.KahaSubscriptionCommand;
 import org.apache.activemq.store.kahadb.data.KahaUpdateMessageCommand;
 import org.apache.activemq.store.kahadb.disk.journal.Location;
 import org.apache.activemq.store.kahadb.disk.page.Transaction;
+import org.apache.activemq.store.kahadb.scheduler.JobSchedulerStoreImpl;
 import org.apache.activemq.usage.MemoryUsage;
 import org.apache.activemq.usage.SystemUsage;
 import org.apache.activemq.util.ServiceStopper;
@@ -172,6 +181,7 @@ public class KahaDBStore extends MessageDatabase implements PersistenceAdapter {
     public int getMaxAsyncJobs() {
         return this.maxAsyncJobs;
     }
+
     /**
      * @param maxAsyncJobs
      *            the maxAsyncJobs to set
@@ -426,6 +436,7 @@ public class KahaDBStore extends MessageDatabase implements PersistenceAdapter {
 
         }
 
+        @Override
         public void updateMessage(Message message) throws IOException {
             if (LOG.isTraceEnabled()) {
                 LOG.trace("updating: " + message.getMessageId() + " with deliveryCount: " + message.getRedeliveryCounter());
@@ -472,7 +483,7 @@ public class KahaDBStore extends MessageDatabase implements PersistenceAdapter {
             indexLock.writeLock().lock();
             try {
                 location = findMessageLocation(key, dest);
-            }finally {
+            } finally {
                 indexLock.writeLock().unlock();
             }
             if (location == null) {
@@ -492,19 +503,17 @@ public class KahaDBStore extends MessageDatabase implements PersistenceAdapter {
                         @Override
                         public Integer execute(Transaction tx) throws IOException {
                             // Iterate through all index entries to get a count
-                            // of
-                            // messages in the destination.
+                            // of messages in the destination.
                             StoredDestination sd = getStoredDestination(dest, tx);
                             int rc = 0;
-                            for (Iterator<Entry<Location, Long>> iterator = sd.locationIndex.iterator(tx); iterator
-                                    .hasNext();) {
+                            for (Iterator<Entry<Location, Long>> iterator = sd.locationIndex.iterator(tx); iterator.hasNext();) {
                                 iterator.next();
                                 rc++;
                             }
                             return rc;
                         }
                     });
-                }finally {
+                } finally {
                     indexLock.writeLock().unlock();
                 }
             } finally {
@@ -525,7 +534,7 @@ public class KahaDBStore extends MessageDatabase implements PersistenceAdapter {
                         return sd.locationIndex.isEmpty(tx);
                     }
                 });
-            }finally {
+            } finally {
                 indexLock.writeLock().unlock();
             }
         }
@@ -552,12 +561,11 @@ public class KahaDBStore extends MessageDatabase implements PersistenceAdapter {
                         }
                     }
                 });
-            }finally {
+            } finally {
                 indexLock.writeLock().unlock();
             }
         }
 
-
         @Override
         public void recoverNextMessages(final int maxReturned, final MessageRecoveryListener listener) throws Exception {
             indexLock.writeLock().lock();
@@ -583,7 +591,7 @@ public class KahaDBStore extends MessageDatabase implements PersistenceAdapter {
                         sd.orderIndex.stoppedIterating();
                     }
                 });
-            }finally {
+            } finally {
                 indexLock.writeLock().unlock();
             }
         }
@@ -628,7 +636,7 @@ public class KahaDBStore extends MessageDatabase implements PersistenceAdapter {
                         });
                 } catch (Exception e) {
                     LOG.error("Failed to reset batching",e);
-                }finally {
+                } finally {
                     indexLock.writeLock().unlock();
                 }
             }
@@ -641,8 +649,7 @@ public class KahaDBStore extends MessageDatabase implements PersistenceAdapter {
                 lockAsyncJobQueue();
 
                 // Hopefully one day the page file supports concurrent read
-                // operations... but for now we must
-                // externally synchronize...
+                // operations... but for now we must externally synchronize...
 
                 indexLock.writeLock().lock();
                 try {
@@ -725,8 +732,7 @@ public class KahaDBStore extends MessageDatabase implements PersistenceAdapter {
 
         @Override
         public void acknowledge(ConnectionContext context, String clientId, String subscriptionName,
-                                MessageId messageId, MessageAck ack)
-                throws IOException {
+                                MessageId messageId, MessageAck ack) throws IOException {
             String subscriptionKey = subscriptionKey(clientId, subscriptionName).toString();
             if (isConcurrentStoreAndDispatchTopics()) {
                 AsyncJobKey key = new AsyncJobKey(messageId, getDestination());
@@ -810,7 +816,7 @@ public class KahaDBStore extends MessageDatabase implements PersistenceAdapter {
                         }
                     }
                 });
-            }finally {
+            } finally {
                 indexLock.writeLock().unlock();
             }
 
@@ -836,7 +842,7 @@ public class KahaDBStore extends MessageDatabase implements PersistenceAdapter {
                                 .getSubscriptionInfo().newInput()));
                     }
                 });
-            }finally {
+            } finally {
                 indexLock.writeLock().unlock();
             }
         }
@@ -859,7 +865,7 @@ public class KahaDBStore extends MessageDatabase implements PersistenceAdapter {
                         return (int) getStoredMessageCount(tx, sd, subscriptionKey);
                     }
                 });
-            }finally {
+            } finally {
                 indexLock.writeLock().unlock();
             }
         }
@@ -890,7 +896,7 @@ public class KahaDBStore extends MessageDatabase implements PersistenceAdapter {
                         sd.orderIndex.resetCursorPosition();
                     }
                 });
-            }finally {
+            } finally {
                 indexLock.writeLock().unlock();
             }
         }
@@ -943,7 +949,7 @@ public class KahaDBStore extends MessageDatabase implements PersistenceAdapter {
                         }
                     }
                 });
-            }finally {
+            } finally {
                 indexLock.writeLock().unlock();
             }
         }
@@ -1358,7 +1364,6 @@ public class KahaDBStore extends MessageDatabase implements PersistenceAdapter {
                     LOG.warn("Failed to aquire lock", e);
                 }
             }
-
         }
 
         @Override
@@ -1422,7 +1427,11 @@ public class KahaDBStore extends MessageDatabase implements PersistenceAdapter {
             if (runnable instanceof StoreTask) {
                ((StoreTask)runnable).releaseLocks();
             }
-
         }
     }
+
+    @Override
+    public JobSchedulerStore createJobSchedulerStore() throws IOException, UnsupportedOperationException {
+        return new JobSchedulerStoreImpl();
+    }
 }

http://git-wip-us.apache.org/repos/asf/activemq/blob/fc244f48/activemq-kahadb-store/src/main/java/org/apache/activemq/store/kahadb/MultiKahaDBPersistenceAdapter.java
----------------------------------------------------------------------
diff --git a/activemq-kahadb-store/src/main/java/org/apache/activemq/store/kahadb/MultiKahaDBPersistenceAdapter.java b/activemq-kahadb-store/src/main/java/org/apache/activemq/store/kahadb/MultiKahaDBPersistenceAdapter.java
index d10c4eb..eca83e8 100644
--- a/activemq-kahadb-store/src/main/java/org/apache/activemq/store/kahadb/MultiKahaDBPersistenceAdapter.java
+++ b/activemq-kahadb-store/src/main/java/org/apache/activemq/store/kahadb/MultiKahaDBPersistenceAdapter.java
@@ -16,12 +16,44 @@
  */
 package org.apache.activemq.store.kahadb;
 
-import org.apache.activemq.broker.*;
-import org.apache.activemq.command.*;
+import java.io.File;
+import java.io.FileFilter;
+import java.io.IOException;
+import java.nio.charset.Charset;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.transaction.xa.Xid;
+
+import org.apache.activemq.broker.BrokerService;
+import org.apache.activemq.broker.BrokerServiceAware;
+import org.apache.activemq.broker.ConnectionContext;
+import org.apache.activemq.broker.Lockable;
+import org.apache.activemq.broker.LockableServiceSupport;
+import org.apache.activemq.broker.Locker;
+import org.apache.activemq.broker.scheduler.JobSchedulerStore;
+import org.apache.activemq.command.ActiveMQDestination;
+import org.apache.activemq.command.ActiveMQQueue;
+import org.apache.activemq.command.ActiveMQTopic;
+import org.apache.activemq.command.LocalTransactionId;
+import org.apache.activemq.command.ProducerId;
+import org.apache.activemq.command.TransactionId;
+import org.apache.activemq.command.XATransactionId;
 import org.apache.activemq.filter.AnyDestination;
 import org.apache.activemq.filter.DestinationMap;
 import org.apache.activemq.filter.DestinationMapEntry;
-import org.apache.activemq.store.*;
+import org.apache.activemq.store.MessageStore;
+import org.apache.activemq.store.PersistenceAdapter;
+import org.apache.activemq.store.SharedFileLocker;
+import org.apache.activemq.store.TopicMessageStore;
+import org.apache.activemq.store.TransactionIdTransformer;
+import org.apache.activemq.store.TransactionIdTransformerAware;
+import org.apache.activemq.store.TransactionStore;
+import org.apache.activemq.store.kahadb.scheduler.JobSchedulerStoreImpl;
 import org.apache.activemq.usage.SystemUsage;
 import org.apache.activemq.util.IOExceptionSupport;
 import org.apache.activemq.util.IOHelper;
@@ -30,13 +62,6 @@ import org.apache.activemq.util.ServiceStopper;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import javax.transaction.xa.Xid;
-import java.io.File;
-import java.io.FileFilter;
-import java.io.IOException;
-import java.nio.charset.Charset;
-import java.util.*;
-
 /**
  * An implementation of {@link org.apache.activemq.store.PersistenceAdapter}  that supports
  * distribution of destinations across multiple kahaDB persistence adapters
@@ -50,6 +75,7 @@ public class MultiKahaDBPersistenceAdapter extends LockableServiceSupport implem
     final int LOCAL_FORMAT_ID_MAGIC = Integer.valueOf(System.getProperty("org.apache.activemq.store.kahadb.MultiKahaDBTransactionStore.localXaFormatId", "61616"));
 
     final class DelegateDestinationMap extends DestinationMap {
+        @Override
         public void setEntries(List<DestinationMapEntry>  entries) {
             super.setEntries(entries);
         }
@@ -252,7 +278,7 @@ public class MultiKahaDBPersistenceAdapter extends LockableServiceSupport implem
         }
         if (adapter instanceof PersistenceAdapter) {
             adapter.removeQueueMessageStore(destination);
-            removeMessageStore((PersistenceAdapter)adapter, destination);
+            removeMessageStore(adapter, destination);
             destinationMap.removeAll(destination);
         }
     }
@@ -267,7 +293,7 @@ public class MultiKahaDBPersistenceAdapter extends LockableServiceSupport implem
         }
         if (adapter instanceof PersistenceAdapter) {
             adapter.removeTopicMessageStore(destination);
-            removeMessageStore((PersistenceAdapter)adapter, destination);
+            removeMessageStore(adapter, destination);
             destinationMap.removeAll(destination);
         }
     }
@@ -453,6 +479,7 @@ public class MultiKahaDBPersistenceAdapter extends LockableServiceSupport implem
         }
     }
 
+    @Override
     public BrokerService getBrokerService() {
         return brokerService;
     }
@@ -503,4 +530,9 @@ public class MultiKahaDBPersistenceAdapter extends LockableServiceSupport implem
         locker.configure(this);
         return locker;
     }
+
+    @Override
+    public JobSchedulerStore createJobSchedulerStore() throws IOException, UnsupportedOperationException {
+        return new JobSchedulerStoreImpl();
+    }
 }

http://git-wip-us.apache.org/repos/asf/activemq/blob/fc244f48/activemq-kahadb-store/src/main/java/org/apache/activemq/store/kahadb/MultiKahaDBTransactionStore.java
----------------------------------------------------------------------
diff --git a/activemq-kahadb-store/src/main/java/org/apache/activemq/store/kahadb/MultiKahaDBTransactionStore.java b/activemq-kahadb-store/src/main/java/org/apache/activemq/store/kahadb/MultiKahaDBTransactionStore.java
index c7ece83..8840a1d 100644
--- a/activemq-kahadb-store/src/main/java/org/apache/activemq/store/kahadb/MultiKahaDBTransactionStore.java
+++ b/activemq-kahadb-store/src/main/java/org/apache/activemq/store/kahadb/MultiKahaDBTransactionStore.java
@@ -31,16 +31,24 @@ import org.apache.activemq.command.MessageAck;
 import org.apache.activemq.command.MessageId;
 import org.apache.activemq.command.TransactionId;
 import org.apache.activemq.command.XATransactionId;
-import org.apache.activemq.store.*;
+import org.apache.activemq.store.AbstractMessageStore;
+import org.apache.activemq.store.ListenableFuture;
+import org.apache.activemq.store.MessageStore;
+import org.apache.activemq.store.PersistenceAdapter;
+import org.apache.activemq.store.ProxyMessageStore;
+import org.apache.activemq.store.ProxyTopicMessageStore;
+import org.apache.activemq.store.TopicMessageStore;
+import org.apache.activemq.store.TransactionRecoveryListener;
+import org.apache.activemq.store.TransactionStore;
 import org.apache.activemq.store.kahadb.data.KahaCommitCommand;
 import org.apache.activemq.store.kahadb.data.KahaEntryType;
 import org.apache.activemq.store.kahadb.data.KahaPrepareCommand;
 import org.apache.activemq.store.kahadb.data.KahaTraceCommand;
-import org.apache.activemq.util.IOHelper;
 import org.apache.activemq.store.kahadb.disk.journal.Journal;
 import org.apache.activemq.store.kahadb.disk.journal.Location;
 import org.apache.activemq.util.DataByteArrayInputStream;
 import org.apache.activemq.util.DataByteArrayOutputStream;
+import org.apache.activemq.util.IOHelper;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -186,6 +194,7 @@ public class MultiKahaDBTransactionStore implements TransactionStore {
         return inflightTransactions.remove(txid);
     }
 
+    @Override
     public void prepare(TransactionId txid) throws IOException {
         Tx tx = getTx(txid);
         for (TransactionStore store : tx.getStores()) {
@@ -193,6 +202,7 @@ public class MultiKahaDBTransactionStore implements TransactionStore {
         }
     }
 
+    @Override
     public void commit(TransactionId txid, boolean wasPrepared, Runnable preCommit, Runnable postCommit)
             throws IOException {
 
@@ -247,6 +257,7 @@ public class MultiKahaDBTransactionStore implements TransactionStore {
         return location;
     }
 
+    @Override
     public void rollback(TransactionId txid) throws IOException {
         Tx tx = removeTx(txid);
         if (tx != null) {
@@ -256,6 +267,7 @@ public class MultiKahaDBTransactionStore implements TransactionStore {
         }
     }
 
+    @Override
     public void start() throws Exception {
         journal = new Journal() {
             @Override
@@ -289,6 +301,7 @@ public class MultiKahaDBTransactionStore implements TransactionStore {
         return new File(multiKahaDBPersistenceAdapter.getDirectory(), "txStore");
     }
 
+    @Override
     public void stop() throws Exception {
         journal.close();
         journal = null;
@@ -334,6 +347,7 @@ public class MultiKahaDBTransactionStore implements TransactionStore {
     }
 
 
+    @Override
     public synchronized void recover(final TransactionRecoveryListener listener) throws IOException {
 
         for (final PersistenceAdapter adapter : multiKahaDBPersistenceAdapter.adapters) {

http://git-wip-us.apache.org/repos/asf/activemq/blob/fc244f48/activemq-kahadb-store/src/main/java/org/apache/activemq/store/kahadb/TempKahaDBStore.java
----------------------------------------------------------------------
diff --git a/activemq-kahadb-store/src/main/java/org/apache/activemq/store/kahadb/TempKahaDBStore.java b/activemq-kahadb-store/src/main/java/org/apache/activemq/store/kahadb/TempKahaDBStore.java
index 66ae496..45e35c6 100644
--- a/activemq-kahadb-store/src/main/java/org/apache/activemq/store/kahadb/TempKahaDBStore.java
+++ b/activemq-kahadb-store/src/main/java/org/apache/activemq/store/kahadb/TempKahaDBStore.java
@@ -22,12 +22,13 @@ import java.util.ArrayList;
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.Map;
-import java.util.Set;
 import java.util.Map.Entry;
+import java.util.Set;
 
 import org.apache.activemq.broker.BrokerService;
 import org.apache.activemq.broker.BrokerServiceAware;
 import org.apache.activemq.broker.ConnectionContext;
+import org.apache.activemq.broker.scheduler.JobSchedulerStore;
 import org.apache.activemq.command.ActiveMQDestination;
 import org.apache.activemq.command.ActiveMQQueue;
 import org.apache.activemq.command.ActiveMQTempQueue;
@@ -51,31 +52,35 @@ import org.apache.activemq.store.TransactionRecoveryListener;
 import org.apache.activemq.store.TransactionStore;
 import org.apache.activemq.store.kahadb.data.KahaAddMessageCommand;
 import org.apache.activemq.store.kahadb.data.KahaDestination;
+import org.apache.activemq.store.kahadb.data.KahaDestination.DestinationType;
 import org.apache.activemq.store.kahadb.data.KahaLocation;
 import org.apache.activemq.store.kahadb.data.KahaRemoveDestinationCommand;
 import org.apache.activemq.store.kahadb.data.KahaRemoveMessageCommand;
 import org.apache.activemq.store.kahadb.data.KahaSubscriptionCommand;
-import org.apache.activemq.store.kahadb.data.KahaDestination.DestinationType;
+import org.apache.activemq.store.kahadb.disk.journal.Location;
+import org.apache.activemq.store.kahadb.disk.page.Transaction;
 import org.apache.activemq.usage.MemoryUsage;
 import org.apache.activemq.usage.SystemUsage;
 import org.apache.activemq.util.ByteSequence;
 import org.apache.activemq.wireformat.WireFormat;
-import org.apache.activemq.store.kahadb.disk.journal.Location;
-import org.apache.activemq.store.kahadb.disk.page.Transaction;
 
 public class TempKahaDBStore extends TempMessageDatabase implements PersistenceAdapter, BrokerServiceAware {
 
     private final WireFormat wireFormat = new OpenWireFormat();
     private BrokerService brokerService;
 
+    @Override
     public void setBrokerName(String brokerName) {
     }
+    @Override
     public void setUsageManager(SystemUsage usageManager) {
     }
 
+    @Override
     public TransactionStore createTransactionStore() throws IOException {
         return new TransactionStore(){
-            
+
+            @Override
             public void commit(TransactionId txid, boolean wasPrepared, Runnable preCommit,Runnable postCommit) throws IOException {
                 if (preCommit != null) {
                     preCommit.run();
@@ -85,18 +90,21 @@ public class TempKahaDBStore extends TempMessageDatabase implements PersistenceA
                     postCommit.run();
                 }
             }
+            @Override
             public void prepare(TransactionId txid) throws IOException {
-            	processPrepare(txid);
+                processPrepare(txid);
             }
+            @Override
             public void rollback(TransactionId txid) throws IOException {
-            	processRollback(txid);
+                processRollback(txid);
             }
+            @Override
             public void recover(TransactionRecoveryListener listener) throws IOException {
                 for (Map.Entry<TransactionId, ArrayList<Operation>> entry : preparedTransactions.entrySet()) {
                     XATransactionId xid = (XATransactionId)entry.getKey();
                     ArrayList<Message> messageList = new ArrayList<Message>();
                     ArrayList<MessageAck> ackList = new ArrayList<MessageAck>();
-                    
+
                     for (Operation op : entry.getValue()) {
                         if( op.getClass() == AddOpperation.class ) {
                             AddOpperation addOp = (AddOpperation)op;
@@ -108,7 +116,7 @@ public class TempKahaDBStore extends TempMessageDatabase implements PersistenceA
                             ackList.add(ack);
                         }
                     }
-                    
+
                     Message[] addedMessages = new Message[messageList.size()];
                     MessageAck[] acks = new MessageAck[ackList.size()];
                     messageList.toArray(addedMessages);
@@ -116,8 +124,10 @@ public class TempKahaDBStore extends TempMessageDatabase implements PersistenceA
                     listener.recover(xid, addedMessages, acks);
                 }
             }
+            @Override
             public void start() throws Exception {
             }
+            @Override
             public void stop() throws Exception {
             }
         };
@@ -136,13 +146,15 @@ public class TempKahaDBStore extends TempMessageDatabase implements PersistenceA
             return destination;
         }
 
+        @Override
         public void addMessage(ConnectionContext context, Message message) throws IOException {
             KahaAddMessageCommand command = new KahaAddMessageCommand();
             command.setDestination(dest);
             command.setMessageId(message.getMessageId().toProducerKey());
             processAdd(command, message.getTransactionId(), wireFormat.marshal(message));
         }
-        
+
+        @Override
         public void removeMessage(ConnectionContext context, MessageAck ack) throws IOException {
             KahaRemoveMessageCommand command = new KahaRemoveMessageCommand();
             command.setDestination(dest);
@@ -150,20 +162,23 @@ public class TempKahaDBStore extends TempMessageDatabase implements PersistenceA
             processRemove(command, ack.getTransactionId());
         }
 
+        @Override
         public void removeAllMessages(ConnectionContext context) throws IOException {
             KahaRemoveDestinationCommand command = new KahaRemoveDestinationCommand();
             command.setDestination(dest);
             process(command);
         }
 
+        @Override
         public Message getMessage(MessageId identity) throws IOException {
             final String key = identity.toProducerKey();
-            
+
             // Hopefully one day the page file supports concurrent read operations... but for now we must
             // externally synchronize...
             ByteSequence data;
             synchronized(indexMutex) {
                 data = pageFile.tx().execute(new Transaction.CallableClosure<ByteSequence, IOException>(){
+                    @Override
                     public ByteSequence execute(Transaction tx) throws IOException {
                         StoredDestination sd = getStoredDestination(dest, tx);
                         Long sequence = sd.messageIdIndex.get(tx, key);
@@ -177,14 +192,16 @@ public class TempKahaDBStore extends TempMessageDatabase implements PersistenceA
             if( data == null ) {
                 return null;
             }
-            
+
             Message msg = (Message)wireFormat.unmarshal( data );
-			return msg;
+            return msg;
         }
-        
+
+        @Override
         public int getMessageCount() throws IOException {
             synchronized(indexMutex) {
                 return pageFile.tx().execute(new Transaction.CallableClosure<Integer, IOException>(){
+                    @Override
                     public Integer execute(Transaction tx) throws IOException {
                         // Iterate through all index entries to get a count of messages in the destination.
                         StoredDestination sd = getStoredDestination(dest, tx);
@@ -199,9 +216,11 @@ public class TempKahaDBStore extends TempMessageDatabase implements PersistenceA
             }
         }
 
+        @Override
         public void recover(final MessageRecoveryListener listener) throws Exception {
             synchronized(indexMutex) {
                 pageFile.tx().execute(new Transaction.Closure<Exception>(){
+                    @Override
                     public void execute(Transaction tx) throws Exception {
                         StoredDestination sd = getStoredDestination(dest, tx);
                         for (Iterator<Entry<Long, MessageRecord>> iterator = sd.orderIndex.iterator(tx); iterator.hasNext();) {
@@ -214,10 +233,12 @@ public class TempKahaDBStore extends TempMessageDatabase implements PersistenceA
         }
 
         long cursorPos=0;
-        
+
+        @Override
         public void recoverNextMessages(final int maxReturned, final MessageRecoveryListener listener) throws Exception {
             synchronized(indexMutex) {
                 pageFile.tx().execute(new Transaction.Closure<Exception>(){
+                    @Override
                     public void execute(Transaction tx) throws Exception {
                         StoredDestination sd = getStoredDestination(dest, tx);
                         Entry<Long, MessageRecord> entry=null;
@@ -238,20 +259,22 @@ public class TempKahaDBStore extends TempMessageDatabase implements PersistenceA
             }
         }
 
+        @Override
         public void resetBatching() {
             cursorPos=0;
         }
 
-        
+
         @Override
         public void setBatch(MessageId identity) throws IOException {
             final String key = identity.toProducerKey();
-            
+
             // Hopefully one day the page file supports concurrent read operations... but for now we must
             // externally synchronize...
             Long location;
             synchronized(indexMutex) {
                 location = pageFile.tx().execute(new Transaction.CallableClosure<Long, IOException>(){
+                    @Override
                     public Long execute(Transaction tx) throws IOException {
                         StoredDestination sd = getStoredDestination(dest, tx);
                         return sd.messageIdIndex.get(tx, key);
@@ -261,7 +284,7 @@ public class TempKahaDBStore extends TempMessageDatabase implements PersistenceA
             if( location!=null ) {
                 cursorPos=location+1;
             }
-            
+
         }
 
         @Override
@@ -273,14 +296,15 @@ public class TempKahaDBStore extends TempMessageDatabase implements PersistenceA
         @Override
         public void stop() throws Exception {
         }
-        
+
     }
-        
+
     class KahaDBTopicMessageStore extends KahaDBMessageStore implements TopicMessageStore {
         public KahaDBTopicMessageStore(ActiveMQTopic destination) {
             super(destination);
         }
-        
+
+        @Override
         public void acknowledge(ConnectionContext context, String clientId, String subscriptionName,
                                 MessageId messageId, MessageAck ack) throws IOException {
             KahaRemoveMessageCommand command = new KahaRemoveMessageCommand();
@@ -294,6 +318,7 @@ public class TempKahaDBStore extends TempMessageDatabase implements PersistenceA
             processRemove(command, null);
         }
 
+        @Override
         public void addSubscription(SubscriptionInfo subscriptionInfo, boolean retroactive) throws IOException {
             String subscriptionKey = subscriptionKey(subscriptionInfo.getClientId(), subscriptionInfo.getSubscriptionName());
             KahaSubscriptionCommand command = new KahaSubscriptionCommand();
@@ -305,6 +330,7 @@ public class TempKahaDBStore extends TempMessageDatabase implements PersistenceA
             process(command);
         }
 
+        @Override
         public void deleteSubscription(String clientId, String subscriptionName) throws IOException {
             KahaSubscriptionCommand command = new KahaSubscriptionCommand();
             command.setDestination(dest);
@@ -312,11 +338,13 @@ public class TempKahaDBStore extends TempMessageDatabase implements PersistenceA
             process(command);
         }
 
+        @Override
         public SubscriptionInfo[] getAllSubscriptions() throws IOException {
-            
+
             final ArrayList<SubscriptionInfo> subscriptions = new ArrayList<SubscriptionInfo>();
             synchronized(indexMutex) {
                 pageFile.tx().execute(new Transaction.Closure<IOException>(){
+                    @Override
                     public void execute(Transaction tx) throws IOException {
                         StoredDestination sd = getStoredDestination(dest, tx);
                         for (Iterator<Entry<String, KahaSubscriptionCommand>> iterator = sd.subscriptions.iterator(tx); iterator.hasNext();) {
@@ -328,16 +356,18 @@ public class TempKahaDBStore extends TempMessageDatabase implements PersistenceA
                     }
                 });
             }
-            
+
             SubscriptionInfo[]rc=new SubscriptionInfo[subscriptions.size()];
             subscriptions.toArray(rc);
             return rc;
         }
 
+        @Override
         public SubscriptionInfo lookupSubscription(String clientId, String subscriptionName) throws IOException {
             final String subscriptionKey = subscriptionKey(clientId, subscriptionName);
             synchronized(indexMutex) {
                 return pageFile.tx().execute(new Transaction.CallableClosure<SubscriptionInfo, IOException>(){
+                    @Override
                     public SubscriptionInfo execute(Transaction tx) throws IOException {
                         StoredDestination sd = getStoredDestination(dest, tx);
                         KahaSubscriptionCommand command = sd.subscriptions.get(tx, subscriptionKey);
@@ -349,11 +379,13 @@ public class TempKahaDBStore extends TempMessageDatabase implements PersistenceA
                 });
             }
         }
-       
+
+        @Override
         public int getMessageCount(String clientId, String subscriptionName) throws IOException {
             final String subscriptionKey = subscriptionKey(clientId, subscriptionName);
             synchronized(indexMutex) {
                 return pageFile.tx().execute(new Transaction.CallableClosure<Integer, IOException>(){
+                    @Override
                     public Integer execute(Transaction tx) throws IOException {
                         StoredDestination sd = getStoredDestination(dest, tx);
                         Long cursorPos = sd.subscriptionAcks.get(tx, subscriptionKey);
@@ -362,7 +394,7 @@ public class TempKahaDBStore extends TempMessageDatabase implements PersistenceA
                             return 0;
                         }
                         cursorPos += 1;
-                        
+
                         int counter = 0;
                         for (Iterator<Entry<Long, MessageRecord>> iterator = sd.orderIndex.iterator(tx, cursorPos); iterator.hasNext();) {
                             iterator.next();
@@ -371,18 +403,20 @@ public class TempKahaDBStore extends TempMessageDatabase implements PersistenceA
                         return counter;
                     }
                 });
-            }        
+            }
         }
 
+        @Override
         public void recoverSubscription(String clientId, String subscriptionName, final MessageRecoveryListener listener) throws Exception {
             final String subscriptionKey = subscriptionKey(clientId, subscriptionName);
             synchronized(indexMutex) {
                 pageFile.tx().execute(new Transaction.Closure<Exception>(){
+                    @Override
                     public void execute(Transaction tx) throws Exception {
                         StoredDestination sd = getStoredDestination(dest, tx);
                         Long cursorPos = sd.subscriptionAcks.get(tx, subscriptionKey);
                         cursorPos += 1;
-                        
+
                         for (Iterator<Entry<Long, MessageRecord>> iterator = sd.orderIndex.iterator(tx, cursorPos); iterator.hasNext();) {
                             Entry<Long, MessageRecord> entry = iterator.next();
                             listener.recoverMessage( (Message) wireFormat.unmarshal(entry.getValue().data ) );
@@ -392,10 +426,12 @@ public class TempKahaDBStore extends TempMessageDatabase implements PersistenceA
             }
         }
 
+        @Override
         public void recoverNextMessages(String clientId, String subscriptionName, final int maxReturned, final MessageRecoveryListener listener) throws Exception {
             final String subscriptionKey = subscriptionKey(clientId, subscriptionName);
             synchronized(indexMutex) {
                 pageFile.tx().execute(new Transaction.Closure<Exception>(){
+                    @Override
                     public void execute(Transaction tx) throws Exception {
                         StoredDestination sd = getStoredDestination(dest, tx);
                         Long cursorPos = sd.subscriptionCursors.get(subscriptionKey);
@@ -403,7 +439,7 @@ public class TempKahaDBStore extends TempMessageDatabase implements PersistenceA
                             cursorPos = sd.subscriptionAcks.get(tx, subscriptionKey);
                             cursorPos += 1;
                         }
-                        
+
                         Entry<Long, MessageRecord> entry=null;
                         int counter = 0;
                         for (Iterator<Entry<Long, MessageRecord>> iterator = sd.orderIndex.iterator(tx, cursorPos); iterator.hasNext();) {
@@ -422,11 +458,13 @@ public class TempKahaDBStore extends TempMessageDatabase implements PersistenceA
             }
         }
 
+        @Override
         public void resetBatching(String clientId, String subscriptionName) {
             try {
                 final String subscriptionKey = subscriptionKey(clientId, subscriptionName);
                 synchronized(indexMutex) {
                     pageFile.tx().execute(new Transaction.Closure<IOException>(){
+                        @Override
                         public void execute(Transaction tx) throws IOException {
                             StoredDestination sd = getStoredDestination(dest, tx);
                             sd.subscriptionCursors.remove(subscriptionKey);
@@ -442,11 +480,13 @@ public class TempKahaDBStore extends TempMessageDatabase implements PersistenceA
     String subscriptionKey(String clientId, String subscriptionName){
         return clientId+":"+subscriptionName;
     }
-    
+
+    @Override
     public MessageStore createQueueMessageStore(ActiveMQQueue destination) throws IOException {
         return new KahaDBMessageStore(destination);
     }
 
+    @Override
     public TopicMessageStore createTopicMessageStore(ActiveMQTopic destination) throws IOException {
         return new KahaDBTopicMessageStore(destination);
     }
@@ -457,6 +497,7 @@ public class TempKahaDBStore extends TempMessageDatabase implements PersistenceA
      *
      * @param destination Destination to forget
      */
+    @Override
     public void removeQueueMessageStore(ActiveMQQueue destination) {
     }
 
@@ -466,18 +507,22 @@ public class TempKahaDBStore extends TempMessageDatabase implements PersistenceA
      *
      * @param destination Destination to forget
      */
+    @Override
     public void removeTopicMessageStore(ActiveMQTopic destination) {
     }
 
+    @Override
     public void deleteAllMessages() throws IOException {
     }
-    
-    
+
+
+    @Override
     public Set<ActiveMQDestination> getDestinations() {
         try {
             final HashSet<ActiveMQDestination> rc = new HashSet<ActiveMQDestination>();
             synchronized(indexMutex) {
                 pageFile.tx().execute(new Transaction.Closure<IOException>(){
+                    @Override
                     public void execute(Transaction tx) throws IOException {
                         for (Iterator<Entry<String, StoredDestination>> iterator = destinations.iterator(tx); iterator.hasNext();) {
                             Entry<String, StoredDestination> entry = iterator.next();
@@ -491,11 +536,13 @@ public class TempKahaDBStore extends TempMessageDatabase implements PersistenceA
             throw new RuntimeException(e);
         }
     }
-    
+
+    @Override
     public long getLastMessageBrokerSequenceId() throws IOException {
         return 0;
     }
-    
+
+    @Override
     public long size() {
         if ( !started.get() ) {
             return 0;
@@ -507,32 +554,36 @@ public class TempKahaDBStore extends TempMessageDatabase implements PersistenceA
         }
     }
 
+    @Override
     public void beginTransaction(ConnectionContext context) throws IOException {
         throw new IOException("Not yet implemented.");
     }
+    @Override
     public void commitTransaction(ConnectionContext context) throws IOException {
         throw new IOException("Not yet implemented.");
     }
+    @Override
     public void rollbackTransaction(ConnectionContext context) throws IOException {
         throw new IOException("Not yet implemented.");
     }
-    
+
+    @Override
     public void checkpoint(boolean sync) throws IOException {
-    }    
+    }
 
     ///////////////////////////////////////////////////////////////////
     // Internal conversion methods.
     ///////////////////////////////////////////////////////////////////
-    
 
-    
+
+
     KahaLocation convert(Location location) {
         KahaLocation rc = new KahaLocation();
         rc.setLogId(location.getDataFileId());
         rc.setOffset(location.getOffset());
         return rc;
     }
-    
+
     KahaDestination convert(ActiveMQDestination dest) {
         KahaDestination rc = new KahaDestination();
         rc.setName(dest.getPhysicalName());
@@ -561,7 +612,7 @@ public class TempKahaDBStore extends TempMessageDatabase implements PersistenceA
         }
         int type = Integer.parseInt(dest.substring(0, p));
         String name = dest.substring(p+1);
-        
+
         switch( KahaDestination.DestinationType.valueOf(type) ) {
         case QUEUE:
             return new ActiveMQQueue(name);
@@ -571,11 +622,12 @@ public class TempKahaDBStore extends TempMessageDatabase implements PersistenceA
             return new ActiveMQTempQueue(name);
         case TEMP_TOPIC:
             return new ActiveMQTempTopic(name);
-        default:    
+        default:
             throw new IllegalArgumentException("Not in the valid destination format");
         }
     }
-    
+
+    @Override
     public long getLastProducerSequenceId(ProducerId id) {
         return -1;
     }
@@ -592,4 +644,8 @@ public class TempKahaDBStore extends TempMessageDatabase implements PersistenceA
         }
         super.load();
     }
+    @Override
+    public JobSchedulerStore createJobSchedulerStore() throws IOException, UnsupportedOperationException {
+        throw new UnsupportedOperationException();
+    }
 }

http://git-wip-us.apache.org/repos/asf/activemq/blob/fc244f48/activemq-kahadb-store/src/main/java/org/apache/activemq/store/kahadb/Visitor.java
----------------------------------------------------------------------
diff --git a/activemq-kahadb-store/src/main/java/org/apache/activemq/store/kahadb/Visitor.java b/activemq-kahadb-store/src/main/java/org/apache/activemq/store/kahadb/Visitor.java
index be4f2ff..43fc152 100644
--- a/activemq-kahadb-store/src/main/java/org/apache/activemq/store/kahadb/Visitor.java
+++ b/activemq-kahadb-store/src/main/java/org/apache/activemq/store/kahadb/Visitor.java
@@ -20,11 +20,16 @@ import java.io.IOException;
 
 import org.apache.activemq.store.kahadb.data.KahaAckMessageFileMapCommand;
 import org.apache.activemq.store.kahadb.data.KahaAddMessageCommand;
+import org.apache.activemq.store.kahadb.data.KahaAddScheduledJobCommand;
 import org.apache.activemq.store.kahadb.data.KahaCommitCommand;
+import org.apache.activemq.store.kahadb.data.KahaDestroySchedulerCommand;
 import org.apache.activemq.store.kahadb.data.KahaPrepareCommand;
 import org.apache.activemq.store.kahadb.data.KahaProducerAuditCommand;
 import org.apache.activemq.store.kahadb.data.KahaRemoveDestinationCommand;
 import org.apache.activemq.store.kahadb.data.KahaRemoveMessageCommand;
+import org.apache.activemq.store.kahadb.data.KahaRemoveScheduledJobCommand;
+import org.apache.activemq.store.kahadb.data.KahaRemoveScheduledJobsCommand;
+import org.apache.activemq.store.kahadb.data.KahaRescheduleJobCommand;
 import org.apache.activemq.store.kahadb.data.KahaRollbackCommand;
 import org.apache.activemq.store.kahadb.data.KahaSubscriptionCommand;
 import org.apache.activemq.store.kahadb.data.KahaTraceCommand;
@@ -62,6 +67,21 @@ public class Visitor {
     public void visit(KahaAckMessageFileMapCommand kahaProducerAuditCommand) throws IOException {
     }
 
+    public void visit(KahaAddScheduledJobCommand kahaAddScheduledJobCommand) throws IOException {
+    }
+
+    public void visit(KahaRescheduleJobCommand KahaRescheduleJobCommand) throws IOException {
+    }
+
+    public void visit(KahaRemoveScheduledJobCommand kahaRemoveScheduledJobCommand) throws IOException {
+    }
+
+    public void visit(KahaRemoveScheduledJobsCommand kahaRemoveScheduledJobsCommand) throws IOException {
+    }
+
+    public void visit(KahaDestroySchedulerCommand KahaDestroySchedulerCommand) throws IOException {
+    }
+
     public void visit(KahaUpdateMessageCommand kahaUpdateMessageCommand) throws IOException {
     }
 }

http://git-wip-us.apache.org/repos/asf/activemq/blob/fc244f48/activemq-kahadb-store/src/main/java/org/apache/activemq/store/kahadb/scheduler/JobImpl.java
----------------------------------------------------------------------
diff --git a/activemq-kahadb-store/src/main/java/org/apache/activemq/store/kahadb/scheduler/JobImpl.java b/activemq-kahadb-store/src/main/java/org/apache/activemq/store/kahadb/scheduler/JobImpl.java
index 86b9fa3..217bc1f 100644
--- a/activemq-kahadb-store/src/main/java/org/apache/activemq/store/kahadb/scheduler/JobImpl.java
+++ b/activemq-kahadb-store/src/main/java/org/apache/activemq/store/kahadb/scheduler/JobImpl.java
@@ -25,8 +25,8 @@ public class JobImpl implements Job {
     private final JobLocation jobLocation;
     private final byte[] payload;
 
-    protected JobImpl(JobLocation location,ByteSequence bs) {
-        this.jobLocation=location;
+    protected JobImpl(JobLocation location, ByteSequence bs) {
+        this.jobLocation = location;
         this.payload = new byte[bs.getLength()];
         System.arraycopy(bs.getData(), bs.getOffset(), this.payload, 0, bs.getLength());
     }
@@ -38,22 +38,22 @@ public class JobImpl implements Job {
 
     @Override
     public byte[] getPayload() {
-       return this.payload;
+        return this.payload;
     }
 
     @Override
     public long getPeriod() {
-       return this.jobLocation.getPeriod();
+        return this.jobLocation.getPeriod();
     }
 
     @Override
     public int getRepeat() {
-       return this.jobLocation.getRepeat();
+        return this.jobLocation.getRepeat();
     }
 
     @Override
     public long getStart() {
-       return this.jobLocation.getStartTime();
+        return this.jobLocation.getStartTime();
     }
 
     @Override
@@ -76,4 +76,13 @@ public class JobImpl implements Job {
         return JobSupport.getDateTime(getStart());
     }
 
+    @Override
+    public int getExecutionCount() {
+        return this.jobLocation.getRescheduledCount();
+    }
+
+    @Override
+    public String toString() {
+        return "Job: " + getJobId();
+    }
 }

http://git-wip-us.apache.org/repos/asf/activemq/blob/fc244f48/activemq-kahadb-store/src/main/java/org/apache/activemq/store/kahadb/scheduler/JobLocation.java
----------------------------------------------------------------------
diff --git a/activemq-kahadb-store/src/main/java/org/apache/activemq/store/kahadb/scheduler/JobLocation.java b/activemq-kahadb-store/src/main/java/org/apache/activemq/store/kahadb/scheduler/JobLocation.java
index 13cf376..cb66145 100644
--- a/activemq-kahadb-store/src/main/java/org/apache/activemq/store/kahadb/scheduler/JobLocation.java
+++ b/activemq-kahadb-store/src/main/java/org/apache/activemq/store/kahadb/scheduler/JobLocation.java
@@ -36,6 +36,8 @@ class JobLocation {
     private long period;
     private String cronEntry;
     private final Location location;
+    private int rescheduledCount;
+    private Location lastUpdate;
 
     public JobLocation(Location location) {
         this.location = location;
@@ -52,8 +54,12 @@ class JobLocation {
         this.delay = in.readLong();
         this.nextTime = in.readLong();
         this.period = in.readLong();
-        this.cronEntry=in.readUTF();
+        this.cronEntry = in.readUTF();
         this.location.readExternal(in);
+        if (in.readBoolean()) {
+            this.lastUpdate = new Location();
+            this.lastUpdate.readExternal(in);
+        }
     }
 
     public void writeExternal(DataOutput out) throws IOException {
@@ -63,11 +69,17 @@ class JobLocation {
         out.writeLong(this.delay);
         out.writeLong(this.nextTime);
         out.writeLong(this.period);
-        if (this.cronEntry==null) {
-            this.cronEntry="";
+        if (this.cronEntry == null) {
+            this.cronEntry = "";
         }
         out.writeUTF(this.cronEntry);
         this.location.writeExternal(out);
+        if (lastUpdate != null) {
+            out.writeBoolean(true);
+            this.lastUpdate.writeExternal(out);
+        } else {
+            out.writeBoolean(false);
+        }
     }
 
     /**
@@ -123,7 +135,8 @@ class JobLocation {
     }
 
     /**
-     * @param nextTime the nextTime to set
+     * @param nextTime
+     *            the nextTime to set
      */
     public synchronized void setNextTime(long nextTime) {
         this.nextTime = nextTime;
@@ -152,7 +165,8 @@ class JobLocation {
     }
 
     /**
-     * @param cronEntry the cronEntry to set
+     * @param cronEntry
+     *            the cronEntry to set
      */
     public synchronized void setCronEntry(String cronEntry) {
         this.cronEntry = cronEntry;
@@ -173,7 +187,8 @@ class JobLocation {
     }
 
     /**
-     * @param delay the delay to set
+     * @param delay
+     *            the delay to set
      */
     public void setDelay(long delay) {
         this.delay = delay;
@@ -186,15 +201,55 @@ class JobLocation {
         return this.location;
     }
 
+    /**
+     * @returns the location in the journal of the last update issued for this
+     *          Job.
+     */
+    public Location getLastUpdate() {
+        return this.lastUpdate;
+    }
+
+    /**
+     * Sets the location of the last update command written to the journal for
+     * this Job. The update commands set the next execution time for this job.
+     * We need to keep track of only the latest update as it's the only one we
+     * really need to recover the correct state from the journal.
+     *
+     * @param location
+     *            The location in the journal of the last update command.
+     */
+    public void setLastUpdate(Location location) {
+        this.lastUpdate = location;
+    }
+
+    /**
+     * @return the number of time this job has been rescheduled.
+     */
+    public int getRescheduledCount() {
+        return rescheduledCount;
+    }
+
+    /**
+     * Sets the number of time this job has been rescheduled.  A newly added job will return
+     * zero and increment this value each time a scheduled message is dispatched to its
+     * target destination and the job is rescheduled for another cycle.
+     *
+     * @param executionCount
+     *        the new execution count to assign the JobLocation.
+     */
+    public void setRescheduledCount(int rescheduledCount) {
+        this.rescheduledCount = rescheduledCount;
+    }
+
     @Override
     public String toString() {
-        return "Job [id=" + jobId + ", startTime=" + new Date(startTime)
-                + ", delay=" + delay + ", period=" + period + ", repeat="
-                + repeat + ", nextTime=" + new Date(nextTime) + "]";
+        return "Job [id=" + jobId + ", startTime=" + new Date(startTime) + ", delay=" + delay + ", period=" + period + ", repeat=" + repeat + ", nextTime="
+            + new Date(nextTime) + ", executionCount = " + (rescheduledCount + 1) + "]";
     }
 
     static class JobLocationMarshaller extends VariableMarshaller<List<JobLocation>> {
         static final JobLocationMarshaller INSTANCE = new JobLocationMarshaller();
+
         @Override
         public List<JobLocation> readPayload(DataInput dataIn) throws IOException {
             List<JobLocation> result = new ArrayList<JobLocation>();
@@ -228,6 +283,7 @@ class JobLocation {
         result = prime * result + (int) (period ^ (period >>> 32));
         result = prime * result + repeat;
         result = prime * result + (int) (startTime ^ (startTime >>> 32));
+        result = prime * result + (rescheduledCount ^ (rescheduledCount >>> 32));
         return result;
     }
 
@@ -286,6 +342,9 @@ class JobLocation {
         if (startTime != other.startTime) {
             return false;
         }
+        if (rescheduledCount != other.rescheduledCount) {
+            return false;
+        }
 
         return true;
     }