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.CacheLoaderInterceptor;
import org.jboss.cache.interceptors.CacheStoreInterceptor;
import org.jboss.cache.loader.CacheLoader;
import org.jboss.cache.xml.XmlHelper;
import org.w3c.dom.Element;

/**
 * Simple functional tests for CacheLoaderInterceptor and CacheStoreInterceptor statistics
 * @author Jerry Gauthier
 * @version $Id: CacheLoaderTest.java 1512 2006-04-07 05:15:03Z genman $
 */
public class CacheLoaderTest 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 testCacheLoaderMgmt() 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.
      CacheLoaderInterceptor loader = getCacheLoaderInterceptor(cache);
      assertNotNull("CacheLoaderInterceptor not found.", loader);
      CacheStoreInterceptor store = getCacheStoreInterceptor(cache);
      assertNotNull("CacheStoreInterceptor not found.", store);
     
      // verify cache loader statistics for entries loaded into cache
      int miss = 0;
      int load = 0;
      int stores = 11;
      assertEquals("CacheLoaderLoads count error: ", new Long(0), new Long(loader.getCacheLoaderLoads()));
      assertEquals("CacheLoaderMisses count error: ", new Long(miss), new Long(loader.getCacheLoaderMisses()));
      assertEquals("CacheLoaderStores count error: ", new Long(stores), new Long(store.getCacheLoaderStores()));
      
      // 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 - misses should still be same since nodes were already loaded
      load++;
      assertEquals("CacheLoaderLoads count error: ", new Long(load), new Long(loader.getCacheLoaderLoads()));
      assertEquals("CacheLoaderMisses count error: ", new Long(miss), new Long(loader.getCacheLoaderMisses()));
      assertEquals("CacheLoaderStores count error: ", new Long(stores), new Long(store.getCacheLoaderStores()));
      
      // 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 for after retrieving entries - misses should now be +1 after attempt to load Poland
      miss++;
      assertEquals("CacheLoaderLoads count error: ", new Long(1), new Long(loader.getCacheLoaderLoads()));
      assertEquals("CacheLoaderMisses count error: ", new Long(miss), new Long(loader.getCacheLoaderMisses()));
      assertEquals("CacheLoaderStores count error: ", new Long(stores), new Long(store.getCacheLoaderStores()));
      
      // 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));
      
      // now try retrieving its 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 should now be +1
      load++;
      assertEquals("CacheLoaderLoads count error: ", new Long(load), new Long(loader.getCacheLoaderLoads()));
      assertEquals("CacheLoaderMisses count error: ", new Long(miss), new Long(loader.getCacheLoaderMisses()));
      assertEquals("CacheLoaderStores count error: ", new Long(stores), new Long(store.getCacheLoaderStores()));
      
      // 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(load), new Long(loader.getCacheLoaderLoads()));
      assertEquals("CacheLoaderMisses count error: ", new Long(miss), new Long(loader.getCacheLoaderMisses()));
      assertEquals("CacheLoaderStores count error: ", new Long(stores), new Long(store.getCacheLoaderStores()));
      
      // 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(load), new Long(loader.getCacheLoaderLoads()));
      assertEquals("CacheLoaderMisses count error: ", new Long(miss), new Long(loader.getCacheLoaderMisses()));
      assertEquals("CacheLoaderStores count error: ", new Long(stores), new Long(store.getCacheLoaderStores()));
      
      // add a new node - this should cause a store
      stores++;
      cache.put("Europe/Poland", new HashMap());
      assertEquals("CacheLoaderLoads count error: ", new Long(load), new Long(loader.getCacheLoaderLoads()));
      assertEquals("CacheLoaderMisses count error: ", new Long(miss), new Long(loader.getCacheLoaderMisses()));
      assertEquals("CacheLoaderStores count error: ", new Long(stores), new Long(store.getCacheLoaderStores()));
      
      // add two attributes - this should cause two stores
      stores+=2;
      cache.put("Europe/Poland", CAPITAL, "Warsaw");
      cache.put("Europe/Poland", CURRENCY, "Zloty");
      assertEquals("CacheLoaderLoads count error: ", new Long(load), new Long(loader.getCacheLoaderLoads()));
      assertEquals("CacheLoaderMisses count error: ", new Long(miss), new Long(loader.getCacheLoaderMisses()));
      assertEquals("CacheLoaderStores count error: ", new Long(stores), new Long(store.getCacheLoaderStores()));
      
      // evict Poland and then try to retrieve an invalid attribute - this will cause a load as the node is restored
      load++;
      key = Fqn.fromString("Europe/Poland");
      cache.evict(key);
      assertNull("Retrieval error: did not expect to retrieve " + AREA + " for " + key, cache.get(key, AREA));
      assertEquals("CacheLoaderLoads count error: ", new Long(load), new Long(loader.getCacheLoaderLoads()));
      assertEquals("CacheLoaderMisses count error: ", new Long(miss), new Long(loader.getCacheLoaderMisses()));
      assertEquals("CacheLoaderStores count error: ", new Long(stores), new Long(store.getCacheLoaderStores()));
      
      // reset statistics
      loader.resetStatistics();
      store.resetStatistics();
     
      // check the statistics again
      assertEquals("CacheLoaderLoads count error after reset: ", new Long(0), new Long(loader.getCacheLoaderLoads()));
      assertEquals("CacheLoaderMisses count error after reset: ", new Long(0), new Long(loader.getCacheLoaderMisses()));
      assertEquals("CacheLoaderStores count error after reset: ", new Long(0), new Long(store.getCacheLoaderStores()));
   }
   
   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 CacheLoaderInterceptor getCacheLoaderInterceptor(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 CacheLoaderInterceptor)
           return (CacheLoaderInterceptor)o;
     }
     return null;
   }
   
   private CacheStoreInterceptor getCacheStoreInterceptor(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 CacheStoreInterceptor)
           return (CacheStoreInterceptor)o;
     }
     return null;
   }
   
   private Element getCacheLoaderConfig(String properties) throws Exception
   {
      String xml = "<config>\n" +
         "<passivation>false</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(CacheLoaderTest.class);
   }

}
