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

import java.util.Date;
import java.util.HashMap;
import java.io.ByteArrayOutputStream;
import java.io.ObjectOutputStream;
import java.io.ByteArrayInputStream;
import java.io.ObjectInputStream;

import javax.ejb.SessionBean;
import javax.ejb.SessionContext;
import javax.ejb.TimedObject;
import javax.ejb.Timer;
import javax.ejb.TimerHandle;
import javax.ejb.TimerService;
import javax.ejb.EJBException;
import javax.ejb.NoSuchObjectLocalException;

import org.apache.log4j.Logger;

/**
 * Stateless Session Bean Timer Test
 *
 * @ejb:bean name="test/timer/TimerSLSB"
 *           display-name="Timer in Stateless Session Bean"
 *           type="Stateless"
 *           transaction-type="Container"
 *           view-type="remote"
 *           jndi-name="ejb/test/timer/TimerSLSB"
 *
 * @ejb:transaction type="Required"
 * @author Thomas Diesler
 * @author Scott.Stark@jboss.org
 * @version $Revision: 1.4.2.1 $
 **/
public class TimerSLSBean
   implements SessionBean, TimedObject
{
   // -------------------------------------------------------------------------
   // Static
   // -------------------------------------------------------------------------
   private static HashMap timeoutCounts = new HashMap();
   private static Logger log = Logger.getLogger(TimerSLSBean.class);

   // -------------------------------------------------------------------------
   // Members
   // -------------------------------------------------------------------------
   private SessionContext context;

   // -------------------------------------------------------------------------
   // Methods
   // -------------------------------------------------------------------------

   /**
    * Start a single timer (if not already set) with the start date plus the period
    *
    * @param pPeriod Time that will elapse between now and the timed event in milliseconds
    *
    * @ejb:interface-method view-type="remote"
    **/
   public byte[] startSingleTimer(long pPeriod)
   {
      log.info("TimerSLSBean.startSingleTimer(), try to get a Timer Service from the Session Context");
      TimerService ts = context.getTimerService();
      long exp = System.currentTimeMillis() + pPeriod;
      Timer timer = ts.createTimer(new Date(exp), "TimerSLSBean.startSingleTimer");
      log.info("TimerSLSBean.startSingleTimer(), create a timer: "+timer);
      byte[] handle = getHandle(timer);
      return handle;
   }

   /**
    * Start a timer (if not already set) with the start date plus the period
    * and an interval of the given period
    *
    * @param pPeriod Time that will elapse between two events in milliseconds
    *
    * @ejb:interface-method view-type="remote"
    **/
   public byte[] startTimer(long pPeriod)
   {
      log.info("TimerSLSBean.startTimer(), try to get a Timer Service from the Session Context");
      TimerService ts = context.getTimerService();
      long exp = System.currentTimeMillis() + pPeriod;
      Timer timer = ts.createTimer(new Date(exp), pPeriod, "TimerSLSBean.startTimer");
      log.info("TimerSLSBean.startTimer(), create a timer: "+timer);
      byte[] handle = getHandle(timer);
      return handle;
   }

   /**
    * @ejb:interface-method view-type="remote"
    **/
   public void stopTimer(byte[] handle)
   {
      Timer timer = getTimer(handle);
      timer.cancel();
      log.info("TimerSLSBean.stopTimer(), create a timer: "+timer);
      synchronized( TimerSLSBean.class )
      {
         Long key = getKey(handle);
         timeoutCounts.remove(key);
      }
   }

   /**
    * @ejb:interface-method view-type="remote"
    **/
   public int getTimeoutCount(byte[] handle)
   {
      Integer count = null;
      try
      {
         Long key = getKey(handle);         
         count = (Integer) timeoutCounts.get(key);
      }
      catch(NoSuchObjectLocalException e)
      {
         // Expected if the timer has been stopped
      }
      log.info("TimerSLSBean.getTimeoutCount(): " + count);
      return count !=  null ? count.intValue() : 0;
   }

   /**
    * @return Date of the next timed event
    *
    * @ejb:interface-method view-type="remote"
    **/
   public Date getNextTimeout(byte[] handle)
   {
      Timer timer = getTimer(handle);
      return timer.getNextTimeout();
   }

   /**
    * @return Time remaining until next timed event in milliseconds
    *
    * @ejb:interface-method view-type="remote"
    **/
   public long getTimeRemaining(byte[] handle)
   {
      Timer timer = getTimer(handle);
      return timer.getTimeRemaining();
   }

   /**
    * @return User object of the timer
    *
    * @ejb:interface-method view-type="remote"
    **/
   public Object getInfo(byte[] handle)
   {
      Timer timer = getTimer(handle);
      return timer.getInfo();
   }

   /**
    * Create the Session Bean
    *
    * @ejb:create-method view-type="both"
    **/
   public void ejbCreate()
   {
      log.info("TimerSLSBean.ejbCreate()");
   }

   public void ejbTimeout(Timer timer)
   {
      Integer count = null;
      Long key = null;
      synchronized( TimerSLSBean.class )
      {
         byte[] handle = getHandle(timer);
         key = getKey(handle);
         count = (Integer) timeoutCounts.get(key);
         if( count == null )
            count = new Integer(1);
         else
            count = new Integer(1 + count.intValue());
         timeoutCounts.put(key, count);
      }
      log.info("ejbTimeout(), timer: " + timer+", key: "+key+", count: "+count);
   }

   /**
    * Describes the instance and its content for debugging purpose
    *
    * @return Debugging information about the instance and its content
    **/
   public String toString()
   {
      return "TimerSLSBean [ " + " ]";
   }

   // -------------------------------------------------------------------------
   // Framework Callbacks
   // -------------------------------------------------------------------------

   public void setSessionContext(SessionContext aContext)
   {
      context = aContext;
   }

   public void ejbActivate()
   {
   }

   public void ejbPassivate()
   {
   }

   public void ejbRemove()
   {
   }

   private Long getKey(byte[] handle)
   {
      long key = 0;
      for(int n = 0; n < handle.length; n ++)
         key += handle[n];
      log.info("HandleKey: "+key);
      return new Long(key);
   }
   private byte[] getHandle(Timer timer)
      throws EJBException
   {
      try
      {
         ByteArrayOutputStream baos = new ByteArrayOutputStream();
         ObjectOutputStream oos = new ObjectOutputStream(baos);
         oos.writeObject(timer.getHandle());
         oos.close();
         byte[] handle = baos.toByteArray();
         return handle;
      }
      catch (Exception e)
      {
         throw new EJBException("Failed to get timer from handle", e);
      }
   }
   private Timer getTimer(byte[] handle)
      throws NoSuchObjectLocalException, EJBException
   {
      try
      {
         ByteArrayInputStream bais = new ByteArrayInputStream(handle);
         ObjectInputStream ois = new ObjectInputStream(bais);
         TimerHandle th = null;
         th = (TimerHandle) ois.readObject();
         ois.close();
         Timer timer = th.getTimer();
         return timer;
      }
      catch(NoSuchObjectLocalException e)
      {
         throw e;
      }
      catch (Exception e)
      {
         throw new EJBException("Failed to get timer from handle", e);
      }
   }
}