/*
 * 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.observation;

import org.exoplatform.services.jcr.JcrAPIBaseTest;
import org.exoplatform.services.jcr.core.CredentialsImpl;
import org.exoplatform.services.jcr.impl.core.RepositoryImpl;
import org.exoplatform.services.jcr.observation.ExtendedEvent;
import org.exoplatform.services.log.Log;

import java.util.Calendar;
import java.util.Map;

import javax.jcr.Node;
import javax.jcr.Property;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
import javax.jcr.observation.Event;
import javax.jcr.observation.EventIterator;
import javax.jcr.observation.EventListener;
import javax.jcr.observation.EventListenerIterator;
import javax.jcr.observation.ObservationManager;

/**
 * Created by The eXo Platform SAS.
 * 
 * @author <a href="mailto:geaz@users.sourceforge.net">Gennady Azarenkov</a>
 * @version $Id: TestObservationManager.java 15053 2008-06-02 10:31:38Z
 *          andrew00x $
 */
public class TestObservationManager extends JcrAPIBaseTest
{

   private static int counter;

   private Node testRoot;

   public void setUp() throws Exception
   {
      super.setUp();
      counter = 0;
      testRoot = root.addNode("testRoot");
      root.save();
   }

   public void tearDown() throws Exception
   {
      EventListenerIterator listeners = this.workspace.getObservationManager().getRegisteredEventListeners();
      while (listeners.hasNext())
      {
         this.workspace.getObservationManager().removeEventListener(listeners.nextEventListener());
      }
      testRoot.remove();
      root.save();
      // testRoot.save();ipossible to call save() on removed node

      super.tearDown();
   }

   public void testObtainObservationManager() throws RepositoryException
   {
      ObservationManager om = this.workspace.getObservationManager();
      assertNotNull(om);

      // Not the same as in ws
      Session session2 = repository.login(credentials, "ws2");
      ObservationManager om2 = session2.getWorkspace().getObservationManager();
      assertNotNull(om2);
      assertFalse(om.equals(om2));
   }

   public void testListenerRegistration() throws RepositoryException
   {
      ObservationManager observationManager = this.workspace.getObservationManager();
      assertEquals(0, observationManager.getRegisteredEventListeners().getSize());
      EventListener listener = new DummyListener(log);
      observationManager.addEventListener(listener, Event.PROPERTY_ADDED | Event.NODE_ADDED, "/", true,
         new String[]{"0"}, new String[]{"nt:base"}, false);
      assertEquals(1, observationManager.getRegisteredEventListeners().getSize());

      // [PN] 16.06.07
      // Listener in observation manager is per session, global listeners is
      // registered in observation
      // registry.
      // Listeners list can be acquired there (Impl level).
      // Session session1 = repository.login(credentials,
      // this.workspace.getName()); // the same ws
      // ObservationManager observationManager1 =
      // session1.getWorkspace().getObservationManager();
      // assertEquals(1,
      // observationManager1.getRegisteredEventListeners().getSize());

      Session session2 = repository.login(credentials, "ws2"); // another ws
      ObservationManager observationManager2 = session2.getWorkspace().getObservationManager();
      assertEquals(0, observationManager2.getRegisteredEventListeners().getSize());

      observationManager.removeEventListener(listener);
      assertEquals(0, observationManager.getRegisteredEventListeners().getSize());
   }

