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

import junit.framework.Assert;
import junit.framework.Test;
import junit.framework.TestCase;
import junit.framework.TestSuite;
import junit.textui.TestRunner;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jboss.cache.Fqn;
import org.jboss.cache.TreeCache;
import org.jboss.cache.misc.TestingUtil;
import org.jboss.cache.xml.XmlHelper;
import org.w3c.dom.Element;

import javax.transaction.RollbackException;
import javax.transaction.Transaction;
import javax.transaction.TransactionManager;
import java.io.File;

/**
 * Tests the async interceptor
 *
 * @author <a href="mailto:manik@jboss.org">Manik Surtani (manik@jboss.org)</a>
 */
public class InvalidationInterceptorTest extends TestCase
{

    private static Log log = LogFactory.getLog(InvalidationInterceptorTest.class);

    public void testPessimisticNonTransactional() throws Exception
    {
        TreeCache cache1 = createCache(false);
        TreeCache cache2 = createCache(false);

        Fqn fqn = Fqn.fromString("/a/b");
        cache1.put(fqn, "key", "value");

        // test that this has NOT replicated, but rather has been invalidated:
        Assert.assertEquals("value", cache1.get(fqn, "key"));
        Assert.assertNull("Should NOT have replicated!", cache2.get(fqn));

        log.info("***** Node not replicated, as expected.");

        // now make sure cache2 is in sync with cache1:
        cache2.put(fqn, "key", "value");
        Assert.assertNull("Should be null", cache1.get(fqn));
        Assert.assertEquals("value", cache2.get(fqn, "key"));

        // now test the invalidation:
        cache1.put(fqn, "key2", "value2");
        Assert.assertEquals("value2", cache1.get(fqn, "key2"));
        Assert.assertNull("Should have been invalidated!", cache2.get(fqn));

        // clean up.
        cache1.stopService();
        cache2.stopService();
        cache1 = null;
        cache2 = null;
    }

    public void testUnnecessaryEvictions() throws Exception
    {
        TreeCache cache1 = createCache(false);
        TreeCache cache2 = createCache(false);

        Fqn fqn1 = Fqn.fromString("/a/b/c");
        Fqn fqn2 = Fqn.fromString("/a/b/d");

        cache1.put(fqn1, "hello", "world");

        assertEquals("world", cache1.get(fqn1, "hello"));
        assertNull(cache2.get(fqn1, "hello"));

        cache2.put(fqn2, "hello", "world");
        assertEquals("world", cache1.get(fqn1, "hello"));
        assertNull(cache2.get(fqn1, "hello"));
        assertEquals("world", cache2.get(fqn2, "hello"));
        assertNull(cache1.get(fqn2, "hello"));

        cache2.put(fqn1, "hello", "world");
        assertEquals("world", cache2.get(fqn1, "hello"));
        assertEquals("world", cache2.get(fqn2, "hello"));
        assertNull(cache1.get(fqn1, "hello"));
        assertNull(cache1.get(fqn2, "hello"));

        cache1.stopService();
        cache2.stopService();
        cache1 = null;
        cache2 = null;

    }



    public void testPessimisticNonTransactionalAsync() throws Exception
    {
        TreeCache cache1 = createUnstartedCache( false );
        TreeCache cache2 = createUnstartedCache( false );
        cache1.setCacheMode(TreeCache.INVALIDATION_ASYNC);
        cache2.setCacheMode(TreeCache.INVALIDATION_ASYNC);
        cache1.startService();
        cache2.startService();

        Fqn fqn = Fqn.fromString("/a/b");
        cache1.put(fqn, "key", "value");
        TestingUtil.sleepThread(500);  // give it time to broadcast the evict call
        // test that this has NOT replicated, but rather has been invalidated:
        Assert.assertEquals("value", cache1.get(fqn, "key"));
        Assert.assertNull("Should NOT have replicated!", cache2.get(fqn));

        log.info("***** Node not replicated, as expected.");

        // now make sure cache2 is in sync with cache1:
        cache2.put(fqn, "key", "value");
        TestingUtil.sleepThread(500);  // give it time to broadcast the evict call
        Assert.assertNull("Should be null", cache1.get(fqn));
        Assert.assertEquals("value", cache2.get(fqn, "key"));

        // now test the invalidation:
        cache1.put(fqn, "key2", "value2");
        Assert.assertEquals("value2", cache1.get(fqn, "key2"));
        TestingUtil.sleepThread(500);  // give it time to broadcast the evict call
        Assert.assertNull("Should have been invalidated!", cache2.get(fqn));

        // clean up.
        cache1.stopService();
        cache2.stopService();
        cache1 = null;
        cache2 = null;
    }


