/*
* JBoss, the OpenSource J2EE webOS
*
* Distributable under LGPL license.
* See terms of license at gnu.org.
*/
package org.jboss.ejb.txtimer;

// $Id: TimerServiceImpl.java,v 1.11.2.2 2005/04/06 21:17:59 starksm Exp $

import org.jboss.logging.Logger;
import org.jboss.mx.util.MBeanProxy;
import org.jboss.mx.util.MBeanServerLocator;
import org.jboss.tm.TxManager;

import javax.ejb.EJBException;
import javax.ejb.Timer;
import javax.ejb.TimerHandle;
import javax.ejb.TimerService;
import javax.management.MBeanServer;
import javax.management.ObjectName;
import javax.naming.InitialContext;
import javax.transaction.SystemException;
import javax.transaction.Transaction;
import javax.transaction.TransactionManager;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

/**
 * The TimerService provides enterprise bean components with access to the
 * container-provided Timer Service. The EJB Timer Service allows entity beans, stateless
 * session beans, and message-driven beans to be registered for timer callback events at
 * a specified time, after a specified elapsed time, or after a specified interval.
 *
 * @author Thomas.Diesler@jboss.org
 * @since 07-Apr-2004
 */
public class TimerServiceImpl implements TimerService
{
   // logging support
   private static Logger log = Logger.getLogger(TimerServiceImpl.class);

   private TransactionManager transactionManager;
   private PersistencePolicy persistencePolicy;

   private TimedObjectId timedObjectId;
   private TimedObjectInvoker timedObjectInvoker;

   // The timerId generator
   private TimerIdGenerator timerIdGenerator;

   // Map<TimerHandleImpl,TimerImpl>
   private Map timers = new HashMap();

   /**
    * Create a Timer service for the given TimedObject
    */
   public TimerServiceImpl(TimedObjectId timedObjectId, TimedObjectInvoker timedObjectInvoker)
   {
      this.timedObjectId = timedObjectId;
      this.timedObjectInvoker = timedObjectInvoker;

      // [todo] refactor dependency injection
      
      // Get the Tx manager
      try
      {
         InitialContext iniCtx = new InitialContext();
         transactionManager = (TransactionManager)iniCtx.lookup("java:/TransactionManager");
      }
      catch (Exception e)
      {
         log.warn("Cannot obtain TransactionManager from JNDI: " + e.toString());
         transactionManager = TxManager.getInstance();
      }

      // Get a proxy to the persistence policy
      try
      {
         MBeanServer server = MBeanServerLocator.locateJBoss();
         ObjectName persistencePolicyName = (ObjectName)server.getAttribute(EJBTimerService.OBJECT_NAME, "PersistencePolicy");
         persistencePolicy = (PersistencePolicy)MBeanProxy.get(PersistencePolicy.class, persistencePolicyName, server);
      }
      catch (Exception e)
      {
         log.warn("Cannot obtain the implementation of a PersistencePolicy: " + e.toString());
         persistencePolicy = new NoopPersistencePolicy();
      }

      // Get the timerId generator
      try
      {
         MBeanServer server = MBeanServerLocator.locateJBoss();
         String timerIdGeneratorClassName = (String)server.getAttribute(EJBTimerService.OBJECT_NAME, "TimerIdGeneratorClassName");
         Class timerIdGeneratorClass = getClass().getClassLoader().loadClass(timerIdGeneratorClassName);
         timerIdGenerator = (TimerIdGenerator)timerIdGeneratorClass.newInstance();
      }
      catch (Exception e)
      {
         log.warn("Cannot obtain the implementation of a TimerIdGenerator: " + e.toString());
         timerIdGenerator = new BigIntegerTimerIdGenerator();
      }
   }

   /**
    * Create a single-action txtimer that expires after a specified duration.
    *
    * @param duration The number of milliseconds that must elapse before the txtimer expires.
    * @param info     Application information to be delivered along with the txtimer expiration
    *                 notification. This can be null.
    * @return The newly created Timer.
    * @throws IllegalArgumentException If duration is negative
    * @throws IllegalStateException    If this method is invoked while the instance is in
    *                                  a state that does not allow access to this method.
    * @throws javax.ejb.EJBException   If this method could not complete due to a system-level failure.
    */
   public Timer createTimer(long duration, Serializable info) throws IllegalArgumentException, IllegalStateException, EJBException
   {
      if (duration < 0)
         throw new IllegalArgumentException("duration is negative");

      return createTimer(new Date(System.currentTimeMillis() + duration), 0, info);
   }

   /**
    * Create an interval txtimer whose first expiration occurs after a specified duration,
    * and whose subsequent expirations occur after a specified interval.
    *
    * @param initialDuration  The number of milliseconds that must elapse before the first
    *                         txtimer expiration notification.
    * @param intervalDuration The number of milliseconds that must elapse between txtimer
    *                         expiration notifications. Expiration notifications are
    *                         scheduled relative to the time of the first expiration. If
    *                         expiration is delayed(e.g. due to the interleaving of other
    *                         method calls on the bean) two or more expiration notifications
    *                         may occur in close succession to "catch up".
    * @param info             Application information to be delivered along with the txtimer expiration
    *                         notification. This can be null.
    * @return The newly created Timer.
    * @throws IllegalArgumentException If initialDuration is negative, or intervalDuration
    *                                  is negative.
    * @throws IllegalStateException    If this method is invoked while the instance is in
    *                                  a state that does not allow access to this method.
    * @throws javax.ejb.EJBException   If this method could not complete due to a system-level failure.
    */
   public Timer createTimer(long initialDuration, long intervalDuration, Serializable info) throws IllegalArgumentException, IllegalStateException, EJBException
   {
      if (initialDuration < 0)
         throw new IllegalArgumentException("initial duration is negative");
      if (intervalDuration < 0)
         throw new IllegalArgumentException("interval duration is negative");

      return createTimer(new Date(System.currentTimeMillis() + initialDuration), intervalDuration, info);
   }