   public void testNodeEventGeneration() throws RepositoryException
   {
      // ObservationManager observationManager =
      // this.workspace.getObservationManager();
      ObservationManager observationManager = repository.getSystemSession("ws").getWorkspace().getObservationManager();
      EventListener listener = new DummyListener(log);

      // Add/remove node by explicit path
      observationManager.addEventListener(listener, Event.NODE_ADDED | Event.NODE_REMOVED, "/childNode", false, null,
         null, false);
      testRoot.addNode("childNode", "nt:unstructured");
      testRoot.addNode("childNode1", "nt:unstructured");
      root.save();
      checkEventNumAndCleanCounter(0);
      testRoot.getNode("childNode").remove();
      testRoot.getNode("childNode1").remove();
      testRoot.save();
      checkEventNumAndCleanCounter(0);
      observationManager.removeEventListener(listener);

      // Add node by descendant path
      observationManager.addEventListener(listener, Event.NODE_ADDED, "/", true, null, null, false);
      testRoot.addNode("childNode", "nt:unstructured");
      testRoot.addNode("childNode1", "nt:unstructured");
      testRoot.save();
      checkEventNumAndCleanCounter(2);
      testRoot.getNode("childNode").remove();
      testRoot.getNode("childNode1").remove();
      testRoot.save();
      checkEventNumAndCleanCounter(0); // no remove event
      observationManager.removeEventListener(listener);

      // Add node by node type
      observationManager.addEventListener(listener, Event.NODE_ADDED, "/", true, null, new String[]{"nt:unstructured"},
         false);
      Node cn = testRoot.addNode("childNode", "nt:folder");
      // associated parent is not 'nt:unstructured' - no event will be generated
      cn.addNode("childNode1", "nt:hierarchyNode");
      testRoot.save();
      checkEventNumAndCleanCounter(1);
      observationManager.removeEventListener(listener);

      // Add node by UUID (never knows the UUID before adding :) )
      observationManager.addEventListener(listener, Event.NODE_ADDED, "/", true, new String[]{"0"},
         new String[]{"nt:unstructured"}, false);
      testRoot.addNode("childNode", "nt:unstructured");
      testRoot.save();
      checkEventNumAndCleanCounter(0);
      observationManager.removeEventListener(listener);
   }

   public void testRemoveNodeEvents() throws Exception
   {
      session.getWorkspace().getObservationManager().addEventListener(new DummyListener(log),
         Event.NODE_ADDED | Event.NODE_REMOVED, "/", true, null, new String[]{"nt:file"}, false);

      Node file = testRoot.addNode("test", "nt:file");
      Node d = file.addNode("jcr:content", "nt:resource");
      d.setProperty("jcr:mimeType", "text/plain");
      d.setProperty("jcr:lastModified", Calendar.getInstance());
      d.setProperty("jcr:data", "test content");
      session.save();

      checkEventNumAndCleanCounter(1);

      file.remove();
      session.save();
      checkEventNumAndCleanCounter(1);
   }

   public void testMoveNodeEvents() throws Exception
   {
      DummyListener listener = new DummyListener(log);
      session.getWorkspace().getObservationManager().addEventListener(listener,
              ExtendedEvent.NODE_MOVED, "/", true, null, null, false);

      Node n1 = testRoot.addNode("n1");
      Node n2 = n1.addNode("n2");
      Node n3 = testRoot.addNode("n3");
      Node n4 = testRoot.addNode("n4");
      n4.addNode("n5");
      session.save();

      session.move(n4.getPath(), n2.getPath());
      session.save();
      checkEventNumAndCleanCounter(1);
      assertEquals("/testRoot/n4", listener.getInfo().get(ExtendedEvent.SRC_ABS_PATH));
      assertEquals("/testRoot/n1/n2[2]", listener.getInfo().get(ExtendedEvent.DEST_ABS_PATH));

      session.move(n1.getPath(), n3.getPath()+"/n6");
      session.save();
      checkEventNumAndCleanCounter(1);
      assertEquals("/testRoot/n1", listener.getInfo().get(ExtendedEvent.SRC_ABS_PATH));
      assertEquals("/testRoot/n3/n6", listener.getInfo().get(ExtendedEvent.DEST_ABS_PATH));

      session.getWorkspace().move("/testRoot/n3/n6","/testRoot/n7");
      checkEventNumAndCleanCounter(1);
      assertEquals("/testRoot/n3/n6", listener.getInfo().get(ExtendedEvent.SRC_ABS_PATH));
      assertEquals("/testRoot/n7", listener.getInfo().get(ExtendedEvent.DEST_ABS_PATH));

      session.getWorkspace().move("/testRoot/n3","/testRoot/n7");
      checkEventNumAndCleanCounter(1);
      assertEquals("/testRoot/n3", listener.getInfo().get(ExtendedEvent.SRC_ABS_PATH));
      assertEquals("/testRoot/n7[2]", listener.getInfo().get(ExtendedEvent.DEST_ABS_PATH));
   }