    public void testPessimisticTransactional() throws Exception
    {
        TreeCache cache1 = createCache(false);
        TreeCache cache2 = createCache(false);

        Fqn fqn = Fqn.fromString("/a/b");
        cache1.put(fqn, "key", "value");

        // test that this has NOT replicated, but rather has been invalidated:
        Assert.assertEquals("value", cache1.get(fqn, "key"));
        Assert.assertNull("Should NOT have replicated!", cache2.get(fqn));

        log.info("***** Node not replicated, as expected.");

        // now make sure cache2 is in sync with cache1:
        // make sure this is in a tx
        TransactionManager txm = cache2.getTransactionManager();
        Assert.assertEquals("value", cache1.get(fqn, "key"));

        txm.begin();
        cache2.put(fqn, "key", "value");
        Assert.assertEquals("value", cache2.get(fqn, "key"));
        txm.commit();

        Assert.assertNull("Should be null", cache1.get(fqn));
        Assert.assertEquals("value", cache2.get(fqn, "key"));

        // now test the invalidation again
        txm = cache1.getTransactionManager();
        Assert.assertEquals("value", cache2.get(fqn, "key"));

        txm.begin();
        cache1.put(fqn, "key2", "value2");
        Assert.assertEquals("value2", cache1.get(fqn, "key2"));
        txm.commit();

        Assert.assertEquals("value2", cache1.get(fqn, "key2"));
        Assert.assertNull("Should have been invalidated!", cache2.get(fqn));

        // test a rollback
        txm = cache2.getTransactionManager();
        Assert.assertEquals("value2", cache1.get(fqn, "key2"));

        txm.begin();
        cache2.put(fqn, "key", "value");
        Assert.assertEquals("value", cache2.get(fqn, "key"));
        txm.rollback();

        Assert.assertEquals("value2", cache1.get(fqn, "key2"));
        Assert.assertNull("Should not have committed", cache2.get(fqn));

        // clean up.
        cache1.stopService();
        cache2.stopService();
        cache1 = null;
        cache2 = null;

    }


    public void testOptSyncUnableToEvict() throws Exception
    {
        TreeCache cache1 = createCache(true);
        TreeCache cache2 = createCache(true);

        Fqn fqn = Fqn.fromString("/a/b");

        cache2.put(fqn, "key", "value");
        Assert.assertEquals("value", cache2.get(fqn, "key"));
        Assert.assertNull(cache1.get(fqn));

        // start a tx that cache1 will have to send out an evict ...
        TransactionManager mgr1 = cache1.getTransactionManager();
        TransactionManager mgr2 = cache2.getTransactionManager();

        mgr1.begin();
        cache1.put(fqn, "key2", "value2");
        Transaction tx1 = mgr1.suspend();
        mgr2.begin();
        cache2.put(fqn, "key3", "value3");
        Transaction tx2 = mgr2.suspend();
        mgr1.resume(tx1);
        // this oughtta fail
        try
        {
            mgr1.commit();
            Assert.assertTrue("Ought to have succeeded!", true);
        }
        catch (RollbackException roll)
        {
            Assert.assertTrue("Ought to have succeeded!", false);
        }

        mgr2.resume(tx2);
        try
        {
            mgr2.commit();
            Assert.assertTrue("Ought to have failed!", false);
        }
        catch (RollbackException roll)
        {
            Assert.assertTrue("Ought to have failed!", true);
        }
        // clean up.
        cache1.stopService();
        cache2.stopService();
        cache1 = null;
        cache2 = null;
    }

