/* * 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; import org.exoplatform.commons.utils.PrivilegedFileHelper; import org.exoplatform.commons.utils.SecurityHelper; import org.exoplatform.container.xml.Deserializer; import org.exoplatform.services.jcr.config.RepositoryEntry; import org.exoplatform.services.jcr.config.RepositoryServiceConfiguration; import org.exoplatform.services.jcr.ext.backup.server.RepositoryRestoreExeption; import org.exoplatform.services.jcr.impl.Constants; import org.exoplatform.services.jcr.impl.util.JCRDateFormat; import org.exoplatform.services.log.ExoLogger; import org.exoplatform.services.log.Log; import org.jibx.runtime.BindingDirectory; import org.jibx.runtime.IBindingFactory; import org.jibx.runtime.IMarshallingContext; import org.jibx.runtime.IUnmarshallingContext; import org.jibx.runtime.JiBXException; import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.UnsupportedEncodingException; import java.security.PrivilegedActionException; import java.security.PrivilegedExceptionAction; import java.util.ArrayList; import java.util.Calendar; import java.util.List; import java.util.regex.PatternSyntaxException; import javax.xml.stream.FactoryConfigurationError; import javax.xml.stream.XMLInputFactory; import javax.xml.stream.XMLOutputFactory; import javax.xml.stream.XMLStreamException; import javax.xml.stream.XMLStreamReader; import javax.xml.stream.XMLStreamWriter; import javax.xml.stream.events.StartElement; /** * 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 RepositoryBackupChainLog { private class LogWriter { protected Log logger = ExoLogger.getLogger("exo.jcr.component.ext.LogWriter"); private File logFile; XMLStreamWriter writer; public LogWriter(File file) throws FileNotFoundException, XMLStreamException, FactoryConfigurationError { this.logFile = file; try { writer = SecurityHelper.doPrivilegedExceptionAction(new PrivilegedExceptionAction<XMLStreamWriter>() { public XMLStreamWriter run() throws Exception { return XMLOutputFactory.newInstance().createXMLStreamWriter(new FileOutputStream(logFile), Constants.DEFAULT_ENCODING); } }); } catch (PrivilegedActionException pae) { Throwable cause = pae.getCause(); if (cause instanceof FileNotFoundException) { throw (FileNotFoundException)cause; } else if (cause instanceof XMLStreamException) { throw (XMLStreamException)cause; } else if (cause instanceof FactoryConfigurationError) { throw (FactoryConfigurationError)cause; } else if (cause instanceof RuntimeException) { throw (RuntimeException)cause; } else { throw new RuntimeException(cause); } }; writer.writeStartDocument(); writer.writeStartElement("repository-backup-chain-log"); writer.writeStartElement("version-log"); writer.writeCharacters(versionLog); writer.writeEndElement(); writer.writeStartElement("start-time"); writer.writeCharacters(JCRDateFormat.format(startedTime)); writer.writeEndElement(); writer.flush(); } public void writeSystemWorkspaceName(String wsName) throws XMLStreamException { writer.writeStartElement("system-workspace"); writer.writeCharacters(wsName); writer.writeEndElement(); writer.flush(); } public void writeBackupsPath(List<String> wsLogFilePathList, RepositoryBackupConfig config) throws XMLStreamException, IOException { writer.writeStartElement("workspaces-backup-info"); for (String path : wsLogFilePathList) { writer.writeStartElement("url"); writer.writeCharacters(RepositoryChainLogPathHelper.getRelativePath(path, PrivilegedFileHelper.getCanonicalPath(config .getBackupDir()))); writer.writeEndElement(); } writer.writeEndElement(); writer.flush(); } public synchronized void write(RepositoryBackupConfig config, String fullBackupType, String incrementalBackupType, File serviceBackupDir) throws XMLStreamException, IOException { writer.writeStartElement("repository-backup-config"); writer.writeStartElement("backup-type"); writer.writeCharacters(String.valueOf(config.getBackupType())); writer.writeEndElement(); writer.writeStartElement("full-backup-type"); writer.writeCharacters(fullBackupType); writer.writeEndElement(); writer.writeStartElement("incremental-backup-type"); writer.writeCharacters(incrementalBackupType); writer.writeEndElement(); if (config.getBackupDir() != null) { String backupDir = PrivilegedFileHelper.getCanonicalPath(config.getBackupDir()); String serviceBackupDirPath = PrivilegedFileHelper.getCanonicalPath(serviceBackupDir); if (backupDir.startsWith(serviceBackupDirPath)) { backupDir = "." + backupDir.replace(serviceBackupDirPath, ""); if (File.separator.equals("\\")) { backupDir = backupDir.replaceAll("\\\\", "/"); } } writer.writeStartElement("backup-dir"); writer.writeCharacters(backupDir); writer.writeEndElement(); } if (config.getRepository() != null) { writer.writeStartElement("repository"); writer.writeCharacters(config.getRepository()); writer.writeEndElement(); } writer.writeStartElement("incremental-job-period"); writer.writeCharacters(Long.toString(config.getIncrementalJobPeriod())); writer.writeEndElement(); writer.writeStartElement("incremental-job-number"); writer.writeCharacters(Integer.toString(config.getIncrementalJobNumber())); writer.writeEndElement(); writer.writeEndElement(); writer.flush(); } public synchronized void writeEndLog() { try { writer.writeStartElement("finish-time"); writer.writeCharacters(JCRDateFormat.format(finishedTime)); writer.writeEndElement(); writer.writeEndElement(); writer.writeEndDocument(); writer.flush(); } catch (XMLStreamException e) { logger.error("Can't write end log", e); } } public synchronized void writeRepositoryEntry(RepositoryEntry rEntry, RepositoryServiceConfiguration serviceConfiguration) throws XMLStreamException, IOException, JiBXException { File config = new File(PrivilegedFileHelper.getCanonicalPath(RepositoryBackupChainLog.this.config.getBackupDir()) + File.separator + "original-repository-config.xml"); PrivilegedFileHelper.createNewFile(config); OutputStream saveStream = PrivilegedFileHelper.fileOutputStream(config); ArrayList<RepositoryEntry> repositoryEntries = new ArrayList<RepositoryEntry>(); repositoryEntries.add(rEntry); RepositoryServiceConfiguration newRepositoryServiceConfiguration = new RepositoryServiceConfiguration(serviceConfiguration.getDefaultRepositoryName(), repositoryEntries); IBindingFactory bfact; try { bfact = SecurityHelper.doPrivilegedExceptionAction(new PrivilegedExceptionAction<IBindingFactory>() { public IBindingFactory run() throws Exception { return BindingDirectory.getFactory(RepositoryServiceConfiguration.class); } }); } catch (PrivilegedActionException pae) { Throwable cause = pae.getCause(); if (cause instanceof JiBXException) { throw (JiBXException) cause; } else if (cause instanceof RuntimeException) { throw (RuntimeException) cause; } else { throw new RuntimeException(cause); } } IMarshallingContext mctx = bfact.createMarshallingContext(); mctx.marshalDocument(newRepositoryServiceConfiguration, "ISO-8859-1", null, saveStream); saveStream.close(); writer.writeStartElement("original-repository-config"); writer.writeCharacters(config.getName()); writer.writeEndElement(); } } private class LogReader { protected Log logger = ExoLogger.getLogger("exo.jcr.component.ext.LogReader"); private File logFile; private XMLStreamReader reader; private String version; public LogReader(File logFile) throws FileNotFoundException, XMLStreamException, FactoryConfigurationError { this.logFile = logFile; reader = XMLInputFactory.newInstance().createXMLStreamReader(PrivilegedFileHelper.fileInputStream(logFile), Constants.DEFAULT_ENCODING); } public void readLogFile() throws UnsupportedEncodingException, Exception { boolean endDocument = false; while (!endDocument) { int eventCode = reader.next(); switch (eventCode) { case StartElement.START_ELEMENT : String name = reader.getLocalName(); if (name.equals("repository-backup-config")) config = readBackupConfig(); if (name.equals("system-workspace")) workspaceSystem = readContent(); if (name.equals("backup-config")) config = readBackupConfig(); if (name.equals("workspaces-backup-info")) workspaceBackupsInfo = readWorkspaceBackupInfo(); if (name.equals("start-time")) startedTime = JCRDateFormat.parse(readContent()); if (name.equals("finish-time")) finishedTime = JCRDateFormat.parse(readContent()); if (name.equals("original-repository-config")) originalRepositoryEntry = readRepositoryEntry(); if (name.equals("version-log")) { this.version = readContent(); } break; case StartElement.END_DOCUMENT : endDocument = true; break; } } } private RepositoryEntry readRepositoryEntry() throws UnsupportedEncodingException, Exception { String configName = readContent(); File configFile = new File(PrivilegedFileHelper.getCanonicalPath(getBackupConfig().getBackupDir()) + File.separator + configName); if (!PrivilegedFileHelper.exists(configFile)) { throw new RepositoryRestoreExeption("The backup set is not contains original repository configuration : " + PrivilegedFileHelper.getCanonicalPath(getBackupConfig().getBackupDir())); } IBindingFactory factory; try { factory = SecurityHelper.doPrivilegedExceptionAction(new PrivilegedExceptionAction<IBindingFactory>() { public IBindingFactory run() throws Exception { return BindingDirectory.getFactory(RepositoryServiceConfiguration.class); } }); } catch (PrivilegedActionException pae) { Throwable cause = pae.getCause(); if (cause instanceof JiBXException) { throw (JiBXException)cause; } else if (cause instanceof RuntimeException) { throw (RuntimeException)cause; } else { throw new RuntimeException(cause); } } IUnmarshallingContext uctx = factory.createUnmarshallingContext(); RepositoryServiceConfiguration conf = (RepositoryServiceConfiguration) uctx.unmarshalDocument(PrivilegedFileHelper .fileInputStream(configFile), null); if (conf.getRepositoryConfigurations().size() != 1) { throw new RepositoryRestoreExeption( "The oririginal configuration should be contains only one repository entry :" + PrivilegedFileHelper.getCanonicalPath(configFile)); } if (!conf.getRepositoryConfiguration(getBackupConfig().getRepository()).getName().equals( getBackupConfig().getRepository())) { throw new RepositoryRestoreExeption( "The oririginal configuration should be contains only one repository entry with name \"" + getBackupConfig().getRepository() + "\" :" + PrivilegedFileHelper.getCanonicalPath(configFile)); } return conf.getRepositoryConfiguration(getBackupConfig().getRepository()); } private List<String> readWorkspaceBackupInfo() throws XMLStreamException, IOException { List<String> wsBackupInfo = new ArrayList<String>(); boolean endWorkspaceBackupInfo = false; while (!endWorkspaceBackupInfo) { int eventCode = reader.next(); switch (eventCode) { case StartElement.START_ELEMENT : String name = reader.getLocalName(); if (name.equals("url")) { if (version != null && version.equals(VERSION_LOG_1_1)) { String path = readContent(); wsBackupInfo.add(RepositoryChainLogPathHelper.getPath(path, PrivilegedFileHelper.getCanonicalPath(config .getBackupDir()))); } else { wsBackupInfo.add(readContent()); } } break; case StartElement.END_ELEMENT : String tagName = reader.getLocalName(); if (tagName.equals("workspaces-backup-info")) endWorkspaceBackupInfo = true; break; } } return wsBackupInfo; } private BackupConfig readBackupConfig() throws XMLStreamException, IOException { BackupConfig conf = new BackupConfig(); boolean endBackupConfig = false; while (!endBackupConfig) { int eventCode = reader.next(); switch (eventCode) { case StartElement.START_ELEMENT : String name = reader.getLocalName(); if (name.equals("backup-dir")) { if (version != null && version.equals(VERSION_LOG_1_1)) { String dir = readContent(); if (dir.equals(".")) { String path = PrivilegedFileHelper.getCanonicalPath(logFile.getParentFile()); conf.setBackupDir(new File(path)); } else if (dir.startsWith("./")) { String path = PrivilegedFileHelper.getCanonicalPath(logFile.getParentFile()); dir = dir.replace("./", "/"); if (File.separator.equals("\\")) { dir = dir.replaceAll("/", "\\\\"); } conf.setBackupDir(new File(path + dir)); } else { conf.setBackupDir(new File(Deserializer.resolveVariables(dir))); } } else { conf.setBackupDir(new File(readContent())); } } if (name.equals("backup-type")) conf.setBackupType(Integer.valueOf(readContent())); if (name.equals("repository")) conf.setRepository(readContent()); if (name.equals("workspace")) conf.setWorkspace(readContent()); if (name.equals("incremental-job-period")) conf.setIncrementalJobPeriod(Long.valueOf(readContent()).longValue()); if (name.equals("incremental-job-number")) conf.setIncrementalJobNumber(Integer.valueOf(readContent()).intValue()); if (name.equals("full-backup-type")) fullBackupType = readContent(); if (name.equals("incremental-backup-type")) increnetalBackupType = readContent(); break; case StartElement.END_ELEMENT : String tagName = reader.getLocalName(); if (tagName.equals("repository-backup-config")) endBackupConfig = true; break; } } return conf; } private String readContent() throws XMLStreamException { String content = null; int eventCode = reader.next(); if (eventCode == StartElement.CHARACTERS) content = reader.getText(); return content; } public String getVersionLog() { return version; } } protected static Log logger = ExoLogger.getLogger("exo.jcr.component.ext.BackupChainLog"); /** * Start for 1.1 version log will be stored relative paths. */ protected static String VERSION_LOG_1_1 = "1.1"; public static final String PREFIX = "repository-backup-"; private static final String SUFFIX = ".xml"; private File log; private LogWriter logWriter; private LogReader logReader; private RepositoryBackupConfig config; private String backupId; private Calendar startedTime; private Calendar finishedTime; private boolean finalized; private List<String> workspaceBackupsInfo; private String workspaceSystem; private String fullBackupType; private String increnetalBackupType; private RepositoryEntry originalRepositoryEntry; private final String versionLog; /** * @param logDirectory * @param config * @param systemWorkspace * @param wsLogFilePathList * @param backupId * @param startTime * @param rEntry * @throws BackupOperationException */ public RepositoryBackupChainLog(File logDirectory, RepositoryBackupConfig config, String fullBackupType, String incrementalBackupType, String systemWorkspace, List<String> wsLogFilePathList, String backupId, Calendar startTime, RepositoryEntry rEntry, RepositoryServiceConfiguration repositoryServiceConfiguration) throws BackupOperationException { try { this.finalized = false; this.versionLog = VERSION_LOG_1_1; this.log = new File(PrivilegedFileHelper.getCanonicalPath(logDirectory) + File.separator + (PREFIX + backupId + SUFFIX)); PrivilegedFileHelper.createNewFile(this.log); this.backupId = backupId; this.config = config; this.startedTime = Calendar.getInstance(); this.fullBackupType = fullBackupType; this.increnetalBackupType = incrementalBackupType; this.originalRepositoryEntry = rEntry; logWriter = new LogWriter(log); logWriter.write(config, fullBackupType, incrementalBackupType, logDirectory); logWriter.writeSystemWorkspaceName(systemWorkspace); logWriter.writeBackupsPath(wsLogFilePathList, config); logWriter.writeRepositoryEntry(rEntry, repositoryServiceConfiguration); this.workspaceBackupsInfo = wsLogFilePathList; this.workspaceSystem = systemWorkspace; } catch (IOException e) { throw new BackupOperationException("Can not create backup log ...", e); } catch (XMLStreamException e) { throw new BackupOperationException("Can not create backup log ...", e); } catch (FactoryConfigurationError e) { throw new BackupOperationException("Can not create backup log ...", e); } catch (JiBXException e) { throw new BackupOperationException("Can not create backup log ...", e); } } /** * @param log * @throws BackupOperationException */ public RepositoryBackupChainLog(File log) throws BackupOperationException { this.log = log; this.backupId = log.getName().replaceAll(PREFIX, "").replaceAll(SUFFIX, ""); try { logReader = new LogReader(log); logReader.readLogFile(); this.versionLog = logReader.getVersionLog(); } catch (FileNotFoundException e) { throw new BackupOperationException("Can not read RepositoryBackupChainLog from file :" + PrivilegedFileHelper.getAbsolutePath(log), e); } catch (XMLStreamException e) { throw new BackupOperationException("Can not read RepositoryBackupChainLog from file :" + PrivilegedFileHelper.getAbsolutePath(log), e); } catch (UnsupportedEncodingException e) { throw new BackupOperationException("Can not read RepositoryBackupChainLog from file :" + PrivilegedFileHelper.getAbsolutePath(log), e); } catch (Exception e) { throw new BackupOperationException("Can not read RepositoryBackupChainLog from file :" + PrivilegedFileHelper.getAbsolutePath(log), e); } } /** * Getting log file path. * * @return String * return the path to backup log */ public String getLogFilePath() { return PrivilegedFileHelper.getAbsolutePath(log); } /** * Getting repository backup configuration. * * @return ReposiotoryBackupConfig * return the repository backup configuration */ public RepositoryBackupConfig getBackupConfig() { return config; } /** * Getting the started time. * * @return Calendar * return the started time */ public Calendar getStartedTime() { return startedTime; } /** * Getting the finished time. * * @return Calendar * return the finished time */ public Calendar getFinishedTime() { return finishedTime; } public boolean isFinilized() { return finalized; } /** * Finalize log. * */ public synchronized void endLog() { if (!finalized) { finishedTime = Calendar.getInstance(); finalized = true; logWriter.writeEndLog(); //copy backup chain log file in into Backupset files itself for portability (e.g. on another server) try { InputStream in = PrivilegedFileHelper.fileInputStream(log); File dest = new File(config.getBackupDir() + File.separator + log.getName()); if (!PrivilegedFileHelper.exists(dest)) { OutputStream out = PrivilegedFileHelper.fileOutputStream(dest); byte[] buf = new byte[(int) (PrivilegedFileHelper.length(log))]; in.read(buf); String sConfig = new String(buf, Constants.DEFAULT_ENCODING); sConfig = sConfig.replaceAll("<backup-dir>.+</backup-dir>", "<backup-dir>.</backup-dir>"); out.write(sConfig.getBytes(Constants.DEFAULT_ENCODING)); in.close(); out.close(); } } catch (PatternSyntaxException e) { logger.error("Can't write log", e); } catch (FileNotFoundException e) { logger.error("Can't write log", e); } catch (IOException e) { logger.error("Can't write log", e); } } } /** * Getting the system workspace name. * * @return String * return the system workspace name. */ public String getSystemWorkspace() { return workspaceSystem; } /** * Getting the workspace backups info. * * @return Collection * return the list with path to backups. */ public List<String> getWorkspaceBackupsInfo() { return workspaceBackupsInfo; } /** * Getting the backup id. * * @return int * return the backup id */ public String getBackupId() { return backupId; } /** * Getting original repository configuration * * @return RepositoryEntry * return the original repository configuration */ public RepositoryEntry getOriginalRepositoryEntry() { return originalRepositoryEntry; } }