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

import java.io.*;
import java.net.URI;
import java.util.*;

import javax.jcr.*;
import javax.jcr.lock.LockException;
import javax.jcr.nodetype.ConstraintViolationException;
import javax.jcr.version.VersionException;
import javax.ws.rs.core.MultivaluedMap;

import org.exoplatform.container.xml.InitParams;
import org.exoplatform.container.xml.PropertiesParam;
import org.exoplatform.services.jcr.config.*;
import org.exoplatform.services.jcr.core.ManageableRepository;
import org.exoplatform.services.jcr.core.WorkspaceContainerFacade;
import org.exoplatform.services.jcr.ext.BaseStandaloneTest;
import org.exoplatform.services.jcr.ext.app.SessionProviderService;
import org.exoplatform.services.jcr.ext.backup.impl.*;
import org.exoplatform.services.jcr.ext.backup.server.bean.response.ShortInfo;
import org.exoplatform.services.jcr.ext.common.SessionProvider;
import org.exoplatform.services.jcr.impl.clean.rdbms.DBCleanService;
import org.exoplatform.services.jcr.impl.core.SessionImpl;
import org.exoplatform.services.jcr.impl.core.SessionRegistry;
import org.exoplatform.services.jcr.impl.core.query.SystemSearchManager;
import org.exoplatform.services.jcr.impl.storage.jdbc.JDBCDataContainerConfig.DatabaseStructureType;
import org.exoplatform.services.jcr.impl.storage.value.fs.FileValueStorage;
import org.exoplatform.services.jcr.impl.util.io.DirectoryHelper;
import org.exoplatform.services.jcr.util.TesterConfigurationHelper;
import org.exoplatform.services.rest.ContainerResponseWriter;
import org.exoplatform.services.rest.RequestHandler;
import org.exoplatform.services.rest.impl.*;
import org.exoplatform.services.rest.tools.ByteArrayContainerResponseWriter;
import org.exoplatform.services.security.ConversationState;
import org.exoplatform.services.security.Identity;
import org.exoplatform.ws.frameworks.json.JsonHandler;
import org.exoplatform.ws.frameworks.json.JsonParser;
import org.exoplatform.ws.frameworks.json.impl.*;
import org.exoplatform.ws.frameworks.json.value.JsonValue;

/**
 * Created by The eXo Platform SAS Author : Peter Nedonosko peter.nedonosko@exoplatform.com.ua
 * 04.02.2008
 * 
 * @author <a href="mailto:peter.nedonosko@exoplatform.com.ua">Peter Nedonosko</a>
 * @version $Id: AbstractBackupTestCase.java 760 2008-02-07 15:08:07Z pnedonosko $
 */
public abstract class AbstractBackupTestCase extends BaseStandaloneTest
{

   protected TesterConfigurationHelper helper = TesterConfigurationHelper.getInstance();

   protected File blob;

   protected ExtendedBackupManager backup;

   protected File backupDir;

   protected RequestHandler handler;

   /**
    * {@inheritDoc}
    */
   public void setUp() throws Exception
   {
      super.setUp();// this

      backup = getBackupManager();
      forceStopAutoStopperThreads();

      blob = createBLOBTempFile(300);

      backupDir = new File("target/temp/backup/" + System.currentTimeMillis());
      backupDir.mkdirs();

      handler = (RequestHandler)container.getComponentInstanceOfType(RequestHandler.class);

      SessionProviderService sessionProviderService =
         (SessionProviderService)container.getComponentInstanceOfType(SessionProviderService.class);
      assertNotNull(sessionProviderService);
      sessionProviderService.setSessionProvider(null, new SessionProvider(new ConversationState(new Identity("root"))));
   }

   private void forceStopAutoStopperThreads()
   {
      ((BackupManagerImpl)backup).stop();
   }

   /**
    * {@inheritDoc}
    */
   protected void tearDown() throws Exception
   {
      super.tearDown();

      blob.delete();
   }

   protected abstract ExtendedBackupManager getBackupManager();

