/*
 * Copyright (C) 2012 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.quota;

import org.exoplatform.services.jcr.config.SimpleParameterEntry;
import org.exoplatform.services.jcr.config.WorkspaceEntry;
import org.exoplatform.services.jcr.core.ManageableRepository;
import org.exoplatform.services.jcr.impl.RepositoryContainer;
import org.exoplatform.services.jcr.impl.core.JCRPath;
import org.exoplatform.services.jcr.impl.core.PropertyImpl;
import org.exoplatform.services.jcr.impl.core.SessionImpl;
import org.exoplatform.services.jcr.impl.storage.jdbc.JDBCDataContainerConfig.DatabaseStructureType;
import org.exoplatform.services.jcr.impl.util.io.DirectoryHelper;
import org.exoplatform.services.jcr.storage.WorkspaceDataContainer;
import org.exoplatform.services.jcr.util.TesterConfigurationHelper;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;

import javax.jcr.Node;
import javax.jcr.NodeIterator;
import javax.jcr.PropertyIterator;
import javax.jcr.RepositoryException;
import javax.jcr.Value;

/**
 * @author <a href="abazko@exoplatform.com">Anatoliy Bazko</a>
 * @version $Id: TestQuotas.java 34360 2009-07-22 23:58:59Z tolusha $
 */
public class TestQuotas extends AbstractQuotaManagerTest
{

   private long timeout = 5000;
   /**
    * Check the index size of non system workspace.
    */
   public void testWorkspaceIndexSize() throws Exception
   {
      long measuredSize = ws1QuotaManager.getWorkspaceIndexSize();
      long exptectedSize = DirectoryHelper.getSize(new File("target/temp/index/db2/ws1"));

      assertEquals(exptectedSize, measuredSize);
   }

   /**
    * Check the index size of system workspace.
    */
   public void testSystemWorkspaceIndexSize() throws Exception
   {
      long measuredSize = wsQuotaManager.getWorkspaceIndexSize();

      long expectedSize = DirectoryHelper.getSize(new File("target/temp/index/db2/ws"));
      expectedSize += DirectoryHelper.getSize(new File("target/temp/index/db2/ws_system"));

      assertEquals(expectedSize, measuredSize);
   }

   /**
    * Checks if workspace data size is positive value.
    */
   public void testWorkspaceDataSize() throws Exception
   {
      long measuredSize = wsQuotaManager.getWorkspaceDataSize();

      assertTrue(measuredSize > 0);
   }

   /**
    * Checks if data size of root node is equals to data size of workspace.
    */
   public void testRootNodeDataSize() throws Exception
   {
      assertEquals(wsQuotaManager.getWorkspaceDataSize(), wsQuotaManager.getNodeDataSize(JCRPath.ROOT_PATH));
   }

   /**
    * Checks if node data size returns correct value.
    */
   public void testNodeDataSize() throws Exception
   {
      Node node = session.getRootNode().addNode("test");
      session.save();

      long measuredSize = wsQuotaManager.getNodeDataSizeDirectly("/test");
      long expectedSize = ((ByteArrayInputStream)node.getProperty("jcr:primaryType").getStream()).available();

      assertEquals(expectedSize, measuredSize);
   }

   /**
    * Checks if node data size returns correct value.
    */
   public void testNodeDataSizeFromValueStorage() throws Exception
   {
      Node node = session.getRootNode().addNode("test");
      node.setProperty("value", new FileInputStream(createBLOBTempFile(1000)));
      session.save();

      long measuredSize = wsQuotaManager.getNodeDataSizeDirectly("/test");

      long expectedSize = ((ByteArrayInputStream)node.getProperty("jcr:primaryType").getStream()).available();
      expectedSize += node.getProperty("value").getStream().available();

      assertEquals(expectedSize, measuredSize);
   }

   /**
    * Checks if workspace data size is equals to size of all children nodes.
    */
   public void testWorkspaceSizeEqualToAllNodesSize() throws Exception
   {
      long workspaceSize = wsQuotaManager.getWorkspaceDataSizeDirectly();
      long nodesSize = 0;

      NodeIterator nodes = session.getRootNode().getNodes();
      while (nodes.hasNext())
      {
         nodesSize += wsQuotaManager.getNodeDataSizeDirectly(nodes.nextNode().getPath());
      }

      PropertyIterator props = session.getRootNode().getProperties();
      while (props.hasNext())
      {
         PropertyImpl prop = (PropertyImpl)props.nextProperty();
         if (!prop.isMultiValued())
         {
            Value value = prop.getValue();
            nodesSize += ((ByteArrayInputStream)value.getStream()).available();
         }
         else
         {
            for (Value value : prop.getValues())
            {
               nodesSize += ((ByteArrayInputStream)value.getStream()).available();
            }
         }
      }

      assertEquals(nodesSize, workspaceSize);
   }

