Index: server/src/main/org/jboss/ejb/txtimer/TimerRestoringTimerService.java
===================================================================
--- server/src/main/org/jboss/ejb/txtimer/TimerRestoringTimerService.java	(revision 0)
+++ server/src/main/org/jboss/ejb/txtimer/TimerRestoringTimerService.java	(revision 0)
@@ -0,0 +1,59 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2008, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.jboss.ejb.txtimer;
+
+import java.io.Serializable;
+import java.util.Date;
+
+import javax.ejb.EJBException;
+import javax.ejb.Timer;
+import javax.ejb.TimerService;
+
+/**
+ * {@link TimerService} implementation which is able to restore a persisted timer
+ *
+ * @author Jaikiran Pai
+ * @version $Revision: $
+ */
+public interface TimerRestoringTimerService extends PersistentIdTimerService
+{
+   /**
+    * Restores a timer from the state provided through the passed parameters.
+    * <p>
+    *   This involves scheduling any appropriate task(s), for the appropriate next timeout.
+    * </p>
+    * 
+    * @param initialExpiration The first expiry {@link Date} of the {@link Timer}, when it was created
+    * @param intervalDuration The repeat interval (in milli. seconds) for the timeouts  
+    * @param nextExpiry The next expiry {@link Date} of the timer. This need not be same as the <code>intialExpiration</code>.
+    *                   The next expiry date of a periodic timer keeps changing whereas the intial (first) expiry date remains constant.
+    * @param info The info to be passed to the timeout method
+    * @param timerId The id of the timer being restored
+    * @return Returns the restored timer
+    * @throws IllegalArgumentException
+    * @throws IllegalStateException
+    * @throws EJBException
+    */
+   public Timer restoreTimer(Date initialExpiration, long intervalDuration, Date nextExpiry, Serializable info, String timerId)
+   throws IllegalArgumentException, IllegalStateException, EJBException;
+
+}
Index: server/src/main/org/jboss/ejb/txtimer/TimerImpl.java
===================================================================
--- server/src/main/org/jboss/ejb/txtimer/TimerImpl.java	(revision 107043)
+++ server/src/main/org/jboss/ejb/txtimer/TimerImpl.java	(working copy)
@@ -98,6 +98,12 @@
    private int timerState;
    private Timer utilTimer;
    private int hashCode;
+   
+   /**
+    * Flag which indicates if one or more timeouts were missed due to (for example) server
+    * being down
+    */
+   private boolean missedTimeout;
 
    /**
     * Schedules the txtimer for execution at the specified time with a specified periode.
@@ -115,15 +121,59 @@
 
    void startTimer(Date firstTime, long periode)
    {
-      this.firstTime = firstTime;
-      this.nextExpire = firstTime.getTime();
+      this.startTimer(firstTime, firstTime, periode);
+
+   }
+   
+   /**
+    * Schedules any tasks for the timer, based on the passed <code>firstTimeout<code>,
+    * the <code>nextTimeout</code> and the <code>periode</code>
+    * 
+    * @param firstTimeout {@link Date} on which the first timeout of the timer occurs/occurred
+    * @param nextTimeout The {@link Date} on which the next timeout of the timer occurs. Can be null, in which case
+    *                   the <code>firstTimeout</code> is considered to be the next timeout. 
+    * @param periode The repeat interval of the timer
+    */
+   void startTimer(Date firstTimeout, Date nextTimeout, long periode)
+   {
+      this.firstTime = firstTimeout;
+      if (nextTimeout == null)
+      {
+         nextTimeout = firstTimeout;
+      }
+      this.nextExpire = nextTimeout.getTime();
       this.periode = periode;
+      
+      Date now = new Date();
+      // if the next timeout points to a time in the past, then 
+      // it means that the server was either down or the timeout wasn't fired for
+      // some other reason. As per the EJB spec, we should fire the timeout
+      // for the *missed* timeout atleast once. Here we create a single action
+      // timeout which will fire immediately. At the same time we also (re)compute the 
+      // actual next timeout *from now* and create a periodic timer task for the same
+      if (nextTimeout.before(now))
+      {
+         long next = nextTimeout.getTime();
+         long current = now.getTime(); 
+         // recompute the next timeout based on the current time
+         while (next < current)
+         {
+            next += periode;
+         }
+         this.nextExpire = next;
+         // persist the recomputed next timeout
+         this.persistNextTimeout();
+         // set the flag which indicates that this timer has missed one (or more) timeouts.
+         // This flag will later be used for firing a single action (backlog) timer
+         this.missedTimeout = true;
+      }
 
       timerService.addTimer(this);
       registerTimerWithTx();
       
       // the timer will actually go ACTIVE on tx commit
       startInTx();
+
    }
 
    public String getTimerId()
@@ -371,6 +421,13 @@
       else
       {
          setTimerState(ACTIVE);
+         // if some timeouts were missed, then trigger a single action
+         // timer task for the backlog 
+         if (this.missedTimeout)
+         {
+            this.triggerBacklogTimeoutNow();
+         }
+         // schedule the regular timeouts
          scheduleTimeout();
       }
    }
@@ -386,12 +443,26 @@
    private void scheduleTimeout()
    {
       if (periode > 0)
+      {
+         // schedule the periodic timer task
          utilTimer.schedule(new TimerTaskImpl(this), new Date(nextExpire), periode);
+      }
       else
-         utilTimer.schedule(new TimerTaskImpl(this), new Date(nextExpire));      
+      {
+         utilTimer.schedule(new TimerTaskImpl(this), new Date(nextExpire));
+      }
    }
    
    /**
+    * Schedules a timer task to fire once, immediately. Used for
+    * triggering a task for backlog timeouts (i.e. timeouts which were missed)
+    */
+   private void triggerBacklogTimeoutNow()
+   {
+      utilTimer.schedule(new TimerTaskImpl(this, true), new Date());
+   }
+   
+   /**
     * Throws NoSuchObjectLocalException if the txtimer was canceled or has expired
     */
    private void assertTimedOut()
