Merge pull request #6 from nuxeo/GERONIMO-6805

GERONIMO-6805: Use a time SPI abstraction to provide time to the transaction manager
diff --git a/geronimo-transaction/src/main/java/org/apache/geronimo/transaction/manager/CurrentTimeMsProvider.java b/geronimo-transaction/src/main/java/org/apache/geronimo/transaction/manager/CurrentTimeMsProvider.java
new file mode 100644
index 0000000..6b3426b
--- /dev/null
+++ b/geronimo-transaction/src/main/java/org/apache/geronimo/transaction/manager/CurrentTimeMsProvider.java
@@ -0,0 +1,32 @@
+/**
+ *  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.geronimo.transaction.manager;
+
+/**
+ * An abstraction of a time provider with millisecond resolution.
+ */
+@FunctionalInterface
+public interface CurrentTimeMsProvider {
+
+    /**
+     * Returns the current time, in milliseconds.
+     *
+     * @return the current time, in milliseconds
+     */
+    long now();
+
+}
diff --git a/geronimo-transaction/src/main/java/org/apache/geronimo/transaction/manager/SystemCurrentTime.java b/geronimo-transaction/src/main/java/org/apache/geronimo/transaction/manager/SystemCurrentTime.java
new file mode 100644
index 0000000..3d23e9f
--- /dev/null
+++ b/geronimo-transaction/src/main/java/org/apache/geronimo/transaction/manager/SystemCurrentTime.java
@@ -0,0 +1,38 @@
+/**
+ *  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.geronimo.transaction.manager;
+
+/**
+ * A time provider returning the standard {@link System#currentTimeMillis()}.
+ */
+public class SystemCurrentTime implements CurrentTimeMsProvider {
+
+    /**
+     * A time provider returning the standard {@link System#currentTimeMillis()}.
+     */
+    public static final CurrentTimeMsProvider INSTANCE = new SystemCurrentTime();
+
+    private SystemCurrentTime() {
+        // singleton
+    }
+
+    @Override
+    public long now() {
+        return System.currentTimeMillis();
+    }
+
+}
diff --git a/geronimo-transaction/src/main/java/org/apache/geronimo/transaction/manager/TransactionImpl.java b/geronimo-transaction/src/main/java/org/apache/geronimo/transaction/manager/TransactionImpl.java
index 0b8ad6b..e40c37a 100644
--- a/geronimo-transaction/src/main/java/org/apache/geronimo/transaction/manager/TransactionImpl.java
+++ b/geronimo-transaction/src/main/java/org/apache/geronimo/transaction/manager/TransactionImpl.java
@@ -50,6 +50,7 @@
 
     private final TransactionManagerImpl txManager;
     private final Xid xid;
+    private final CurrentTimeMsProvider timeProvider;
     private final long timeout;
     private final List<Synchronization> syncList = new ArrayList<Synchronization>(5);
     private final List<Synchronization> interposedSyncList = new ArrayList<Synchronization>(3);
@@ -66,10 +67,19 @@
         this(txManager.getXidFactory().createXid(), txManager, transactionTimeoutMilliseconds);
     }
 
