package org.jboss.cache.api;

import org.jboss.cache.Cache;
import org.jboss.cache.CacheSPI;
import org.jboss.cache.DefaultCacheFactory;
import org.jboss.cache.Fqn;
import org.jboss.cache.Node;
import org.jboss.cache.config.Configuration;
import org.jboss.cache.factories.UnitTestCacheConfigurationFactory;
import org.jboss.cache.util.TestingUtil;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;

import javax.transaction.TransactionManager;

/**
 * Tests whether, in a single tx, deleting a parent node with an pre-existing
 * child and then re-adding a node with the parent Fqn results
 * in the pre-existing child remaining in the cache after tx commit.
 *
 * @author Brian Stansberry
 * @since 2.1.0
 */
@Test(groups = {"functional"})
public class DeletedChildResurrectionTest
{
   private Cache<Object, Object> cache;
   private static final Fqn<String> A_B = Fqn.fromString("/a/b");
   private static final Fqn<String> A = Fqn.fromString("/a");
   private static final Fqn<String> A_C = Fqn.fromString("/a/c");
   private static final String KEY = "key";
   private static final String VALUE = "value";
   private static final String K2 = "k2";
   private static final String V2 = "v2";

   @BeforeMethod(alwaysRun = true)
   public void setUp()
   {
      cache = new DefaultCacheFactory<Object, Object>().createCache(UnitTestCacheConfigurationFactory.createConfiguration(Configuration.CacheMode.LOCAL, true), false);
      cache.getConfiguration().setCacheMode(Configuration.CacheMode.LOCAL);
   }

   @AfterMethod(alwaysRun = true)
   public void tearDown() throws Exception
   {
      TestingUtil.killCaches(cache);
   }

   /**
    * Tests whether the deleted child re-appears if the parent node is re-added
    * via a simple node.addChild(parentFqn) call. Pessimistic locking case.
    *
    * @throws Exception
    */
   public void testDeletedChildResurrectionPessimistic1() throws Exception
   {
      deletedChildResurrectionTest1(false);
   }

   /**
    * Tests whether the deleted child re-appears if the parent node is re-added
    * via a simple node.addChild(parentFqn) call. Optimistic locking case.
    *
    * @throws Exception
    */
   public void testDeletedChildResurrectionOptimistic1() throws Exception
   {
      deletedChildResurrectionTest1(true);
   }

   /**
    * Tests whether the deleted child re-appears if the parent node is re-added
    * via a node.addChild(differentChildofParentFqn) call. Pessimistic locking case.
    *
    * @throws Exception
    */
   public void testDeletedChildResurrectionPessimistic2() throws Exception
   {
      deletedChildResurrectionTest2(false);
   }

   /**
    * Tests whether the deleted child re-appears if the parent node is re-added
    * via a node.addChild(differentChildofParentFqn) call. Optimistic locking case.
    *
    * @throws Exception
    */
   public void testDeletedChildResurrectionOptimistic2() throws Exception
   {
      deletedChildResurrectionTest2(true);
   }

   /**
    * Tests whether, in a single tx, deleting a parent node with an pre-existing
    * child and then inserting a different child under the parent Fqn results
    * in the pre-existing child remaining in the cache after tx commit.
    */
   private void deletedChildResurrectionTest1(boolean optimistic) throws Exception
   {
      cache.getConfiguration().setNodeLockingOptimistic(optimistic);
      cache.start();
      CacheSPI spi = (CacheSPI) cache;
      Node<Object, Object> root = cache.getRoot();

      TransactionManager txManager = cache.getConfiguration().getRuntimeConfig().getTransactionManager();

      root.addChild(A_B).put(KEY, VALUE);
      cache.put(A, "key","value");
      txManager.begin();
      root.removeChild(A);
      root.addChild(A);
      txManager.commit();
      assert !root.hasChild(A_B);
      assert null == cache.get(A, "key");
      // do a peek to ensure the node really has been removed and not just marked for removal
      assert spi.peek(A_B, true, true) == null;
      assert root.hasChild(A);
   }

   /**
    * Tests whether, in a single tx, deleting a parent node with an pre-existing
    * child and then inserting a different child under the parent Fqn results
    * in the pre-existing child remaining in the cache after tx commit.
    */
   private void deletedChildResurrectionTest2(boolean optimistic) throws Exception
   {
      cache.getConfiguration().setNodeLockingOptimistic(optimistic);
      cache.start();
      Node<Object, Object> root = cache.getRoot();

      TransactionManager txManager = cache.getConfiguration().getRuntimeConfig().getTransactionManager();

      root.addChild(A_B).put(KEY, VALUE);

      txManager.begin();
      root.removeChild(A);
      root.addChild(A_C).put(K2, V2);
      txManager.commit();

      assert !root.hasChild(A_B);
      assert root.hasChild(A_C);
      assert V2.equals(root.getChild(A_C).get(K2));
   }
}
