package org.jboss.cache.mgmt;

import junit.framework.Test;
import junit.framework.TestCase;
import junit.framework.TestSuite;
import org.jboss.cache.Fqn;
import org.jboss.cache.PropertyConfigurator;
import org.jboss.cache.TreeCache;
import org.jboss.cache.TreeCacheListener;
import org.jboss.cache.interceptors.CacheMgmtInterceptor;
import org.jboss.cache.loader.CacheLoader;
import org.jboss.cache.xml.XmlHelper;
import org.w3c.dom.Element;
import org.jgroups.View;

import javax.management.MBeanServer;
import javax.management.MBeanServerFactory;
import javax.management.Notification;
import javax.management.NotificationListener;
import javax.management.ObjectName;
import java.util.HashMap;
import java.util.List;
import java.util.ArrayList;

/**
 * Functional tests for CacheMgmtInterceptor broadcast of cache event notifications
 * @author Jerry Gauthier
 * @version $Id: NotificationTest.java,v 1.2 2006/06/15 23:33:56 msurtani Exp $
 */
public class NotificationTest extends TestCase
{
   private static final String MGMT_SERVICE = ",treecache-interceptor=CacheMgmtInterceptor";
   private static final String CLUSTER_NAME = "NotificationTestCluster";

   private static final String CAPITAL = "capital";
   private static final String CURRENCY = "currency";
   private static final String POPULATION = "population";
   private static final String EUROPE_NODE = "Europe";

   private boolean m_cacheStarted = false;
   private boolean m_cacheStopped = false;
   private boolean m_nodeCreated = false;
   private boolean m_nodeEvicted = false;
   private boolean m_nodeLoaded = false;
   private boolean m_nodeModified = false;
   private boolean m_nodeRemoved = false;
   private boolean m_nodeVisited = false;
   private boolean m_viewChange = false;
   private boolean m_nodeActivatePre = false;
   private boolean m_nodeActivatePost = false;
   private boolean m_nodeEvictPre = false;
   private boolean m_nodeEvictPost = false;
   private boolean m_nodeModifyPre = false;
   private boolean m_nodeModifyPost = false;
   private boolean m_nodePassivatePre = false;
   private boolean m_nodePassivatePost = false;
   private boolean m_nodeRemovePre = false;
   private boolean m_nodeRemovePost = false;

   private MBeanServer m_server;

   TreeCache cache = null;
   boolean optimistic = false;

   protected void setUp() throws Exception
   {
      super.setUp();
      m_server = MBeanServerFactory.createMBeanServer();
      cache = createCache(CLUSTER_NAME);
   }

   protected void tearDown() throws Exception
   {
      super.tearDown();
      if(cache != null)
      {
         // stop the cache before the listener is unregistered
         //cache1.stopService();
         cache.destroyService();
         cache = null;
      }
   }

