package org.jboss.cache.mgmt;

import java.util.HashMap;
import java.util.List;

import junit.framework.Test;
import junit.framework.TestCase;
import junit.framework.TestSuite;

import org.jboss.cache.Fqn;
import org.jboss.cache.TreeCache;
import org.jboss.cache.interceptors.ActivationInterceptor;
import org.jboss.cache.interceptors.PassivationInterceptor;
import org.jboss.cache.loader.CacheLoader;
import org.jboss.cache.xml.XmlHelper;
import org.w3c.dom.Element;

/**
 * Simple functional tests for ActivationInterceptor and PassivationInterceptor statistics
 * @author Jerry Gauthier
 * @version $Id: PassivationTest.java 1512 2006-04-07 05:15:03Z genman $
 */
public class PassivationTest extends TestCase
{
   private static final String CAPITAL = "capital";
   private static final String CURRENCY = "currency";
   private static final String POPULATION = "population";
   private static final String AREA = "area";
   private static final String EUROPE_NODE = "Europe";
   
   TreeCache cache = null;

   protected void setUp() throws Exception
   {
      super.setUp();
      cache = createCache();
   }

   protected void tearDown() throws Exception
   {
      super.tearDown();
      if(cache != null)
      {
         CacheLoader cl = cache.getCacheLoader();
         cl.remove(Fqn.fromString(EUROPE_NODE));
         cache.stopService();
         cache.destroyService();
         cache = null;
      }
   }