    public void testPessTxSyncUnableToEvict() throws Exception
    {
        TreeCache cache1 = createCache(false);
        TreeCache cache2 = createCache(false);

        Fqn fqn = Fqn.fromString("/a/b");

        cache1.put("/a/b", "key", "value");
        Assert.assertEquals("value", cache1.get(fqn, "key"));
        Assert.assertNull(cache2.get(fqn));

        // start a tx that cacahe1 will have to send out an evict ...
        TransactionManager mgr1 = cache1.getTransactionManager();
        TransactionManager mgr2 = cache2.getTransactionManager();

        mgr1.begin();
        cache1.put(fqn, "key2", "value2");
        Transaction tx1 = mgr1.suspend();
        mgr2.begin();
        cache2.put(fqn, "key3", "value3");
        Transaction tx2 = mgr2.suspend();
        mgr1.resume(tx1);
        // this oughtta fail
        try
        {
            mgr1.commit();
            Assert.assertTrue("Ought to have failed!", false);
        }
        catch (RollbackException roll)
        {
            Assert.assertTrue("Ought to have failed!", true);
        }

        mgr2.resume(tx2);
        try
        {
            mgr2.commit();
            Assert.assertTrue("Ought to have succeeded!", true);
        }
        catch (RollbackException roll)
        {
            Assert.assertTrue("Ought to have succeeded!", false);
        }
        // clean up.
        cache1.stopService();
        cache2.stopService();
        cache1 = null;
        cache2 = null;
    }

    public void testPessTxAsyncUnableToEvict() throws Exception
    {
        TreeCache cache1 = createUnstartedCache(false);
        TreeCache cache2 = createUnstartedCache(false);
        cache1.setCacheMode(TreeCache.INVALIDATION_ASYNC);
        cache2.setCacheMode(TreeCache.INVALIDATION_ASYNC);
        cache1.startService();
        cache2.startService();

        Fqn fqn = Fqn.fromString("/a/b");

        cache1.put("/a/b", "key", "value");
        Assert.assertEquals("value", cache1.get(fqn, "key"));
        Assert.assertNull(cache2.get(fqn));

        // start a tx that cacahe1 will have to send out an evict ...
        TransactionManager mgr1 = cache1.getTransactionManager();
        TransactionManager mgr2 = cache2.getTransactionManager();

        mgr1.begin();
        cache1.put(fqn, "key2", "value2");
        Transaction tx1 = mgr1.suspend();
        mgr2.begin();
        cache2.put(fqn, "key3", "value3");
        Transaction tx2 = mgr2.suspend();
        mgr1.resume(tx1);
        // this oughtta fail
        try
        {
            mgr1.commit();
            Assert.assertTrue("Ought to have succeeded!", true);
        }
        catch (RollbackException roll)
        {
            Assert.assertTrue("Ought to have succeeded!", false);
        }

        mgr2.resume(tx2);
        try
        {
            mgr2.commit();
            Assert.assertTrue("Ought to have succeeded!", true);
        }
        catch (RollbackException roll)
        {
            Assert.assertTrue("Ought to have succeeded!", false);
        }
        // clean up.
        cache1.stopService();
        cache2.stopService();
        cache1 = null;
        cache2 = null;
    }


    public void testOptimistic() throws Exception
    {
        TreeCache cache1 = createCache(true);
        TreeCache cache2 = createCache(true);

        Fqn fqn = Fqn.fromString("/a/b");
        cache1.put(fqn, "key", "value");

        // test that this has NOT replicated, but rather has been invalidated:
        Assert.assertEquals("value", cache1.get(fqn, "key"));
        Assert.assertNull("Should NOT have replicated!", cache2.get(fqn));

        log.info("***** Node not replicated, as expected.");

        // now make sure cache2 is in sync with cache1:
        cache2.put(fqn, "key", "value");
        Assert.assertNull("Should be null", cache1.get(fqn));
        Assert.assertEquals("value", cache2.get(fqn, "key"));

        // now test the invalidation:
        cache1.put(fqn, "key2", "value2");
        Assert.assertEquals("value2", cache1.get(fqn, "key2"));
        Assert.assertNull("Should have been invalidated!", cache2.get(fqn));

        // with tx's
        TransactionManager txm = cache2.getTransactionManager();

        txm.begin();
        cache2.put(fqn, "key", "value");
        Assert.assertEquals("value", cache2.get(fqn, "key"));
        Assert.assertEquals("value2", cache1.get(fqn, "key2"));
        txm.commit();

        Assert.assertNull("Should be null", cache1.get(fqn));
        Assert.assertEquals("value", cache2.get(fqn, "key"));

        // now test the invalidation again
        txm = cache1.getTransactionManager();

        txm.begin();
        cache1.put(fqn, "key2", "value2");
        Assert.assertEquals("value", cache2.get(fqn, "key"));
        Assert.assertEquals("value2", cache1.get(fqn, "key2"));
        txm.commit();

        Assert.assertEquals("value2", cache1.get(fqn, "key2"));
        Assert.assertNull("Should have been invalidated!", cache2.get(fqn));

        // test a rollback
        txm = cache2.getTransactionManager();

        txm.begin();
        cache2.put(fqn, "key", "value");
        Assert.assertEquals("value2", cache1.get(fqn, "key2"));
        Assert.assertEquals("value", cache2.get(fqn, "key"));
        txm.rollback();

        Assert.assertEquals("value2", cache1.get(fqn, "key2"));
        Assert.assertNull("Should not have committed", cache2.get(fqn));

        // clean up.
        cache1.stopService();
        cache2.stopService();
        cache1 = null;
        cache2 = null;

    }