   protected ExtendedBackupManager getJCRBackupManager()
   {
      if (backup == null)
      {
         InitParams initParams = new InitParams();
         PropertiesParam pps = new PropertiesParam();
         pps.setProperty(BackupManagerImpl.FULL_BACKUP_TYPE,
            "org.exoplatform.services.jcr.ext.backup.impl.fs.FullBackupJob");
         pps.setProperty(BackupManagerImpl.INCREMENTAL_BACKUP_TYPE,
            "org.exoplatform.services.jcr.ext.backup.impl.fs.IncrementalBackupJob");
         pps.setProperty(BackupManagerImpl.BACKUP_DIR, "target/backup");
         pps.setProperty(BackupManagerImpl.DEFAULT_INCREMENTAL_JOB_PERIOD, "3600");

         initParams.put(BackupManagerImpl.BACKUP_PROPERTIES, pps);

         BackupManagerImpl backup = new BackupManagerImpl(initParams, repositoryService);
         backup.start();

         return backup;
      }

      return backup;
   }

   protected ExtendedBackupManager getRDBMSBackupManager()
   {
      if (backup == null)
      {
         InitParams initParams = new InitParams();
         PropertiesParam pps = new PropertiesParam();
         pps.setProperty(BackupManagerImpl.FULL_BACKUP_TYPE,
            "org.exoplatform.services.jcr.ext.backup.impl.rdbms.FullBackupJob");
         pps.setProperty(BackupManagerImpl.INCREMENTAL_BACKUP_TYPE,
            "org.exoplatform.services.jcr.ext.backup.impl.fs.IncrementalBackupJob");
         pps.setProperty(BackupManagerImpl.BACKUP_DIR, "target/backup");
         pps.setProperty(BackupManagerImpl.DEFAULT_INCREMENTAL_JOB_PERIOD, "3600");

         initParams.put(BackupManagerImpl.BACKUP_PROPERTIES, pps);

         BackupManagerImpl backup = new BackupManagerImpl(initParams, repositoryService);
         backup.start();

         return backup;
      }

      return backup;
   }

   protected void addContent(Node node, int startIndex, int stopIndex, long sleepTime) throws ValueFormatException,
            VersionException, LockException, ConstraintViolationException, ItemExistsException, PathNotFoundException,
            RepositoryException, InterruptedException
   {
      for (int i = startIndex; i <= stopIndex; i++)
      {
         node.addNode("node_" + i).setProperty("exo:data", "property-" + i);
         Thread.sleep(sleepTime);
         if (i % 10 == 0)
            node.save(); // log here via listener
      }
      node.save();
   }

   protected void waitTime(Date time) throws InterruptedException
   {
      while (Calendar.getInstance().getTime().before(time))
      {
         Thread.yield();
         Thread.sleep(50);
      }
      Thread.sleep(250);
   }

   protected void removeWorkspaceFully(String repositoryName, String workspaceName) throws Exception
   {
      // get current workspace configuration
      WorkspaceEntry wEntry = null;;
      for (WorkspaceEntry entry : repositoryService.getRepository(repositoryName).getConfiguration()
         .getWorkspaceEntries())
      {
         if (entry.getName().equals(workspaceName))
         {
            wEntry = entry;
            break;
         }
      }

      if (wEntry == null)
      {
         throw new WorkspaceRestoreException("Workspace " + workspaceName + " did not found in current repository "
            + repositoryName + " configuration");
      }

      boolean isSystem =
         repositoryService.getRepository(repositoryName).getConfiguration().getSystemWorkspaceName()
            .equals(wEntry.getName());

      // remove workspace 
      forceCloseSession(repositoryName, wEntry.getName());
      repositoryService.getRepository(repositoryName).removeWorkspace(wEntry.getName());

      // clean db
      DBCleanService.cleanWorkspaceData(wEntry);

      // clean value storage
      if (wEntry.getContainer().getValueStorages() != null)
      {
         for (ValueStorageEntry valueStorage : wEntry.getContainer().getValueStorages())
         {
            DirectoryHelper.removeDirectory(new File(valueStorage.getParameterValue(FileValueStorage.PATH)));
         }
      }

      // clean index
      if (wEntry.getQueryHandler() != null)
      {
         DirectoryHelper.removeDirectory(new File(wEntry.getQueryHandler().getParameterValue(
            QueryHandlerParams.PARAM_INDEX_DIR, null)));
         if (isSystem)
         {
            DirectoryHelper.removeDirectory(new File(wEntry.getQueryHandler().getParameterValue(
               QueryHandlerParams.PARAM_INDEX_DIR,
               null)
               + "_" + SystemSearchManager.INDEX_DIR_SUFFIX));
         }
      }
   }