   /**
    * Checks if data size of removed node equals to zero.
    */
   public void testDataSizeRemovedNode() throws Exception
   {
      assertTrue(dataSizeShouldNotExists(wsQuotaManager, "/a/b"));

      wsQuotaManager.setNodeQuota("/a/b", 1000, false);

      root.addNode("a").addNode("b");
      root.save();

      assertNodeDataSize(wsQuotaManager, "/a/b");

      root.getNode("a").remove();
      root.save();

      assertNodeDataSize(wsQuotaManager, "/a/b");

      wsQuotaManager.removeNodeQuota("/a/b");
   }

   /**
    * Checks if data size of moved node equals to zero.
    */
   public void testDataSizeMovedNode() throws Exception
   {
      assertTrue(dataSizeShouldNotExists(wsQuotaManager, "/a/b"));

      wsQuotaManager.setNodeQuota("/a/b", 1000, false);

      root.addNode("a").addNode("b");
      root.save();

      assertNodeDataSize(wsQuotaManager, "/a/b");

      session.move("/a/b", "/a/c");
      root.save();

      assertNodeDataSize(wsQuotaManager, "/a/b");

      wsQuotaManager.removeNodeQuota("/a/b");
   }

   /**
    * Checks if data size of moved node equals to zero.
    */
   public void testDataSizeMovedNodeNoDescendentEvents() throws Exception
   {
      TesterConfigurationHelper helper = TesterConfigurationHelper.getInstance();
      WorkspaceEntry wsEntry = helper.createWorkspaceEntry(DatabaseStructureType.ISOLATED, null);
      wsEntry.getContainer().addParameter(
         new SimpleParameterEntry(WorkspaceDataContainer.TRIGGER_EVENTS_FOR_DESCENDANTS_ON_RENAME, "false"));

      ManageableRepository repository = helper.createRepository(container, DatabaseStructureType.ISOLATED, null);
      helper.addWorkspace(repository, wsEntry);

      SessionImpl session = (SessionImpl)repository.login(credentials, wsEntry.getName());

      Node root = session.getRootNode();
      WorkspaceQuotaManager wsQuotaManager =
         (WorkspaceQuotaManager)repository.getWorkspaceContainer(wsEntry.getName()).getComponent(
            WorkspaceQuotaManager.class);

      assertTrue(dataSizeShouldNotExists(wsQuotaManager, "/a/b"));
      assertTrue(dataSizeShouldNotExists(wsQuotaManager, "/a/c"));

      wsQuotaManager.setNodeQuota("/a/b", 1000, false);
      wsQuotaManager.setNodeQuota("/a/c", 1000, false);

      root.addNode("a").addNode("b");
      root.save();

      assertNodeDataSize(wsQuotaManager, "/a/b", session);

      session.move("/a/b", "/a/c");
      root.getNode("a").getNode("c").addNode("d");
      root.save();

      assertNodeDataSize(wsQuotaManager, "/a/b", session);
      assertNodeDataSize(wsQuotaManager, "/a/c", session);

      wsQuotaManager.removeNodeQuota("/a/b");
      wsQuotaManager.removeNodeQuota("/a/c");
   }