@@ -459,6 +530,13 @@
          switch (timerState)
          {
             case STARTED_IN_TX:
+               // if some timeouts were missed, then trigger a single action
+               // timer task for the backlog 
+               if (this.missedTimeout)
+               {
+                  this.triggerBacklogTimeoutNow();
+               }
+               // schedule the regular timeouts
                scheduleTimeout();
                setTimerState(ACTIVE);
                break;
@@ -525,10 +603,30 @@
    {
       private TimerImpl timer;
 
+      /**
+       * A backlog indicates that this {@link TimerTaskImpl} was fired
+       * for a timeout which occurred in the past (for example, the timeout occurred when the server
+       * was down and this timer task was fired to account for that)
+       */
+      private boolean backlog;
+      
       public TimerTaskImpl(TimerImpl timer)
       {
          this.timer = timer;
       }
+      
+      /**
+       * 
+       * @param timer The timer instance
+       * @param backlog True if this {@link TimerTaskImpl} was fired for a timeout which occurred in the past
+       *                (for example, the timeout occurred when the server
+       *                    was down and this timer task was fired to account for that)
+       */
+      public TimerTaskImpl(TimerImpl timer, boolean backlog)
+      {
+         this.timer = timer;
+         this.backlog = backlog;
+      }
 
       /**
        * The action to be performed by this txtimer task.
@@ -538,11 +636,15 @@
          log.debug("run: " + timer);
 
          // Set next scheduled execution attempt. This is used only
-         // for reporting (getTimeRemaining()/getNextTimeout())
+         // for reporting (getTimeRemaining()/getNextTimeout()) and for persisting
          // and not from the underlying jdk timer implementation.
-         if (isActive() && periode > 0)
+         // If it's a backlog task, then do not change the next timeout, since 
+         // a backlog task is "fire only once" task.
+         if (isActive() && periode > 0 && !this.backlog)
          {
             nextExpire += periode;
+            
+            TimerImpl.this.persistNextTimeout();
          }
          
          // If a retry thread is in progress, we don't want to allow another
@@ -583,4 +685,16 @@
          }
       }
    }
+   
+   /**
+    * Persist the next timeout of the timer to the persistence store
+    */
+   private void persistNextTimeout()
+   {
+      PersistencePolicy persistencePolicy = this.timerService.getPersistencePolicy();
+      if (persistencePolicy != null && persistencePolicy instanceof PersistencePolicyExt)
+      {
+         ((PersistencePolicyExt) persistencePolicy).updateNextTimeout(this.timerId, this.timedObjectId, new Date(nextExpire));
+      }
+   }
 }
Index: server/src/main/org/jboss/ejb/txtimer/EJBTimerServiceImpl.java
===================================================================
--- server/src/main/org/jboss/ejb/txtimer/EJBTimerServiceImpl.java	(revision 107043)
+++ server/src/main/org/jboss/ejb/txtimer/EJBTimerServiceImpl.java	(working copy)
@@ -23,19 +23,20 @@
 
 // $Id$
 
+import java.io.Serializable;
 import java.lang.reflect.Constructor;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.Date;
 import java.util.HashMap;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
-import java.util.Date;
-import java.io.Serializable;
 
+import javax.ejb.EJBException;
+import javax.ejb.Timer;
 import javax.ejb.TimerService;
-import javax.ejb.Timer;
-import javax.ejb.EJBException;
+import javax.management.ObjectInstance;
 import javax.management.ObjectName;
 import javax.transaction.TransactionManager;
 
@@ -241,7 +242,16 @@
       // Get a proxy to the persistence policy
       try
       {
-         persistencePolicy = (PersistencePolicy)MBeanProxyExt.create(PersistencePolicy.class, persistencePolicyName, server);
+         // JBPAPP-4681
+         if (this.isPersistencePolicyExt(persistencePolicyName))
+         {
+            persistencePolicy = (PersistencePolicy)MBeanProxyExt.create(PersistencePolicyExt.class, persistencePolicyName, server);  
+         }
+         else
+         {
+            persistencePolicy = (PersistencePolicy)MBeanProxyExt.create(PersistencePolicy.class, persistencePolicyName, server);   
+         }
+         
       }
       catch (Exception e)
       {
@@ -249,6 +259,9 @@
          persistencePolicy = new NoopPersistencePolicy();
       }
 
+      
+      
+      
       // Get the timerId generator
       try
       {
@@ -478,8 +491,14 @@
                ContainerMBean container = (ContainerMBean)MBeanProxyExt.create(ContainerMBean.class, containerId, server);               
                TimerService timerService = container.getTimerService(targetId.getInstancePk());
 
+               // JBPAPP-4681
+               if (timerService instanceof TimerRestoringTimerService)
+               {
+                  TimerRestoringTimerService timerRestoringTimerService = (TimerRestoringTimerService) timerService;
+                  timerRestoringTimerService.restoreTimer(handle.getFirstTime(), handle.getPeriode(), handle.getNextTimeout(), handle.getInfo(), handle.getTimerId());
+               }
                // Fix for JBPAPP-3926
-               if (timerService instanceof PersistentIdTimerService)
+               else if (timerService instanceof PersistentIdTimerService)
                {
                   PersistentIdTimerService persistentIdTimerService = (PersistentIdTimerService) timerService;
                   persistentIdTimerService.createTimer(handle.getFirstTime(), handle.getPeriode(), handle.getInfo(), handle.getTimerId());
@@ -530,4 +549,46 @@
       }
       return retBuffer.toString();
    } 
+   
+   /**
+    * Returns true if the {@link Class} which backs the MBean instance represented by the
+    * passed <code>persistencePolicyObjectName</code> is of type {@link PersistencePolicyExt}.
+    * Else returns false
+    * 
+    * @param persistencePolicyObjectName The {@link ObjectName} of the persistence policy MBean
+    * @return
+    */
+   // JBPAPP-4681
+   private boolean isPersistencePolicyExt(ObjectName persistencePolicyObjectName)
+   {
+      if (persistencePolicyObjectName == null)
+      {
+         return false;
+      }
+      try
+      {
+         // get the MBean ObjectInstance from the MBean server
+         ObjectInstance persistencePolicyMBeanInstance = this.server.getObjectInstance(persistencePolicyObjectName);
+         // get hold of the class name
+         String persistencePolicyInstanceClassName = persistencePolicyMBeanInstance.getClassName();
+
+         // load the persistence policy class
+         ClassLoader tccl = Thread.currentThread().getContextClassLoader();
+         Class<?> persistencePolicyClass = tccl.loadClass(persistencePolicyInstanceClassName);
+         // check whether it's of type PersistencePolicyExt
+         if (PersistencePolicyExt.class.isAssignableFrom(persistencePolicyClass))
+         {
+            return true;
+         }
+      }
+      catch (Exception e)
+      {
+         // ignore
+         log.debug("Could not determine whether: " + persistencePolicyObjectName + " implements "
+               + PersistencePolicyExt.class + " - will assume that it doesn't");
+         return false;
+      }
+      
+      return false;
+   }
 }
