/*
 * Copyright (C) 2009 eXo Platform SAS.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */
package org.exoplatform.services.jcr.api.xa;

import org.exoplatform.services.jcr.JcrAPIBaseTest;
import org.exoplatform.services.jcr.datamodel.ItemData;
import org.exoplatform.services.jcr.datamodel.ItemType;
import org.exoplatform.services.jcr.datamodel.NodeData;
import org.exoplatform.services.jcr.datamodel.QPathEntry;
import org.exoplatform.services.jcr.impl.core.NodeImpl;
import org.exoplatform.services.jcr.impl.core.SessionImpl;
import org.exoplatform.services.transaction.TransactionService;

import java.util.ArrayList;
import java.util.List;

import javax.jcr.Node;
import javax.jcr.PathNotFoundException;
import javax.jcr.Session;
import javax.jcr.SimpleCredentials;
import javax.naming.InitialContext;
import javax.transaction.Transaction;
import javax.transaction.UserTransaction;

/**
 * Created by The eXo Platform SAS. <br>
 * 
 * @author <a href="mailto:geaz@users.sourceforge.net">Gennady Azarenkov</a>
 * @version $Id: TestUserTransaction.java 11907 2008-03-13 15:36:21Z ksm $
 */
public class TestUserTransaction extends JcrAPIBaseTest
{

   private TransactionService txService;

   public void setUp() throws Exception
   {
      super.setUp();
      txService = (TransactionService)container.getComponentInstanceOfType(TransactionService.class);
   }

   private List<Session> openSomeSessions() throws Exception
   {
      assertNotNull(txService);
      List<Session> someSessions = new ArrayList<Session>();

      Session s1 =
         repository.login(new SimpleCredentials("admin", "admin".toCharArray()), session.getWorkspace().getName());
      Node rootS1 = s1.getRootNode();
      rootS1.addNode("someNode1");
      rootS1.save();
      someSessions.add(s1);
      Session s2 =
         repository.login(new SimpleCredentials("exo", "exo".toCharArray()), session.getWorkspace().getName());
      Node rootS2 = s2.getRootNode();
      rootS2.addNode("someNode2");
      rootS2.save();
      someSessions.add(s2);
      Session s3 =
         repository.login(new SimpleCredentials("exo", "exo".toCharArray()), session.getWorkspace().getName());
      Node rootS3 = s3.getRootNode();
      rootS3.addNode("someNode3");
      rootS3.getNode("someNode2").remove();
      rootS3.save();
      someSessions.add(s3);
      Session s4 =
         repository.login(new SimpleCredentials("admin", "admin".toCharArray()), session.getWorkspace().getName());
      Node rootS4 = s4.getRootNode();
      Node n = rootS4.getNode("someNode3");
      n.addNode("someNode4");
      rootS4.getNode("someNode1").remove();
      rootS4.save();
      someSessions.add(s4);

      // some logouts
      session.logout();
      someSessions.add(session);
      s1.logout();

      // ...from setUp()
      session = (SessionImpl)repository.login(credentials, "ws");
      someSessions.add(session);

      workspace = session.getWorkspace();
      root = session.getRootNode();
      valueFactory = session.getValueFactory();

      return someSessions;
   }

   public void testCommit() throws Exception
   {
      assertNotNull(txService);
      List<Session> someSessions = openSomeSessions();
      UserTransaction ut = txService.getUserTransaction();
      ut.begin();
      // we need to create the session within the transaction to ensure that it will be enlisted
      Session session = repository.login(credentials, "ws");
      session.getRootNode().addNode("txcommit");
      session.save();
      assertNotNull(session.getItem("/txcommit"));
      Session s1 =
         repository.login(new SimpleCredentials("admin", "admin".toCharArray()), session.getWorkspace().getName());
      try
      {
         assertNotNull(s1.getItem("/txcommit"));
         fail("PathNotFoundException should have be thrown");
      }
      catch (PathNotFoundException e)
      {
         //ok
      }
      ut.commit();
      assertNotNull(s1.getItem("/txcommit"));

      someSessions.clear();
   }

   public void testRollback() throws Exception
   {
      assertNotNull(txService);
      UserTransaction ut = txService.getUserTransaction();

      ut.begin();
      // we need to create the session within the transaction to ensure that it will be enlisted
      Session session = repository.login(credentials, "ws");
      session.getRootNode().addNode("txrollback");
      session.save();
      assertNotNull(session.getItem("/txrollback"));

      // Session s1 = repository.login(new SimpleCredentials("admin","admin".toCharArray()),
      // session.getWorkspace().getName());
      ut.rollback();
      try
      {
         assertNotNull(session.getItem("/txrollback"));
         fail("PathNotFoundException should have be thrown");
      }
      catch (PathNotFoundException e)
      {
      }
   }

   public void testVersionable() throws Exception
   {
      //System workspace
      testVersionable("ws");
   }

   public void testVersionable2() throws Exception
   {
      //Normal workspace
      testVersionable("ws1");
   }
  
