/*
 * 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.impl.dataflow.session;

import org.exoplatform.services.jcr.JcrImplBaseTest;
import org.exoplatform.services.jcr.dataflow.ItemState;
import org.exoplatform.services.jcr.datamodel.InternalQName;
import org.exoplatform.services.jcr.datamodel.NodeData;
import org.exoplatform.services.jcr.impl.Constants;
import org.exoplatform.services.jcr.impl.core.DummySession;
import org.exoplatform.services.jcr.impl.core.NodeImpl;
import org.exoplatform.services.jcr.impl.core.SessionDataManager;
import org.exoplatform.services.jcr.impl.core.SessionImpl;
import org.exoplatform.services.jcr.impl.core.SessionRegistry;
import org.exoplatform.services.jcr.impl.dataflow.TransientNodeData;
import org.exoplatform.services.jcr.impl.dataflow.TransientPropertyData;
import org.exoplatform.services.jcr.impl.dataflow.TransientValueData;
import org.exoplatform.services.jcr.util.IdGenerator;

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

import javax.jcr.Credentials;
import javax.jcr.PropertyType;
import javax.jcr.Repository;
import javax.jcr.RepositoryException;

/**
 * Created by The eXo Platform SAS
 * 
 * Jun 13, 2007
 * 
 * @author <a href="mailto:peter.nedonosko@exoplatform.com.ua">Peter Nedonosko</a>
 * @version $Id: SessionChangesLogTest.java 11962 2008-03-16 16:31:14Z gazarenkov $
 */
public class SessionChangesLogTest extends JcrImplBaseTest
{

   // for makeNotThisThreadLog() method, contains SessionChangesLog and its sessionId
   private class SessionChangesLogInfo
   {

      private final SessionChangesLog slog;

      private final String sessionId;

      SessionChangesLogInfo(SessionChangesLog slog, String sessionId)
      {
         this.slog = slog;
         this.sessionId = sessionId;
      }

      public SessionChangesLog getLog()
      {
         return slog;
      }

      public String getSessionId()
      {
         return sessionId;
      }
   }

   @Override
   public void setUp() throws Exception
   {
      super.setUp();

      NodeImpl testRoot = (NodeImpl)root.addNode("sessionLinkRoot");
      session.save();
   }

   @Override
   protected void tearDown() throws Exception
   {

      if (root.hasNode("sessionLinkRoot"))
      {
         NodeImpl testRoot = (NodeImpl)root.getNode("sessionLinkRoot");
         testRoot.remove();
         session.save();
      }

      super.tearDown();
   }

   private SessionChangesLogInfo makeNotThisThreadLog() throws Exception
   {

      final SessionChangesLogInfo[] chlog = new SessionChangesLogInfo[1];

      final Repository frepository = repository;
      final Credentials fcredentials = this.credentials /* session.getCredentials() */;

      Thread thread = new Thread()
      {

         @Override
         public void run()
         {

            SessionImpl userSession;
            try
            {
               userSession = (SessionImpl)frepository.login(fcredentials, "ws");
               chlog[0] = new SessionChangesLogInfo(new SessionChangesLog(userSession), userSession.getId());
            }
            catch (RepositoryException e)
            {
               throw new RuntimeException("testSessionLinkGCedSession(), " + e, e);
            }

            userSession = null;
         }
      };

      thread.start();
      thread.join();

      return chlog[0];
   }

   private SessionImpl getRegisteredSession(String sessionId)
   {

      SessionRegistry sreg = (SessionRegistry)session.getContainer().getComponentInstanceOfType(SessionRegistry.class);

      return sreg.getSession(sessionId);
   }

   public void testSameSession()
   {

      SessionChangesLog chlog = new SessionChangesLog(session);
      assertEquals("Session must be same as given id owns", session, getRegisteredSession(chlog.getSessionId()));
   }

   public void testWithoutSession() throws Exception
   {

      String id = IdGenerator.generate();
      SessionChangesLog chlog = new SessionChangesLog(new DummySession(id));

      assertNull("No session should be linked to the log", getRegisteredSession(chlog.getSessionId()));
   }

   public void testAnotherThreadSession() throws Exception
   {

      SessionChangesLogInfo chlog = makeNotThisThreadLog();
      assertEquals("Session must be same as given id owns", chlog.getSessionId(), getRegisteredSession(
         chlog.getLog().getSessionId()).getId());
   }

   public void testSessionLogout() throws Exception
   {

      // test if WeakHashMap does remove at logout

      SessionChangesLogInfo chlog = makeNotThisThreadLog();

      getRegisteredSession(chlog.getLog().getSessionId()).logout();

      // gc and wait
      System.gc();
      Thread.currentThread().sleep(5000);

      assertNull("No session should be linked to the log", getRegisteredSession(chlog.getLog().getSessionId()));
   }

   public void testMultipleSessionsLogout() throws Exception
   {

      List<SessionChangesLogInfo> logs = new ArrayList<SessionChangesLogInfo>();

      NodeImpl testRoot = (NodeImpl)root.getNode("sessionLinkRoot");

      NodeData parent = (NodeData)testRoot.getData();

      for (int i = 1; i <= 1000; i++)
      {
         SessionChangesLogInfo logInfo = makeNotThisThreadLog();

         SessionChangesLog slog = logInfo.getLog();

         InternalQName qname = InternalQName.parse("[]node" + i);

         TransientNodeData ndata = TransientNodeData.createNodeData(parent, qname, Constants.NT_UNSTRUCTURED);

         // jcr:primaryType
         TransientPropertyData ndpt =
            TransientPropertyData.createPropertyData(ndata, Constants.JCR_PRIMARYTYPE, PropertyType.NAME, false,
               new TransientValueData(ndata.getPrimaryTypeName()));

         slog.add(ItemState.createAddedState(ndata));
         slog.add(ItemState.createAddedState(ndpt));

         SessionDataManager sdm = getRegisteredSession(logInfo.getLog().getSessionId()).getTransientNodesManager();
         sdm.getTransactManager().save(slog); // persist changes

         logs.add(logInfo);
      }

      List<String> sessions = new ArrayList<String>();

      for (Iterator<SessionChangesLogInfo> iter = logs.iterator(); iter.hasNext();)
      {
         SessionChangesLogInfo slog = iter.next();
         iter.remove();

         getRegisteredSession(slog.getLog().getSessionId()).logout();
         // sessions.add(new String(slog.getSessionId()));
      }

      // gc and wait
      System.gc();
      Thread.sleep(5000);

      // ItemDataChangesLogHood registry = new ItemDataChangesLogHood();

      // int linked = 0;
      // for (String sessionId: sessions) {
      // if (registry.isSessionLinked(sessionId))
      // linked++;
      // }

      // assertEquals("No session should be linked ", 0, registry.linkedSessionsCount());

      // assertEquals("No session should be linked ", 0, linked);
   }

   public void testAddRootChanges() throws Exception
   {
      SessionChangesLog changesLog = new SessionChangesLog(session);
      try
      {
         changesLog.add(new ItemState(new TransientPropertyData(Constants.ROOT_PATH, Constants.ROOT_UUID, 0,
            PropertyType.STRING, null, false), ItemState.ADDED, false, Constants.ROOT_PATH));
      }
      catch (Exception e)
      {
         fail("Exception should not be thrown");
      }
   }
}