Index: server/src/main/org/jboss/ejb/txtimer/GeneralPurposeDatabasePersistencePlugin.java
===================================================================
--- server/src/main/org/jboss/ejb/txtimer/GeneralPurposeDatabasePersistencePlugin.java	(revision 107043)
+++ server/src/main/org/jboss/ejb/txtimer/GeneralPurposeDatabasePersistencePlugin.java	(working copy)
@@ -63,7 +63,7 @@
  * @version $Revision$
  * @since 23-Sep-2004
  */
-public class GeneralPurposeDatabasePersistencePlugin implements DatabasePersistencePluginExt
+public class GeneralPurposeDatabasePersistencePlugin implements UpdateableDatabasePersistencePlugin
 {
    /** logging support */
    private static Logger log = Logger.getLogger(GeneralPurposeDatabasePersistencePlugin.class);
@@ -152,6 +152,7 @@
                     " " + getColumnTimerID() + " varchar(80) not null," +
                     " " + getColumnTargetID() + " varchar(250) not null," +
                     " " + getColumnInitialDate() + " " + dateType + " not null," +
+                    " " + getColumnNextDate() + " " + dateType + "," +
                     " " + getColumnTimerInterval() + " " + longType + "," +
                     " " + getColumnInstancePK() + " " + objectType + "," +
                     " " + getColumnInfo() + " " + objectType + ", ");
@@ -174,6 +175,27 @@
             st = con.createStatement();
             st.executeUpdate(createTableDDL.toString());
          }
+         else if (this.isNextDateColumnPresent() == false) // the timer table exists but the next timeout date column is absent, so create the column
+         {
+        	 // JBPAPP-4681 https://jira.jboss.org/browse/JBPAPP-4681 introduces a new column
+        	 // in the table. Here, we make sure to create the column in already existing DBs
+        	 
+        	 con = ds.getConnection();
+
+             String dateType = typeMapping.getTypeMappingMetaData(Timestamp.class).getSqlType();
+
+             // The alter table DDL
+             StringBuffer alterTableDDL = new StringBuffer("alter table " + getTableName() + " ADD " +
+                     " " + getColumnNextDate() + " " + dateType);
+
+				log.debug("Adding new column " + getColumnNextDate()
+						+ " to table " + getTableName() + " - executing DDL: "
+						+ alterTableDDL);
+
+             st = con.createStatement();
+             st.executeUpdate(alterTableDDL.toString());
+        	 
+         }
       }
       catch (SQLException e)
       {
@@ -201,8 +223,8 @@
          con = ds.getConnection();
 
          String sql = "insert into " + getTableName() + " " +
-                 "(" + getColumnTimerID() + "," + getColumnTargetID() + "," + getColumnInitialDate() + "," + getColumnTimerInterval() + "," + getColumnInstancePK() + "," + getColumnInfo() + ") " +
-                 "values (?,?,?,?,?,?)";
+                 "(" + getColumnTimerID() + "," + getColumnTargetID() + "," + getColumnInitialDate() + "," + getColumnTimerInterval() + "," + getColumnInstancePK() + "," + getColumnInfo() + ","  + getColumnNextDate() + ") " +
+                 "values (?,?,?,?,?,?,?)";
          st = con.prepareStatement(sql);
 
          st.setString(1, timerId);
@@ -229,7 +251,10 @@
          {
             st.setBytes(6, bytes);
          }
-
+         // set the next timeout date, which when the timer is being created, is equal to the initial
+         // date of expiry.
+         st.setTimestamp(7, new Timestamp(initialExpiration.getTime()));
+         
          int rows = st.executeUpdate();
          if (rows != 1)
             log.error("Unable to insert timer for: " + timedObjectId);
@@ -267,6 +292,7 @@
             if (containerId == null || containerId.equals(targetId.getContainerId()))
             {
                Date initialDate = rs.getTimestamp(getColumnInitialDate());
+               Date nextTimeout = rs.getTimestamp(getColumnNextDate());
                long interval = rs.getLong(getColumnTimerInterval());
                Serializable pKey = (Serializable)deserialize(rs.getBytes(getColumnInstancePK()));
                Serializable info = null;
@@ -283,7 +309,7 @@
                }
                // is this really needed? targetId encapsulates pKey as well!
                targetId = new TimedObjectId(targetId.getContainerId(), pKey);
-               TimerHandleImpl handle = new TimerHandleImpl(timerId, targetId, initialDate, interval, info);
+               TimerHandleImpl handle = new TimerHandleImpl(timerId, targetId, initialDate, nextTimeout, interval, info);
                list.add(handle);
             }
          }
@@ -355,7 +381,79 @@
          JDBCUtil.safeClose(con);
       }
    }