+    TransactionImpl(TransactionManagerImpl txManager, long transactionTimeoutMilliseconds, CurrentTimeMsProvider timeProvider) throws SystemException {
+        this(txManager.getXidFactory().createXid(), txManager, transactionTimeoutMilliseconds, timeProvider);
+    }
+
     TransactionImpl(Xid xid, TransactionManagerImpl txManager, long transactionTimeoutMilliseconds) throws SystemException {
+        this(xid, txManager, transactionTimeoutMilliseconds, null);
+    }
+
+    TransactionImpl(Xid xid, TransactionManagerImpl txManager, long transactionTimeoutMilliseconds, CurrentTimeMsProvider timeProvider) throws SystemException {
         this.txManager = txManager;
         this.xid = xid;
-        this.timeout = transactionTimeoutMilliseconds + TransactionTimer.getCurrentTime();
+        this.timeProvider = timeProvider == null ? SystemCurrentTime.INSTANCE : timeProvider;
+        this.timeout = transactionTimeoutMilliseconds + this.timeProvider.now();
         try {
             txManager.getTransactionLog().begin(xid);
         } catch (LogException e) {
@@ -87,6 +97,7 @@
         this.txManager = txManager;
         this.xid = xid;
         status = Status.STATUS_PREPARED;
+        timeProvider = SystemCurrentTime.INSTANCE;
         //TODO is this a good idea?
         this.timeout = Long.MAX_VALUE;
     }
@@ -208,7 +219,7 @@
             Xid branchId = txManager.getXidFactory().createBranch(xid, resourceManagers.size() + 1);
             xaRes.start(branchId, XAResource.TMNOFLAGS);
             //Set the xaresource timeout in seconds to match the time left in this tx.
-            xaRes.setTransactionTimeout((int)(timeout - TransactionTimer.getCurrentTime())/1000);
+            xaRes.setTransactionTimeout((int)(timeout - timeProvider.now())/1000);
             activeXaResources.put(xaRes, addBranchXid(xaRes, branchId));
             return true;
         } catch (XAException e) {
@@ -262,7 +273,7 @@
         beforePrepare();
 
         try {
-            if (TransactionTimer.getCurrentTime() > timeout) {
+            if (timeProvider.now() > timeout) {
                 markRollbackCause(new Exception("Transaction has timed out"));
                 status = Status.STATUS_MARKED_ROLLBACK;
             }
diff --git a/geronimo-transaction/src/main/java/org/apache/geronimo/transaction/manager/TransactionManagerImpl.java b/geronimo-transaction/src/main/java/org/apache/geronimo/transaction/manager/TransactionManagerImpl.java
index 1db17f0..81c975f 100644
--- a/geronimo-transaction/src/main/java/org/apache/geronimo/transaction/manager/TransactionManagerImpl.java
+++ b/geronimo-transaction/src/main/java/org/apache/geronimo/transaction/manager/TransactionManagerImpl.java
@@ -60,6 +60,7 @@
     private final TransactionLog transactionLog;
     private final XidFactory xidFactory;
     private final int defaultTransactionTimeoutMilliseconds;
+    private final CurrentTimeMsProvider timeProvider;
     private final ThreadLocal<Long> transactionTimeoutMilliseconds = new ThreadLocal<Long>();
     private final ThreadLocal<Transaction> threadTx = new ThreadLocal<Transaction>();
     private final ConcurrentHashMap<Transaction, Thread> associatedTransactions = new ConcurrentHashMap<Transaction, Thread>();
@@ -95,10 +96,16 @@
     }
 
     public TransactionManagerImpl(int defaultTransactionTimeoutSeconds, XidFactory xidFactory, TransactionLog transactionLog) throws XAException {
+        this(defaultTransactionTimeoutSeconds, xidFactory, transactionLog, null);
+    }
+
+    public TransactionManagerImpl(int defaultTransactionTimeoutSeconds, XidFactory xidFactory,
+            TransactionLog transactionLog, CurrentTimeMsProvider timeProvider) throws XAException {
         if (defaultTransactionTimeoutSeconds <= 0) {
             throw new IllegalArgumentException("defaultTransactionTimeoutSeconds must be positive: attempted value: " + defaultTransactionTimeoutSeconds);
         }
         this.defaultTransactionTimeoutMilliseconds = defaultTransactionTimeoutSeconds * 1000;
+        this.timeProvider = timeProvider;
 
         if (transactionLog == null) {
             this.transactionLog = new UnrecoverableLog();
@@ -132,7 +139,7 @@
             fireThreadAssociated(tx);
             activeCount.getAndIncrement();
         }
-    } 
+    }
 
     private void unassociate() {
         Transaction tx = getTransaction();
@@ -168,7 +175,7 @@
         if (getStatus() != Status.STATUS_NO_TRANSACTION) {
             throw new NotSupportedException("Nested Transactions are not supported");
         }
-        TransactionImpl tx = new TransactionImpl(this, getTransactionTimeoutMilliseconds(transactionTimeoutMilliseconds));
+        TransactionImpl tx = new TransactionImpl(this, getTransactionTimeoutMilliseconds(transactionTimeoutMilliseconds), timeProvider);
 //        timeoutTimer.schedule(tx, getTransactionTimeoutMilliseconds(transactionTimeoutMilliseconds));
         try {
             associate(tx);
@@ -197,7 +204,7 @@
             if (!(tx instanceof TransactionImpl)) {
                 throw new InvalidTransactionException("Cannot resume foreign transaction: " + tx);
             }
-            
+
             associate((TransactionImpl) tx);
         }
     }
@@ -224,7 +231,7 @@
     }
 
     public Object getTransactionKey() {
-    	TransactionImpl tx = (TransactionImpl) getTransaction();
+        TransactionImpl tx = (TransactionImpl) getTransaction();
         return tx == null ? null: tx.getTransactionKey();
     }
 