   /**
    * Testing setNodeQuota.
    */
   public void testSetNodeQuota() throws Exception
   {
      assertTrue(dataSizeShouldNotExists(wsQuotaManager, "/a/b"));
      assertTrue(dataSizeShouldNotExists(wsQuotaManager, "/a"));

      assertTrue(quotaShouldNotExists(wsQuotaManager, "/a"));
      assertTrue(quotaShouldNotExists(wsQuotaManager, "/a/b"));

      wsQuotaManager.setNodeQuota("/a", 20000, false);
      wsQuotaManager.setNodeQuota("/a/b", 1000, false);

      Node testA = root.addNode("a");
      Node testB = testA.addNode("b");
      root.save();

      assertNodeDataSize(wsQuotaManager, "/a/b");
      assertNodeDataSize(wsQuotaManager, "/a");

      // small changes
      testA.setProperty("prop1", "value1");
      testA.save();

      // big changes NOT OK for B
      testB.setProperty("prop1", new FileInputStream(createBLOBTempFile(5)));
      try
      {
         root.save();
         fail();
      }
      catch (RepositoryException e)
      {
      }

      session.refresh(false);

      // big changes OK for A
      testA.setProperty("prop2", new FileInputStream(createBLOBTempFile(5)));
      root.save();

      // increase quota
      wsQuotaManager.setNodeQuota("/a/b", 10000, false);

      // big changes OK for B now
      testB.setProperty("prop1", new FileInputStream(createBLOBTempFile(5)));
      root.save();

      wsQuotaManager.setNodeQuota("/a/b", 10, false);

      // should be possible to remove content
      testB.getProperty("prop1").remove();
      root.save();

      wsQuotaManager.removeNodeQuota("/a");
      wsQuotaManager.removeNodeQuota("/a/b");

      assertTrue(quotaShouldNotExists(wsQuotaManager, "/a"));
      assertTrue(dataSizeShouldNotExists(wsQuotaManager, "/a"));
      assertTrue(quotaShouldNotExists(wsQuotaManager, "/a/b"));
      assertTrue(dataSizeShouldNotExists(wsQuotaManager, "/a/b"));
   }

   /**
    * Testing setGroupOfNodeQuota.
    */
   public void testSetGroupOfNodeQuota() throws Exception
   {
      assertTrue(quotaShouldNotExists(wsQuotaManager, "/a"));
      assertTrue(quotaShouldNotExists(wsQuotaManager, "/b"));

      assertTrue(dataSizeShouldNotExists(wsQuotaManager, "/a"));
      assertTrue(dataSizeShouldNotExists(wsQuotaManager, "/b"));

      wsQuotaManager.setGroupOfNodesQuota("/*", 1000, false);
      wsQuotaManager.setNodeQuota("/b", 10000, false);

      Node testA = root.addNode("a");
      Node testB = root.addNode("b");
      root.save();

      assertNodeDataSize(wsQuotaManager, "/b");

      // small changes
      testA.setProperty("prop1", "value1");
      root.save();

      assertNodeDataSize(wsQuotaManager, "/a");

      // big changes OK for B
      testB.setProperty("prop1", new FileInputStream(createBLOBTempFile(5)));
      root.save();

      // big changes NOT OK for A
      testA.setProperty("prop1", new FileInputStream(createBLOBTempFile(5)));
      try
      {
         root.save();
         fail();
      }
      catch (RepositoryException e)
      {
      }

      session.refresh(false);

      // increase quota
      wsQuotaManager.setGroupOfNodesQuota("/*", 10000, false);

      // big changes OK for A now
      testA.setProperty("prop1", new FileInputStream(createBLOBTempFile(5)));
      root.save();

      wsQuotaManager.removeGroupOfNodesQuota("/*");
      assertTrue(quotaShouldNotExists(wsQuotaManager, "/a"));
      assertTrue(dataSizeShouldNotExists(wsQuotaManager, "/a"));

      assertTrue(wsQuotaManager.getNodeQuota("/b") > 0);
      assertTrue(wsQuotaManager.getNodeDataSize("/b") > 0);

      wsQuotaManager.removeNodeQuota("/b");
      assertTrue(quotaShouldNotExists(wsQuotaManager, "/b"));
      assertTrue(dataSizeShouldNotExists(wsQuotaManager, "/b"));
   }

   /**
    * Testing setWorkspaceQuota method.
    */
   public void testSetWorkspaceQuota() throws Exception
   {
      assertWorkspaceSize(wsQuotaManager);

      // set quota to current data size, so we should not be allowed to add any data
      wsQuotaManager.setWorkspaceQuota(wsQuotaManager.getWorkspaceDataSize());

      root.addNode("testQuotaNode");
      try
      {
         root.save();
         fail();
      }
      catch (RepositoryException e)
      {
      }

      // set quota to more than the current data size, so we can add some data
      wsQuotaManager.setWorkspaceQuota(wsQuotaManager.getWorkspaceDataSize() + 10000);

      root.addNode("testQuotaNode");
      root.save();

      // set quota to current data size
      wsQuotaManager.setWorkspaceQuota(wsQuotaManager.getWorkspaceDataSize());

      assertWorkspaceSize(wsQuotaManager);

      // should possible to remove
      root.getNode("testQuotaNode").remove();
      root.save();

      wsQuotaManager.removeWorkspaceQuota();

      assertTrue(wsQuotaManager.getWorkspaceDataSize() > 0);
   }