+   
+   /**
+    * {@inheritDoc}
+    * @throws NullPointerException If either <code>timerId</code> or <code>timedObjectId</code> or both are null
+    */
+   @Override
+   public void updateNextTimeout(String timerId, TimedObjectId timedObjectId, Date nextTimeout) throws SQLException
+   {
+      if (timerId == null || timedObjectId == null)
+      {
+         throw new NullPointerException();
+      }
+      
+      log.debug("Updating next timeout date to " + nextTimeout + " for timer: " + timerId + " ,timedObjectId: "
+            + timedObjectId.toString());
+      
+      Connection con = null;
+      PreparedStatement st = null;
 
+      try
+      {
+         con = ds.getConnection();
+
+         // update the next timeout date column value
+         String sql = "update " + getTableName() +
+         " " + " SET " + getColumnNextDate() + "=?" +
+         " where " + getColumnTimerID() + "=? and " + getColumnTargetID() + "=?";
+         
+         st = con.prepareStatement(sql);
+
+         if (nextTimeout == null)
+         {
+            JDBCTypeMappingMetaData typeMapping = (JDBCTypeMappingMetaData)server.getAttribute(metaDataName, "TypeMappingMetaData");
+            if (typeMapping == null)
+            {
+               throw new IllegalStateException("Cannot obtain type mapping from: " + metaDataName);
+            }
+            int dateType = typeMapping.getTypeMappingMetaData(Timestamp.class).getJdbcType();
+            // set the next timeout as null
+            st.setNull(1, dateType);
+         }
+         else
+         {
+            st.setTimestamp(1, new Timestamp(nextTimeout.getTime()));
+         }
+         st.setString(2, timerId);
+         st.setString(3, timedObjectId.toString());
+
+         // run the update sql
+         int rows = st.executeUpdate();
+         
+         if (rows != 1)
+         {
+            log.debug("Unexpected update row count: " + rows + " for timer: " + timerId + " timedObjectId: "
+                  + timedObjectId.toString());
+         }
+      }
+      catch (SQLException sqle)
+      {
+         throw sqle;
+      }
+      catch (Exception e)
+      {
+         log.error("Could not update next timeout date for timerId: " + timerId + " timedObjectId: "
+               + timedObjectId.toString(), e);
+      }
+      finally
+      {
+         JDBCUtil.safeClose(st);
+         JDBCUtil.safeClose(con);
+      }
+   }
+
    /** Get the timer table name */
    public String getTableName()
    {
@@ -380,6 +478,12 @@
       return "INITIALDATE";
    }
 
+   /** Returns the column name of the next timeout date */
+   public String getColumnNextDate()
+   {
+	   return "NEXTDATE";
+   }
+   
    /** Get the timer interval column name */
    public String getColumnTimerInterval()
    {
@@ -463,5 +567,49 @@
          return null;
       }
    }
+   
+   /**
+    * Returns true if the timer table already contains the next timeout date column (represented
+    * by {@link #getColumnNextDate()}). Else returns false.
+    * @return
+    */
+   // JBPAPP-4681
+   private boolean isNextDateColumnPresent()
+   {
+      // Just fire a query on the timer table with the next timeout date column in the
+      // select clause. If the query fails, then the column in considered absent.
+      // I don't like this implementation, but this is the simplest. And since this method
+      // is only there to take care of already existing Timer table, this impl should be OK for now
+      Connection con = null;
+      PreparedStatement st = null;
+      try
+      {
+         con = ds.getConnection();
+
+         String sql = "select " + getColumnNextDate() + " from " + getTableName() + " where " + getColumnTimerID()
+               + "=?";
+
+         st = con.prepareStatement(sql);
+         st.setString(1, "");
+
+         st.executeQuery();
+         return true;
+
+      }
+      catch (SQLException sqle)
+      {
+         // consider any sqlexception as an indication of column absence.
+         // I don't like this, but this is the simplest way to figure out the presence of the 
+         // column, instead of having to go via the DatabaseMetaData JDBC API. 
+         // After all, this method comes into picture only when the timer table is already present
+         // (i.e. on existing deployments)
+         return false;
+      }
+      finally
+      {
+         JDBCUtil.safeClose(st);
+         JDBCUtil.safeClose(con);
+      }
+   }
 }
 
Index: server/src/main/org/jboss/ejb/txtimer/OracleDatabasePersistencePlugin.java
===================================================================
--- server/src/main/org/jboss/ejb/txtimer/OracleDatabasePersistencePlugin.java	(revision 107043)
+++ server/src/main/org/jboss/ejb/txtimer/OracleDatabasePersistencePlugin.java	(working copy)
@@ -75,8 +75,8 @@
          String sql = "insert into " + getTableName() + " " +
                  "(" + getColumnTimerID() + "," + getColumnTargetID() +
                  "," + getColumnInitialDate() + "," + getColumnTimerInterval() +
-                 "," + getColumnInstancePK() + "," + getColumnInfo() + ") " +
-                 "values (?,?,?,?,?,?)";
+                 "," + getColumnInstancePK() + "," + getColumnInfo() + "," + getColumnNextDate() +  ") " +
+                 "values (?,?,?,?,?,?,?)";
          st = con.prepareStatement(sql);
 
          st.setString(1, timerId);
@@ -105,6 +105,9 @@
          {
             st.setBytes(6, null);
          }
+         // set the next timeout date, which when the timer is being created, is equal to the initial
+         // date of expiry.
+         st.setTimestamp(7, new Timestamp(initialExpiration.getTime()));
 
          int rows = st.executeUpdate();
          if (rows != 1)
@@ -145,6 +148,8 @@
             if (containerId == null || containerId.equals(targetId.getContainerId()))
             {
                Date initialDate = rs.getTimestamp(getColumnInitialDate());
+               Date nextTimeout = rs.getTimestamp(getColumnNextDate());
+               
                long interval = rs.getLong(getColumnTimerInterval());
                
                InputStream isPk = rs.getBinaryStream(getColumnInstancePK());
@@ -164,7 +169,7 @@
                }
                // is this really needed? targetId encapsulates pKey as well!
                targetId = new TimedObjectId(targetId.getContainerId(), pKey);
-               TimerHandleImpl handle = new TimerHandleImpl(timerId, targetId, initialDate, interval, info);
+               TimerHandleImpl handle = new TimerHandleImpl(timerId, targetId, initialDate, nextTimeout, interval, info);
                
                list.add(handle);
             }
Index: server/src/main/org/jboss/ejb/txtimer/TimerHandleImpl.java
===================================================================
--- server/src/main/org/jboss/ejb/txtimer/TimerHandleImpl.java	(revision 107043)
+++ server/src/main/org/jboss/ejb/txtimer/TimerHandleImpl.java	(working copy)
@@ -23,17 +23,18 @@
 
 // $Id$
 