   public void testOrderNodeEvents() throws Exception
   {
      DummyListener listener = new DummyListener(log);
      session.getWorkspace().getObservationManager().addEventListener(listener,
                ExtendedEvent.NODE_MOVED, "/", true, null, null, false);

      testRoot.addNode("n1");
      testRoot.addNode("n2");
      testRoot.addNode("n1");
      testRoot.addNode("n3");
      testRoot.addNode("n2");
      testRoot.addNode("n4");
      testRoot.save();
       
      testRoot.orderBefore("n1[2]","n1");
      session.save();
      checkEventNumAndCleanCounter(1);
      assertEquals("/testRoot/n1[2]", listener.getInfo().get(ExtendedEvent.SRC_CHILD_REL_PATH));
      assertEquals("/testRoot/n1", listener.getInfo().get(ExtendedEvent.DEST_CHILD_REL_PATH));

      testRoot.orderBefore("n2","n3");
      session.save();
      checkEventNumAndCleanCounter(0);
   }

   public void testPropertyEventGeneration() throws RepositoryException
   {
      ObservationManager observationManager = this.workspace.getObservationManager();
      EventListener listener = new DummyListener(log);

      if (log.isDebugEnabled())
         log.debug("SET PROP>>");
      // Add/remove node by explicit path
      observationManager.addEventListener(listener, Event.PROPERTY_ADDED | Event.PROPERTY_CHANGED
         | Event.PROPERTY_REMOVED, "/", true, null, null, false);
      Node node = testRoot.addNode("childNode", "nt:unstructured");
      Property prop = node.setProperty("prop", "prop");
      root.save();
      if (log.isDebugEnabled())
         log.debug("SET PROP>>");

      // SET /childNode/jcr:primaryType and /childNode/prop
      checkEventNumAndCleanCounter(2);
      prop.setValue("test1");
      testRoot.save();
      checkEventNumAndCleanCounter(1);
      prop.remove();
      testRoot.save();
      checkEventNumAndCleanCounter(1);
      observationManager.removeEventListener(listener);
   }

   /*
    * public void testMultiEventGeneration() throws RepositoryException {
    * ObservationManager observationManager =
    * this.workspace.getObservationManager(); EventListener listener = new
    * SimpleListener(log); observationManager.addEventListener(listener,
    * Event.NODE_ADDED|Event.PROPERTY_ADDED
    * |Event.PROPERTY_REMOVED|Event.NODE_REMOVED|Event.PROPERTY_CHANGED, "/",
    * true, null, null, false); Node node = root.addNode("childNode",
    * "nt:unstructured"); root.save(); Property prop = node.setProperty("prop",
    * "test"); root.save(); checkAndCleanCounter(3); prop.setValue("test1");
    * root.save(); checkAndCleanCounter(1); prop.remove(); root.save();
    * checkAndCleanCounter(1); node.remove(); root.save();
    * checkAndCleanCounter(1); observationManager.removeEventListener(listener);
    * }
    */

   public void testMultiListener() throws RepositoryException
   {

      ObservationManager observationManager = this.workspace.getObservationManager();
      EventListener listener = new DummyListener(log);
      EventListener listener1 = new DummyListener1(log);

      observationManager.addEventListener(listener, Event.NODE_ADDED | Event.PROPERTY_ADDED | Event.PROPERTY_REMOVED
         | Event.NODE_REMOVED | Event.PROPERTY_CHANGED, "/testRoot", true, null, null, false);
      observationManager.addEventListener(listener1, Event.NODE_ADDED | Event.PROPERTY_ADDED | Event.PROPERTY_REMOVED
         | Event.NODE_REMOVED | Event.PROPERTY_CHANGED, "/testRoot", false, null, null, false);

      Node node = testRoot.addNode("childNode", "nt:unstructured");
      root.save();
      Property prop = testRoot.setProperty("prop", "test");
      root.save();
      checkEventNumAndCleanCounter(5); // 3 by Dy listener + 2 by listener1

      observationManager.removeEventListener(listener);
      observationManager.removeEventListener(listener1);

   }

   public void testCloneEvents() throws RepositoryException
   {
      ObservationManager observationManager = this.workspace.getObservationManager();

      Session session2 = repository.login(credentials, "ws2");
      Node root = session2.getRootNode();
      Node node = root.addNode("testCloneEvents");
      node.addMixin("mix:referenceable");
      session2.save();

      // Node node = testRoot.addNode("testCloneEvents", "nt:unstructured");
      session.save();

      checkEventNumAndCleanCounter(0);

      EventListener listener = new DummyListener(log);
      observationManager.addEventListener(listener, Event.NODE_ADDED | Event.PROPERTY_ADDED | Event.PROPERTY_REMOVED
         | Event.NODE_REMOVED | Event.PROPERTY_CHANGED, "/", true, null, null, false);

      session.getWorkspace().clone("ws2", "/testCloneEvents", "/testRoot/testCloneEvents", true);

      checkEventNumAndCleanCounter(4);

      observationManager.removeEventListener(listener);

   }

