/* * 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 org.exoplatform.commons.utils.PrivilegedFileHelper; import org.exoplatform.commons.utils.SecurityHelper; import org.exoplatform.services.jcr.config.RepositoryConfigurationException; import org.exoplatform.services.jcr.config.RepositoryEntry; import org.exoplatform.services.jcr.config.RepositoryServiceConfiguration; import org.exoplatform.services.jcr.config.WorkspaceEntry; import org.exoplatform.services.jcr.ext.backup.server.WorkspaceRestoreExeption; 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.net.MalformedURLException; import java.net.URL; import java.security.PrivilegedActionException; import java.security.PrivilegedExceptionAction; import java.util.ArrayList; import java.util.Calendar; import java.util.Collection; import java.util.HashMap; import java.util.List; import javax.jcr.RepositoryException; import javax.jcr.ValueFormatException; 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 SARL .<br> * * @author Gennady Azarenkov * @version $Id: $ */ public class BackupChainLog { /** * Start for 1.1 version log will be stored relative paths. */ protected static String VERSION_LOG_1_1 = "1.1"; protected static Log logger = ExoLogger.getLogger("exo.jcr.component.ext.BackupChainLog"); public static final String PREFIX = "backup-"; private static final String SUFFIX = ".xml"; private File log; private final List<JobEntryInfo> jobEntries; private LogWriter logWriter; private LogReader logReader; private BackupConfig config; private String backupId; private Calendar startedTime; private Calendar finishedTime; private boolean finalized; private WorkspaceEntry originalWorkspaceEntry; private final String versionLog; private File rootDir; private String fullBackupType; private String incrementalBackupType; /** * BackupChainLog constructor. * * @param logDir * File, the directory to logs * @param config * BackupConfig, the backup config * @param fullBackupType * Sting, FQN for full backup * @param incrementalBackupType * Sting, FQN for incremental backup * @param backupId * String, the identifier of backup * @param repositoryServiceConfiguration * @param rootDir * @throws BackupOperationException * will be generate the exception BackupOperationException */ public BackupChainLog(File logDir, BackupConfig config, String fullBackupType, String incrementalBackupType, String backupId, RepositoryServiceConfiguration repositoryServiceConfiguration, File rootDir) throws BackupOperationException { try { this.finalized = false; this.versionLog = VERSION_LOG_1_1; this.log = new File(PrivilegedFileHelper.getCanonicalPath(logDir) + File.separator + (PREFIX + backupId + SUFFIX)); PrivilegedFileHelper.createNewFile(this.log); this.rootDir = rootDir; this.backupId = backupId; this.fullBackupType = fullBackupType; this.incrementalBackupType = incrementalBackupType; this.config = config; this.jobEntries = new ArrayList<JobEntryInfo>(); this.originalWorkspaceEntry = getWorkspaceEntry(config, repositoryServiceConfiguration); // write config info here logWriter = new LogWriter(log); logWriter.write(config, fullBackupType, incrementalBackupType); logWriter.writeWorkspaceEntry(originalWorkspaceEntry, repositoryServiceConfiguration); } catch (IOException e) { throw new BackupOperationException(e); } catch (XMLStreamException e) { throw new BackupOperationException(e); } catch (FactoryConfigurationError e) { throw new BackupOperationException(e); } catch (JiBXException e) { throw new BackupOperationException(e); } catch (RepositoryException e) { throw new BackupOperationException(e); } catch (RepositoryConfigurationException e) { throw new BackupOperationException(e); } } private WorkspaceEntry getWorkspaceEntry(BackupConfig config, RepositoryServiceConfiguration repositoryServiceConfiguration) throws BackupOperationException { RepositoryEntry repository = null; try { repository = repositoryServiceConfiguration.getRepositoryConfiguration(config.getRepository()); } catch (RepositoryConfigurationException e) { throw new BackupOperationException("Can not get repository \"" + config.getRepository() + "\"", e); } WorkspaceEntry wEntry = null; for (WorkspaceEntry entry : repository.getWorkspaceEntries()) { if (entry.getName().equals(config.getWorkspace())) { wEntry = entry; break; } } if (wEntry == null) { throw new BackupOperationException("Worksapce \"" + config.getWorkspace() + "\" was not exsisted in repository \"" + repository.getName() + "\"."); } return wEntry; } /** * BackupChainLog constructor. * * @param log * File, the backup log * @throws BackupOperationException * will be generate the BackupOperationException */ public BackupChainLog(File log) throws BackupOperationException { this.log = log; this.backupId = log.getName().replaceAll(PREFIX, "").replaceAll(SUFFIX, ""); try { logReader = new LogReader(log); logReader.readLogFile(); logReader.jobEntrysNormalize(); this.versionLog = logReader.getVersionLog(); this.config = logReader.getBackupConfig(); this.startedTime = logReader.getBeginTime(); this.finishedTime = logReader.getEndTime(); this.jobEntries = logReader.getJobEntryInfoNormalizeList(); this.originalWorkspaceEntry = logReader.getOriginalWorkspaceEntry(); this.fullBackupType = logReader.getFullBackupType(); this.incrementalBackupType = logReader.getIncrementalBackupType(); for (JobEntryInfo info : jobEntries) { if (info.getType() == BackupJob.INCREMENTAL) { config.setBackupType(BackupManager.FULL_AND_INCREMENTAL); break; } } } catch (FileNotFoundException e) { throw new BackupOperationException(e); } catch (XMLStreamException e) { throw new BackupOperationException(e); } catch (FactoryConfigurationError e) { throw new BackupOperationException(e); } catch (MalformedURLException e) { throw new BackupOperationException(e); } catch (ValueFormatException e) { throw new BackupOperationException(e); } catch (Exception e) { throw new BackupOperationException(e); } } /** * Adding the the backup job. * * @param job * BackupJob, the backup job */ public void addJobEntry(BackupJob job) { // jobEntries try { JobEntryInfo info = new JobEntryInfo(); info.setDate(Calendar.getInstance()); info.setType(job.getType()); info.setState(job.getState()); info.setURL(job.getStorageURL()); logWriter.write(info, config); } catch (IOException e) { logger.error("Can't add job", e); } catch (XMLStreamException e) { logger.error("Can't add job", e); } catch (BackupOperationException e) { logger.error("Can't add job", e); } } /** * Getting the backup id. * * @return int * return the backup id */ public String getBackupId() { return backupId; } /** * Getting the config info. * * @return String * return the config info */ public String getConfigInfo() { return null; } /** * Getting the job informations. * * @return List * return the job informations. */ public List<JobEntryInfo> getJobEntryInfos() { return jobEntries; } /** * Finalize log. * */ public void endLog() { 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 (FileNotFoundException e) { logger.error("Can't write log", e); } catch (UnsupportedEncodingException e) { logger.error("Can't write log", e); } catch (IOException e) { logger.error("Can't write log", e); } } /** * Getting the states for jobs. * * @return Collection * return the collection of states for jobs * */ public Collection<JobEntryInfo> getJobEntryStates() { HashMap<Integer, JobEntryInfo> infos = new HashMap<Integer, JobEntryInfo>(); for (JobEntryInfo jobEntry : jobEntries) { infos.put(jobEntry.getID(), jobEntry); } return infos.values(); } /** * Getting backup config. * * @return BackupConfig * return the backup config */ public BackupConfig getBackupConfig() { return config; } /** * Getting log file path. * * @return String * return the path to backup log */ public String getLogFilePath() { return PrivilegedFileHelper.getAbsolutePath(log); } /** * 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; } /** * Getting original workspace configuration * * @return WorkspaceEntry * return the original workspace configuration */ public WorkspaceEntry getOriginalWorkspaceEntry() { return originalWorkspaceEntry; } private class LogReader { protected Log logger = ExoLogger.getLogger("exo.jcr.component.ext.LogReader"); private File logFile; private XMLStreamReader reader; private BackupConfig config; private List<JobEntryInfo> jobEntries; private List<JobEntryInfo> jobEntriesNormalize; private WorkspaceEntry originalWorkspaceEntry; private String version; private String iBackupType; private String fBackupType; public LogReader(File logFile) throws FileNotFoundException, XMLStreamException, FactoryConfigurationError { this.logFile = logFile; jobEntries = new ArrayList<JobEntryInfo>(); reader = XMLInputFactory.newInstance().createXMLStreamReader( PrivilegedFileHelper.fileInputStream(this.logFile), Constants.DEFAULT_ENCODING); } public String getIncrementalBackupType() { return iBackupType; } public String getFullBackupType() { return fBackupType; } public String getVersionLog() { return version; } public WorkspaceEntry getOriginalWorkspaceEntry() { return originalWorkspaceEntry; } public BackupConfig getBackupConfig() { return config; } public List<JobEntryInfo> getJobEntryInfoList() { return jobEntries; } public List<JobEntryInfo> getJobEntryInfoNormalizeList() { return jobEntriesNormalize; } public Calendar getBeginTime() { return jobEntries.get(0).getDate(); } public Calendar getEndTime() { return jobEntries.get(jobEntries.size() - 1).getDate(); } public void readLogFile() throws Exception { boolean endDocument = false; while (!endDocument) { int eventCode = reader.next(); switch (eventCode) { case StartElement.START_ELEMENT : String name = reader.getLocalName(); if (name.equals("backup-config")) config = readBackupConfig(); if (name.equals("job-entry-info")) jobEntries.add(readJobEntryInfo()); if (name.equals("original-workspace-config")) this.originalWorkspaceEntry = readWorkspaceEntry(); if (name.equals("version-log")) { this.version = readContent(); } break; case StartElement.END_DOCUMENT : endDocument = true; break; } } } private WorkspaceEntry readWorkspaceEntry() throws Exception { String configName = readContent(); File configFile = new File(PrivilegedFileHelper.getCanonicalPath(getBackupConfig().getBackupDir()) + File.separator + configName); if (!PrivilegedFileHelper.exists(configFile)) { throw new WorkspaceRestoreExeption("The backup set is not contains original workspace 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); RepositoryEntry repositoryEntry = conf.getRepositoryConfiguration(getBackupConfig().getRepository()); if (repositoryEntry.getWorkspaceEntries().size() != 1) { throw new WorkspaceRestoreExeption( "The oririginal configuration should be contains only one workspace entry :" + PrivilegedFileHelper.getCanonicalPath(configFile)); } if (!repositoryEntry.getWorkspaceEntries().get(0).getName().equals(getBackupConfig().getWorkspace())) { throw new WorkspaceRestoreExeption( "The oririginal configuration should be contains only one workspace entry with name \"" + getBackupConfig().getWorkspace() + "\" :" + PrivilegedFileHelper.getCanonicalPath(configFile)); } return repositoryEntry.getWorkspaceEntries().get(0); } private JobEntryInfo readJobEntryInfo() throws XMLStreamException, ValueFormatException, IOException { JobEntryInfo info = new JobEntryInfo(); boolean endJobEntryInfo = false; while (!endJobEntryInfo) { int eventCode = reader.next(); switch (eventCode) { case StartElement.START_ELEMENT : String name = reader.getLocalName(); if (name.equals("type")) info.setType(getType(readContent())); if (name.equals("state")) info.setState(getState(readContent())); if (name.equals("url")) { if (version != null && version.equals(VERSION_LOG_1_1)) { String path = readContent().replace( "file:", "file:" + PrivilegedFileHelper.getCanonicalPath(config.getBackupDir()) + File.separator); info.setURL(new URL(path)); } else { info.setURL(new URL(readContent())); } } if (name.equals("date")) info.setDate(JCRDateFormat.parse(readContent())); break; case StartElement.END_ELEMENT : String tagName = reader.getLocalName(); if (tagName.equals("job-entry-info")) endJobEntryInfo = true; break; } } return info; } private int getState(String content) { int state = -1; if (content.equals("FINISHED")) state = BackupJob.FINISHED; if (content.equals("STARTING")) state = BackupJob.STARTING; if (content.equals("WAITING")) state = BackupJob.WAITING; if (content.equals("WORKING")) state = BackupJob.WORKING; return state; } private int getType(String content) { int type = -1; if (content.equals("FULL")) type = BackupJob.FULL; if (content.equals("INCREMENTAL")) type = BackupJob.INCREMENTAL; return type; } 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 { conf.setBackupDir(new File(dir)); } } else { conf.setBackupDir(new File(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")) this.fBackupType = readContent(); if (name.equals("incremental-backup-type")) this.iBackupType = readContent(); break; case StartElement.END_ELEMENT : String tagName = reader.getLocalName(); if (tagName.equals("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 void jobEntrysNormalize() { jobEntriesNormalize = new ArrayList<JobEntryInfo>(); for (int i = 0; i < jobEntries.size(); i++) { JobEntryInfo entryInfo = jobEntries.get(i); boolean alreadyExist = false; for (int j = 0; j < jobEntriesNormalize.size(); j++) if (jobEntriesNormalize.get(j).getURL().toString().equals(entryInfo.getURL().toString())) alreadyExist = true; if (!alreadyExist) jobEntriesNormalize.add(entryInfo); } } } 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("backup-chain-log"); writer.writeStartElement("version-log"); writer.writeCharacters(versionLog); writer.writeEndElement(); writer.flush(); } public void writeWorkspaceEntry(WorkspaceEntry originalWorkspaceEntry, RepositoryServiceConfiguration serviceConfiguration) throws XMLStreamException, IOException, JiBXException, RepositoryException, RepositoryConfigurationException { File config = new File(PrivilegedFileHelper.getCanonicalPath(BackupChainLog.this.config.getBackupDir()) + File.separator + "original-workspace-config.xml"); PrivilegedFileHelper.createNewFile(config); OutputStream saveStream = PrivilegedFileHelper.fileOutputStream(config); RepositoryEntry baseRepositoryEntry = serviceConfiguration.getRepositoryConfiguration(BackupChainLog.this.config.getRepository()); RepositoryEntry repositoryEntry = new RepositoryEntry(); repositoryEntry.addWorkspace(originalWorkspaceEntry); repositoryEntry.setSystemWorkspaceName(baseRepositoryEntry.getSystemWorkspaceName()); repositoryEntry.setAccessControl(baseRepositoryEntry.getAccessControl()); repositoryEntry.setAuthenticationPolicy(baseRepositoryEntry.getAuthenticationPolicy()); repositoryEntry.setDefaultWorkspaceName(baseRepositoryEntry.getDefaultWorkspaceName()); repositoryEntry.setName(baseRepositoryEntry.getName()); repositoryEntry.setSecurityDomain(baseRepositoryEntry.getSecurityDomain()); repositoryEntry.setSessionTimeOut(baseRepositoryEntry.getSessionTimeOut()); ArrayList<RepositoryEntry> repositoryEntries = new ArrayList<RepositoryEntry>(); repositoryEntries.add(repositoryEntry); 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-workspace-config"); writer.writeCharacters(config.getName()); writer.writeEndElement(); } public synchronized void write(BackupConfig config, String fullBackupType, String incrementalBackupType) throws XMLStreamException, IOException { writer.writeStartElement("backup-config"); writer.writeStartElement("full-backup-type"); writer.writeCharacters(fullBackupType); writer.writeEndElement(); writer.writeStartElement("incremental-backup-type"); writer.writeCharacters(incrementalBackupType); writer.writeEndElement(); if (config.getBackupDir() != null) { writer.writeStartElement("backup-dir"); String path = (isRootBackupManagerDir(logFile) ? PrivilegedFileHelper.getCanonicalPath(config.getBackupDir()) : "."); writer.writeCharacters(path); writer.writeEndElement(); } if (config.getRepository() != null) { writer.writeStartElement("repository"); writer.writeCharacters(config.getRepository()); writer.writeEndElement(); } if (config.getWorkspace() != null) { writer.writeStartElement("workspace"); writer.writeCharacters(config.getWorkspace()); 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 write(JobEntryInfo info, BackupConfig config) throws XMLStreamException, IOException { writer.writeStartElement("job-entry-info"); writer.writeStartElement("type"); writer.writeCharacters((info.getType() == BackupJob.FULL ? "FULL" : "INCREMENTAL")); writer.writeEndElement(); writer.writeStartElement("state"); writer.writeCharacters(getState(info.getState())); writer.writeEndElement(); writer.writeStartElement("url"); writer.writeCharacters(getRelativeUrl(info.getURL(), config.getBackupDir())); writer.writeEndElement(); writer.writeStartElement("date"); writer.writeCharacters(JCRDateFormat.format(info.getDate())); writer.writeEndElement(); writer.writeEndElement(); writer.flush(); } private String getRelativeUrl(URL url, File backupDir) throws IOException { String str = PrivilegedFileHelper.getCanonicalPath(new File(url.getFile())); return url.getProtocol() + ":" + str.replace(PrivilegedFileHelper.getCanonicalPath(config.getBackupDir()) + File.separator, ""); } private boolean isRootBackupManagerDir(File log) throws IOException { return (PrivilegedFileHelper.getCanonicalPath(log.getParentFile()).equals(PrivilegedFileHelper .getCanonicalPath(rootDir))); } public synchronized void writeEndLog() { try { writer.writeEndElement(); writer.writeEndDocument(); writer.flush(); } catch (XMLStreamException e) { logger.error("Can't write log", e); } } private String getState(int iState) { String sState = "" + iState; switch (iState) { case BackupJob.FINISHED : sState = "FINISHED"; break; case BackupJob.STARTING : sState = "STARTING"; break; case BackupJob.WAITING : sState = "WAITING"; break; case BackupJob.WORKING : sState = "WORKING"; break; } return sState; } } public boolean isFinilized() { return finalized; } public String getFullBackupType() { return fullBackupType; } public String getIncrementalBackupType() { return incrementalBackupType; } }