/* Copyright 2004-2014 Jim Voris * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.qumasoft.server.clientrequest; import com.qumasoft.qvcslib.AbstractProjectProperties; import com.qumasoft.qvcslib.ArchiveDirManagerInterface; import com.qumasoft.qvcslib.ArchiveInfoInterface; import com.qumasoft.qvcslib.DirectoryCoordinate; import com.qumasoft.qvcslib.QVCSConstants; import com.qumasoft.qvcslib.QVCSException; import com.qumasoft.qvcslib.ServerResponseFactoryInterface; import com.qumasoft.qvcslib.SkinnyLogfileInfo; import com.qumasoft.qvcslib.Utility; import com.qumasoft.qvcslib.commandargs.CheckInCommandArgs; import com.qumasoft.qvcslib.commandargs.CreateArchiveCommandArgs; import com.qumasoft.qvcslib.commandargs.LockRevisionCommandArgs; import com.qumasoft.qvcslib.requestdata.ClientRequestCreateArchiveData; import com.qumasoft.qvcslib.response.ServerResponseCreateArchive; import com.qumasoft.qvcslib.response.ServerResponseError; import com.qumasoft.qvcslib.response.ServerResponseInterface; import com.qumasoft.qvcslib.response.ServerResponseMessage; import com.qumasoft.server.ActivityJournalManager; import com.qumasoft.server.ArchiveDigestManager; import com.qumasoft.server.ArchiveDirManager; import com.qumasoft.server.ArchiveDirManagerFactoryForServer; import com.qumasoft.server.ArchiveDirManagerForTranslucentBranch; import com.qumasoft.server.LogFile; import java.io.File; import java.io.IOException; import java.util.logging.Level; import java.util.logging.Logger; /** * Client request create archive. * @author Jim Voris */ public class ClientRequestCreateArchive implements ClientRequestInterface { // Create our logger object private static final Logger LOGGER = Logger.getLogger("com.qumasoft.server"); private final ClientRequestCreateArchiveData request; /** * Creates a new instance of ClientRequestCreateArchive. * * @param data the request data. */ public ClientRequestCreateArchive(ClientRequestCreateArchiveData data) { request = data; } // TODO -- This may need significant work if we change the practice of 'deleting' a file // by marking it obsolete. As I write this, I've begun to think that a 'delete' should // really delete the archive from the directory (actually move it an flatten its name) to // a project archive attic: the archive filename would be based on the file's fileID, and // it would be located in a special project specific attic directory. This means that // we would not need the code below that checks for an obsolete file. One issue with // this new approach would be the migration of an existing archive directory tree to // the new regime: all obsolete files would need to be migrated to the attic as part // of the 'upgrade' process. This is a pain. @Override public ServerResponseInterface execute(String userName, ServerResponseFactoryInterface response) { ServerResponseCreateArchive serverResponse; ServerResponseInterface returnObject; CreateArchiveCommandArgs commandArgs = request.getCommandArgs(); String projectName = request.getProjectName(); String viewName = request.getViewName(); String appendedPath = request.getAppendedPath(); String shortWorkfileName = Utility.convertWorkfileNameToShortWorkfileName(commandArgs.getWorkfileName()); try { DirectoryCoordinate directoryCoordinate = new DirectoryCoordinate(projectName, viewName, appendedPath); ArchiveDirManagerInterface archiveDirManagerInterface = ArchiveDirManagerFactoryForServer.getInstance().getDirectoryManager(QVCSConstants.QVCS_SERVER_SERVER_NAME, directoryCoordinate, QVCSConstants.QVCS_SERVED_PROJECT_TYPE, QVCSConstants.QVCS_SERVER_USER, response, true); if (archiveDirManagerInterface instanceof ArchiveDirManager) { LogFile logfile = null; ArchiveDirManager archiveDirManager = (ArchiveDirManager) archiveDirManagerInterface; if (!archiveDirManager.directoryExists()) { // Log an error. The client is supposed to separately request the creation of the archive directory // before it tries to create an archive. LOGGER.log(Level.WARNING, "Requested creation of archive file, but archive directory does not yet exist for: " + appendedPath); // Return a command error. ServerResponseError error = new ServerResponseError("Archive directory not found for " + appendedPath, projectName, viewName, appendedPath); returnObject = error; } else { LOGGER.log(Level.FINE, "Creating archive for: " + appendedPath + File.separator + shortWorkfileName); java.io.File tempFile = java.io.File.createTempFile("QVCS", ".tmp"); tempFile.deleteOnExit(); try (java.io.FileOutputStream outputStream = new java.io.FileOutputStream(tempFile)) { outputStream.write(request.getBuffer()); } // Check to see if the archive already exists -- maybe the file had been marked as obsolete, so the archive file may already exist. LogFile obsoleteLogfile = (LogFile) archiveDirManager.getArchiveInfo(shortWorkfileName); if (obsoleteLogfile != null) { if (obsoleteLogfile.getIsObsolete()) { // A flag we use to figure out if it's okay to continue with the work we're doing here... boolean continueFlag = true; // Mark the archive as not obsolete. obsoleteLogfile.setIsObsolete(userName, false); // If lock checking is enabled for this archive, we'll have to lock its tip revision, and then check-in this new revision. if (obsoleteLogfile.getAttributes().getIsCheckLock()) { LockRevisionCommandArgs lockCommandArgs = new LockRevisionCommandArgs(); lockCommandArgs.setRevisionString(QVCSConstants.QVCS_DEFAULT_REVISION); lockCommandArgs.setUserName(userName); lockCommandArgs.setFullWorkfileName(commandArgs.getWorkfileName()); lockCommandArgs.setShortWorkfileName(shortWorkfileName); lockCommandArgs.setOutputFileName(commandArgs.getWorkfileName()); continueFlag = obsoleteLogfile.lockRevision(lockCommandArgs); } if (continueFlag) { // Now just check-in the new revision. CheckInCommandArgs checkInCommandArgs = new CheckInCommandArgs(); checkInCommandArgs.setUserName(userName); checkInCommandArgs.setFullWorkfileName(commandArgs.getWorkfileName()); checkInCommandArgs.setCheckInTimestamp(commandArgs.getCheckInTimestamp()); checkInCommandArgs.setInputfileTimeStamp(commandArgs.getInputfileTimeStamp()); checkInCommandArgs.setLockedRevisionString(QVCSConstants.QVCS_DEFAULT_REVISION); checkInCommandArgs.setCheckInComment(commandArgs.getArchiveDescription()); checkInCommandArgs.setLockFlag(commandArgs.getLockFlag()); if (obsoleteLogfile.checkInRevision(checkInCommandArgs, tempFile.getAbsolutePath(), false)) { serverResponse = new ServerResponseCreateArchive(); logfile = (LogFile) archiveDirManager.getArchiveInfo(shortWorkfileName); SkinnyLogfileInfo skinnyInfo = new SkinnyLogfileInfo(logfile.getLogfileInfo(), File.separator, logfile.getIsObsolete(), logfile.getDefaultRevisionDigest(), logfile.getShortWorkfileName(), logfile.getIsOverlap()); // Set the index so the client can match this response with the cached workfile. skinnyInfo.setCacheIndex(request.getIndex()); serverResponse.setSkinnyLogfileInfo(skinnyInfo); serverResponse.setLogfileInfo(logfile.getLogfileInfo()); serverResponse.setProjectName(projectName); serverResponse.setViewName(viewName); serverResponse.setAppendedPath(appendedPath); serverResponse.setLockFlag(commandArgs.getLockFlag()); returnObject = serverResponse; tempFile.delete(); ActivityJournalManager.getInstance().addJournalEntry("User: [" + userName + "] reusing archive for [" + Utility.formatFilenameForActivityJournal(projectName, viewName, appendedPath, shortWorkfileName) + "]. Obsolete archive is re-activated."); } else { LOGGER.log(Level.WARNING, "Creation of archive file failed for: '" + appendedPath + File.separator + shortWorkfileName + "'. Unable to checkin revision to existing archive file!"); // Return a command error. ServerResponseError error = new ServerResponseError("Creation of archive file failed for: '" + appendedPath + File.separator + shortWorkfileName + "'. Unable to checkin revision to existing archive file!", projectName, viewName, appendedPath); returnObject = error; } } else { LOGGER.log(Level.WARNING, "Creation of archive file failed for: '" + appendedPath + File.separator + shortWorkfileName + "'. Unable to lock existing archive file!"); // Return a command error. ServerResponseError error = new ServerResponseError("Creation of archive file failed for: '" + appendedPath + File.separator + shortWorkfileName + "'. Unable to lock existing archive file!", projectName, viewName, appendedPath); returnObject = error; } } else { LOGGER.log(Level.WARNING, "Creation of archive file failed for: '" + appendedPath + File.separator + shortWorkfileName + "'. Archive file already exists!"); // Return a command error. ServerResponseError error = new ServerResponseError("Creation of archive file failed for: '" + appendedPath + File.separator + shortWorkfileName + "'. Archive file already exists!", projectName, viewName, appendedPath); returnObject = error; } } else if (archiveDirManager.createArchive(commandArgs, tempFile.getAbsolutePath(), response)) { serverResponse = new ServerResponseCreateArchive(); logfile = (LogFile) archiveDirManager.getArchiveInfo(shortWorkfileName); SkinnyLogfileInfo skinnyInfo = new SkinnyLogfileInfo(logfile.getLogfileInfo(), File.separator, logfile.getIsObsolete(), ArchiveDigestManager.getInstance().addRevision(logfile, logfile.getDefaultRevisionString()), logfile.getShortWorkfileName(), logfile.getIsOverlap()); // Set the index so the client can match this response with the cached workfile. skinnyInfo.setCacheIndex(request.getIndex()); serverResponse.setSkinnyLogfileInfo(skinnyInfo); serverResponse.setLogfileInfo(logfile.getLogfileInfo()); serverResponse.setProjectName(projectName); serverResponse.setViewName(viewName); serverResponse.setAppendedPath(appendedPath); serverResponse.setLockFlag(commandArgs.getLockFlag()); returnObject = serverResponse; tempFile.delete(); ActivityJournalManager.getInstance().addJournalEntry("User: '" + userName + "' creating archive for [" + Utility.formatFilenameForActivityJournal(projectName, viewName, appendedPath, shortWorkfileName) + "]."); } else { LOGGER.log(Level.WARNING, "Creation of archive file failed for: " + appendedPath + File.separator + shortWorkfileName); // Return a command error. ServerResponseError error = new ServerResponseError("Creation of archive file failed for: " + appendedPath + File.separator + shortWorkfileName, projectName, viewName, appendedPath); returnObject = error; } } // Create a reference copy if we need to. if ((returnObject instanceof ServerResponseCreateArchive) && (logfile != null)) { AbstractProjectProperties projectProperties = archiveDirManager.getProjectProperties(); if (projectProperties.getCreateReferenceCopyFlag()) { archiveDirManager.createReferenceCopy(projectProperties, logfile, request.getBuffer()); } } } else if (archiveDirManagerInterface instanceof ArchiveDirManagerForTranslucentBranch) { ArchiveDirManagerForTranslucentBranch archiveDirManagerForTranslucentBranch = (ArchiveDirManagerForTranslucentBranch) archiveDirManagerInterface; LOGGER.log(Level.INFO, "Creating branch archive for: [" + appendedPath + File.separator + shortWorkfileName + "]"); java.io.File tempFile = java.io.File.createTempFile("QVCS", ".tmp"); tempFile.deleteOnExit(); try (java.io.FileOutputStream outputStream = new java.io.FileOutputStream(tempFile)) { outputStream.write(request.getBuffer()); } if (archiveDirManagerForTranslucentBranch.createArchive(commandArgs, tempFile.getAbsolutePath(), response)) { serverResponse = new ServerResponseCreateArchive(); ArchiveInfoInterface archiveInfo = archiveDirManagerForTranslucentBranch.getArchiveInfo(shortWorkfileName); SkinnyLogfileInfo skinnyInfo = new SkinnyLogfileInfo(archiveInfo.getLogfileInfo(), File.separator, archiveInfo.getIsObsolete(), archiveInfo.getDefaultRevisionDigest(), archiveInfo.getShortWorkfileName(), archiveInfo.getIsOverlap()); // Set the index so the client can match this response with the cached workfile. skinnyInfo.setCacheIndex(request.getIndex()); serverResponse.setSkinnyLogfileInfo(skinnyInfo); serverResponse.setLogfileInfo(archiveInfo.getLogfileInfo()); serverResponse.setProjectName(projectName); serverResponse.setViewName(viewName); serverResponse.setAppendedPath(appendedPath); serverResponse.setLockFlag(commandArgs.getLockFlag()); returnObject = serverResponse; tempFile.delete(); ActivityJournalManager.getInstance().addJournalEntry("User: [" + userName + "] creating branch archive for [" + Utility.formatFilenameForActivityJournal(projectName, viewName, appendedPath, shortWorkfileName) + "]."); } else { LOGGER.log(Level.WARNING, "Creation of archive file failed for: " + appendedPath + File.separator + shortWorkfileName); // Return a command error. ServerResponseError error = new ServerResponseError("Creation of archive file failed for: " + appendedPath + File.separator + shortWorkfileName, projectName, viewName, appendedPath); returnObject = error; } } else { // Explain the error. ServerResponseMessage message = new ServerResponseMessage("Create archive is not allowed for read-only view.", projectName, viewName, appendedPath, ServerResponseMessage.HIGH_PRIORITY); message.setShortWorkfileName(shortWorkfileName); returnObject = message; } } catch (QVCSException | IOException e) { LOGGER.log(Level.WARNING, Utility.expandStackTraceToString(e)); ServerResponseMessage message = new ServerResponseMessage(e.getLocalizedMessage(), projectName, viewName, appendedPath, ServerResponseMessage.HIGH_PRIORITY); message.setShortWorkfileName(shortWorkfileName); returnObject = message; } return returnObject; } }