/*
 * Copyright (C) 2003-2010 eXo Platform SAS.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Affero General Public License
 * as published by the Free Software Foundation; either version 3
 * of the License, or (at your option) any later version.
 *
 * This program 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, see<http://www.gnu.org/licenses/>.
 */
package org.exoplatform.services.jcr.ext.backup.impl;

import org.exoplatform.commons.utils.ClassLoading;
import org.exoplatform.services.jcr.RepositoryService;
import org.exoplatform.services.jcr.config.RepositoryConfigurationException;
import org.exoplatform.services.jcr.config.RepositoryEntry;
import org.exoplatform.services.jcr.config.SimpleParameterEntry;
import org.exoplatform.services.jcr.config.WorkspaceEntry;
import org.exoplatform.services.jcr.config.WorkspaceInitializerEntry;
import org.exoplatform.services.jcr.core.ManageableRepository;
import org.exoplatform.services.jcr.core.WorkspaceContainerFacade;
import org.exoplatform.services.jcr.ext.backup.BackupChainLog;
import org.exoplatform.services.jcr.ext.backup.BackupOperationException;
import org.exoplatform.services.jcr.ext.backup.RepositoryBackupChainLog;
import org.exoplatform.services.jcr.ext.backup.RepositoryRestoreExeption;
import org.exoplatform.services.jcr.ext.backup.impl.fs.FullBackupJob;
import org.exoplatform.services.jcr.ext.backup.impl.rdbms.RdbmsBackupWorkspaceInitializer;
import org.exoplatform.services.jcr.impl.core.BackupWorkspaceInitializer;
import org.exoplatform.services.jcr.impl.core.RepositoryImpl;
import org.exoplatform.services.jcr.impl.core.SessionRegistry;
import org.exoplatform.services.log.ExoLogger;
import org.exoplatform.services.log.Log;

import java.io.File;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;
import java.util.Map;

import javax.jcr.InvalidItemStateException;
import javax.jcr.NoSuchWorkspaceException;
import javax.jcr.RepositoryException;

/**
 * Created by The eXo Platform SAS.
 * 
 * <br>Date: 2010
 *
 * @author <a href="mailto:alex.reshetnyak@exoplatform.com.ua">Alex Reshetnyak</a> 
 * @version $Id$
 */
public class JobRepositoryRestore extends Thread
{
   /**
    * The apache logger.
    */
   protected static final Log LOG = ExoLogger.getLogger("exo.jcr.component.ext.JobRepositoryRestore");

   /**
    * REPOSITORY_RESTORE_STARTED. The state of start restore.
    */
   public static final int REPOSITORY_RESTORE_STARTED = 1;

   /**
    * REPOSITORY_RESTORE_SUCCESSFUL. The state of restore successful.
    */
   public static final int REPOSITORY_RESTORE_SUCCESSFUL = 2;

   /**
    * REPOSITORY_RESTORE_FAIL. The state of restore fail.
    */
   public static final int REPOSITORY_RESTORE_FAIL = 3;

   /**
    * REPOSITORY_RESTORE_STARTED. The state of initialized restore.
    */
   public static final int REPOSITORY_RESTORE_INITIALIZED = 4;

   /**
    * The state of restore.
    */
   private int stateRestore;

   /**
    * The start time of restore.
    */
   private Calendar startTime;

   /**
    * The end time of restore.
    */
   private Calendar endTime;

   /**
    * The exception on restore.
    */
   private Throwable restoreException = null;

   protected RepositoryService repositoryService;

   private BackupManagerImpl backupManager;

   protected RepositoryEntry repositoryEntry;

   protected Map<String, File> workspacesMapping;

   private File repositoryBackupChainLogFile;
   
   /**
    * removeJobOnceOver, in 'true' then restore job well remove after restore.  
    */
   private boolean removeJobOnceOver;

   public JobRepositoryRestore(RepositoryService repoService, BackupManagerImpl backupManagerImpl,
      RepositoryEntry repositoryEntry, Map<String, File> workspacesMapping,
      File backupChainLog, boolean removeJobOnceOver)
   {
      super("JobRepositoryRestore " + repositoryEntry.getName());
      this.repositoryService = repoService;
      this.backupManager = backupManagerImpl;
      this.repositoryEntry = repositoryEntry;
      this.workspacesMapping = workspacesMapping;
      this.repositoryBackupChainLogFile = backupChainLog;
      this.removeJobOnceOver = removeJobOnceOver;
   }

   /**
    * Restore repository. Provide information about start and finish process.
    * 
    * @throws RepositoryRestoreExeption
    *       if exception occurred during restore 
    */
   final protected void restore() throws RepositoryRestoreExeption
   {
      try
      {
         stateRestore = REPOSITORY_RESTORE_STARTED;
         startTime = Calendar.getInstance();

         restoreRepository();

         stateRestore = REPOSITORY_RESTORE_SUCCESSFUL;
         endTime = Calendar.getInstance();
      }
      catch (Throwable t) //NOSONAR
      {
         stateRestore = REPOSITORY_RESTORE_FAIL;
         restoreException = t;

         throw new RepositoryRestoreExeption(t.getMessage(), t);
      }
      finally
      {
         if (removeJobOnceOver)
         {
            backupManager.restoreRepositoryJobs.remove(this);
         }  
      }
   }