   public void testNotifications() throws Exception
   {
      assertNotNull("MBeanServer is null.", m_server);
      assertNotNull("Cache is null.", cache);

      MyListener listener = new MyListener();

      ObjectName mgmt = new ObjectName(cache.getServiceName()+MGMT_SERVICE);
      m_server.addNotificationListener(mgmt, listener, null, null);

      // start the cache after registering listener - this will trigger CacheStarted
      // since cache is defined with cluster, thiswill also trigger ViewChange
      cache.startService();

      // add a node - this will trigger NodeCreated, NodeModify(pre/post) and NodeModified
      HashMap albania = new HashMap(4);
      albania.put(CAPITAL, "Tirana");
      albania.put(CURRENCY, "Lek");
      cache.put("Europe/Albania", albania);

      // modify a node - this will trigger NodeModified and NodeModify(pre/post)
      cache.put("Europe/Albania", POPULATION, new Integer(3563112));

      // retrieve an attribute - this will trigger NodeVisited
      Fqn key = Fqn.fromString("Europe/Albania");
      assertNotNull("Retrieval error: expected to retrieve " + CURRENCY + " for " + key, cache.get(key, CURRENCY));

      // evict the node - this will trigger NodePassivate, NodeEvicted and NodeEvict(pre/post)
      cache.evict(key);

      // retrieve the attribute again - this will trigger NodeVisited and NodeActivate
      assertNotNull("Retrieval error: expected to retrieve " + CURRENCY + " for " + key, cache.get(key, CURRENCY));

      // remove the node - this will trigger NodeRemoved and NodeRemove(pre/post)
      cache.remove(key);

      // clean up before stopping  the cache
      CacheLoader cl = cache.getCacheLoader();
      cl.remove(Fqn.fromString(EUROPE_NODE));

      // stop the cache
      cache.stopService();
      m_server.removeNotificationListener(mgmt, listener);

      // run the tests
      assertTrue("Expected CacheStarted notification", m_cacheStarted);
      assertTrue("Expected CacheStopped notification", m_cacheStopped);
      assertTrue("Expected NodeCreated notification", m_nodeCreated);
      assertTrue("Expected NodeEvicted notification", m_nodeEvicted);
      assertTrue("Expected NodeLoaded notification", m_nodeLoaded);
      assertTrue("Expected NodeModified notification", m_nodeModified);
      assertTrue("Expected NodeRemoved notification", m_nodeRemoved);
      assertTrue("Expected NodeVisited notification", m_nodeVisited);
      // currently only pre=false notifications are emitted by ActivationInterceptor
      //assertTrue("Expected NodeActivate pre=true notification", m_nodeActivatePre);
      assertTrue("Expected NodeActivate pre=false notification", m_nodeActivatePost);
      assertTrue("Expected NodeEvict pre=true notification", m_nodeEvictPre);
      assertTrue("Expected NodeEvict pre=false notification", m_nodeEvictPost);
      assertTrue("Expected NodeModify pre=true notification", m_nodeModifyPre);
      assertTrue("Expected NodeModify pre=false notification", m_nodeModifyPost);
      assertTrue("Expected NodePassivate pre=true notification", m_nodePassivatePre);
      // currently only pre=true notifications are emitted by PassivationInterceptor
      //assertTrue("Expected NodePassivate pre=false notification", m_nodePassivatePost);
      assertTrue("Expected NodeRemove pre=true notification", m_nodeRemovePre);
      assertTrue("Expected NodeRemove pre=false notification", m_nodeRemovePost);
      assertTrue("Expected NodeEvict pre=true notification", m_nodeEvictPre);
      assertTrue("Expected ViewChange notification", m_viewChange);
   }

   public void testModifyNotification() throws Exception
   {
       final List notifications = new ArrayList();
       assertNotNull("MBeanServer is null.", m_server);
       assertNotNull("Cache is null.", cache);

       TreeCacheListener listener = new TreeCacheListener()
       {

           public void nodeCreated(Fqn fqn)
           {
               notifications.add("Created " + fqn);
           }

           public void nodeRemoved(Fqn fqn)
           {
               notifications.add("Removed " + fqn);
           }

           public void nodeLoaded(Fqn fqn)
           {
               notifications.add("Loaded " + fqn);
           }

           public void nodeEvicted(Fqn fqn)
           {
               notifications.add("Evicted " + fqn);
           }

           public void nodeModified(Fqn fqn)
           {
               notifications.add("Modified " + fqn);
           }

           public void nodeVisited(Fqn fqn)
           {
               notifications.add("Visited " + fqn);
           }

           public void cacheStarted(TreeCache cache)
           {
               notifications.add("Started");
           }

           public void cacheStopped(TreeCache cache)
           {
               notifications.add("Stopped");
           }

           public void viewChange(View new_view)  // might be MergeView after merging
           {
           }
       };

       cache.addTreeCacheListener(listener);
       cache.startService();

       cache.put("/hello", "k", "v");
       assertEquals("Started", notifications.remove(0));
       assertEquals("Created /hello", notifications.remove(0));
       assertEquals("Modified /hello", notifications.remove(0));
       assertEquals(0, notifications.size());

       cache.get("/hello", "k");
       assertEquals("Visited /hello", notifications.remove(0));
       assertEquals(0, notifications.size());

       cache.put("/hello", "k", "v2");
       assertEquals("Modified /hello", notifications.remove(0));
       assertEquals(0, notifications.size());
   }