   public void testPassivationMgmt() throws Exception
   {
      assertNotNull("Cache is null.", cache);
      
      // note -it's essential to flush any existing test data from the cache loader
      // otherwise the loads and misses counts will be incorrect
      CacheLoader cl = cache.getCacheLoader();
      assertNotNull("CacheLoader is null.", cl);
      cl.remove(Fqn.fromString(EUROPE_NODE));
      
      // populate cache with test data 
      loadCache(cache);      
 
      // Note: because these tests are normally executed without a server, the interceptor
      // MBeans are usually not available for use in the tests.  Consequently it's necessary 
      // to obtain references to the interceptors and work with them directly.
      ActivationInterceptor act = getActivationInterceptor(cache);
      assertNotNull("ActivationInterceptor not found.", act);
      PassivationInterceptor pass = getPassivationInterceptor(cache);
      assertNotNull("PassivationInterceptor not found.", pass);

      System.out.println("count of misses " + act.getCacheLoaderMisses());
      int miss = 2; 
      // was 5 in 1.3 (one per attribute) 
      // now just Europe/Albania and Europe/Hungary were loaded
     
      // verify statistics for entries loaded into cache
      assertEquals("CacheLoaderLoads count error: ", new Long(0), new Long(act.getCacheLoaderLoads()));
      assertEquals("CacheLoaderMisses count error: ", new Long(miss), new Long(act.getCacheLoaderMisses()));
      assertEquals("Activations count error: ", new Long(0), new Long(act.getActivations()));
      assertEquals("Passivations count error: ", new Long(0), new Long(pass.getPassivations()));
      
      // now try retrieving a valid attribute and an invalid attribute
      Fqn key = Fqn.fromString("Europe/Austria");
      assertNotNull("Retrieval error: expected to retrieve " + CAPITAL + " for " + key, cache.get(key, CAPITAL));
      assertNull("Retrieval error: did not expect to retrieve " + AREA + " for " + key, cache.get(key, AREA));
      
      // verify statistics after retrieving entries - no change since nodes were already loaded
      assertEquals("CacheLoaderLoads count error: ", new Long(0), new Long(act.getCacheLoaderLoads()));
      assertEquals("CacheLoaderMisses count error: ", new Long(miss), new Long(act.getCacheLoaderMisses()));
      assertEquals("Activations count error: ", new Long(0), new Long(act.getActivations()));
      assertEquals("Passivations count error: ", new Long(0), new Long(pass.getPassivations()));
      
      // now try retrieving an attribute for an invalid node
      key = Fqn.fromString("Europe/Poland");
      assertNull("Retrieval error: did not expect to retrieve " + CAPITAL + " for " + key, cache.get(key, CAPITAL));
      
      // verify statistics after retrieving invalid node - misses should now be incremented
      miss++;
      assertEquals("CacheLoaderLoads count error: ", new Long(0), new Long(act.getCacheLoaderLoads()));
      assertEquals("CacheLoaderMisses count error: ", new Long(miss), new Long(act.getCacheLoaderMisses()));
      assertEquals("Activations count error: ", new Long(0), new Long(act.getActivations()));
      assertEquals("Passivations count error: ", new Long(0), new Long(pass.getPassivations()));
      
      // now evict Austria and confirm that it's no longer in cache
      key = Fqn.fromString("Europe/Austria");
      cache.evict(key);
      assertFalse("Retrieval error: did not expect to find node " + key + " in cache", cache.exists(key));
      
      // passivations should noe be 1
      assertEquals("Passivations count error: ", new Long(1), new Long(pass.getPassivations()));
      
      // now try retrieving the attributes again - first retrieval should trigger a cache load
      assertNotNull("Retrieval error: expected to retrieve " + CAPITAL + " for " + key, cache.get(key, CAPITAL));
      assertNotNull("Retrieval error: expected to retrieve " + CURRENCY + " for " + key, cache.get(key, CURRENCY));
      
      // verify statistics after retrieving evicted entry - loads and activations should now be 1
      assertEquals("CacheLoaderLoads count error: ", new Long(1), new Long(act.getCacheLoaderLoads()));
      assertEquals("CacheLoaderMisses count error: ", new Long(miss), new Long(act.getCacheLoaderMisses()));
      assertEquals("Activations count error: ", new Long(1), new Long(act.getActivations()));
      assertEquals("Passivations count error: ", new Long(1), new Long(pass.getPassivations()));
      
      // now remove Austria and confirm that it's not in cache or loader
      cache.remove(key);
      assertFalse("Retrieval error: did not expect to find node " + key + " in cache", cache.exists(key));
      assertFalse("Retrieval error: did not expect to find node " + key + " in loader", cl.exists(key));
      
      // verify statistics after removing entry - should be unchanged
      assertEquals("CacheLoaderLoads count error: ", new Long(1), new Long(act.getCacheLoaderLoads()));
      assertEquals("CacheLoaderMisses count error: ", new Long(miss), new Long(act.getCacheLoaderMisses()));
      assertEquals("Activations count error: ", new Long(1), new Long(act.getActivations()));
      assertEquals("Passivations count error: ", new Long(1), new Long(pass.getPassivations()));
      
      // now try retrieving attributes again - each attempt should fail and cause a miss since node is now removed
      assertNull("Retrieval error: did not expect to retrieve " + CAPITAL + " for " + key, cache.get(key, CAPITAL));
      assertNull("Retrieval error: did not expect to retrieve " + CURRENCY + " for " + key, cache.get(key, CURRENCY));
      
      // verify statistics after trying to retrieve removed node's attributes - should be two more misses
      miss += 2;
      assertEquals("CacheLoaderLoads count error: ", new Long(1), new Long(act.getCacheLoaderLoads()));
      assertEquals("CacheLoaderMisses count error: ", new Long(miss), new Long(act.getCacheLoaderMisses()));
      assertEquals("Activations count error: ", new Long(1), new Long(act.getActivations()));
      assertEquals("Passivations count error: ", new Long(1), new Long(pass.getPassivations()));
      
      // add a new node and two attributes - this should cause a miss only
      miss++;
      cache.put("Europe/Poland", new HashMap());
      cache.put("Europe/Poland", CAPITAL, "Warsaw");
      cache.put("Europe/Poland", CURRENCY, "Zloty");
      assertEquals("CacheLoaderLoads count error: ", new Long(1), new Long(act.getCacheLoaderLoads()));
      assertEquals("CacheLoaderMisses count error: ", new Long(miss), new Long(act.getCacheLoaderMisses()));
      assertEquals("Activations count error: ", new Long(1), new Long(act.getActivations()));
      assertEquals("Passivations count error: ", new Long(1), new Long(pass.getPassivations()));
      
      // evict Poland - this will cause a passivation
      key = Fqn.fromString("Europe/Poland");
      cache.evict(key);
      assertEquals("CacheLoaderLoads count error: ", new Long(1), new Long(act.getCacheLoaderLoads()));
      assertEquals("CacheLoaderMisses count error: ", new Long(miss), new Long(act.getCacheLoaderMisses()));
      assertEquals("Activations count error: ", new Long(1), new Long(act.getActivations()));
      assertEquals("Passivations count error: ", new Long(2), new Long(pass.getPassivations()));
      
      // retrieve a valid attribute - this will cause an activation and a load
      assertNotNull("Retrieval error: expected to retrieve " + CURRENCY + " for " + key, cache.get(key, CURRENCY));
      assertEquals("CacheLoaderLoads count error: ", new Long(2), new Long(act.getCacheLoaderLoads()));
      assertEquals("CacheLoaderMisses count error: ", new Long(miss), new Long(act.getCacheLoaderMisses()));
      assertEquals("Activations count error: ", new Long(2), new Long(act.getActivations()));
      assertEquals("Passivations count error: ", new Long(2), new Long(pass.getPassivations()));
      
      // evict again - causing another passivation
      cache.evict(key);
      assertEquals("CacheLoaderLoads count error: ", new Long(2), new Long(act.getCacheLoaderLoads()));
      assertEquals("CacheLoaderMisses count error: ", new Long(miss), new Long(act.getCacheLoaderMisses()));
      assertEquals("Activations count error: ", new Long(2), new Long(act.getActivations()));
      assertEquals("Passivations count error: ", new Long(3), new Long(pass.getPassivations()));
      
      // retrieve an invalid attribute - this will cause an activation and a load
      assertNull("Retrieval error: did not expect to retrieve " + AREA + " for " + key, cache.get(key, AREA));
      assertEquals("CacheLoaderLoads count error: ", new Long(3), new Long(act.getCacheLoaderLoads()));
      assertEquals("CacheLoaderMisses count error: ", new Long(miss), new Long(act.getCacheLoaderMisses()));
      assertEquals("Activations count error: ", new Long(3), new Long(act.getActivations()));
      assertEquals("Passivations count error: ", new Long(3), new Long(pass.getPassivations()));
      
      // reset statistics
      act.resetStatistics();
      pass.resetStatistics();
     
      // check the statistics again
      assertEquals("CacheLoaderLoads count error after reset: ", new Long(0), new Long(act.getCacheLoaderLoads()));
      assertEquals("CacheLoaderMisses count error after reset: ", new Long(0), new Long(act.getCacheLoaderMisses()));
      assertEquals("Activations count error: ", new Long(0), new Long(act.getActivations()));
      assertEquals("Passivations count error: ", new Long(0), new Long(pass.getPassivations()));
   }
   