   protected void removeWorkspaceFullySingleDB(String repositoryName, String workspaceName) throws Exception
   {
      // get current workspace configuration
      WorkspaceEntry wEntry = null;;
      for (WorkspaceEntry entry : repositoryService.getRepository(repositoryName).getConfiguration()
         .getWorkspaceEntries())
      {
         if (entry.getName().equals(workspaceName))
         {
            wEntry = entry;
            break;
         }
      }

      if (wEntry == null)
      {
         throw new WorkspaceRestoreException("Workspace " + workspaceName + " did not found in current repository "
            + repositoryName + " configuration");
      }

      boolean isSystem =
         repositoryService.getRepository(repositoryName).getConfiguration().getSystemWorkspaceName()
            .equals(wEntry.getName());

      //close all session
      forceCloseSession(repositoryName, wEntry.getName());

      repositoryService.getRepository(repositoryName).removeWorkspace(wEntry.getName());

      DBCleanService.cleanWorkspaceData(wEntry);

      if (wEntry.getContainer().getValueStorages() != null)
      {
         for (ValueStorageEntry valueStorage : wEntry.getContainer().getValueStorages())
         {
            DirectoryHelper.removeDirectory(new File(valueStorage.getParameterValue(FileValueStorage.PATH)));
         }
      }

      if (wEntry.getQueryHandler() != null)
      {
         DirectoryHelper.removeDirectory(new File(wEntry.getQueryHandler().getParameterValue(
            QueryHandlerParams.PARAM_INDEX_DIR, null)));
         if (isSystem)
         {
            DirectoryHelper.removeDirectory(new File(wEntry.getQueryHandler().getParameterValue(
               QueryHandlerParams.PARAM_INDEX_DIR,
               null)
               + "_" + SystemSearchManager.INDEX_DIR_SUFFIX));
         }
      }
   }

   protected void removeRepositoryFully(String repositoryName) throws Exception
   {
      // get current repository configuration
      RepositoryEntry repositoryEntry = repositoryService.getConfig().getRepositoryConfiguration(repositoryName);

      if (repositoryEntry == null)
      {
         throw new RepositoryRestoreExeption("Current repository configuration " + repositoryName + " did not found");
      }

      //Create local copy of WorkspaceEntry for all workspaces
      ArrayList<WorkspaceEntry> workspaceList = new ArrayList<WorkspaceEntry>();
      workspaceList.addAll(repositoryEntry.getWorkspaceEntries());

      //close all session
      for (WorkspaceEntry wEntry : workspaceList)
      {
         forceCloseSession(repositoryEntry.getName(), wEntry.getName());
      }

      String systemWorkspaceName =
         repositoryService.getRepository(repositoryName).getConfiguration().getSystemWorkspaceName();

      //remove repository
      repositoryService.removeRepository(repositoryEntry.getName());

      // clean data
      for (WorkspaceEntry wEntry : workspaceList)
      {
         DBCleanService.cleanWorkspaceData(wEntry);

         if (wEntry.getContainer().getValueStorages() != null)
         {
            for (ValueStorageEntry valueStorage : wEntry.getContainer().getValueStorages())
            {
               DirectoryHelper.removeDirectory(new File(valueStorage.getParameterValue(FileValueStorage.PATH)));
            }
         }

         boolean isSystem = systemWorkspaceName.equals(wEntry.getName());

         if (wEntry.getQueryHandler() != null)
         {
            DirectoryHelper.removeDirectory(new File(wEntry.getQueryHandler().getParameterValue(
               QueryHandlerParams.PARAM_INDEX_DIR, null)));
            if (isSystem)
            {
               DirectoryHelper.removeDirectory(new File(wEntry.getQueryHandler().getParameterValue(
                  QueryHandlerParams.PARAM_INDEX_DIR, null)
                  + "_" + SystemSearchManager.INDEX_DIR_SUFFIX));
            }
         }
      }
   }

