/* * 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.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.core.ManageableRepository; import org.exoplatform.services.jcr.ext.backup.BackupChain; import org.exoplatform.services.jcr.ext.backup.BackupChainLog; import org.exoplatform.services.jcr.ext.backup.BackupConfig; import org.exoplatform.services.jcr.ext.backup.BackupConfigurationException; import org.exoplatform.services.jcr.ext.backup.BackupJob; import org.exoplatform.services.jcr.ext.backup.BackupJobListener; import org.exoplatform.services.jcr.ext.backup.BackupManager; import org.exoplatform.services.jcr.ext.backup.BackupOperationException; import org.exoplatform.services.log.ExoLogger; import org.exoplatform.services.log.Log; import java.io.File; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Calendar; import java.util.Date; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; import java.util.Timer; import java.util.TimerTask; import javax.jcr.RepositoryException; /** * Created by The eXo Platform SARL .<br> * * @author Gennady Azarenkov * @version $Id: $ */ public class BackupChainImpl implements BackupChain { private final BackupConfig config; private List<BackupJob> jobs; private AbstractFullBackupJob fullBackup; private AbstractIncrementalBackupJob incrementalBackup; private final BackupChainLog chainLog; private final String backupId; private int state; private PeriodConroller periodConroller; private Timer timer; private final Calendar timeStamp; private Set<BackupJobListener> listeners = new LinkedHashSet<BackupJobListener>(); public BackupChainImpl(BackupConfig config, File logDirectory, RepositoryService repositoryService, String fullBackupType, String incrementalBackupType, String backupId, File rootDir, Calendar startTime) throws BackupOperationException, BackupConfigurationException { this.config = config; this.jobs = new ArrayList<BackupJob>(); this.timeStamp = startTime; this.chainLog = new BackupChainLog(logDirectory, config, fullBackupType, incrementalBackupType, backupId, repositoryService.getConfig(), rootDir); this.backupId = backupId; ManageableRepository repository = null; try { repository = repositoryService.getRepository(config.getRepository()); } catch (RepositoryConfigurationException e) { throw new BackupOperationException("Can not get repository \"" + config.getRepository() + "\"", e); } catch (RepositoryException e) { throw new BackupOperationException("Can not get repository \"" + config.getRepository() + "\"", e); } try { this.fullBackup = (AbstractFullBackupJob)ClassLoading.forName(fullBackupType, this).newInstance(); } catch (ClassNotFoundException e) { throw new BackupConfigurationException("FullBackupType error, " + e, e); } catch (InstantiationException e) { throw new BackupConfigurationException("FullBackupType error, " + e, e); } catch (IllegalAccessException e) { throw new BackupConfigurationException("FullBackupType error, " + e, e); } fullBackup.init(repository, config.getWorkspace(), config, timeStamp); if (config.getBackupType() == BackupManager.FULL_AND_INCREMENTAL) { try { this.incrementalBackup = (AbstractIncrementalBackupJob)ClassLoading.forName(incrementalBackupType, this).newInstance(); } catch (ClassNotFoundException e) { throw new BackupConfigurationException("IncrementalBackupType error, " + e, e); } catch (InstantiationException e) { throw new BackupConfigurationException("IncrementalBackupType error, " + e, e); } catch (IllegalAccessException e) { throw new BackupConfigurationException("IncrementalBackupType error, " + e, e); } incrementalBackup.init(repository, config.getWorkspace(), config, timeStamp); periodConroller = new PeriodConroller(config.getIncrementalJobPeriod() * 1000); // sec --> ms } this.state = INITIALIZED; this.timer = new Timer("BackupChain_" + getBackupConfig().getRepository() + "@" + getBackupConfig().getWorkspace() + "_PeriodTimer_" + new SimpleDateFormat("yyyyMMdd.HHmmss.SSS").format(new Date()), true); } /** * Add all listeners to a given job. Used in startBackup() which itself is synchronized. * * @param job */ private void addJobListeners(BackupJob job) { for (BackupJobListener jl : listeners) job.addListener(jl); } /** * Remove all listeners from a given job. Used in stoptBackup() which itself is synchronized. * * @param job */ private void removeJobListeners(BackupJob job) { for (BackupJobListener jl : listeners) job.removeListener(jl); } public void addListener(BackupJobListener listener) { if (listener != null) { synchronized (jobs) { for (BackupJob job : jobs) job.addListener(listener); } synchronized (listeners) { listeners.add(listener); } } } public void removeListener(BackupJobListener listener) { if (listener != null) { try { synchronized (jobs) { for (BackupJob job : jobs) job.removeListener(listener); } } finally { // remove anyway synchronized (listeners) { listeners.remove(listener); } } } } public List<BackupJob> getBackupJobs() { return jobs; } public final synchronized void startBackup() { addJobListeners(fullBackup); Thread fexecutor = new Thread(fullBackup, config.getRepository() + "@" + config.getWorkspace() + "-" + fullBackup.getId()); fexecutor.start(); state |= FULL_WORKING; chainLog.addJobEntry(fullBackup); jobs.add(fullBackup); if (incrementalBackup != null) { addJobListeners(incrementalBackup); Thread iexecutor = new Thread(incrementalBackup, config.getRepository() + "@" + config.getWorkspace() + "-" + incrementalBackup.getId()); iexecutor.start(); state |= INCREMENTAL_WORKING; chainLog.addJobEntry(incrementalBackup); jobs.add(incrementalBackup); if (config.getIncrementalJobPeriod() > 0) periodConroller.start(); } } public final synchronized void stopBackup() { if (!chainLog.isFinilized()) { fullBackup.stop(); chainLog.addJobEntry(fullBackup); removeJobListeners(fullBackup); if (incrementalBackup != null) { if (config.getIncrementalJobPeriod() > 0) periodConroller.stop(); incrementalBackup.stop(); chainLog.addJobEntry(incrementalBackup); removeJobListeners(incrementalBackup); } this.state |= FINISHED; chainLog.endLog(); } } public void restartIncrementalBackup() { incrementalBackup.suspend(); incrementalBackup.resume(); chainLog.addJobEntry(incrementalBackup); } public BackupConfig getBackupConfig() { return config; } public int getFullBackupState() { return fullBackup.getState(); } public int getIncrementalBackupState() { if (incrementalBackup == null) { throw new IllegalStateException("The incremental bacup was not configured. Only full backup."); } return incrementalBackup.getState(); } public String getLogFilePath() { return chainLog.getLogFilePath(); } private class PeriodConroller extends TimerTask { protected Log log = ExoLogger.getLogger("exo.jcr.component.ext.PeriodConroller"); protected Long period; private boolean isFirst = false; public PeriodConroller(long period) { this.period = period; } @Override public void run() { if (incrementalBackup.getState() == BackupJob.FINISHED) { this.stop(); } else if (!isFirst) { isFirst = true; } else { Thread starter = new Thread("IncrementalBackup Starter") { @Override public void run() { restartIncrementalBackup(); if (log.isDebugEnabled()) log.debug("Restart incrementalBackup :" + new Date().toString()); } }; starter.start(); } } public void start() { timer.schedule(this, new Date(), period); } public boolean stop() { log.info("Stop period controller"); return this.cancel(); } } public int getState() { return state; } public boolean isFinished() { return ((state & BackupChain.FINISHED) == BackupChain.FINISHED ? true : false); } public String getBackupId() { return backupId; } public Calendar getStartedTime() { return timeStamp; } }