   /**
    * Create a single-action txtimer that expires at a given point in time.
    *
    * @param expiration The point in time at which the txtimer must expire.
    * @param info       Application information to be delivered along with the txtimer expiration
    *                   notification. This can be null.
    * @return The newly created Timer.
    * @throws IllegalArgumentException If expiration is null, or expiration.getTime() is negative.
    * @throws IllegalStateException    If this method is invoked while the instance is in
    *                                  a state that does not allow access to this method.
    * @throws javax.ejb.EJBException   If this method could not complete due to a system-level failure.
    */
   public Timer createTimer(Date expiration, Serializable info) throws IllegalArgumentException, IllegalStateException, EJBException
   {
      if (expiration == null)
         throw new IllegalArgumentException("expiration is null");

      return createTimer(expiration, 0, info);
   }

   /**
    * Create an interval txtimer whose first expiration occurs at a given point in time and
    * whose subsequent expirations occur after a specified interval.
    *
    * @param initialExpiration The point in time at which the first txtimer expiration must occur.
    * @param intervalDuration  The number of milliseconds that must elapse between txtimer
    *                          expiration notifications. Expiration notifications are
    *                          scheduled relative to the time of the first expiration. If
    *                          expiration is delayed(e.g. due to the interleaving of other
    *                          method calls on the bean) two or more expiration notifications
    *                          may occur in close succession to "catch up".
    * @param info              Application information to be delivered along with the txtimer expiration
    *                          notification. This can be null.
    * @return The newly created Timer.
    * @throws IllegalArgumentException If initialExpiration is null, or initialExpiration.getTime()
    *                                  is negative, or intervalDuration is negative.
    * @throws IllegalStateException    If this method is invoked while the instance is in
    *                                  a state that does not allow access to this method.
    * @throws javax.ejb.EJBException   If this method could not complete due to a system-level failure.
    */
   public Timer createTimer(Date initialExpiration, long intervalDuration, Serializable info) throws IllegalArgumentException, IllegalStateException, EJBException
   {
      if (initialExpiration == null)
         throw new IllegalArgumentException("initial expiration is null");
      if (intervalDuration < 0)
         throw new IllegalArgumentException("interval duration is negative");

      try
      {
         String timerId = timerIdGenerator.nextTimerId();
         TimerImpl timer = new TimerImpl(timerId, timedObjectId, timedObjectInvoker, info);
         persistencePolicy.insertTimer(timerId, timedObjectId, initialExpiration, intervalDuration, info);
         timer.startTimer(initialExpiration, intervalDuration);
         return timer;
      }
      catch (Exception e)
      {
         log.error("Cannot create txtimer", e);
         return null;
      }
   }

   /**
    * Get all the active timers associated with this bean.
    *
    * @return A collection of javax.ejb.Timer objects.
    * @throws IllegalStateException  If this method is invoked while the instance is in
    *                                a state that does not allow access to this method.
    * @throws javax.ejb.EJBException If this method could not complete due to a system-level failure.
    */
   public Collection getTimers() throws IllegalStateException, EJBException
   {
      ArrayList activeTimers = new ArrayList();
      synchronized (timers)
      {
         Iterator it = timers.values().iterator();
         while (it.hasNext())
         {
            TimerImpl timer = (TimerImpl)it.next();
            if (timer.isActive())
               activeTimers.add(timer);
         }
      }
      return activeTimers;
   }

   /**
    * Get the list of all registerd timers, both active and inactive
    */
   public Collection getAllTimers()
   {
      synchronized (timers)
      {
         return new ArrayList(timers.values());
      }
   }

   /**
    * Get the Timer for the given timedObjectId
    */
   public Timer getTimer(TimerHandle handle)
   {
      TimerImpl timer = (TimerImpl)timers.get(handle);
      if (timer != null && timer.isActive())
         return timer;
      else
         return null;
   }

   /**
    * Kill the timer for the given handle
    */
   public void killTimer(TimerHandle handle)
   {
      TimerImpl timer = (TimerImpl)timers.get(handle);
      if (timer != null)
      {
         removeTimer(timer);
         timer.killTimer();
      }
   }

   /**
    * Kill all timers
    */
   public void killAllTimers()
   {
      synchronized (timers)
      {
         Iterator it = timers.values().iterator();
         while (it.hasNext())
         {
            TimerImpl timer = (TimerImpl)it.next();
            it.remove();
            timer.killTimer();
         }
      }
   }

   /**
    * Get the TimedObjectInvoker associated with this TimerService
    */
   public TimedObjectInvoker getTimedObjectInvoker()
   {
      return timedObjectInvoker;
   }

   /**
    * Get the current transaction
    */
   public Transaction getTransaction()
   {
      try
      {
         return transactionManager.getTransaction();
      }
      catch (SystemException e)
      {
         return null;
      }
   }

   /**
    * Add a txtimer to the list of active timers
    */
   void addTimer(TimerImpl txtimer)
   {
      synchronized (timers)
      {
         TimerHandle handle = new TimerHandleImpl(txtimer);
         timers.put(handle, txtimer);
      }
   }

   /**
    * Remove a txtimer from the list of active timers
    */
   void removeTimer(TimerImpl txtimer)
   {
      synchronized (timers)
      {
         persistencePolicy.deleteTimer(txtimer.getTimerId(), txtimer.getTimedObjectId());
         timers.remove(new TimerHandleImpl(txtimer));
      }
   }
}