   /**
    * forceCloseSession. Close sessions on specific workspace.
    * 
    * @param repositoryName
    *          repository name
    * @param workspaceName
    *          workspace name
    * @return int return the how many sessions was closed
    * @throws RepositoryConfigurationException
    *           will be generate RepositoryConfigurationException
    * @throws RepositoryException
    *           will be generate RepositoryException
    */
   private int forceCloseSession(String repositoryName, String workspaceName) throws RepositoryException,
            RepositoryConfigurationException
   {
      ManageableRepository mr = repositoryService.getRepository(repositoryName);
      WorkspaceContainerFacade wc = mr.getWorkspaceContainer(workspaceName);

      SessionRegistry sessionRegistry = (SessionRegistry) wc.getComponent(SessionRegistry.class);

      return sessionRegistry.closeSessions(workspaceName);
   }

   public void waitEndOfBackup(BackupChain bch) throws Exception
   {
      while (bch.getFullBackupState() != BackupChain.FINISHED)
      {
         Thread.yield();
         Thread.sleep(50);
      }
   }

   public void waitEndOfBackup(RepositoryBackupChain bch) throws Exception
   {
      while (bch.getState() != RepositoryBackupChain.FINISHED
         && bch.getState() != RepositoryBackupChain.FULL_BACKUP_FINISHED_INCREMENTAL_BACKUP_WORKING)
      {
         Thread.yield();
         Thread.sleep(50);
      }
   }

   public void waitEndOfRestore(String repositoryName) throws Exception
   {
      while (backup.getLastRepositoryRestore(repositoryName).getStateRestore() != JobRepositoryRestore.REPOSITORY_RESTORE_SUCCESSFUL
         && backup.getLastRepositoryRestore(repositoryName).getStateRestore() != JobRepositoryRestore.REPOSITORY_RESTORE_FAIL)
      {
         Thread.sleep(50);
      }
   }

   public void waitEndOfRestore(String repositoryName, String workspaceName) throws Exception
   {
      while (backup.getLastRestore(repositoryName, workspaceName).getStateRestore() != JobWorkspaceRestore.RESTORE_SUCCESSFUL
         && backup.getLastRestore(repositoryName, workspaceName).getStateRestore() != JobWorkspaceRestore.RESTORE_FAIL)
      {
         Thread.sleep(50);
      }
   }

   public void addIncrementalConent(ManageableRepository repository, String wsName) throws Exception
   {
      SessionImpl session = (SessionImpl)repository.login(credentials, wsName);
      Node rootNode = session.getRootNode().addNode("testIncremental");

      // add some changes which will be logged in incremental log
      rootNode.addNode("node1").setProperty("prop1", "value1");
      rootNode.addNode("node2").setProperty("prop2", new FileInputStream(blob));
      rootNode.addNode("node3").addMixin("mix:lockable");
      session.save();
   }

   public void addConent(ManageableRepository repository, String wsName) throws Exception
   {
      SessionImpl session = (SessionImpl)repository.login(credentials, wsName);
      Node rootNode = session.getRootNode().addNode("test");

      // add some changes which will be logged in incremental log
      rootNode.addNode("node1").setProperty("prop1", "value1");
      rootNode.addNode("node2").setProperty("prop2", new FileInputStream(blob));
      rootNode.addNode("node3").addMixin("mix:lockable");
      session.save();
   }

   public void checkConent(ManageableRepository repository, String wsName) throws Exception
   {
      SessionImpl session = (SessionImpl)repository.login(credentials, wsName);

      Node rootNode = session.getRootNode().getNode("test");
      assertEquals(rootNode.getNode("node1").getProperty("prop1").getString(), "value1");

      InputStream in = rootNode.getNode("node2").getProperty("prop2").getStream();
      try
      {
         compareStream(new FileInputStream(blob), in);
      }
      finally
      {
         in.close();
      }
   }

   public void checkIncrementalConent(ManageableRepository repository, String wsName) throws Exception
   {
      SessionImpl session = (SessionImpl)repository.login(credentials, wsName);

      Node rootNode = session.getRootNode().getNode("testIncremental");
      assertEquals(rootNode.getNode("node1").getProperty("prop1").getString(), "value1");

      InputStream in = rootNode.getNode("node2").getProperty("prop2").getStream();
      try
      {
         compareStream(new FileInputStream(blob), in);
      }
      finally
      {
         in.close();
      }
   }