   /**
    * Testing setRepositoryQuota method.
    */
   public void testSetRepositoryQuota() throws Exception
   {
      assertWorkspaceSize(wsQuotaManager);

      assertTrue(dbQuotaManager.getRepositoryDataSize() > 0);

      // set quota to current data size, so we should not be allowed to add any data
      dbQuotaManager.setRepositoryQuota(dbQuotaManager.getRepositoryDataSize());

      root.addNode("testQuotaNode");
      try
      {
         root.save();
         fail();
      }
      catch (RepositoryException e)
      {
      }

      // set quota to more than the current data size, so we can add some data
      dbQuotaManager.setRepositoryQuota(dbQuotaManager.getRepositoryDataSize() + 10000);

      root.addNode("testQuotaNode");
      root.save();

      // set quota to current data size
      dbQuotaManager.setRepositoryQuota(dbQuotaManager.getRepositoryDataSize());

      assertWorkspaceSize(wsQuotaManager);

      // should possible to remove
      root.getNode("testQuotaNode").remove();
      root.save();

      dbQuotaManager.removeRepositoryQuota();
      assertTrue(dbQuotaManager.getRepositoryDataSize() > 0);
   }

   /**
    * Testing setGlobalQuota method.
    */
   public void testGlobalQuota() throws Exception
   {
      assertWorkspaceSize(wsQuotaManager);

      assertTrue(quotaManager.getGlobalDataSize() > 0);

      // set quota to current data size, so we should not be allowed to add any data
      quotaManager.setGlobalQuota(quotaManager.getGlobalDataSize());

      root.addNode("testQuotaNode");
      try
      {
         root.save();
         fail();
      }
      catch (RepositoryException e)
      {
      }

      // set quota to more than the current data size, so we can add some data
      quotaManager.setGlobalQuota(quotaManager.getGlobalDataSize() + 10000);

      root.addNode("testQuotaNode");
      root.save();

      // set quota to current data size
      quotaManager.setGlobalQuota(quotaManager.getGlobalDataSize());

      assertWorkspaceSize(wsQuotaManager);

      // should possible to remove
      root.getNode("testQuotaNode").remove();
      root.save();

      quotaManager.removeGlobalQuota();
      assertTrue(quotaManager.getGlobalDataSize() > 0);
   }

   /**
    * Checks that quota is decreased when workspace is removed but not
    * when is just stopped.
    */
   public void testQuotaWhenWorkspaceIsRemoved() throws Exception
   {
      ManageableRepository repository = helper.createRepository(container, DatabaseStructureType.MULTI, null);
      WorkspaceEntry ws1Entry = helper.createWorkspaceEntry(DatabaseStructureType.MULTI, null);
      WorkspaceEntry ws2Entry = helper.createWorkspaceEntry(DatabaseStructureType.MULTI, null);

      helper.addWorkspace(repository, ws1Entry);
      helper.addWorkspace(repository, ws2Entry);

      WorkspaceQuotaManager ws1QuotaManager =
         (WorkspaceQuotaManager)repository.getWorkspaceContainer(ws1Entry.getName()).getComponent(
            WorkspaceQuotaManager.class);

      Thread.sleep(timeout); // wait to apply persisted changes task

      long dataSize = quotaManager.getGlobalDataSize();
      long ws1DataSize = ws1QuotaManager.getWorkspaceDataSize();

      assertWorkspaceSize(ws1QuotaManager);

      repository.removeWorkspace(ws1Entry.getName());
      assertEquals(dataSize - ws1DataSize, quotaManager.getGlobalDataSize());

      dataSize = quotaManager.getGlobalDataSize();

      RepositoryContainer repoContainer =
         (RepositoryContainer)container.getComponentInstance(repository.getConfiguration().getName());
      repoContainer.stop();

      assertEquals(dataSize, quotaManager.getGlobalDataSize());
   }
}
