/*
 * JBoss, Home of Professional Open Source
 *
 * Distributable under LGPL license.
 * See terms of license at gnu.org.
 */
package org.jboss.cache.passivation;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jboss.cache.CacheException;
import org.jboss.cache.CacheFactory;
import org.jboss.cache.CacheSPI;
import org.jboss.cache.DefaultCacheFactory;
import org.jboss.cache.Fqn;
import org.jboss.cache.RegionManager;
import org.jboss.cache.config.CacheLoaderConfig;
import org.jboss.cache.config.EvictionConfig;
import org.jboss.cache.config.EvictionRegionConfig;
import org.jboss.cache.eviction.LRUConfiguration;
import org.jboss.cache.loader.CacheLoader;
import org.jboss.cache.loader.DummyInMemoryCacheLoader;
import org.jboss.cache.lock.IsolationLevel;
import org.jboss.cache.notifications.annotation.NodeActivated;
import org.jboss.cache.notifications.annotation.NodePassivated;
import org.jboss.cache.notifications.event.NodeEvent;
import static org.testng.AssertJUnit.*;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

/**
 * Tests that the TreeCacheListener implementation used by EJB3 SFSBs works.
 *
 * @author Brian Stansberry
 */
@Test(groups = {"functional"})
public class PassivationActivationCallbacksTestCase
{
   private static final Fqn<String> BASE = Fqn.fromString("/base");
   private static final Log log = LogFactory.getLog(PassivationActivationCallbacksTestCase.class);

   //Cache Loader fields
   private CacheSPI<String, String> cache;
   private CacheLoader loader = null;
   private CacheListener listener = null;

   @BeforeMethod(alwaysRun = true)
   public void setUp() throws Exception
   {
      log.debug("");
      CacheFactory<String, String> instance = new DefaultCacheFactory();
      cache = (CacheSPI<String, String>) instance.createCache(false);
      cache.getConfiguration().setCacheMode("local");
      configureEviction();
      configureCacheLoader();
      cache.getConfiguration().setTransactionManagerLookupClass("org.jboss.cache.transaction.DummyTransactionManagerLookup");
      cache.getConfiguration().setIsolationLevel(IsolationLevel.SERIALIZABLE);
      listener = new CacheListener();
      cache.addCacheListener(listener);
      cache.create();
      cache.start();
      loader = cache.getCacheLoaderManager().getCacheLoader();
   }

   protected void configureCacheLoader() throws Exception
   {
      CacheLoaderConfig clc = new CacheLoaderConfig();
      clc.setPassivation(true);
      clc.setShared(false);
      clc.setPreload("/");

      CacheLoaderConfig.IndividualCacheLoaderConfig dummyConfig = new CacheLoaderConfig.IndividualCacheLoaderConfig();
      dummyConfig.setAsync(false);
      dummyConfig.setFetchPersistentState(true);
      dummyConfig.setIgnoreModifications(false);
      dummyConfig.setClassName(DummyInMemoryCacheLoader.class.getName());
      clc.addIndividualCacheLoaderConfig(dummyConfig);
      cache.getConfiguration().setCacheLoaderConfig(clc);
   }

   protected void configureEviction() throws Exception
   {
      EvictionConfig ec = new EvictionConfig();
      ec.setWakeupIntervalSeconds(1);

      List<EvictionRegionConfig> ercs = new ArrayList<EvictionRegionConfig>();

      EvictionRegionConfig erc = new EvictionRegionConfig();
      erc.setRegionFqn(RegionManager.DEFAULT_REGION);
      LRUConfiguration epc = new LRUConfiguration();
      epc.setMaxNodes(0);
      epc.setTimeToLiveSeconds(5);
      erc.setEvictionPolicyConfig(epc);
      ercs.add(erc);

      erc = new EvictionRegionConfig();
      erc.setRegionFqn(BASE);
      epc = new LRUConfiguration();
      epc.setMaxNodes(0);
      epc.setTimeToLiveSeconds(1);
      erc.setEvictionPolicyConfig(epc);
      ercs.add(erc);

      ec.setEvictionRegionConfigs(ercs);

      cache.getConfiguration().setEvictionConfig(ec);
   }

   @AfterMethod(alwaysRun = true)
   public void tearDown() throws Exception
   {
      cache.removeNode(Fqn.ROOT);
      loader.remove(Fqn.fromString("/"));
      cache.stop();
      cache.destroy();
   }

   public void testSimpleLifecycle() throws Exception
   {
      Fqn fqn = Fqn.fromRelativeElements(BASE, "bean1");
      cache.put(fqn, "bean", "A bean");

      //TestingUtil.sleepThread(3000);
      cache.evict(fqn, false);

      assertNull("No activation exception", listener.activationException);
      assertNull("No passivation exception", listener.passivationException);
      assertTrue(listener.passivated.contains(fqn));
      assertFalse(listener.activated.contains(fqn));

      Object obj = cache.get(fqn, "bean");
      assertEquals("Got bean", "A bean", obj);

      assertNull("No activation exception", listener.activationException);
      assertNull("No passivation exception", listener.passivationException);
      assertTrue(listener.activated.contains(fqn));
   }

   /**
    * Mimics the CacheListener used by EJB3 SFSBs.
    */
   @org.jboss.cache.notifications.annotation.CacheListener
   public class CacheListener
   {
      protected Log log = LogFactory.getLog(CacheListener.class);

      protected Set<Fqn<String>> passivated = new HashSet<Fqn<String>>();
      protected Set<Fqn<String>> activated = new HashSet<Fqn<String>>();
      protected Exception passivationException;
      protected Exception activationException;

      @SuppressWarnings("unchecked")
      @NodeActivated
      public void nodeActivated(NodeEvent e)
      {
         if (e.isPre())
            return; // we are not interested in preActivate event

         if (!e.getFqn().isChildOrEquals(BASE))
            return; // don't care about fqn that doesn't belong to me.

         Object bean = null;
         try
         {
            bean = cache.get(e.getFqn(), "bean");
         }
         catch (CacheException ex)
         {
            log.error("nodeActivate(): can't retrieve bean instance from: " + e.getFqn() + " with exception: " + ex);
            activationException = ex;
            return;
         }
         if (bean == null)
         {
            activationException = new IllegalStateException("nodeActivate(): null bean instance.");
            throw (IllegalStateException) activationException;
         }

         if (log.isTraceEnabled())
         {
            log.trace("nodeActivate(): saw postActivate event on fqn: " + e.getFqn());
         }

         activated.add(e.getFqn());
      }

      @SuppressWarnings("unchecked")
      @NodePassivated
      public void nodePassivated(NodeEvent e)
      {
         if (!e.isPre())
            return; // we are not interested in postPassivate event
         Fqn<String> fqn = e.getFqn();
         if (!fqn.isChildOrEquals(BASE))
            return; // don't care about fqn that doesn't belong to me.

         try
         {
            Object bean = cache.get(fqn, "bean");
            if (bean != null)
            {
               if (log.isTraceEnabled())
               {
                  log.trace("nodePassivate(): send prePassivate event on fqn: " + fqn);
               }
               passivated.add(fqn);
            }

         }
         catch (CacheException ex)
         {
            log.error("nodePassivate(): can't retrieve bean instance from: " + fqn + " with exception: " + ex);
            passivationException = ex;
            return;
         }

      }
   }

}