-import javax.ejb.EJBException;
-import javax.ejb.NoSuchObjectLocalException;
-import javax.ejb.Timer;
-import javax.ejb.TimerHandle;
-import javax.management.ObjectName;
 import java.io.Serializable;
 import java.text.ParseException;
 import java.text.SimpleDateFormat;
 import java.util.Date;
 import java.util.StringTokenizer;
 
+import javax.ejb.EJBException;
+import javax.ejb.NoSuchObjectLocalException;
+import javax.ejb.Timer;
+import javax.ejb.TimerHandle;
+import javax.management.ObjectName;
+
 /**
  * An implementation of the TimerHandle
  *
@@ -52,6 +53,7 @@
    private String timerId;
    private TimedObjectId timedObjectId;
    private Date firstTime;
+   private Date nextTimeout;
    private long periode;
    private Serializable info;
    private int hashCode;
@@ -64,13 +66,16 @@
       timerId = timer.getTimerId();
       timedObjectId = timer.getTimedObjectId();
       firstTime = timer.getFirstTime();
+      nextTimeout = new Date(timer.getNextExpire());
       periode = timer.getPeriode();
       info = timer.getInfoInternal();
    }
 
    /**
     * Construct a handle from individual parameters
+    * @deprecated Use {@link #TimerHandleImpl(String, TimedObjectId, Date, Date, long, Serializable)}
     */
+   @Deprecated
    TimerHandleImpl(String timerId, TimedObjectId timedObjectId, Date firstTime, long periode, Serializable info)
    {
       this.timerId = timerId;
@@ -79,6 +84,25 @@
       this.periode = periode;
       this.info = info;
    }
+   
+   /**
+    * Constructs an timer handle from the passed parameters
+    * @param timerId
+    * @param timedObjectId
+    * @param firstTime
+    * @param nextTimeout
+    * @param periode
+    * @param info
+    */
+   TimerHandleImpl(String timerId, TimedObjectId timedObjectId, Date firstTime, Date nextTimeout, long periode, Serializable info)
+   {
+      this.timerId = timerId;
+      this.timedObjectId = timedObjectId;
+      this.firstTime = firstTime;
+      this.nextTimeout = nextTimeout;
+      this.periode = periode;
+      this.info = info;
+   }
 
    /**
     * Construct a handle from external form
@@ -177,6 +201,11 @@
    {
       return info;
    }
+   
+   public Date getNextTimeout()
+   {
+      return this.nextTimeout;
+   }
 
    /**
     * Obtain a reference to the txtimer represented by this handle.
Index: server/src/main/org/jboss/ejb/txtimer/UpdateableDatabasePersistencePlugin.java
===================================================================
--- server/src/main/org/jboss/ejb/txtimer/UpdateableDatabasePersistencePlugin.java	(revision 0)
+++ server/src/main/org/jboss/ejb/txtimer/UpdateableDatabasePersistencePlugin.java	(revision 0)
@@ -0,0 +1,45 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2008, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.jboss.ejb.txtimer;
+
+import java.sql.SQLException;
+import java.util.Date;
+
+/**
+ * Extension to {@link DatabasePersistencePluginExt} which allows for updating the next timeout
+ * date of an existing timer
+ *
+ * @author Jaikiran Pai
+ * @version $Revision: $
+ */
+public interface UpdateableDatabasePersistencePlugin extends DatabasePersistencePluginExt
+{
+
+   /**
+    * Updates the next timeout date of a timer, in the database
+    *
+    * @param timerId The timer id
+    * @param timedObjectId The id of the timed object
+    */
+   void updateNextTimeout(String timerId, TimedObjectId timedObjectId, Date nextTimeout) throws SQLException;
+
+}
Index: server/src/main/org/jboss/ejb/txtimer/TimerServiceImpl.java
===================================================================
--- server/src/main/org/jboss/ejb/txtimer/TimerServiceImpl.java	(revision 107043)
+++ server/src/main/org/jboss/ejb/txtimer/TimerServiceImpl.java	(working copy)
@@ -34,7 +34,6 @@
 import javax.ejb.EJBException;
 import javax.ejb.Timer;
 import javax.ejb.TimerHandle;
-import javax.ejb.TimerService;
 import javax.transaction.SystemException;
 import javax.transaction.Transaction;
 import javax.transaction.TransactionManager;
@@ -53,7 +52,7 @@
  * @version $Revision$
  * @since 07-Apr-2004
  */
-public class TimerServiceImpl implements PersistentIdTimerService
+public class TimerServiceImpl implements TimerRestoringTimerService
 {
    // logging support
    private static Logger log = Logger.getLogger(TimerServiceImpl.class);
@@ -322,6 +321,42 @@
       return activeTimers;
    }
    