   private void testVersionable(String workspace) throws Exception
   {   
      assertNotNull(txService);
      List<Session> someSessions = openSomeSessions();
      UserTransaction ut = txService.getUserTransaction();
      ut.begin();
      // we need to create the session within the transaction to ensure that it will be enlisted
      Session session = repository.login(credentials, workspace);
      Node node = session.getRootNode().addNode("txcommit");
      node.addMixin("mix:versionable");
      session.save();
      assertNotNull(session.getItem("/txcommit"));
      Session s1 =
         repository.login(new SimpleCredentials("admin", "admin".toCharArray()), session.getWorkspace().getName());
      try
      {
         assertNotNull(s1.getItem("/txcommit"));
         fail("PathNotFoundException should have be thrown");
      }
      catch (PathNotFoundException e)
      {
      }
      node.checkin();
      ut.commit();
      assertNotNull(s1.getItem("/txcommit"));
      Node txNode = (Node)s1.getItem("/txcommit");
      txNode.checkout();
      txNode.checkin();
      txNode.checkout();
      someSessions.clear();
   }  
   // we don't have JNID for JBossTS in standalone now  
   public void _testUserTransactionFromJndi() throws Exception
   {
      assertNotNull(txService);

      InitialContext ctx = new InitialContext();
      Object obj = ctx.lookup("UserTransaction");
      UserTransaction ut = (UserTransaction)obj;

      ut.begin();
      Session s1 =
         repository.login(new SimpleCredentials("admin", "admin".toCharArray()), session.getWorkspace().getName());
      s1.getRootNode().addNode("txcommit1");
      s1.save();
      ut.commit();
      assertNotNull(session.getItem("/txcommit1"));
   }

   public void testReuseUT() throws Exception
   {
      assertNotNull(txService);
      // TODO in JNDI only JOTM today
      //InitialContext ctx = new InitialContext();
      //Object obj = ctx.lookup("UserTransaction");
      //UserTransaction ut = (UserTransaction)obj;

      UserTransaction ut = txService.getUserTransaction();

      ut.begin();

      Session s1 =
         repository.login(new SimpleCredentials("admin", "admin".toCharArray()), session.getWorkspace().getName());

      Node tx2 = s1.getRootNode().addNode("txcommit2");
      ut.commit();

      ut.begin();

      tx2.addNode("txcommit21");
      s1.save();
      ut.commit();
      assertNotNull(session.getItem("/txcommit2/txcommit21"));

   }

   public void testAddRemoveNode() throws Exception
   {
      UserTransaction ut = txService.getUserTransaction();

      root.addNode("Node");
      session.save();

      ut.begin();
      Node node = root.getNode("Node");
      String uuid = ((NodeImpl)node).getIdentifier();
      node.setProperty("name", "value");

      session.save();

      node.remove();
      session.save();

      ItemData itemData = session.getTransientNodesManager().getItemData(uuid);
      boolean hasItemData = session.getTransientNodesManager().hasItemData((NodeData)((NodeImpl)root).getData(), QPathEntry.parse("[]Node:1"),
         ItemType.NODE);
      ut.commit();

      assertNull(itemData);
      assertFalse(hasItemData);
   }

   public void testSaveException() throws Exception
   {
      assertNotNull(txService);
      // TODO in JNDI only JOTM today
      //InitialContext ctx = new InitialContext();
      //Object obj = ctx.lookup("UserTransaction");
      //UserTransaction ut = (UserTransaction)obj;

      UserTransaction ut = txService.getUserTransaction();

      Session s0 =
         repository.login(new SimpleCredentials("root", "exo".toCharArray()), session.getWorkspace().getName());
      Node pretx = s0.getRootNode().addNode("pretx");
      s0.save();

      pretx.remove(); // don't save now

      ut.begin();

      Session s1 =
         repository.login(new SimpleCredentials("admin", "admin".toCharArray()), session.getWorkspace().getName());

      Session s2 =
         repository.login(new SimpleCredentials("admin", "admin".toCharArray()), session.getWorkspace().getName());

      Node tx1 = s1.getRootNode().getNode("pretx").addNode("tx1");

      Node tx2 = s2.getRootNode().getNode("pretx").addNode("tx2");

      // keep this change out of the current Tx, this is necessary since
      // we have now auto-enlistment mechanism that is triggered at session save
      Transaction tx = txService.getTransactionManager().suspend();
      s0.save(); // save that parent of tx1 removed
      txService.getTransactionManager().resume(tx);

      s1.save();
      s2.save();

      try
      {
         ut.commit();
         // internally XAException should be thrown
         fail("Exception should occurs");
      }
      catch (Exception e)
      {
         // ok
      }

      s1.logout();
      s2.logout();

      try
      {
         session.getItem("/pretx/tx1");
         fail("PathNotFoundException should be thrown");
      }
      catch (PathNotFoundException e)
      {
         // ok
      }
   }

   public void testSetRollbackOnly() throws Exception
   {
      assertNotNull(txService);
      UserTransaction ut = txService.getUserTransaction();
      ut.begin();
      // we need to create the session within the transaction to ensure that it will be enlisted
      Session session = repository.login(credentials, "ws");
      session.getRootNode().addNode("txrollback");

      ut.setRollbackOnly();
      try
      {
         session.save();
         fail("IllegalStateException should have be thrown as a save is forbidden after a setRollbackOnly");
      }
      catch (IllegalStateException e)
      {
      }
      
      try
      {
         assertNotNull(session.getItem("/txrollback"));
         fail("PathNotFoundException should have be thrown");
      }
      catch (PathNotFoundException e)
      {
      }
      
      ut.rollback();
      try
      {
         assertNotNull(session.getItem("/txrollback"));
         fail("PathNotFoundException should have be thrown");
      }
      catch (PathNotFoundException e)
      {
      }
   }
}