   private void loadCache(TreeCache cache) throws Exception
   {
      cache.put(EUROPE_NODE, new HashMap());
      cache.put("Europe/Austria", new HashMap());      
      cache.put("Europe/Austria", CAPITAL, "Vienna");      
      cache.put("Europe/Austria", CURRENCY, "Euro");      
      cache.put("Europe/Austria", POPULATION, new Integer(8184691));
      
      cache.put("Europe/England", new HashMap());
      cache.put("Europe/England", CAPITAL, "London");
      cache.put("Europe/England", CURRENCY, "British Pound");
      cache.put("Europe/England", POPULATION, new Integer(60441457));
      
      HashMap albania = new HashMap(4);
      albania.put(CAPITAL, "Tirana");
      albania.put(CURRENCY, "Lek");
      albania.put(POPULATION, new Integer(3563112));
      albania.put(AREA, new Integer(28748));
      cache.put("Europe/Albania", albania);
      
      HashMap hungary = new HashMap(4);
      hungary.put(CAPITAL, "Budapest");
      hungary.put(CURRENCY, "Forint");
      hungary.put(POPULATION, new Integer(10006835));
      hungary.put(AREA, new Integer(93030));
      cache.put("Europe/Hungary", hungary);      
   }
   
   private TreeCache createCache() throws Exception
   {
      TreeCache cache = new TreeCache();
      cache.setCacheMode(TreeCache.LOCAL);
      cache.setCacheLoaderConfiguration(getCacheLoaderConfig("location="+getTempDir()));
      cache.setUseInterceptorMbeans(true);
      cache.createService();
      cache.startService();
      return cache;
   }
   
   private ActivationInterceptor getActivationInterceptor(TreeCache cache)
   {
      List interceptors = cache.getInterceptors();
      if (interceptors.isEmpty())
         return null;
     
     for (int i = 0; i < interceptors.size(); i++)
     {
        Object o = interceptors.get(i);
        if (o instanceof ActivationInterceptor)
           return (ActivationInterceptor)o;
     }
     return null;
   }
   
   private PassivationInterceptor getPassivationInterceptor(TreeCache cache)
   {
      List interceptors = cache.getInterceptors();
      if (interceptors.isEmpty())
         return null;
     
     for (int i = 0; i < interceptors.size(); i++)
     {
        Object o = interceptors.get(i);
        if (o instanceof PassivationInterceptor)
           return (PassivationInterceptor)o;
     }
     return null;
   }
   
   private Element getCacheLoaderConfig(String properties) throws Exception
   {
      String xml = "<config>\n" +
         "<passivation>true</passivation>\n" +
         "<preload></preload>\n" +
         "<shared>false</shared>\n" + 
         "<cacheloader>\n" +
         "<class>org.jboss.cache.loader.FileCacheLoader</class>\n" +
         "<properties>" + properties + "</properties>\n" +
         "<async>false</async>\n" +
         "<fetchPersistentState>false</fetchPersistentState>\n" +
         "<ignoreModifications>false</ignoreModifications>\n" +
         "</cacheloader>\n" +
         "</config>";
      return XmlHelper.stringToElement(xml);
   }
   
   private String getTempDir()
   {
      return System.getProperty("java.io.tempdir", "/tmp");
   }

   public static Test suite()
   {
      return new TestSuite(PassivationTest.class);
   }

}