   protected RepoInfo createRepositoryAndGetSession() throws Exception
   {
      ManageableRepository repository = helper.createRepository(container, DatabaseStructureType.MULTI, null);
      WorkspaceEntry wsEntry = helper.createWorkspaceEntry(DatabaseStructureType.MULTI, null);
      helper.addWorkspace(repository, wsEntry);

      RepoInfo rInfo = new RepoInfo();
      rInfo.rName = repository.getConfiguration().getName();
      rInfo.wsName = wsEntry.getName();
      rInfo.sysWsName = repository.getConfiguration().getSystemWorkspaceName();
      rInfo.session = repositoryService.getRepository(rInfo.rName).login(credentials, rInfo.wsName);

      return rInfo;
   }

   /**
    * Class for tests purpose only. To have ability to access to {@link ContainerResponseWriter}.
    */
   protected class TesterContainerResponce extends ContainerResponse
   {

      public ByteArrayContainerResponseWriter responseWriter;

      public TesterContainerResponce(ByteArrayContainerResponseWriter responseWriter)
      {
         super(responseWriter);
         this.responseWriter = responseWriter;
      }
   }

   /**
    * Aggregate info about newly created repository.
    */
   protected class RepoInfo
   {
      public String rName;

      public String wsName;

      public String sysWsName;

      public Session session;
   }

   protected boolean isRepositoryExists(String rName)
   {
      return isWorkspaceExists(rName, null);
   }

   protected boolean isWorkspaceExists(String rName, String wsName)
   {
      ManageableRepository repository = null;
      try
      {
         repository = repositoryService.getRepository(rName);
      }
      catch (RepositoryException e)
      {
         return false;
      }
      catch (RepositoryConfigurationException e)
      {
         return false;
      }

      try
      {
         repository.login(credentials, wsName);
      }
      catch (LoginException e)
      {
         return false;
      }
      catch (NoSuchWorkspaceException e)
      {
         return false;
      }
      catch (RepositoryException e)
      {
         return false;
      }

      return true;
   }

   /**
    * Will be created the Object from JSON binary data.
    * 
    * @param cl
    *          Class
    * @param data
    *          binary data (JSON)
    * @return Object
    * @throws Exception
    *           will be generated Exception
    */
   protected Object getObject(Class cl, byte[] data) throws Exception
   {
      JsonHandler jsonHandler = new JsonDefaultHandler();
      JsonParser jsonParser = new JsonParserImpl();
      InputStream inputStream = new ByteArrayInputStream(data);
      jsonParser.parse(inputStream, jsonHandler);
      JsonValue jsonValue = jsonHandler.getJsonObject();

      return new BeanBuilder().createObject(cl, jsonValue);
   }

   protected TesterContainerResponce makeGetRequest(URI uri) throws Exception
   {
      MultivaluedMap<String, String> headers = new MultivaluedMapImpl();

      ContainerRequestUserRole creq =
         new ContainerRequestUserRole("GET", uri, new URI(""), null, new InputHeadersMap(headers));

      ByteArrayContainerResponseWriter responseWriter = new ByteArrayContainerResponseWriter();
      TesterContainerResponce cres = new TesterContainerResponce(responseWriter);
      handler.handleRequest(creq, cres);

      return cres;
   }

   protected TesterContainerResponce makePostRequest(URI uri, Object object) throws Exception
   {
      JsonGeneratorImpl generatorImpl = new JsonGeneratorImpl();
      JsonValue json = generatorImpl.createJsonObject(object);

      MultivaluedMap<String, String> headers = new MultivaluedMapImpl();

      headers.putSingle("Content-Type", "application/json; charset=UTF-8");
      ContainerRequestUserRole creq =
         new ContainerRequestUserRole("POST", uri, new URI(""), new ByteArrayInputStream(json.toString().getBytes(
            "UTF-8")), new InputHeadersMap(headers));

      ByteArrayContainerResponseWriter responseWriter = new ByteArrayContainerResponseWriter();
      TesterContainerResponce cres = new TesterContainerResponce(responseWriter);
      handler.handleRequest(creq, cres);

      return cres;
   }

}