    public void testPessimisticNonTransactionalWithCacheLoader() throws Exception
    {
        TreeCache[] caches = createCachesWithSharedCL(false);

        Fqn fqn = Fqn.fromString("/a/b");
        caches[0].put(fqn, "key", "value");

        Assert.assertEquals("value", caches[0].get(fqn, "key"));
        Assert.assertEquals("value", caches[1].get(fqn, "key"));

        // now make sure cache2 is in sync with cache1:
        caches[1].put(fqn, "key", "value");
        Assert.assertEquals("value", caches[1].get(fqn, "key"));
        Assert.assertEquals("value", caches[0].get(fqn, "key"));

        // now test the invalidation:
        caches[0].put(fqn, "key2", "value2");
        Assert.assertEquals("value2", caches[0].get(fqn, "key2"));
        Assert.assertEquals("value2", caches[1].get(fqn, "key2"));
        Assert.assertEquals("value", caches[0].get(fqn, "key"));
        Assert.assertEquals("value", caches[1].get(fqn, "key"));

        // clean up.
        caches[0].remove( fqn );
        caches[1].remove( fqn );
        caches[0].stopService();
        caches[1].stopService();
        caches[0] = null;
        caches[1] = null;
    }

    public void testPessimisticTransactionalWithCacheLoader() throws Exception
    {
        TreeCache[] caches = createCachesWithSharedCL(false);

        Fqn fqn = Fqn.fromString("/a/b");
        TransactionManager mgr = caches[0].getTransactionManager();
        Assert.assertNull("Should be null", caches[0].get(fqn, "key"));
        Assert.assertNull("Should be null", caches[1].get(fqn, "key"));
        mgr.begin();
        caches[0].put(fqn, "key", "value");
        Assert.assertEquals("value", caches[0].get(fqn, "key"));
        mgr.commit();
        Assert.assertEquals("value", caches[1].get(fqn, "key"));
        Assert.assertEquals("value", caches[0].get(fqn, "key"));

        mgr.begin();
        caches[0].put(fqn, "key2", "value2");
        Assert.assertEquals("value2", caches[0].get(fqn, "key2"));
        mgr.rollback();
        Assert.assertEquals("value", caches[1].get(fqn, "key"));
        Assert.assertEquals("value", caches[0].get(fqn, "key"));
        Assert.assertNull("Should be null", caches[0].get(fqn, "key2"));
        Assert.assertNull("Should be null", caches[1].get(fqn, "key2"));

        // clean up.
        caches[0].remove(fqn);
        caches[1].remove(fqn);
        caches[0].stopService();
        caches[1].stopService();
        caches[0] = null;
        caches[1] = null;
    }

    public void testOptimisticWithCacheLoader() throws Exception
    {
        TreeCache[] caches = createCachesWithSharedCL(true);

        Fqn fqn = Fqn.fromString("/a/b");
        TransactionManager mgr = caches[0].getTransactionManager();
        Assert.assertNull("Should be null", caches[0].get(fqn, "key"));
        Assert.assertNull("Should be null", caches[1].get(fqn, "key"));
        mgr.begin();
        caches[0].put(fqn, "key", "value");
        Assert.assertEquals("value", caches[0].get(fqn, "key"));
        Assert.assertNull("Should be null", caches[1].get(fqn, "key"));
        mgr.commit();
        Assert.assertEquals("value", caches[1].get(fqn, "key"));
        Assert.assertEquals("value", caches[0].get(fqn, "key"));

        mgr.begin();
        caches[0].put(fqn, "key2", "value2");
        Assert.assertEquals("value2", caches[0].get(fqn, "key2"));
        Assert.assertNull("Should be null", caches[1].get(fqn, "key2"));
        mgr.rollback();
        Assert.assertEquals("value", caches[1].get(fqn, "key"));
        Assert.assertEquals("value", caches[0].get(fqn, "key"));
        Assert.assertNull("Should be null", caches[0].get(fqn, "key2"));
        Assert.assertNull("Should be null", caches[1].get(fqn, "key2"));

        // clean up.
        caches[0].remove(fqn);
        caches[1].remove(fqn);
        caches[0].stopService();
        caches[1].stopService();
        caches[0] = null;
        caches[1] = null;
    }