+   /**
+    * {@inheritDoc}
+    */
+   @Override
+   public Timer restoreTimer(Date initialExpiration, long intervalDuration, Date nextExpiry, Serializable info,
+         String timerId) throws IllegalArgumentException, IllegalStateException, EJBException
+   {
+      if (initialExpiration == null)
+         throw new IllegalArgumentException("initial expiration is null");
+      if (intervalDuration < 0)
+         throw new IllegalArgumentException("interval duration is negative");
+      if (timerId == null)
+         throw new IllegalArgumentException("timerId is null");
+
+      try
+      {
+         TimerImpl timer = new TimerImpl(this, timerId, timedObjectId, timedObjectInvoker, info);
+         // store the timer info
+         persistencePolicy.insertTimer(timerId, timedObjectId, initialExpiration, intervalDuration, info);
+         // additionally, persist the next timeout too (can't store this next timeout date
+         // through the insert API, because it doesn't allow a way to pass the next timeout date). So
+         // this additional update
+         if (persistencePolicy instanceof PersistencePolicyExt)
+         {
+            ((PersistencePolicyExt) persistencePolicy).updateNextTimeout(timerId, timedObjectId, nextExpiry);
+         }
+         // now start the timer
+         timer.startTimer(initialExpiration, nextExpiry, intervalDuration);
+         return timer;
+      }
+      catch (Exception e)
+      {
+         throw new EJBException("Failed to restore timer", e);
+      }
+   }
+   
    // Package protected ---------------------------------------------
    
    /**
@@ -374,4 +409,9 @@
          log.error("Retry timeout failed for timer: " + txtimer, e);
       }
    }
+   
+   PersistencePolicy getPersistencePolicy()
+   {
+      return this.persistencePolicy;
+   }
 }
Index: server/src/main/org/jboss/ejb/txtimer/PersistencePolicyExt.java
===================================================================
--- server/src/main/org/jboss/ejb/txtimer/PersistencePolicyExt.java	(revision 0)
+++ server/src/main/org/jboss/ejb/txtimer/PersistencePolicyExt.java	(revision 0)
@@ -0,0 +1,43 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2008, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.jboss.ejb.txtimer;
+
+import java.util.Date;
+
+/**
+ * An extension of {@link PersistencePolicy} that allows updating the next timeout
+ * date of an existing timer
+ *
+ * @author Jaikiran Pai
+ * @version $Revision: $
+ */
+public interface PersistencePolicyExt extends PersistencePolicy
+{
+
+   /**
+    * Updates the next timeout date of a timer, in the persistence store
+    *
+    * @param timerId The timer id
+    * @param timedObjectId The id of the timed object
+    */
+   void updateNextTimeout(String timerId, TimedObjectId timedObjectId, Date nextTimeout);
+}
Index: server/src/main/org/jboss/ejb/txtimer/DatabasePersistencePolicy.java
===================================================================
--- server/src/main/org/jboss/ejb/txtimer/DatabasePersistencePolicy.java	(revision 107043)
+++ server/src/main/org/jboss/ejb/txtimer/DatabasePersistencePolicy.java	(working copy)
@@ -238,6 +238,27 @@
          log.warn("Unable to clear timers", e);
       }
    }
+   
+   /**
+    * {@inheritDoc}
+    */
+   @Override
+   public void updateNextTimeout(String timerId, TimedObjectId timedObjectId, Date nextTimeout)
+   {
+      try
+      {
+         if (this.dbpPlugin instanceof UpdateableDatabasePersistencePlugin)
+         {
+            ((UpdateableDatabasePersistencePlugin) this.dbpPlugin).updateNextTimeout(timerId, timedObjectId, nextTimeout);
+         }
+      }
+      catch (SQLException sqle)
+      {
+         log.error("Could not update the next timeout date for timer: " + timerId + " timedObjectId: " + timedObjectId,
+               sqle);
+      }
+      
+   }
 
    /** Re-read the current persistent timers list, clear the db of timers,
     * and restore the timers.
Index: server/src/main/org/jboss/ejb/txtimer/DatabasePersistencePolicyMBean.java
===================================================================
--- server/src/main/org/jboss/ejb/txtimer/DatabasePersistencePolicyMBean.java	(revision 107043)
+++ server/src/main/org/jboss/ejb/txtimer/DatabasePersistencePolicyMBean.java	(working copy)
@@ -36,7 +36,7 @@
  * @version $Revision$
  * @since 09-Sep-2004
  */
-public interface DatabasePersistencePolicyMBean extends ServiceMBean, PersistencePolicy
+public interface DatabasePersistencePolicyMBean extends ServiceMBean, PersistencePolicyExt
 {
    /** The default object name */
    ObjectName OBJECT_NAME = ObjectNameFactory.create("jboss.ejb:service=EJBTimerService,persistencePolicy=database");
Index: ejb3/src/main/org/jboss/as/ejb3/timerservice/TimerServiceFacade.java
===================================================================
--- ejb3/src/main/org/jboss/as/ejb3/timerservice/TimerServiceFacade.java	(revision 107043)
+++ ejb3/src/main/org/jboss/as/ejb3/timerservice/TimerServiceFacade.java	(working copy)
@@ -34,12 +34,11 @@
 import javax.ejb.TimerService;
 import javax.management.ObjectName;
 
+import org.jboss.ejb.AllowedOperationsAssociation;
+import org.jboss.ejb.txtimer.PersistentIdTimerService;
+import org.jboss.ejb.txtimer.TimerRestoringTimerService;
 import org.jboss.logging.Logger;
 
-import org.jboss.ejb.txtimer.PersistentIdTimerService;
-
-import org.jboss.ejb.AllowedOperationsAssociation;
-
 /**
  * Holds the association with the container, without exposing it.
  *
@@ -48,7 +47,7 @@
  * 
  * @version $Revision$
  */
-public class TimerServiceFacade implements PersistentIdTimerService
+public class TimerServiceFacade implements TimerRestoringTimerService
 {
    private static Logger log = Logger.getLogger(TimerServiceFacade.class);
    
@@ -86,6 +85,7 @@
    }
    
    // JBPAPP-3926
+   @Override
    public Timer createTimer(Date initialExpiration, long intervalDuration, Serializable info, String timerId) throws IllegalArgumentException, IllegalStateException, EJBException
    {
 	  if (delegate instanceof PersistentIdTimerService)
@@ -99,6 +99,28 @@
 		  return delegate.createTimer(initialExpiration, intervalDuration, info);
 	  }
    }
