/* 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.ArchiveDirManagerInterface;
import com.qumasoft.qvcslib.ArchiveInfoInterface;
import com.qumasoft.qvcslib.DirectoryCoordinate;
import com.qumasoft.qvcslib.FilePromotionInfo;
import com.qumasoft.qvcslib.LogFileInterface;
import com.qumasoft.qvcslib.MutableByteArray;
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.requestdata.ClientRequestPromoteFileData;
import com.qumasoft.qvcslib.response.ServerResponseInterface;
import com.qumasoft.qvcslib.response.ServerResponseMessage;
import com.qumasoft.qvcslib.response.ServerResponsePromoteFile;
import com.qumasoft.server.ArchiveDirManager;
import com.qumasoft.server.ArchiveDirManagerFactoryForServer;
import com.qumasoft.server.ArchiveInfoForTranslucentBranch;
import com.qumasoft.server.DatabaseCache;
import com.qumasoft.server.FileIDDictionary;
import com.qumasoft.server.FileIDInfo;
import com.qumasoft.server.ServerTransactionManager;
import com.qumasoft.server.ServerUtility;
import com.qumasoft.server.dataaccess.FileDAO;
import com.qumasoft.server.dataaccess.PromotionCandidateDAO;
import com.qumasoft.server.dataaccess.impl.FileDAOImpl;
import com.qumasoft.server.dataaccess.impl.PromotionCandidateDAOImpl;
import com.qumasoft.server.datamodel.PromotionCandidate;
import java.io.File;
import java.io.IOException;
import java.sql.SQLException;
import java.util.Collection;
import java.util.Date;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Promote a file.
*
* @author Jim Voris
*/
class ClientRequestPromoteFile implements ClientRequestInterface {
// Create our logger object
private static final Logger LOGGER = Logger.getLogger("com.qumasoft.server");
private final ClientRequestPromoteFileData request;
private final MutableByteArray commonAncestorBuffer = new MutableByteArray();
private final MutableByteArray branchParentTipRevisionBuffer = new MutableByteArray();
private final MutableByteArray branchTipRevisionBuffer = new MutableByteArray();
/**
* Creates a new instance of ClientRequestPromoteFile.
*
* @param data the command line data, etc.
*/
public ClientRequestPromoteFile(ClientRequestPromoteFileData data) {
request = data;
}
@Override
public ServerResponseInterface execute(String userName, ServerResponseFactoryInterface response) {
ServerResponseInterface returnObject;
String projectName = request.getProjectName();
String viewName = request.getViewName();
int fileId = request.getFileID();
FilePromotionInfo filePromotionInfo = request.getFilePromotionInfo();
// Lookup the file.
FileIDInfo fileIDInfo = FileIDDictionary.getInstance().lookupFileIDInfo(projectName, viewName, fileId);
if (fileIDInfo != null) {
try {
DirectoryCoordinate directoryCoordinate = new DirectoryCoordinate(projectName, viewName, filePromotionInfo.getAppendedPath());
ArchiveDirManagerInterface directoryManager = ArchiveDirManagerFactoryForServer.getInstance().getDirectoryManager(QVCSConstants.QVCS_SERVER_SERVER_NAME,
directoryCoordinate, QVCSConstants.QVCS_SERVED_PROJECT_TYPE, QVCSConstants.QVCS_SERVER_USER, response, true);
LOGGER.log(Level.INFO, "Promote file: project name: [" + projectName + "] branch name: [" + viewName + "] appended path: ["
+ filePromotionInfo.getAppendedPath() + "] short workfile name: [" + filePromotionInfo.getShortWorkfileName() + "]");
ArchiveInfoInterface archiveInfo = directoryManager.getArchiveInfo(filePromotionInfo.getShortWorkfileName());
if (archiveInfo != null) {
if (archiveInfo instanceof ArchiveInfoForTranslucentBranch) {
ArchiveInfoForTranslucentBranch archiveInfoForTranslucentBranch = (ArchiveInfoForTranslucentBranch) archiveInfo;
Date date = ServerTransactionManager.getInstance().getTransactionTimeStamp(response);
ServerResponsePromoteFile serverResponsePromoteFile;
switch (filePromotionInfo.getTypeOfMerge()) {
case SIMPLE_MERGE_TYPE:
serverResponsePromoteFile = buildResponseData(fileIDInfo, archiveInfoForTranslucentBranch);
if (archiveInfoForTranslucentBranch.promoteFile(userName, date)) {
returnObject = handleSimpleMerge(archiveInfoForTranslucentBranch, serverResponsePromoteFile);
} else {
returnObject = buildPromoteFailedErrorMessage();
}
break;
case CHILD_CREATED_MERGE_TYPE:
serverResponsePromoteFile = buildResponseDataForCreate(fileIDInfo, archiveInfoForTranslucentBranch);
returnObject = handleChildCreatedMerge(archiveInfoForTranslucentBranch, serverResponsePromoteFile, response);
break;
default:
// Return an error message.
ServerResponseMessage message = new ServerResponseMessage("Merge type is not supported yet: [" + filePromotionInfo.getTypeOfMerge()
+ "]", projectName,
viewName, filePromotionInfo.getAppendedPath(), ServerResponseMessage.HIGH_PRIORITY);
message.setShortWorkfileName(filePromotionInfo.getShortWorkfileName());
LOGGER.log(Level.WARNING, message.getMessage());
returnObject = message;
break;
}
} else {
// Return an error message.
ServerResponseMessage message = new ServerResponseMessage("Promote file is only supported for translucent branches.", projectName, viewName,
filePromotionInfo.getAppendedPath(), ServerResponseMessage.HIGH_PRIORITY);
message.setShortWorkfileName(filePromotionInfo.getShortWorkfileName());
LOGGER.log(Level.WARNING, message.getMessage());
returnObject = message;
}
} else {
// Return an error message.
ServerResponseMessage message = new ServerResponseMessage("Archive not found for " + filePromotionInfo.getShortWorkfileName(), projectName, viewName,
filePromotionInfo.getAppendedPath(), ServerResponseMessage.HIGH_PRIORITY);
message.setShortWorkfileName(filePromotionInfo.getShortWorkfileName());
LOGGER.log(Level.WARNING, message.getMessage());
returnObject = message;
}
} catch (QVCSException | IOException e) {
LOGGER.log(Level.WARNING, Utility.expandStackTraceToString(e));
// Return an error message.
ServerResponseMessage message = new ServerResponseMessage("Caught exception trying to promote a file: [" + filePromotionInfo.getShortWorkfileName()
+ "]. Exception string: " + e.getMessage(), projectName, viewName, filePromotionInfo.getAppendedPath(), ServerResponseMessage.HIGH_PRIORITY);
message.setShortWorkfileName(filePromotionInfo.getShortWorkfileName());
returnObject = message;
}
} else {
// Return an error message.
ServerResponseMessage message = new ServerResponseMessage("Did not find file information for file id: [" + fileId + "]", projectName, viewName,
filePromotionInfo.getAppendedPath(), ServerResponseMessage.HIGH_PRIORITY);
message.setShortWorkfileName(filePromotionInfo.getShortWorkfileName());
LOGGER.log(Level.WARNING, message.getMessage());
returnObject = message;
}
return returnObject;
}
private ServerResponseMessage buildPromoteFailedErrorMessage() {
ServerResponseMessage message = new ServerResponseMessage("Promote file failed for " + request.getFilePromotionInfo().getShortWorkfileName(), request.getProjectName(),
request.getViewName(), request.getFilePromotionInfo().getAppendedPath(), ServerResponseMessage.HIGH_PRIORITY);
message.setShortWorkfileName(request.getFilePromotionInfo().getShortWorkfileName());
LOGGER.log(Level.WARNING, message.getMessage());
return message;
}
/**
* Build the data that goes into the response message. This is where we perform the merge to a temp file and discover it that
* merge is successful, etc.
*
* @param archiveInfoForTranslucentBranch the archive info for the translucent branch.
*
* @return a populated response filled in with those 'files' that the client will need to complete the merge.
*/
private ServerResponsePromoteFile buildResponseData(FileIDInfo fileIDInfo, ArchiveInfoForTranslucentBranch archiveInfoForTranslucentBranch) throws QVCSException, IOException {
ServerResponsePromoteFile serverResponsePromoteFile = new ServerResponsePromoteFile();
serverResponsePromoteFile.setAppendedPath(fileIDInfo.getAppendedPath());
serverResponsePromoteFile.setBranchName(request.getMergedInfoBranchName());
serverResponsePromoteFile.setProjectName(request.getProjectName());
serverResponsePromoteFile.setShortWorkfileName(fileIDInfo.getShortFilename());
serverResponsePromoteFile.setMergeType(request.getFilePromotionInfo().getTypeOfMerge());
byte[] mergedResultBuffer = ServerUtility.createMergedResultBuffer(archiveInfoForTranslucentBranch, commonAncestorBuffer,
branchTipRevisionBuffer, branchParentTipRevisionBuffer);
if (mergedResultBuffer != null) {
serverResponsePromoteFile.setMergedResultBuffer(mergedResultBuffer);
} else {
serverResponsePromoteFile.setBranchTipRevisionBuffer(branchTipRevisionBuffer.getValue());
serverResponsePromoteFile.setCommonAncestorBuffer(commonAncestorBuffer.getValue());
serverResponsePromoteFile.setBranchParentTipRevisionBuffer(branchParentTipRevisionBuffer.getValue());
}
return serverResponsePromoteFile;
}
private ServerResponsePromoteFile buildResponseDataForCreate(FileIDInfo fileIDInfo,
ArchiveInfoForTranslucentBranch archiveInfoForTranslucentBranch) throws QVCSException, IOException {
ServerResponsePromoteFile serverResponsePromoteFile = new ServerResponsePromoteFile();
serverResponsePromoteFile.setAppendedPath(fileIDInfo.getAppendedPath());
serverResponsePromoteFile.setBranchName(request.getMergedInfoBranchName());
serverResponsePromoteFile.setProjectName(request.getProjectName());
serverResponsePromoteFile.setShortWorkfileName(fileIDInfo.getShortFilename());
serverResponsePromoteFile.setMergeType(request.getFilePromotionInfo().getTypeOfMerge());
serverResponsePromoteFile.setMergedResultBuffer(archiveInfoForTranslucentBranch.getCurrentLogFile()
.getRevisionAsByteArray(archiveInfoForTranslucentBranch.getBranchTipRevisionString()));
return serverResponsePromoteFile;
}
/**
* Handle the simple merge use case.
*
* @param archiveInfoForTranslucentBranch the archive info for the translucent branch.
* @param serverResponsePromoteFile the response that we're building.
* @return the completed response.
* @throws QVCSException if we cannot delete the promotion candidate record.
*/
private ServerResponseInterface handleSimpleMerge(ArchiveInfoForTranslucentBranch archiveInfoForTranslucentBranch,
ServerResponsePromoteFile serverResponsePromoteFile) throws QVCSException {
deletePromotionCandidate(archiveInfoForTranslucentBranch);
// Send back the logfile info if it's needed for keyword expansion.
if (archiveInfoForTranslucentBranch.getAttributes().getIsExpandKeywords()) {
serverResponsePromoteFile.setLogfileInfo(archiveInfoForTranslucentBranch.getLogfileInfo());
}
LogFileInterface logFileInterface = (LogFileInterface) archiveInfoForTranslucentBranch;
serverResponsePromoteFile.setSkinnyLogfileInfo(new SkinnyLogfileInfo(logFileInterface.getLogfileInfo(), File.separator,
logFileInterface.getIsObsolete(), logFileInterface.getDefaultRevisionDigest(), archiveInfoForTranslucentBranch.getShortWorkfileName(),
archiveInfoForTranslucentBranch.getIsOverlap()));
return serverResponsePromoteFile;
}
private void deletePromotionCandidate(ArchiveInfoForTranslucentBranch archiveInfoForTranslucentBranch) throws QVCSException {
PromotionCandidateDAO promotionCandidateDAO = new PromotionCandidateDAOImpl();
try {
Integer projectId = DatabaseCache.getInstance().getProjectId(request.getProjectName());
Integer branchId = DatabaseCache.getInstance().getBranchId(projectId, request.getViewName());
PromotionCandidate promotionCandidate = new PromotionCandidate(archiveInfoForTranslucentBranch.getFileID(), branchId);
promotionCandidateDAO.delete(promotionCandidate);
} catch (SQLException e) {
LOGGER.log(Level.WARNING, Utility.expandStackTraceToString(e));
throw new QVCSException("Failed to delete promotion candidate record for [" + archiveInfoForTranslucentBranch.getShortWorkfileName() + "]");
}
}
private ServerResponseInterface handleChildCreatedMerge(ArchiveInfoForTranslucentBranch archiveInfoForTranslucentBranch, ServerResponsePromoteFile serverResponsePromoteFile,
ServerResponseFactoryInterface response) throws QVCSException {
try {
// Step 1: Delete promotion candidate row.
deletePromotionCandidate(archiveInfoForTranslucentBranch);
// Step 2: If parent is trunk: move archive file from branch archive directory to correct appended path and rename it to have the right name.
if (request.getParentBranchName().equals(QVCSConstants.QVCS_TRUNK_VIEW)) {
// Make sure the target directory manager exists.
DirectoryCoordinate directoryCoordinate = new DirectoryCoordinate(request.getProjectName(), QVCSConstants.QVCS_TRUNK_VIEW,
request.getFilePromotionInfo().getAppendedPath());
ArchiveDirManagerInterface targetArchiveDirManager = ArchiveDirManagerFactoryForServer.getInstance().getDirectoryManager(QVCSConstants.QVCS_SERVER_SERVER_NAME,
directoryCoordinate, QVCSConstants.QVCS_SERVED_PROJECT_TYPE, QVCSConstants.QVCS_SERVER_USER, response, true);
ArchiveDirManagerInterface branchArchiveDirManagerInterface = ServerUtility.getBranchArchiveDirManager(request.getProjectName(), response);
ArchiveDirManager branchArchiveDirManager = (ArchiveDirManager) branchArchiveDirManagerInterface;
String shortWorkfilenameInBranchArchiveDirectory = lookupShortWorkfilenameForBranchArchive(branchArchiveDirManager, request.getFilePromotionInfo().getFileId());
if (branchArchiveDirManager.moveArchive(request.getUserName(), shortWorkfilenameInBranchArchiveDirectory, targetArchiveDirManager, response)) {
// And now we have to rename the file in its new home.
if (!targetArchiveDirManager.renameArchive(request.getUserName(), shortWorkfilenameInBranchArchiveDirectory,
request.getFilePromotionInfo().getShortWorkfileName(), response)) {
throw new QVCSException("Rename failed when promoting file to trunk.");
}
LogFileInterface logFileInterface = (LogFileInterface) archiveInfoForTranslucentBranch;
serverResponsePromoteFile.setSkinnyLogfileInfo(new SkinnyLogfileInfo(logFileInterface.getLogfileInfo(), File.separator,
logFileInterface.getIsObsolete(), logFileInterface.getDefaultRevisionDigest(), archiveInfoForTranslucentBranch.getShortWorkfileName(),
archiveInfoForTranslucentBranch.getIsOverlap()));
}
} else {
// Step 3: Update file record to identify branch id as the parent's branch id.
Integer projectId = DatabaseCache.getInstance().getProjectId(request.getProjectName());
Integer branchId = DatabaseCache.getInstance().getBranchId(projectId, request.getViewName());
Integer parentBranchId = DatabaseCache.getInstance().getBranchId(projectId, request.getParentBranchName());
FileDAO fileDAO = new FileDAOImpl();
com.qumasoft.server.datamodel.File file = fileDAO.findById(branchId, archiveInfoForTranslucentBranch.getFileID());
file.setBranchId(parentBranchId);
fileDAO.update(file, false);
// Step 4: Update parent's archive directory manager to include this file.
// Step 5: Make sure all listeners are adjusted correctly: parent's archive directory manager should now be a listener of of the 'moved' archive info,
// child archive info should be a listener of the moved (parent) archive info, child's archive directory manager should be a listener to the child's
// archive info.
}
} catch (IOException e) {
throw new QVCSException("File move failed: " + Utility.expandStackTraceToString(e));
} catch (SQLException e) {
throw new QVCSException("File update failed: " + Utility.expandStackTraceToString(e));
}
return serverResponsePromoteFile;
}
private String lookupShortWorkfilenameForBranchArchive(ArchiveDirManager branchArchiveDirManager, Integer fileId) {
String shortWorkfileName = null;
Collection<ArchiveInfoInterface> archiveInfoCollection = branchArchiveDirManager.getArchiveInfoCollection().values();
int fileID = fileId.intValue();
for (ArchiveInfoInterface archiveInfo : archiveInfoCollection) {
if (archiveInfo.getFileID() == fileID) {
shortWorkfileName = archiveInfo.getShortWorkfileName();
}
}
return shortWorkfileName;
}
}