    public void testInvalidationWithRegionBasedMarshalling() throws Exception
    {
        doRegionBasedTest(false);
    }

    public void testInvalidationWithRegionBasedMarshallingOptimistic() throws Exception
    {
        doRegionBasedTest(true);
    }

    protected void doRegionBasedTest(boolean optimistic) throws Exception
    {
        TreeCache[] caches = new TreeCache[2];
        caches[0] = createUnstartedCache(false);
        caches[1] = createUnstartedCache(false);

        caches[0].setUseRegionBasedMarshalling(true);
        caches[1].setUseRegionBasedMarshalling(true);

        if (optimistic)
        {
            caches[0].setNodeLockingScheme("OPTIMISTIC");
            caches[1].setNodeLockingScheme("OPTIMISTIC");
        }

        caches[0].startService();
        caches[1].startService();

        TestingUtil.blockUntilViewsReceived(caches, 5000);

        Fqn fqn = Fqn.fromString("/a/b");

        assertNull("Should be null", caches[0].get(fqn));
        assertNull("Should be null", caches[1].get(fqn));

        caches[0].put(fqn, "key", "value");
        assertEquals("expecting value", "value", caches[0].get(fqn, "key"));
        assertNull("Should be null", caches[1].get(fqn));

        // now put in caches[1], should fire an eviction
        caches[1].put(fqn, "key", "value2");
        assertEquals("expecting value2", "value2", caches[1].get(fqn, "key"));
        assertNull("Should be null", caches[0].get(fqn));

        // clean up.
        caches[0].remove(fqn);
        caches[1].remove(fqn);
        caches[0].stopService();
        caches[1].stopService();
        caches[0] = null;
        caches[1] = null;
    }

    protected TreeCache createUnstartedCache(boolean optimistic) throws Exception
    {
        TreeCache cache = new TreeCache("MyCluster", null, 3000);
        cache.setCacheMode(TreeCache.INVALIDATION_SYNC);
        if (optimistic) cache.setNodeLockingScheme("OPTIMISTIC");
        cache.setTransactionManagerLookupClass("org.jboss.cache.DummyTransactionManagerLookup");
        return cache;
    }
    protected TreeCache createCache(boolean optimistic) throws Exception
    {
        TreeCache cache = createUnstartedCache(optimistic);
        cache.startService();
        return cache;
    }

    protected TreeCache[] createCachesWithSharedCL(boolean optimistic) throws Exception
    {
        TreeCache[] caches = new TreeCache[2];
        caches[0] = createUnstartedCache(optimistic);
        caches[1] = createUnstartedCache(optimistic);

        caches[0].setCacheLoaderConfiguration(getCacheLoaderConfig());
        caches[1].setCacheLoaderConfiguration(getCacheLoaderConfig());

        caches[0].startService();
        caches[1].startService();
        return caches;
    }


    protected Element getCacheLoaderConfig() throws Exception
    {
        String xml = "            <config>\n" +
                "                \n" +
                "                <passivation>false</passivation>\n" +
                "                <preload></preload>\n" +
                "\n" +
                "                <cacheloader>\n" +
                "                    <class>org.jboss.cache.loader.FileCacheLoader</class>\n" +
                "                    <properties>\n" +
                "                        location=" + getTempDir() + "\n" +
                "                    </properties>\n" +
                "                    <async>false</async>\n" +
                "                    <fetchPersistentState>false</fetchPersistentState>\n" +
                "                    <ignoreModifications>false</ignoreModifications>\n" +
                "                </cacheloader>\n" +
                "                \n" +
                "            </config>";
        return XmlHelper.stringToElement(xml);
    }

    protected String getTempDir()
    {
        String name = "cacheloader";
        String tempDir = System.getProperty("java.io.tmpdir", "/tmp") + File.separator + "JBossCache-InvalidationInterceptorTestCase-WorkingDir" + File.separator + name;
        File tempDirFile = new File(tempDir);
        if (!tempDirFile.exists())
        {
            tempDirFile.mkdirs();
        }
        return tempDir;
    }

    public static void main(String[] args)
    {
        TestRunner.run(suite());
    }

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