+   
+   /**
+    * {@inheritDoc}
+    */
+   @Override
+   public Timer restoreTimer(Date initialExpiration, long intervalDuration, Date nextExpiry, Serializable info,
+         String timerId) throws IllegalArgumentException, IllegalStateException, EJBException
+   {
+      if (delegate instanceof TimerRestoringTimerService)
+      {
+         TimerRestoringTimerService persistentTimerService = (TimerRestoringTimerService) delegate;
+         // restore the timer
+         return persistentTimerService.restoreTimer(initialExpiration, intervalDuration, nextExpiry, info, timerId);
+      }
+      else
+      {
+         log.warn("Unable to restore timer, since the delegate timerservice " + delegate.getClass() + " isn't of type "
+               + TimerRestoringTimerService.class + " - will create the timer afresh");
+          // we can't "restore" the timer state, so let's just recreate the timer afresh, using the initial expiry date and the repeat interval
+          return delegate.createTimer(initialExpiration, intervalDuration, info);
+      }
+   }
 
    public Timer createTimer(long duration, Serializable info) throws IllegalArgumentException, IllegalStateException, EJBException
    {
Index: testsuite/src/main/org/jboss/test/ejb3/jbpapp4681/unit/TimerServiceRestoreTestCase.java
===================================================================
--- testsuite/src/main/org/jboss/test/ejb3/jbpapp4681/unit/TimerServiceRestoreTestCase.java	(revision 0)
+++ testsuite/src/main/org/jboss/test/ejb3/jbpapp4681/unit/TimerServiceRestoreTestCase.java	(revision 0)
@@ -0,0 +1,153 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2008, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.jboss.test.ejb3.jbpapp4681.unit;
+
+import java.util.Date;
+
+import junit.framework.Assert;
+import junit.framework.Test;
+
+import org.jboss.logging.Logger;
+import org.jboss.test.JBossTestCase;
+import org.jboss.test.ejb3.jbpapp4681.SimpleTimer;
+import org.jboss.test.ejb3.jbpapp4681.TimerSLSB;
+
+/**
+ * Tests that timers that are restored after a redeployment of the applications,
+ * fire at the right time.
+ * 
+ * @see JBPAPP-4681 https://jira.jboss.org/browse/JBPAPP-4681
+ *
+ * @author Jaikiran Pai
+ * @version $Revision: $
+ */
+public class TimerServiceRestoreTestCase extends JBossTestCase
+{
+
+   private static Logger logger = Logger.getLogger(TimerServiceRestoreTestCase.class);
+   
+   private static final String DEPLOYMENT_NAME = "jbpapp4681.jar";
+   
+   public TimerServiceRestoreTestCase(String name)
+   {
+      super(name);
+   }
+   
+   /**
+    * 
+    * @return
+    * @throws Exception
+    */
+   public static Test suite() throws Exception
+   {
+      return getDeploySetup(TimerServiceRestoreTestCase.class, DEPLOYMENT_NAME);
+   }
+   
+   /**
+    * Tests that a periodic timer is restored and a timeout is fired at the right time
+    * on redeploy of the application
+    * 
+    * @throws Exception
+    */
+   public void testTimerRestoreForPeriodicTimer() throws Exception
+   {
+      // make sure the deployment was deployed successfully
+      serverFound();
+
+      SimpleTimer bean = (SimpleTimer) this.getInitialContext().lookup(TimerSLSB.JNDI_NAME);
+      // cancel all existing timers
+      bean.cancelAllTimers();
+      
+      Date now = new Date();
+      long TEN_SECONDS = 10000;
+      String testName = "PeriodicTimer";
+      bean.createTimer(now, TEN_SECONDS, testName);
+      
+      // wait for the intial timeout to occur
+      logger.info("Sleeping for 1 seconds for the timeout to happen");
+      Thread.sleep(1000);
+      
+      int timeoutCount = bean.getTimeoutCount(testName);
+      Assert.assertEquals("Unexpected initial timeout count", 1, timeoutCount);
+      
+      // redeploy the app
+      this.redeploy(DEPLOYMENT_NAME);
+      
+      // the time from the initial timeout and the redeployment shouldn't be 10 seconds.
+      // so the timeout count returned from the bean, immediately after redeployment, must
+      // be 0.
+      bean = (SimpleTimer) this.getInitialContext().lookup(TimerSLSB.JNDI_NAME);
+      int timeoutCountImmediatelyAfterRedeploy = bean.getTimeoutCount(testName);
+      Assert.assertEquals("Unexpected timeout count immediately after redeploy", 0, timeoutCountImmediatelyAfterRedeploy);
+      
+      // now wait for a few more seconds for the next timeout to occur (the first one after redeploy)
+      logger.info("Sleeping for 10 seconds (after redeploy) for the timeout to happen");
+      Thread.sleep(TEN_SECONDS);
+      
+      int finalTimeoutCount = bean.getTimeoutCount(testName);
+      Assert.assertEquals("Unexpected final timeout count after redeploy", 1, finalTimeoutCount);
+   }
+   
+   /**
+    * Tests that a single action timer is restored and a timeout is fired at the right time
+    * on redeploy of the application
+    * 
+    * @throws Exception
+    */
+   public void testTimerRestoreForSingleActionTimer() throws Exception
+   {
+      // make sure the deployment was deployed successfully
+      serverFound();
+      
+      SimpleTimer bean = (SimpleTimer) this.getInitialContext().lookup(TimerSLSB.JNDI_NAME);
+      
+      // cancel all existing timers
+      bean.cancelAllTimers();
+      
+      long TEN_SECONDS = 10000;
+      Date tenSecondsFromNow = new Date(System.currentTimeMillis() + TEN_SECONDS);
+      String testName = "SingleActionTimer";
+      bean.createTimer(tenSecondsFromNow, testName);
+      
+      // no timeout should occur, before 10 seconds from now 
+      int timeoutCount = bean.getTimeoutCount(testName);
+      Assert.assertEquals("Unexpected initial timeout count", 0, timeoutCount);
+      
+      // redeploy the app
+      this.redeploy(DEPLOYMENT_NAME);
+      
+      // the time from the initial timeout and the redeployment shouldn't be 10 seconds.
+      // so the timeout count returned from the bean, immediately after redeployment, must
+      // be 0.
+      bean = (SimpleTimer) this.getInitialContext().lookup(TimerSLSB.JNDI_NAME);
+      int timeoutCountImmediatelyAfterRedeploy = bean.getTimeoutCount(testName);
+      Assert.assertEquals("Unexpected timeout count immediately after redeploy", 0, timeoutCountImmediatelyAfterRedeploy);
+      
+      // now wait for a few more seconds for the next timeout to occur (the first one after redeploy)
+      logger.info("Sleeping for 10 seconds (after redeploy) for the timeout to happen");
+      Thread.sleep(TEN_SECONDS);
+      
+      int finalTimeoutCount = bean.getTimeoutCount(testName);
+      Assert.assertEquals("Unexpected final timeout count after redeploy", 1, finalTimeoutCount);
+   }
+
+}
Index: testsuite/src/main/org/jboss/test/ejb3/jbpapp4681/TimeoutTracker.java
===================================================================
--- testsuite/src/main/org/jboss/test/ejb3/jbpapp4681/TimeoutTracker.java	(revision 0)
+++ testsuite/src/main/org/jboss/test/ejb3/jbpapp4681/TimeoutTracker.java	(revision 0)
@@ -0,0 +1,80 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2008, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.jboss.test.ejb3.jbpapp4681;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.jboss.logging.Logger;
+
+/**
+ * TimeoutTracker
+ *
+ * @author Jaikiran Pai
+ * @version $Revision: $
+ */
+public class TimeoutTracker
+{
+
+   private static TimeoutTracker instance;
+
+   private static Logger logger = Logger.getLogger(TimeoutTracker.class);
+   
+   private Map<String, Integer> timeoutCount;
+   
+   public synchronized static TimeoutTracker getInstance()
+   {
+      if (instance == null)
+      {
+         instance = new TimeoutTracker();
+      }
+      return instance;
+   }
+
+   private TimeoutTracker()
+   {
+      this.timeoutCount = new HashMap<String, Integer>();
+   }
+   
+   public void trackTimeout(String name)
+   {
+      logger.info("Tracking timeout for: " + name);
+      Integer count = this.timeoutCount.get(name);
+      if (count == null)
+      {
+         count = new Integer(0);
+      }
+      count++;
+      logger.info("Number of timeouts for: " + name + " = " + count);
+      this.timeoutCount.put(name, count);
+   }
+   
+   public int getTimeoutCount(String name)
+   {
+      Integer count = this.timeoutCount.get(name);
+      if (count == null)
+      {
+         return 0;
+      }
+      return count;
+   }
+}
Index: testsuite/src/main/org/jboss/test/ejb3/jbpapp4681/TimerSLSB.java
===================================================================
--- testsuite/src/main/org/jboss/test/ejb3/jbpapp4681/TimerSLSB.java	(revision 0)
+++ testsuite/src/main/org/jboss/test/ejb3/jbpapp4681/TimerSLSB.java	(revision 0)
@@ -0,0 +1,103 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2008, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.jboss.test.ejb3.jbpapp4681;
+
+import java.util.Collection;
+import java.util.Date;
+
+import javax.annotation.PostConstruct;
+import javax.annotation.Resource;
+import javax.ejb.Remote;
+import javax.ejb.Stateless;
+import javax.ejb.Timeout;
+import javax.ejb.Timer;
+import javax.ejb.TimerService;
+
+import org.jboss.ejb3.annotation.RemoteBinding;
+import org.jboss.logging.Logger;
+
+/**
+ * TimerSLSB
+ *
+ * @author Jaikiran Pai
+ * @version $Revision: $
+ */
+@Stateless
+@Remote
+@RemoteBinding (jndiBinding = TimerSLSB.JNDI_NAME)
+public class TimerSLSB implements SimpleTimer
+{
+
+   public static final String JNDI_NAME = "JBPAPP-4681-TimerSLSB";
+   
+   @Resource
+   private TimerService timerService;
+   
+   private TimeoutTracker timeoutTracker;
+   
+   private static Logger logger = Logger.getLogger(TimerSLSB.class);
+   
+   @PostConstruct
+   public void onConstruct()
+   {
+      this.timeoutTracker = TimeoutTracker.getInstance();
+   }
+   
+   @Override
+   public void createTimer(Date initialExpiry, long interval, String testName)
+   {
+      this.timerService.createTimer(initialExpiry, interval, testName);
+      
+   }
+
+   @Override
+   public void createTimer(Date initialExpiry, String testName)
+   {
+      this.timerService.createTimer(initialExpiry, testName);
+      
+   }
+   
+   @Timeout
+   public void onTimeout(Timer timer)
+   {
+      logger.info("Timeout invoked at : " + new Date() + " for timer " + timer);
+      this.timeoutTracker.trackTimeout(timer.getInfo().toString());
+   }
+   
+   @Override
+   public void cancelAllTimers()
+   {
+      Collection<Timer> timers = this.timerService.getTimers();
+      for (Timer timer : timers)
+      {
+         timer.cancel();
+      }
+      
+   }
+   
+   @Override
+   public int getTimeoutCount(String name)
+   {
+      return this.timeoutTracker.getTimeoutCount(name);
+   }
+
+}
Index: testsuite/src/main/org/jboss/test/ejb3/jbpapp4681/SimpleTimer.java
===================================================================
--- testsuite/src/main/org/jboss/test/ejb3/jbpapp4681/SimpleTimer.java	(revision 0)
+++ testsuite/src/main/org/jboss/test/ejb3/jbpapp4681/SimpleTimer.java	(revision 0)
@@ -0,0 +1,43 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2008, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.jboss.test.ejb3.jbpapp4681;
+
+import java.util.Date;
+
+/**
+ * SimpleTimer
+ *
+ * @author Jaikiran Pai
+ * @version $Revision: $
+ */
+public interface SimpleTimer
+{
+   
+   void createTimer(Date initialExpiry, long interval, String testName);
+   
+   void createTimer(Date initialExpiry, String testName);
+   
+   void cancelAllTimers();
+   
+   int getTimeoutCount(String testName);
+
+}
Index: testsuite/imports/sections/ejb3.xml
===================================================================
--- testsuite/imports/sections/ejb3.xml	(revision 107043)
+++ testsuite/imports/sections/ejb3.xml	(working copy)
@@ -189,13 +189,24 @@
          <fileset dir="${build.resources}/ejb3/webservice" includes="**"/>
       </jar>
    </target>
+    
+	<target name="jbpapp4681" depends="compile">
+	      <mkdir dir="${build.lib}" />
+	      
+	      <jar destfile="${build.lib}/jbpapp4681.jar">
+	         <fileset dir="${build.classes}">
+	            <include name="org/jboss/test/ejb3/jbpapp4681/**" />
+	         </fileset>
+	      </jar>
+	</target>    
    
    <target name="_jars-ejb3" depends="ejb3-servlet,jbas6161,jbas6239,ejbthree1597,jbas5713,
       ejb3iiop,
       ejb3-webservice,
       jbpapp2260,
       jbpapp2473,
-      jbpapp3026
+      jbpapp3026,
+		jbpapp4681
       ">
       <mkdir dir="${build.lib}" />
 