   public void testRemoveSourceNodeEvents() throws RepositoryException
   {
      ObservationManager observationManager = this.workspace.getObservationManager();
      testRoot.addNode("testRemoveSourceNode");
      EventListener listener = new RemoveDummyListener(log, this.repository, this.credentials);
      observationManager.addEventListener(listener, Event.NODE_ADDED, "/", true, null, null, false);
      root.save();
      Session session2 = repository.login(credentials, "ws2");
      Session session = repository.login(credentials, "ws");
      assertFalse(session.itemExists("/testRoot/testRemoveSourceNode"));
      assertTrue(session2.itemExists("/testRemoveSourceNode"));
      observationManager.removeEventListener(listener);
   }

   public void testWithAnUnknownNodeType() throws RepositoryException
   {
      ObservationManager observationManager = this.workspace.getObservationManager();
      checkEventNumAndCleanCounter(0);

      EventListener listener = new DummyListener(log);
      try
      {
         observationManager.addEventListener(listener, Event.NODE_ADDED, "/", true, null, new String[]{"nt:anUnknownNodeType"}, false);
         testRoot.addNode("testWithAnUnknownNodeType");
         root.save();
         checkEventNumAndCleanCounter(0);
      }
      finally
      {
         observationManager.removeEventListener(listener);
      }
   }

   private void checkEventNumAndCleanCounter(int cnt)
   {
      assertEquals(cnt, counter);
      counter = 0;
   }

   private static class DummyListener implements EventListener
   {
      private Log log;

      private Map<String, String> info ;

      public DummyListener(Log log)
      {
         this.log = log;
      }

      public void onEvent(EventIterator events)
      {
         while (events.hasNext())
         {
            Event event = events.nextEvent();
            counter++;
            if(event.getType()== ExtendedEvent.NODE_MOVED)
            {
                try
                {
                    info=((ExtendedEvent)event).getInfo();
                }
                catch (RepositoryException e)
                {
                   log.error(e.getMessage(),e);
                }
            }
            try
            {
               if (log.isDebugEnabled())
                  log.debug("EVENT fired by SimpleListener " + event.getPath() + " " + event.getType());
            }
            catch (RepositoryException e)
            {
               e.printStackTrace();
            }
         }
      }
      public Map<String, String> getInfo()
      {
         return info;
      }
   }

   private static class DummyListener1 implements EventListener
   {
      private Log log;

      public DummyListener1(Log log)
      {
         this.log = log;
      }

      public void onEvent(EventIterator events)
      {
         while (events.hasNext())
         {
            Event event = events.nextEvent();
            counter++;
            if (log.isDebugEnabled())
               log.debug("EVENT fired by SimpleListener-1 " + event + " " + event.getType());
         }
      }
   }

   private static class RemoveDummyListener implements EventListener
   {
      protected Log log;

      protected RepositoryImpl repository;

      protected CredentialsImpl credentials;

      public RemoveDummyListener(Log log, RepositoryImpl repository, CredentialsImpl credentials)
      {
         this.log = log;
         this.repository = repository;
         this.credentials = credentials;
      }

      public void onEvent(EventIterator events)
      {
         while (events.hasNext())
         {
            Event event = events.nextEvent();
            counter++;
            try
            {
               String path = event.getPath();
               Session session2 = repository.login(credentials, "ws2");
               session2.getWorkspace().clone("ws", path, "/testRemoveSourceNode", true);
               Session session = repository.login(credentials, "ws");
               session.getItem(path).remove();
               session.save();
            }
            catch (RepositoryException re)
            {
               System.out.println(re.getMessage());
               if (log.isErrorEnabled())
               {
                  log.error(re.getMessage());
               }
            }
            if (log.isDebugEnabled())
               log.debug("EVENT fired by RemoveDummyListener " + event + " " + event.getType());
         }
      }
   }

}