   /**
    * Will be restored the workspace.
    * @throws RepositoryRestoreExeption 
    * @throws ClassNotFoundException 
    * @throws BackupOperationException 
    * 
    * @throws RepositoryRestoreExeption
    * @throws BackupOperationException
    * @throws ClassNotFoundException
    */
   protected void restoreRepository() throws RepositoryRestoreExeption, BackupOperationException,
      ClassNotFoundException
   {
      List<WorkspaceEntry> originalWorkspaceEntrys = repositoryEntry.getWorkspaceEntries();

      //Getting system workspace entry
      WorkspaceEntry systemWorkspaceEntry = null;

      for (WorkspaceEntry wsEntry : originalWorkspaceEntrys)
      {
         if (wsEntry.getName().equals(repositoryEntry.getSystemWorkspaceName()))
         {
            systemWorkspaceEntry = wsEntry;
            break;
         }
      }

      if (systemWorkspaceEntry == null)
      {
         throw new RepositoryRestoreExeption("Can not restore workspace \"" + repositoryEntry.getSystemWorkspaceName()
            + " in repository \"" + repositoryEntry.getName() + "\"." + " The related configuration cannot be found.");
      }
      WorkspaceInitializerEntry wieOriginal = systemWorkspaceEntry.getInitializer();

      //getting backup chail log to system workspace.
      BackupChainLog systemBackupChainLog = new BackupChainLog(workspacesMapping.get(systemWorkspaceEntry.getName()));

      WorkspaceInitializerEntry wiEntry = getWorkspaceInitializerEntry(systemBackupChainLog);

      // set initializer
      systemWorkspaceEntry.setInitializer(wiEntry);

      ArrayList<WorkspaceEntry> newEntries = new ArrayList<WorkspaceEntry>();
      newEntries.add(systemWorkspaceEntry);

      repositoryEntry.setWorkspaceEntries(newEntries);

      String currennWorkspaceName = repositoryEntry.getSystemWorkspaceName();

      boolean restored = true;
      try
      {
         LOG.info("Trying to create the repository '" + repositoryEntry.getName() + "'");
         repositoryService.createRepository(repositoryEntry);

         //set original initializer to created workspace.
         RepositoryImpl defRep = (RepositoryImpl)repositoryService.getRepository(repositoryEntry.getName());
         WorkspaceContainerFacade wcf = defRep.getWorkspaceContainer(systemWorkspaceEntry.getName());
         WorkspaceEntry createdWorkspaceEntry = (WorkspaceEntry)wcf.getComponent(WorkspaceEntry.class);
         createdWorkspaceEntry.setInitializer(wieOriginal);

         // save configuration to persistence (file or persister)
         repositoryService.getConfig().retain();

         for (WorkspaceEntry wsEntry : originalWorkspaceEntrys)
         {
            if (!(wsEntry.getName().equals(repositoryEntry.getSystemWorkspaceName())))
            {
               currennWorkspaceName = wsEntry.getName();
               LOG.info("Trying to restore the workspace " + wsEntry.getName());
               backupManager.restore(new BackupChainLog(workspacesMapping.get(wsEntry.getName())),
               repositoryEntry.getName(), wsEntry, false);
            }
         }
      }
      catch (InvalidItemStateException e)
      {
         restored = false;

         LOG.error(
            "Can not restore workspace \"" + currennWorkspaceName + " in repository \"" + repositoryEntry.getName()
               + "\".", e);

         throw new RepositoryRestoreExeption("Can not restore workspace \"" + currennWorkspaceName
            + " in repository \"" + repositoryEntry.getName() + "\"." + " There was database error.", e);

      }
      catch (Throwable t) //NOSONAR
      {
         restored = false;

         LOG.error(
            "Can not restore workspace \"" + currennWorkspaceName + " in repository \"" + repositoryEntry.getName()
               + "\".", t);

         throw new RepositoryRestoreExeption("Can not restore workspace \"" + currennWorkspaceName
            + " in repository \"" + repositoryEntry.getName() + "\".", t);

      }
      finally
      {
         if (!restored)
         {
            try
            {
               removeRepository(repositoryService, repositoryEntry.getName());
            }
            catch (Throwable thr) //NOSONAR
            {
               LOG.error("The partly restored repository \"" + repositoryEntry.getName() + "\" can not be removed.",
                  thr);
            }
         }
      }
   }

   /**
    * Remove repository.
    *
    * @param repositoryService
    *          RepositoryService, the repository service
    * @param repositoryName
    *          String, the repository name
    * @throws RepositoryException
    *           will be generated the RepositoryException
    * @throws RepositoryConfigurationException 
    */
   protected void removeRepository(RepositoryService repositoryService, String repositoryName)
      throws RepositoryException,
      RepositoryConfigurationException
   {
      ManageableRepository mr = null;

      try
      {
         mr = repositoryService.getRepository(repositoryName);
      }
      catch (RepositoryException e)
      {
         // The repository not exist.
         if (LOG.isTraceEnabled())
         {
            LOG.trace("An exception occurred: " + e.getMessage());
         }
      }

      if (mr != null)
      {
         closeAllSession(mr);
         repositoryService.removeRepository(repositoryName);
         repositoryService.getConfig().retain(); // save configuration to persistence (file or persister)
      }
   }

