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

import java.util.HashSet;
import java.util.Set;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jboss.cache.AbstractTreeCacheListener;
import org.jboss.cache.CacheException;
import org.jboss.cache.Fqn;
import org.jboss.cache.Node;
import org.jboss.cache.TreeCache;
import org.jboss.cache.loader.CacheLoader;
import org.jboss.cache.lock.IsolationLevel;
import org.jboss.cache.misc.TestingUtil;
import org.jboss.cache.xml.XmlHelper;
import org.w3c.dom.Element;

import junit.framework.TestCase;

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

   //Cache Loader fields
   private TreeCache        cache;
   private CacheLoader      loader=null;
   private CacheListener    listener = null;
   
   protected void setUp() throws Exception
   {
      super.setUp();
      log.debug("Testing " + getName());
      log.debug("");
      cache=new TreeCache();
      cache.setCacheMode("local");
      configureCache();
      cache.setTransactionManagerLookupClass("org.jboss.cache.DummyTransactionManagerLookup");
      cache.setIsolationLevel(IsolationLevel.SERIALIZABLE);
      listener = new CacheListener();
      cache.addTreeCacheListener(listener);
      cache.createService();
      cache.startService();
      loader=cache.getCacheLoader();
   }
   
   protected void configureCache() throws Exception {

      String tmp_location = null;
      String OS = System.getProperty("os.name").toLowerCase();
      if(OS.indexOf("win") > -1 || OS.indexOf("nt") > -1) 
      {
         tmp_location=System.getProperty("java.io.tmpdir", "c:\\tmp\\fcl");
      } 
      else 
      {
         tmp_location=System.getProperty("java.io.tmpdir", "/tmp/fcl");
      }

       cache.setCacheLoaderConfiguration(getCacheLoaderConfig(tmp_location));
       cache.setEvictionPolicyConfig(getEvictionConfig());
   }

   protected Element getEvictionConfig() throws Exception
   {
      String xml = 
         "<attribute name=\"EvictionPolicyConfig\">\n" +
         "   <config>\n" +
         "      <attribute name=\"wakeUpIntervalSeconds\">1</attribute>\n" +
         "      <region name=\"/_default_\" policyClass=\"org.jboss.cache.eviction.LRUPolicy\">\n" +
         "          <attribute name=\"maxNodes\">0</attribute>\n" +
         "          <attribute name=\"timeToLiveSeconds\">5</attribute>\n" +
         "      </region>\n" +
         "      <region name=\"/base\" policyClass=\"org.jboss.cache.eviction.LRUPolicy\">\n" +
         "          <attribute name=\"maxNodes\">0</attribute>\n" +
         "          <attribute name=\"timeToLiveSeconds\">1</attribute>\n" +
         "      </region>\n" +
         "   </config>\n" +
         "</attribute>\n";
      return XmlHelper.stringToElement(xml);
   }
   
   protected Element getCacheLoaderConfig(String loc) throws Exception
   {
       String xml = "            <config>\n" +
               "                \n" +
               "                <passivation>true</passivation>\n" +
               "                <preload>/</preload>\n" +
               "\n" +
               "                <cacheloader>\n" +
               "                    <class>org.jboss.cache.loader.FileCacheLoader</class>\n" +
               "                    <properties>" + loc + "</properties>\n" +
               "                    <async>false</async>\n" +
               "                    <fetchPersistentState>false</fetchPersistentState>\n" +
               "                    <ignoreModifications>false</ignoreModifications>\n" +
               "                </cacheloader>\n" +
               "                \n" +
               "            </config>";
       return XmlHelper.stringToElement(xml);
   }

   protected void tearDown() throws Exception
   {
      super.tearDown();
      cache.remove("/");
      loader.remove(Fqn.fromString("/"));
      cache.stopService();
      cache.destroyService();
   }
   
   public void testSimpleLifecycle() throws Exception
   {
      Fqn fqn = new Fqn(BASE, "bean1");
      cache.put(fqn, "bean", "A bean");
      
      TestingUtil.sleepThread(3000);
      
      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 TreeCacheListener used by EJB3 SFSBs.
    */
   private class CacheListener extends AbstractTreeCacheListener
   {     
      protected Log log = LogFactory.getLog(CacheListener.class);

      protected Set passivated = new HashSet();
      protected Set activated = new HashSet();
      protected Exception passivationException;
      protected Exception activationException;
      
      public void nodeActivate(Fqn fqn, boolean pre) 
      {
         if(pre) 
            return;  // we are not interested in postActivate event
         
         if(!fqn.isChildOrEquals(BASE)) return;  // don't care about fqn that doesn't belong to me.

         Object bean = null;
         try {
            bean = cache.get(fqn, "bean");
         } catch (CacheException e) {
            log.error("nodeActivate(): can't retrieve bean instance from: " +fqn + " with exception: " +e);
            activationException = e;
            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: " +fqn);
         }

         activated.add(fqn);
      }

      public void nodePassivate(Fqn fqn, boolean pre) {
         if(!pre) return;  // we are not interested in postPassivate event
         if(!fqn.isChildOrEquals(BASE)) return;  // don't care about fqn that doesn't belong to me.

         try {
            // TODO Can this cause deadlock in the cache level? Should be ok but need review.
            Node node = cache.get(fqn);
            Object bean = node.getData().get("bean");
            if (bean != null)
            {
               if(log.isTraceEnabled())
               {
                  log.trace("nodePassivate(): send prePassivate event on fqn: " +fqn);
               }
               passivated.add(fqn);
            }

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

      }
   }

}
