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

import org.exoplatform.services.jcr.JcrImplBaseTest;
import org.exoplatform.services.jcr.impl.dataflow.SpoolConfig;
import org.exoplatform.services.jcr.impl.dataflow.TransientValueData;
import org.exoplatform.services.jcr.impl.util.io.FileCleaner;
import org.exoplatform.services.jcr.impl.util.io.SwapFile;

import java.io.File;
import java.io.FileOutputStream;

/**
 * Created by The eXo Platform SAS.
 * 
 * <br>
 * Date: 29.07.2009
 * 
 * @author <a href="mailto:peter.nedonosko@exoplatform.com.ua">Peter Nedonosko</a>
 * @version $Id: TestCleanableFileStreamValueData.java 34664 2009-07-29 16:33:52Z pnedonosko $
 */
public class TestCleanableFileStreamValueData extends JcrImplBaseTest
{

   private static final int CLEANER_TIMEOUT = 100;

   private static final String FILE_NAME = "testFileCleaned";

   private File parentDir = new File("./target");

   private File testFile;

   private FileCleaner testCleaner;

   private CleanableFilePersistedValueData cleanableValueData;

   private static class TestSwapFile extends SwapFile
   {
      /**
       * Dummy constructor.
       */
      protected TestSwapFile(File parent, String child,FileCleaner cleaner)
      {
         super(parent, child,cleaner);
      }

      /**
       * Clean inShare for tearDown.
       * 
       */
      static void cleanShare()
      {
         CURRENT_SWAP_FILES.clear();
      }
   }

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

      testFile = new File(parentDir, FILE_NAME);
      testCleaner = new FileCleaner(CLEANER_TIMEOUT);

      SwapFile sf = SwapFile.get(parentDir, FILE_NAME,testCleaner);
      FileOutputStream fout = new FileOutputStream(sf);
      fout.write("testFileCleaned".getBytes());
      fout.close();
      sf.spoolDone();

      cleanableValueData = new CleanableFilePersistedValueData(1, sf, SpoolConfig.getDefaultSpoolConfig());
   }

   @Override
   protected void tearDown() throws Exception
   {
      cleanableValueData = null;

      testCleaner.halt();
      testCleaner = null;

      if (testFile.exists())
      {
         testFile.delete();
      }

      TestSwapFile.cleanShare();

      System.runFinalization();

      super.tearDown();
   }

   public void testFileCleaned() throws Exception
   {
      assertTrue(testFile.exists());
      cleanableValueData = null; // CleanableVD dies
      assertReleasedFile(testFile);
   }

   public void testSharedFileNotCleaned() throws Exception
   {
      assertTrue(testFile.exists());

      System.runFinalization();
      Thread.sleep(CLEANER_TIMEOUT / 2);

      CleanableFilePersistedValueData cfvd2 =
         new CleanableFilePersistedValueData(1, SwapFile.get(parentDir, FILE_NAME), SpoolConfig.getDefaultSpoolConfig());
      assertTrue(testFile.exists());

      cleanableValueData = null; // CleanableVD dies but another instance points swapped file

      // allows GC to call finalize on vd
      System.runFinalization();
      assertTrue(testFile.exists());

      // clean ValueData
      cfvd2 = null;
      assertReleasedFile(testFile);
   }

   public void testTransientFileNotCleaned() throws Exception
   {

      assertTrue(testFile.exists());

      System.runFinalization();
      Thread.sleep(CLEANER_TIMEOUT / 2);

      TransientValueData trvd = new TransientValueData(false);
      trvd.delegate(cleanableValueData);

      trvd = null; // TransientVD dies

      System.runFinalization();
      assertTrue(testFile.exists());
   }

   public void testTransientFileCleaned() throws Exception
   {

      assertTrue(testFile.exists());

      System.runFinalization();
      Thread.sleep(CLEANER_TIMEOUT / 2);

      TransientValueData trvd = new TransientValueData(false);
      trvd.delegate(cleanableValueData);

      assertTrue(testFile.exists());

      cleanableValueData = null; // CleanableVD dies but TransientVD still uses swapped file

      System.runFinalization();
      assertTrue(testFile.exists());

      trvd = null; // TransientVD dies
      assertReleasedFile(testFile);
   }

   public void testTransientSharedFileCleaned() throws Exception
   {

      assertTrue(testFile.exists());

      System.runFinalization();
      Thread.sleep(CLEANER_TIMEOUT / 2);

      // file shared with TransientVD
      TransientValueData trvd = new TransientValueData(false);
      trvd.delegate(cleanableValueData);

      assertTrue(testFile.exists());

      // 1st CleanableVD die
      cleanableValueData = null;

      System.runFinalization();

      // file shared with third CleanableVD, i.e. file still exists (aquired by TransientVD)
      CleanableFilePersistedValueData cfvd2 =
         new CleanableFilePersistedValueData(1, SwapFile.get(parentDir, FILE_NAME), SpoolConfig.getDefaultSpoolConfig());
      assertTrue(testFile.exists());

      trvd = null; // TransientVD dies

      System.runFinalization();
      assertTrue(testFile.exists()); // still exists, aquired by 2nd CleanableVD

      cfvd2 = null; // 2nd CleanableVD dies
      assertReleasedFile(testFile);
   }

   public static void assertReleasedFile(File file) throws Exception
   {
      long purgeStartTime = System.currentTimeMillis();
      while (file.exists() && (System.currentTimeMillis() - purgeStartTime < 2 * 60 * 1000))
      {
         System.gc();
         try
         {
            Thread.sleep(500);
         }
         catch (InterruptedException e)
         {
         }
      }

      assertFalse(file.exists()); // file released and deleted
   }

}