   private TreeCache createCache(String clusterName) throws Exception
   {
      TreeCache cache = new TreeCache();
      PropertyConfigurator config=new PropertyConfigurator();
      config.configure(cache, "META-INF/replSync-service.xml");
      cache.setCacheMode(TreeCache.REPL_SYNC);
      cache.setCacheLoaderConfiguration(getCacheLoaderConfig("location="+getTempDir()));
      cache.setUseInterceptorMbeans(true);
      cache.setClusterName(clusterName);
      if (optimistic)
      {
          cache.setTransactionManagerLookupClass("org.jboss.cache.DummyTransactionManagerLookup");
          cache.setNodeLockingScheme("OPTIMISTIC");
      }
      cache.createService();
      // start the cache after the listener has been registered
      //cache.startService();
      return cache;
   }

   private String getTempDir()
   {
      return System.getProperty("java.io.tempdir", "/tmp");
   }

   private boolean getPre(Object data)
   {
      assertNotNull("User data is null, should be Object[]", data);
      assertTrue("User data is " + data.getClass().getName() + ", should be Object[]", data instanceof Object[]);

      Object[] parms = (Object[])data;
      assertTrue("Parameter is " + parms[1].getClass().getName() + ", should be Boolean", parms[1] instanceof Boolean);
      Boolean pre = (Boolean)parms[1];
      return pre.booleanValue();
   }

   private Element getCacheLoaderConfig(String properties) throws Exception
   {
      String xml = "<config>\n" +
         "<passivation>true</passivation>\n" +
         "<preload></preload>\n" +
         "<shared>true</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);
   }

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

   private class MyListener implements NotificationListener
   {
      public void handleNotification(Notification notification, Object handback)
      {
         String type = notification.getType();
         Object userData = notification.getUserData();

         if (type.equals(CacheMgmtInterceptor.NOTIF_CACHE_STARTED))
            m_cacheStarted = true;
         else if (type.equals(CacheMgmtInterceptor.NOTIF_CACHE_STOPPED))
            m_cacheStopped = true;
         else if (type.equals(CacheMgmtInterceptor.NOTIF_NODE_CREATED))
            m_nodeCreated = true;
         else if (type.equals(CacheMgmtInterceptor.NOTIF_NODE_EVICTED))
            m_nodeEvicted = true;
         else if (type.equals(CacheMgmtInterceptor.NOTIF_NODE_LOADED))
            m_nodeLoaded = true;
         else if (type.equals(CacheMgmtInterceptor.NOTIF_NODE_MODIFIED))
            m_nodeModified = true;
         else if (type.equals(CacheMgmtInterceptor.NOTIF_NODE_REMOVED))
            m_nodeRemoved = true;
         else if (type.equals(CacheMgmtInterceptor.NOTIF_NODE_VISITED))
            m_nodeVisited = true;
         else if (type.equals(CacheMgmtInterceptor.NOTIF_VIEW_CHANGE))
            m_viewChange = true;
         else if (type.equals(CacheMgmtInterceptor.NOTIF_NODE_ACTIVATE))
            if (getPre(userData))
               m_nodeActivatePre = true;
            else
               m_nodeActivatePost = true;
         else if (type.equals(CacheMgmtInterceptor.NOTIF_NODE_EVICT))
            if (getPre(userData))
               m_nodeEvictPre = true;
            else
               m_nodeEvictPost = true;
         else if (type.equals(CacheMgmtInterceptor.NOTIF_NODE_MODIFY))
            if (getPre(userData))
               m_nodeModifyPre = true;
            else
               m_nodeModifyPost = true;
         else if (type.equals(CacheMgmtInterceptor.NOTIF_NODE_PASSIVATE))
            if (getPre(userData))
               m_nodePassivatePre = true;
            else
               m_nodePassivatePost = true;
         else if (type.equals(CacheMgmtInterceptor.NOTIF_NODE_REMOVE))
            if (getPre(userData))
               m_nodeRemovePre = true;
            else
               m_nodeRemovePost = true;
      }
   }

}
