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

import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Timer;
import java.util.TimerTask;
import javax.ejb.EJBException;
import javax.ejb.EJBNoSuchObjectException;
import org.jboss.annotation.ejb.cache.simple.CacheConfig;
import org.jboss.annotation.ejb.cache.simple.PersistenceManager;
import org.jboss.aop.Advisor;
import org.jboss.ejb3.Container;
import org.jboss.ejb3.Pool;
import org.jboss.ejb3.cache.StatefulCache;
import org.jboss.ejb3.stateful.StatefulBeanContext;
import org.jboss.util.id.GUID;

/**
 * Comment
 *
 * @author <a href="mailto:bill@jboss.org">Bill Burke</a>
 * @version $Revision: 1.4.2.3 $
 */
public class SimpleStatefulCache implements StatefulCache
{
   private Pool pool;
   private CacheMap cacheMap;
   private int maxSize = 1000;
   private StatefulSessionPersistenceManager pm;
   private long sessionTimeout = 300; // 5 minutes
   private SessionTimeoutTask timeoutTask = new SessionTimeoutTask();
   private static Timer cacheTaskManager = new Timer(true);

   private class CacheMap extends LinkedHashMap
   {
      public CacheMap()
      {
         super(maxSize, 0.75F, true);
      }

      public boolean removeEldestEntry(Map.Entry entry)
      {
         boolean removeIt = size() > maxSize;
         if (removeIt)
         {
            StatefulBeanContext centry = (StatefulBeanContext) entry.getValue();
            synchronized (centry)
            {
               if (centry.inUse)
               {
                  centry.markedForPassivation = true;
               }
               else
               {
                  passivate(centry);
               }
            }
         }
         return removeIt;
      }
   }

   private class SessionTimeoutTask extends TimerTask
   {
      public void run()
      {
         synchronized (cacheMap)
         {
            Iterator it = cacheMap.entrySet().iterator();
            long now = System.currentTimeMillis();
            while (it.hasNext())
            {
               Map.Entry entry = (Map.Entry) it.next();
               StatefulBeanContext centry = (StatefulBeanContext) entry.getValue();
               if (now - centry.lastUsed >= sessionTimeout)
               {
                  synchronized (centry)
                  {
                     if (centry.inUse)
                     {
                        centry.markedForPassivation = true;
                     }
                     else
                     {
                        passivate(centry);
                     }
                     it.remove();
                  }
               }
            }
         }
      }
   }

   public void initialize(Container container) throws Exception
   {
      Advisor advisor = (Advisor) container;
      this.pool = container.getPool();
      cacheMap = new CacheMap();
      PersistenceManager pmConfig = (PersistenceManager) advisor.resolveAnnotation(PersistenceManager.class);
      this.pm = (StatefulSessionPersistenceManager) pmConfig.value().newInstance();
      pm.initialize(container);
      CacheConfig config = (CacheConfig) advisor.resolveAnnotation(CacheConfig.class);
      maxSize = config.maxSize();
      sessionTimeout = config.idleTimeoutSeconds();
   }

   public SimpleStatefulCache()
   {
   }

   public void start()
   {
      cacheTaskManager.scheduleAtFixedRate(timeoutTask, sessionTimeout * 1000, sessionTimeout * 1000);
   }

   public void stop()
   {
      synchronized (cacheMap)
      {
         cacheMap.clear();
         timeoutTask.cancel();
         try
         {
            pm.destroy();
         }
         catch (Exception e)
         {
            throw new RuntimeException(e);
         }
      }
   }

   protected void passivate(StatefulBeanContext ctx)
   {
      pm.passivateSession(ctx);
   }

   public StatefulBeanContext create()
   {
      StatefulBeanContext ctx = null;
      try
      {
         GUID guid = new GUID();
         ctx = (StatefulBeanContext) pool.get();
         ctx.setId(guid);
         synchronized (cacheMap)
         {
            cacheMap.put(guid, ctx);
         }
      }
      catch (Exception e)
      {
         e.printStackTrace();
         throw new EJBException(e);
      }
      return ctx;
   }

   public StatefulBeanContext create(Class[] initTypes, Object[] initValues)
   {
      StatefulBeanContext ctx = null;
      try
      {
         GUID guid = new GUID();
         ctx = (StatefulBeanContext) pool.get(initTypes, initValues);
         ctx.setId(guid);
         synchronized (cacheMap)
         {
            cacheMap.put(guid, ctx);
         }
      }
      catch (Exception e)
      {
         e.printStackTrace();
         throw new EJBException(e);
      }
      return ctx;
   }

   public StatefulBeanContext get(Object key) throws EJBException
   {
      StatefulBeanContext entry = null;
      synchronized (cacheMap)
      {
         entry = (StatefulBeanContext) cacheMap.get(key);
      }
      if (entry == null)
      {
         Object bean = pm.activateSession(key);
         if (bean == null)
         {
            throw new EJBNoSuchObjectException("Could not find Stateful bean: " + key);
         }
         entry = (StatefulBeanContext) bean;
         synchronized (cacheMap)
         {
            cacheMap.put(key, entry);
         }
      }
      entry.inUse = true;
      entry.lastUsed = System.currentTimeMillis();
      return entry;
   }

   public void finished(StatefulBeanContext ctx)
   {
      synchronized (ctx)
      {
         ctx.inUse = false;
         ctx.lastUsed = System.currentTimeMillis();
         if (ctx.markedForPassivation)
         {
            passivate(ctx);
         }
      }
   }

   public void remove(Object key)
   {
      StatefulBeanContext ctx = null;
      synchronized (cacheMap)
      {
         ctx = (StatefulBeanContext) cacheMap.remove(key);
      }
      if (ctx != null) pool.remove(ctx);
   }


}