   private WorkspaceInitializerEntry getWorkspaceInitializerEntry(BackupChainLog systemBackupChainLog)
            throws BackupOperationException, ClassNotFoundException
   {
      String fullBackupPath = systemBackupChainLog.getJobEntryInfos().get(0).getURL().getPath();

      String fullbackupType = null;
      try
      {
         if ((ClassLoading.forName(systemBackupChainLog.getFullBackupType(), this).equals(FullBackupJob.class)))
         {
            fullbackupType = systemBackupChainLog.getFullBackupType();
         }
         else if ((ClassLoading.forName(systemBackupChainLog.getFullBackupType(), this)
                  .equals(org.exoplatform.services.jcr.ext.backup.impl.rdbms.FullBackupJob.class)))
         {
            fullbackupType = systemBackupChainLog.getFullBackupType();
         }
         else
         {
            throw new BackupOperationException("Class  \"" + systemBackupChainLog.getFullBackupType()
                     + "\" is not support as full backup.");
         }
      }
      catch (ClassNotFoundException e)
      {
         throw new BackupOperationException("Class \"" + systemBackupChainLog.getFullBackupType() + "\" is not found.",
                  e);
      }

      WorkspaceInitializerEntry wiEntry = new WorkspaceInitializerEntry();
      if ((ClassLoading.forName(fullbackupType, this).equals(FullBackupJob.class)))
      {
         // set the initializer BackupWorkspaceInitializer
         wiEntry.setType(BackupWorkspaceInitializer.class.getCanonicalName());

         List<SimpleParameterEntry> wieParams = new ArrayList<SimpleParameterEntry>();
         wieParams.add(new SimpleParameterEntry(BackupWorkspaceInitializer.RESTORE_PATH_PARAMETER, (new File(
                  fullBackupPath).getParent())));

         wiEntry.setParameters(wieParams);
      }
      else if ((ClassLoading.forName(fullbackupType, this)
               .equals(org.exoplatform.services.jcr.ext.backup.impl.rdbms.FullBackupJob.class)))
      {
         // set the initializer RdbmsBackupWorkspaceInitializer
         wiEntry.setType(RdbmsBackupWorkspaceInitializer.class.getCanonicalName());

         List<SimpleParameterEntry> wieParams = new ArrayList<SimpleParameterEntry>();
         wieParams.add(new SimpleParameterEntry(RdbmsBackupWorkspaceInitializer.RESTORE_PATH_PARAMETER, (new File(
                  fullBackupPath).getParent())));

         wiEntry.setParameters(wieParams);
      }

      return wiEntry;
   }

   /**
    * Close all open session in repository
    * 
    * @param mr
    *  
    * @throws NoSuchWorkspaceException
    */
   private void closeAllSession(ManageableRepository mr) throws NoSuchWorkspaceException
   {
      for (String wsName : mr.getWorkspaceNames())
      {
         if (!mr.canRemoveWorkspace(wsName))
         {
            WorkspaceContainerFacade wc = mr.getWorkspaceContainer(wsName);
            SessionRegistry sessionRegistry = (SessionRegistry)wc.getComponent(SessionRegistry.class);
            sessionRegistry.closeSessions(wsName);
         }
      }
   }

   /**
    * {@inheritDoc}
    */
   @Override
   public void run()
   {
      try
      {
         restore();
      }
      catch (Throwable t) //NOSONAR
      {
         LOG.error("The restore was fail", t);
      }
   }

   /**
    * getRestoreException.
    * 
    * @return Throwable return the exception of repository restore.
    */
   public Throwable getRestoreException()
   {
      return restoreException;
   }

   /**
    * getStateRestore.
    * 
    * @return int return state of restore.
    */
   public int getStateRestore()
   {
      return stateRestore;
   }

   /**
    * getBeginTime.
    * 
    * @return Calendar return the start time of restore
    */
   public Calendar getStartTime()
   {
      return startTime;
   }

   /**
    * getEndTime.
    * 
    * @return Calendar return the end time of restore
    */
   public Calendar getEndTime()
   {
      return endTime;
   }

   /**
    * getRepositoryName.
    *
    * @return String
    *           the name of destination repository
    */
   public String getRepositoryName()
   {
      return repositoryEntry.getName();
   }

   /**
    * GetRepositoryBackupChainLog.
    * 
    * @return repositoryBackupChainLog
    */
   public RepositoryBackupChainLog getRepositoryBackupChainLog() throws BackupOperationException
   {
      return new RepositoryBackupChainLog(repositoryBackupChainLogFile);
   }

   /**
    * getRepositoryEntry.
    * 
    * @return repositoryBackupChainLog
    */
   public RepositoryEntry getRepositoryEntry()
   {
      return repositoryEntry;
   }

}