@@ -286,7 +293,7 @@
         if (transactionTimeoutMilliseconds < 0) {
             throw new SystemException("transaction timeout must be positive or 0 to reset to default");
         }
-        return new TransactionImpl(xid, this, getTransactionTimeoutMilliseconds(transactionTimeoutMilliseconds));
+        return new TransactionImpl(xid, this, getTransactionTimeoutMilliseconds(transactionTimeoutMilliseconds), timeProvider);
     }
 
     public void commit(Transaction tx, boolean onePhase) throws XAException {
diff --git a/geronimo-transaction/src/main/java/org/apache/geronimo/transaction/manager/TransactionTimer.java b/geronimo-transaction/src/main/java/org/apache/geronimo/transaction/manager/TransactionTimer.java
deleted file mode 100644
index c26aad0..0000000
--- a/geronimo-transaction/src/main/java/org/apache/geronimo/transaction/manager/TransactionTimer.java
+++ /dev/null
@@ -1,64 +0,0 @@
-/**
- *  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.geronimo.transaction.manager;
-
-import java.security.AccessController;
-import java.security.PrivilegedAction;
-
-/**
- * TODO improve shutdown
- *
- * @version $Revision$ $Date$
- */
-public class TransactionTimer {
-    private static volatile long currentTime;
-
-    private static class CurrentTime extends Thread {
-        protected CurrentTime() {
-            currentTime = System.currentTimeMillis();
-            setContextClassLoader(null);
-        }
-
-        public void run() {
-            for (; ;) {
-                currentTime = System.currentTimeMillis();
-                try {
-                    Thread.sleep(1000);
-                } catch (InterruptedException e) {
-                    // Ignore exception
-                }
-            }
-        }
-    }
-
-    static {
-	AccessController.doPrivileged(new PrivilegedAction() {
-	    public Object run() {
-		CurrentTime tm = new CurrentTime();
-		tm.setDaemon(true);
-		tm.start();
-		return null;
-	    }
-	});
-    }
-
-    public static long getCurrentTime() {
-        return currentTime;
-    }
-
-}
diff --git a/geronimo-transaction/src/test/java/org/apache/geronimo/transaction/manager/TransactionManagerImplTest.java b/geronimo-transaction/src/test/java/org/apache/geronimo/transaction/manager/TransactionManagerImplTest.java
index c4e26a4..a4d632d 100644
--- a/geronimo-transaction/src/test/java/org/apache/geronimo/transaction/manager/TransactionManagerImplTest.java
+++ b/geronimo-transaction/src/test/java/org/apache/geronimo/transaction/manager/TransactionManagerImplTest.java
@@ -45,19 +45,29 @@
 
     TransactionManagerImpl tm;
 
+    long timeDelta;
+
     protected void setUp() throws Exception {
         tm = createTransactionManager();
+        timeDelta = 0;
     }
 
     private TransactionManagerImpl createTransactionManager() throws XAException {
         return new TransactionManagerImpl(10,
-                new XidFactoryImpl("WHAT DO WE CALL IT?".getBytes()), transactionLog);
+                new XidFactoryImpl("WHAT DO WE CALL IT?".getBytes()), transactionLog, new TestTimeProvider());
     }
 
     protected void tearDown() throws Exception {
         tm = null;
     }
 
+    public class TestTimeProvider implements CurrentTimeMsProvider {
+        @Override
+        public long now() {
+            return System.currentTimeMillis() + timeDelta;
+        }
+
+    }
     public void testNoResourcesCommit() throws Exception {
         assertEquals(Status.STATUS_NO_TRANSACTION, tm.getStatus());
         tm.begin();
@@ -312,8 +322,7 @@
         long timeout = tm.getTransactionTimeoutMilliseconds(0L);
         tm.setTransactionTimeout((int)timeout/4000);
         tm.begin();
-        System.out.println("Test to sleep for " + timeout + " millisecs");
-        Thread.sleep(timeout);
+        timeDelta += timeout;
         try
         {
             tm.commit();
@@ -325,34 +334,33 @@
 
         // Now test if the default timeout is active
         tm.begin();
-        System.out.println("Test to sleep for " + (timeout/2) + " millisecs");
-        Thread.sleep((timeout/2));
+        timeDelta += timeout/2;
         tm.commit();
         // Its a failure if exception occurs.
-    }    
-      
+    }
+
     // resume throws InvalidTransactionException on completed tx (via commit)
     public void testResume1() throws Exception {
         Transaction tx;
         assertEquals(Status.STATUS_NO_TRANSACTION, tm.getStatus());
-        tm.begin();   
+        tm.begin();
         assertEquals(Status.STATUS_ACTIVE, tm.getStatus());
         tx = tm.getTransaction();
         assertNotNull(tx);
         assertEquals(Status.STATUS_ACTIVE, tx.getStatus());
-        
+
         tm.commit();
         assertEquals(Status.STATUS_NO_TRANSACTION, tm.getStatus());
         assertNull(tm.getTransaction());
-        
+
         try {
             tm.resume(tx);
             fail();
         } catch (InvalidTransactionException e) {
             // expected
-        }        
+        }
     }
-    
+
     // resume throws InvalidTransactionException on completed tx (via rollback)
     public void testResume2() throws Exception {
         Transaction tx;
@@ -373,16 +381,16 @@
 
         tm.rollback();
         assertEquals(Status.STATUS_NO_TRANSACTION, tm.getStatus());
-        assertNull(tm.getTransaction());     
-        
+        assertNull(tm.getTransaction());
+
         try {
             tm.resume(tx);
             fail();
         } catch (InvalidTransactionException e) {
             // expected
-        }   
+        }
     }
-    
+
     // resume work on null tx
     public void testResume3() throws Exception {
         Transaction tx;
@@ -400,16 +408,16 @@
         // tx should be null
         tx = tm.suspend();
         assertNull(tx);
-        
+
         try {
             tm.resume(tx);
         } catch (InvalidTransactionException e) {
             // null is considered valid so we don't expect InvalidTransactionException here
             e.printStackTrace();
             fail();
-        }   
+        }
     }
-    
+
     // resume works on any valid tx
     public void testResume4() throws Exception {
         Transaction tx;
@@ -428,10 +436,10 @@
             // tx is considered valid so we don't expect InvalidTransactionException here
             e.printStackTrace();
             fail();
-        }   
+        }
 
         tm.commit();
         assertEquals(Status.STATUS_NO_TRANSACTION, tm.getStatus());
-        assertNull(tm.getTransaction()); 
+        assertNull(tm.getTransaction());
     }
 }