// 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;
import com.qumasoft.qvcslib.QVCSConstants;
import com.qumasoft.qvcslib.QVCSException;
import com.qumasoft.qvcslib.ServerResponseFactoryInterface;
import com.qumasoft.qvcslib.Utility;
import com.qumasoft.server.dataaccess.BranchDAO;
import com.qumasoft.server.dataaccess.DirectoryDAO;
import com.qumasoft.server.dataaccess.DirectoryHistoryDAO;
import com.qumasoft.server.dataaccess.FileDAO;
import com.qumasoft.server.dataaccess.FileHistoryDAO;
import com.qumasoft.server.dataaccess.ProjectDAO;
import com.qumasoft.server.dataaccess.impl.BranchDAOImpl;
import com.qumasoft.server.dataaccess.impl.DirectoryDAOImpl;
import com.qumasoft.server.dataaccess.impl.DirectoryHistoryDAOImpl;
import com.qumasoft.server.dataaccess.impl.FileDAOImpl;
import com.qumasoft.server.dataaccess.impl.FileHistoryDAOImpl;
import com.qumasoft.server.dataaccess.impl.ProjectDAOImpl;
import com.qumasoft.server.datamodel.Branch;
import com.qumasoft.server.datamodel.Directory;
import com.qumasoft.server.datamodel.Project;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Directory Contents Manager.
*
* @author Jim Voris
*/
public class DirectoryContentsManager implements TransactionParticipantInterface {
// Create our logger object.
private static final Logger LOGGER = Logger.getLogger("com.qumasoft.server");
private final String projectName;
private final Integer projectId;
private Integer trunkBranchId;
private final ProjectDAO projectDAO;
private final BranchDAO branchDAO;
private final DirectoryDAO directoryDAO;
private final FileDAO fileDAO;
/**
* Creates a new instance of DirectoryContentsManager.
*
* @param p the name of the project.
*/
public DirectoryContentsManager(final String p) {
this.projectName = p;
this.projectDAO = new ProjectDAOImpl();
this.directoryDAO = new DirectoryDAOImpl();
this.branchDAO = new BranchDAOImpl();
this.fileDAO = new FileDAOImpl();
Project project = projectDAO.findByProjectName(p);
this.projectId = project.getProjectId();
}
/**
* Get the project name.
*
* @return the project name.
*/
public String getProjectName() {
return this.projectName;
}
/**
* Get the collection of directory id's for a date based view.
*
* @param viewName the name of the view we are interested in.
* @param appendedPath the appended path for the directory we're looking in.
* @param directoryId the directory ID of the directory in which we look for directory ID's. i.e. this is the parent directory
* of the directory ID collection that we return.
* @param response the response object that identifies the client for this request.
* @return a Map of directory id/directory names.
* @throws QVCSException if there is a problem in QVCS code.
*/
public synchronized Map<Integer, String> getDirectoryIDCollectionForDateBasedView(final String viewName, final String appendedPath,
final int directoryId, ServerResponseFactoryInterface response)
throws QVCSException {
ProjectView projectView = ViewManager.getInstance().getView(getProjectName(), viewName);
DirectoryContents directoryContents = getDirectoryContentsForDateBasedView(projectView, appendedPath, directoryId, response);
return directoryContents.getChildDirectories();
}
/**
* Get the collection of directory id's for a translucent branch directory.
*
* @param projectView the projectView we are interested in.
* @param appendedPath the appended path for the directory we're looking in.
* @param directoryId the directory ID of the directory in which we look for directory ID's. i.e. this is the parent directory
* of the directory ID collection that we return.
* @param response the response object that identifies the client for this request.
* @return a Map of directory id/directory names.
* @throws QVCSException if there is a problem in QVCS code.
*/
public synchronized Map<Integer, String> getDirectoryIDCollectionForTranslucentBranch(final ProjectView projectView, final String appendedPath, final int directoryId,
ServerResponseFactoryInterface response) throws QVCSException {
DirectoryContents directoryContents = getDirectoryContentsForTranslucentBranch(projectView, appendedPath, directoryId, response);
return directoryContents.getChildDirectories();
}
/**
* Get the collection of directory id's for an opaque branch directory.
*
* @param projectView the projectView we are interested in.
* @param appendedPath the appended path for the directory we're looking in.
* @param directoryId the directory ID of the directory in which we look for directory ID's. i.e. this is the parent directory
* of the directory ID collection that we return.
* @param response the response object that identifies the client for this request.
* @return a Map of directory id/directory names.
* @throws QVCSException if there is a problem in QVCS code.
*/
public synchronized Map<Integer, String> getDirectoryIDCollectionForOpaqueBranch(final ProjectView projectView, final String appendedPath, final int directoryId,
ServerResponseFactoryInterface response) throws QVCSException {
DirectoryContents directoryContents = getDirectoryContentsForOpaqueBranch(projectView, appendedPath, directoryId, response);
return directoryContents.getChildDirectories();
}
/**
* Add a file to the trunk.
*
* @param directoryId the directory where we'll add the file.
* @param fileId the file id.
* @param shortWorkfileName the short workfile name of the file.
* @param response the response object that identifies the client for this request.
* @throws QVCSException if there is no surrounding transaction or if the branch is not found.
* @throws SQLException if there is a problem inserting the record.
*/
public synchronized void addFileToTrunk(final int directoryId, final int fileId, final String shortWorkfileName,
ServerResponseFactoryInterface response) throws QVCSException, SQLException {
addFile(QVCSConstants.QVCS_TRUNK_VIEW, directoryId, fileId, shortWorkfileName, response);
}
/**
* Add a file to an opaque branch.
*
* @param branchName the name of the opaque branch.
* @param directoryId the directory where we'll add the file.
* @param fileId the file id.
* @param shortWorkfileName the short workfile name of the file.
* @param response the response object that identifies the client for this request.
* @throws QVCSException if there is no surrounding transaction or if the branch is not found.
* @throws SQLException if there is a problem inserting the record.
*/
public synchronized void addFileToOpaqueBranch(final String branchName, final int directoryId, final int fileId, final String shortWorkfileName,
ServerResponseFactoryInterface response)
throws QVCSException, SQLException {
addFile(branchName, directoryId, fileId, shortWorkfileName, response);
}
/**
* Add a file to a translucent branch.
*
* @param branchName the name of the translucent branch.
* @param directoryId the directory where we'll add the file.
* @param fileId the file id.
* @param shortWorkfileName the short workfile name of the file.
* @param response the response object that identifies the client for this request.
* @throws QVCSException if there is no surrounding transaction, or if the branch is not found.
* @throws SQLException if there is a problem inserting the record.
*/
public synchronized void addFileToTranslucentBranch(final String branchName, final int directoryId, final int fileId, final String shortWorkfileName,
ServerResponseFactoryInterface response)
throws QVCSException, SQLException {
addFile(branchName, directoryId, fileId, shortWorkfileName, response);
}
/**
* Add a file to the given branch/directory.
*
* @param branchName the branch name.
* @param directoryId the directory id.
* @param fileId the file id.
* @param shortWorkfileName the short workfile name
* @param response the response object that identifies the client for this request.
* @throws SQLException if there is a problem inserting the record.
* @throws QVCSException if the branch is not found.
*/
private void addFile(final String branchName, final int directoryId, final int fileId, final String shortWorkfileName,
ServerResponseFactoryInterface response) throws SQLException, QVCSException {
checkForContainingTransaction("#### INTERNAL ERROR: Attempt to create an archive file without a surrounding transaction.", response);
Branch branch = branchDAO.findByProjectIdAndBranchName(projectId, branchName);
if (branch != null) {
com.qumasoft.server.datamodel.File file = new com.qumasoft.server.datamodel.File();
file.setFileId(fileId);
file.setBranchId(branch.getBranchId());
file.setDirectoryId(directoryId);
file.setDeletedFlag(false);
file.setFileName(shortWorkfileName);
fileDAO.insert(file);
} else {
throw new QVCSException("Branch not found: [" + branchName + "]");
}
}
/**
* Rename a file.
*
* @param directoryId the directory where the rename is done
* @param fileId the file id of the file that is to be renamed.
* @param oldWorkfileName the old workfile name.
* @param newWorkfileName the new workfile name.
* @param response the response object that identifies the client for this request.
* @throws QVCSException if there is no surrounding transaction, or if the branch is not found, or if the old workfile name
* fails to match the existing db record file name.
*/
public synchronized void renameFileOnTrunk(final int directoryId, final int fileId, final String oldWorkfileName, final String newWorkfileName,
ServerResponseFactoryInterface response) throws QVCSException {
checkForContainingTransaction("#### INTERNAL ERROR: Attempt to rename a file without a surrounding transaction.", response);
String branchName = QVCSConstants.QVCS_TRUNK_VIEW;
Branch branch = branchDAO.findByProjectIdAndBranchName(projectId, branchName);
if (branch != null) {
// Verify that the file is where the caller claims it is to begin with, etc.
com.qumasoft.server.datamodel.File datamodelFile = fileDAO.findById(branch.getBranchId(), fileId);
if (!datamodelFile.getFileName().equals(oldWorkfileName)) {
throw new QVCSException("Original filename does not match: db record name: [" + datamodelFile.getFileName()
+ "], caller original filename: [" + oldWorkfileName + "]");
}
datamodelFile.setFileName(newWorkfileName);
try {
fileDAO.update(datamodelFile, false);
} catch (SQLException e) {
LOGGER.log(Level.SEVERE, Utility.expandStackTraceToString(e));
throw new QVCSException("SQLException on rename: " + e.getLocalizedMessage());
}
} else {
throw new QVCSException("Branch not found: [" + branchName + "]");
}
}
/**
* Rename file on opaque branch.
*
* @param branchName branch name.
* @param fileId file id.
* @param oldWorkfileName old workfile name.
* @param newWorkfileName new workfile name.
* @param response the response object that identifies the client for this request.
* @throws QVCSException if there is no surrounding transaction, or if the branch is not found, or if the old workfile name
* fails to match the existing db record file name, or if there is a SQL exception.
*/
public synchronized void renameFileOnOpaqueBranch(final String branchName, final int fileId, final String oldWorkfileName, final String newWorkfileName,
ServerResponseFactoryInterface response) throws QVCSException {
renameFileOnBranch(branchName, fileId, oldWorkfileName, newWorkfileName, response);
}
/**
* Rename file on translucent branch.
*
* @param branchName branch name.
* @param fileId file id.
* @param oldWorkfileName old workfile name.
* @param newWorkfileName new workfile name.
* @param response the response object that identifies the client for this request.
* @throws QVCSException if there is no surrounding transaction, or if the branch is not found, or if the old workfile name
* fails to match the existing db record file name, or if there is a SQL exception.
*/
public synchronized void renameFileOnTranslucentBranch(final String branchName, final int fileId, final String oldWorkfileName, final String newWorkfileName,
ServerResponseFactoryInterface response) throws QVCSException {
renameFileOnBranch(branchName, fileId, oldWorkfileName, newWorkfileName, response);
}
/**
* Rename file on branch.
*
* @param branchName branch name.
* @param fileId file id.
* @param oldWorkfileName old workfile name.
* @param newWorkfileName new workfile name.
* @param response the response object that identifies the client for this request.
* @throws QVCSException if there is no surrounding transaction, or if the branch is not found, or if the old workfile name
* fails to match the existing db record file name, or if there is a SQL exception.
*/
private synchronized void renameFileOnBranch(final String branchName, final int fileId, final String oldWorkfileName, final String newWorkfileName,
ServerResponseFactoryInterface response) throws QVCSException {
checkForContainingTransaction("#### INTERNAL ERROR: Attempt to rename a file without a surrounding transaction.", response);
Branch branch = branchDAO.findByProjectIdAndBranchName(projectId, branchName);
if (branch != null) {
deleteBranchFileRecordWithIsDeletedFlag(branch.getBranchId(), fileId);
com.qumasoft.server.datamodel.File datamodelFile = fileDAO.findById(branch.getBranchId(), fileId);
if (datamodelFile != null) {
/*
* if (file record on branch exists)...
*/
// Verify that it's directory id matches what we found in the database.
if (!datamodelFile.getFileName().equals(oldWorkfileName)) {
throw new QVCSException("Existing filename does not match: db record directory: [" + datamodelFile.getFileName()
+ "], caller filename: [" + oldWorkfileName + "]");
}
datamodelFile.setFileName(newWorkfileName);
try {
// Update the branch record with the new name.
fileDAO.update(datamodelFile, false);
} catch (SQLException e) {
StringBuilder message = new StringBuilder();
message.append("SQLException when updating filename on branch: [").append(datamodelFile.getFileName()).append("].")
.append(" Exception: ").append(e.getLocalizedMessage());
LOGGER.log(Level.SEVERE, message.toString());
throw new QVCSException(message.toString());
}
} else {
/*
* else -- there is no file record for the branch, ergo it uses the parent branch's file record. create a record for
* the branch that has the new name.
*/
// Walk up the parent branch tree until we find the file.
datamodelFile = findFile(branch, fileId);
if (!datamodelFile.getFileName().equals(oldWorkfileName)) {
throw new QVCSException("Existing filename does not match: db record directory: [" + datamodelFile.getFileName()
+ "], caller filename: [" + oldWorkfileName + "]");
}
datamodelFile.setBranchId(branch.getBranchId());
datamodelFile.setFileName(newWorkfileName);
try {
fileDAO.insert(datamodelFile);
} catch (SQLException e) {
StringBuilder message = new StringBuilder();
message.append("SQLException when creating renamed file record on branch file: [")
.append(datamodelFile.getFileName()).append("].").append(" Exception: ")
.append(e.getLocalizedMessage());
LOGGER.log(Level.SEVERE, message.toString());
throw new QVCSException(message.toString());
}
}
} else {
throw new QVCSException("Branch not found: [" + branchName + "]");
}
}
/**
* Move a file from one directory to another on the trunk.
*
* @param branchName the name of the branch on which the move is to occur.
* @param originDirectoryId the origin directory id. This is where the file is currently located.
* @param destinationDirectoryId the destination directory id. This is where the file is to be moved.
* @param fileId the file id.
* @param response the response object that identifies the client for this request.
* @throws QVCSException if there is no surrounding transaction, or if the branch is not found, or if the old directory id fails
* to match the existing db record directory id.
*/
public synchronized void moveFileOnTrunk(final String branchName, final int originDirectoryId, final int destinationDirectoryId,
final int fileId, ServerResponseFactoryInterface response)
throws QVCSException {
checkForContainingTransaction("#### INTERNAL ERROR: Attempt to move a file without a surrounding transaction.", response);
Branch branch = branchDAO.findByProjectIdAndBranchName(projectId, branchName);
if (branch != null) {
// Verify that the file is where the caller claims it is to begin with, etc.
com.qumasoft.server.datamodel.File datamodelFile = fileDAO.findById(branch.getBranchId(), fileId);
if (!datamodelFile.getDirectoryId().equals(originDirectoryId)) {
throw new QVCSException("Original directory does not match: db record directory: [" + datamodelFile.getDirectoryId()
+ "], caller original filename: [" + originDirectoryId + "]");
}
datamodelFile.setDirectoryId(destinationDirectoryId);
try {
fileDAO.update(datamodelFile, false);
} catch (SQLException e) {
LOGGER.log(Level.SEVERE, Utility.expandStackTraceToString(e));
throw new QVCSException("SQLException on move: " + e.getLocalizedMessage());
}
} else {
throw new QVCSException("Branch not found: [" + branchName + "]");
}
}
/**
* Move a file from one directory to another on a translucent branch.
*
* @param branchName the name of the branch on which the move is to occur.
* @param originDirectoryId the origin directory id. This is where the file is currently located.
* @param destinationDirectoryId the destination directory id. This is where the file is to be moved.
* @param fileId the file id.
* @param response the response object that identifies the client for this request.
* @throws QVCSException if there is no surrounding transaction, or if the branch is not found, or if the old directory id fails
* to match the existing db record directory id.
*/
public synchronized void moveFileOnTranslucentBranch(final String branchName, final int originDirectoryId, final int destinationDirectoryId, final int fileId,
ServerResponseFactoryInterface response) throws QVCSException {
moveFileOnBranch(branchName, originDirectoryId, destinationDirectoryId, fileId, response);
}
/**
* Move a file from one directory to another on an opaque branch.
*
* @param branchName the name of the branch on which the move is to occur.
* @param originDirectoryId the origin directory id. This is where the file is currently located.
* @param destinationDirectoryId the destination directory id. This is where the file is to be moved.
* @param fileId the file id.
* @param response the response object that identifies the client for this request.
* @throws QVCSException if there is no surrounding transaction, or if the branch is not found, or if the old directory id fails
* to match the existing db record directory id.
*/
public synchronized void moveFileOnOpaqueBranch(final String branchName, final int originDirectoryId, final int destinationDirectoryId, final int fileId,
ServerResponseFactoryInterface response) throws QVCSException {
moveFileOnBranch(branchName, originDirectoryId, destinationDirectoryId, fileId, response);
}
/**
* Move a file from one directory to another on a branch.
*
* @param branchName the name of the branch on which the move is to occur.
* @param originDirectoryId the origin directory id. This is where the file is currently located.
* @param destinationDirectoryId the destination directory id. This is where the file is to be moved.
* @param fileId the file id.
* @param response the response object that identifies the client for this request.
* @throws QVCSException if there is no surrounding transaction, or if the branch is not found, or if the old directory id fails
* to match the existing db record directory id.
*/
private void moveFileOnBranch(final String branchName, final int originDirectoryId, final int destinationDirectoryId, final int fileId,
ServerResponseFactoryInterface response) throws QVCSException {
checkForContainingTransaction("#### INTERNAL ERROR: Attempt to move file on branch without a surrounding transaction.", response);
Branch branch = branchDAO.findByProjectIdAndBranchName(projectId, branchName);
if (branch != null) {
deleteBranchFileRecordWithIsDeletedFlag(branch.getBranchId(), fileId);
com.qumasoft.server.datamodel.File datamodelFile = fileDAO.findById(branch.getBranchId(), fileId);
if (datamodelFile != null) {
/*
* if (file record on branch exists)...
*/
// Verify that it's origin directory id matches what we found in the database.
if (!datamodelFile.getDirectoryId().equals(originDirectoryId)) {
throw new QVCSException("Original directory does not match: db record directory: [" + datamodelFile.getDirectoryId()
+ "], caller original filename: [" + originDirectoryId + "]");
}
/*
* if (file record exists on parent branch) mark the record deleted on the branch create an entry for the file in
* the branch's destination directory. else update the record to the new destination directory on the branch.
*/
boolean fileExistsOnParentBranch = doesFileExistOnParentBranch(branch, fileId);
if (fileExistsOnParentBranch) {
datamodelFile.setDeletedFlag(true);
try {
// Update the branch record to show it is deleted from the directory so we won't show it in the
// current location.
fileDAO.update(datamodelFile, false);
} catch (SQLException e) {
StringBuilder message = new StringBuilder();
message.append("SQLException when setting deleted flag on branch: [").append(datamodelFile.getFileName()).append("].")
.append(" Exception: ").append(e.getLocalizedMessage());
LOGGER.log(Level.SEVERE, message.toString());
throw new QVCSException(message.toString());
}
datamodelFile.setDeletedFlag(false);
datamodelFile.setDirectoryId(destinationDirectoryId);
try {
fileDAO.insert(datamodelFile);
} catch (SQLException e) {
StringBuilder message = new StringBuilder();
message.append("SQLException when creating destination record on branch file: [").append(datamodelFile.getFileName())
.append("].").append(" Exception: ").
append(e.getLocalizedMessage());
LOGGER.log(Level.SEVERE, message.toString());
throw new QVCSException(message.toString());
}
} else {
datamodelFile.setDirectoryId(destinationDirectoryId);
try {
// Move the file to the destination directory.
fileDAO.update(datamodelFile, false);
} catch (SQLException e) {
StringBuilder message = new StringBuilder();
message.append("SQLException when setting moving file on branch: [").append(datamodelFile.getFileName()).append("].")
.append(" Exception: ").append(e.getLocalizedMessage());
LOGGER.log(Level.SEVERE, message.toString());
throw new QVCSException(message.toString());
}
}
} else {
/*
* else -- there is no file record for the branch, ergo it uses the parent branch's file record. create a record for
* the branch that has its deleted flag set to true. create an entry for the file in the branch's destination
* directory.
*/
// Walk up the parent branch tree until we find the file.
datamodelFile = findFile(branch, fileId);
if (!datamodelFile.getDirectoryId().equals(originDirectoryId)) {
throw new QVCSException("Original directory in parent branch does not match: db record directory: ["
+ datamodelFile.getDirectoryId() + "], caller original filename: ["
+ originDirectoryId + "]");
}
datamodelFile.setBranchId(branch.getBranchId());
datamodelFile.setDeletedFlag(true);
try {
fileDAO.insert(datamodelFile);
} catch (SQLException e) {
StringBuilder message = new StringBuilder();
message.append("SQLException when creating deleted flag record on branch file: [").append(datamodelFile.getFileName()).append("].").append(" Exception: ").
append(e.getLocalizedMessage());
LOGGER.log(Level.SEVERE, message.toString());
throw new QVCSException(message.toString());
}
// Create an entry for the file in the branch's destination directory.
datamodelFile.setDeletedFlag(false);
datamodelFile.setBranchId(branch.getBranchId());
datamodelFile.setDirectoryId(destinationDirectoryId);
try {
fileDAO.insert(datamodelFile);
} catch (SQLException e) {
StringBuilder message = new StringBuilder();
message.append("SQLException when creating file record for branch file: [").append(datamodelFile.getFileName()).append("].").append(" Exception: ").
append(e.getLocalizedMessage());
LOGGER.log(Level.SEVERE, message.toString());
throw new QVCSException(message.toString());
}
}
} else {
throw new QVCSException("Branch not found: [" + branchName + "]");
}
}
/**
* Move a file from the opaque branch cemetery... i.e. restore a file from the cemetery to its former location.
*
* @param branchName the name of the branch.
* @param destinationDirectoryId the directory to which the file will be restored.
* @param fileId the file id.
* @param workfileName the name that the file will be restored to.
* @param response the response object that identifies the client for this request.
* @throws QVCSException if there is no surrounding transaction; if the file was not found in the branch cemetery, or if there
* is a SQL exception in updating the file record.
*/
public synchronized void moveFileFromOpaqueBranchCemetery(final String branchName, final int destinationDirectoryId, final int fileId, final String workfileName,
ServerResponseFactoryInterface response) throws QVCSException {
moveFileFromBranchCemetery(branchName, destinationDirectoryId, fileId, workfileName, response);
}
/**
* Move a file from the translucent branch cemetery... i.e. restore a file from the cemetery to its former location.
*
* @param branchName the name of the branch.
* @param destinationDirectoryId the directory to which the file will be restored.
* @param fileId the file id.
* @param workfileName the name that the file will be restored to.
* @param response the response object that identifies the client for this request.
* @throws QVCSException if there is no surrounding transaction; if the file was not found in the branch cemetery, or if there
* is a SQL exception in updating the file record.
*/
public synchronized void moveFileFromTranslucentBranchCemetery(final String branchName, final int destinationDirectoryId, final int fileId, final String workfileName,
ServerResponseFactoryInterface response) throws QVCSException {
moveFileFromBranchCemetery(branchName, destinationDirectoryId, fileId, workfileName, response);
}
/**
* Move a file from a branch cemetery... i.e. restore a file from the cemetery to its former location.
*
* @param branchName the name of the branch.
* @param destinationDirectoryId the directory to which the file will be restored.
* @param fileId the file id.
* @param workfileName the name that the file will be restored to.
* @param response the response object that identifies the client for this request.
* @throws QVCSException if there is no surrounding transaction; if the file was not found in the branch cemetery, or if there
* is a SQL exception in updating the file record.
*/
private synchronized void moveFileFromBranchCemetery(final String branchName, final int destinationDirectoryId, final int fileId, final String workfileName,
ServerResponseFactoryInterface response) throws QVCSException {
checkForContainingTransaction("#### INTERNAL ERROR: Attempt to move file from branch cemetery without a surrounding transaction.", response);
Branch branch = branchDAO.findByProjectIdAndBranchName(projectId, branchName);
if (branch != null) {
com.qumasoft.server.datamodel.File datamodelFile = fileDAO.findById(branch.getBranchId(), fileId);
if (datamodelFile != null) {
// Verify that the current record is in the 'cemetery' for the branch.
if (!datamodelFile.getDirectoryId().equals(-1)) {
throw new QVCSException("File not found in branch cemetery; fileId: [" + fileId + "]. Was found in directory with directory id of: ["
+ datamodelFile.getDirectoryId() + "]");
}
datamodelFile.setDirectoryId(destinationDirectoryId);
datamodelFile.setFileName(workfileName);
datamodelFile.setDeletedFlag(false);
try {
// Move the file to the destination directory.
fileDAO.update(datamodelFile, false);
} catch (SQLException e) {
StringBuilder message = new StringBuilder();
message.append("SQLException when restoring file from cemetery on branch: [").append(datamodelFile.getFileName()).append("].").append(" Exception: ").
append(e.getLocalizedMessage());
LOGGER.log(Level.SEVERE, message.toString());
throw new QVCSException(message.toString());
}
}
} else {
throw new QVCSException("Branch not found: [" + branchName + "]");
}
}
/**
* Delete a file from the trunk.
*
* @param originAppendedPath the origin appended path.
* @param originDirectoryId the origin directory id.
* @param cemeteryDirectoryId the cemetery directory id.
* @param fileId the file id.
* @param workfileName the workfile name.
* @param response the response object that identifies the client for this request.
* @throws QVCSException if there is no surrounding transaction, or if the origin directory does not match what's in the
* database, or if there is a SQL exception.
*/
public synchronized void deleteFileFromTrunk(final String originAppendedPath, final int originDirectoryId, final int cemeteryDirectoryId,
final int fileId, final String workfileName,
ServerResponseFactoryInterface response) throws QVCSException {
checkForContainingTransaction("#### INTERNAL ERROR: Attempt to delete a file without a surrounding transaction.", response);
String branchName = QVCSConstants.QVCS_TRUNK_VIEW;
Branch branch = branchDAO.findByProjectIdAndBranchName(projectId, branchName);
if (branch != null) {
com.qumasoft.server.datamodel.File datamodelFile = fileDAO.findById(branch.getBranchId(), fileId);
if (!datamodelFile.getDirectoryId().equals(originDirectoryId)) {
throw new QVCSException("Original directory does not match: db record directory: [" + datamodelFile.getDirectoryId()
+ "], caller original filename: [" + originDirectoryId + "]");
}
datamodelFile.setDirectoryId(cemeteryDirectoryId);
datamodelFile.setFileName(Utility.convertArchiveNameToShortWorkfileName(Utility.createCemeteryShortArchiveName(fileId)));
try {
fileDAO.update(datamodelFile, false);
} catch (SQLException e) {
StringBuilder message = new StringBuilder();
message.append("SQLException when deleting file on trunk: [").append(workfileName).append("].").append(" Exception: ").append(e.getLocalizedMessage());
LOGGER.log(Level.SEVERE, message.toString());
throw new QVCSException(message.toString());
}
} else {
throw new QVCSException("Branch not found: [" + branchName + "]");
}
}
/**
* Delete a file from an opaque branch.
* @param branchName the branch name.
* @param originDirectoryId the directory id of the directory containing the file.
* @param cemeteryDirectoryId the cemetery directory id.
* @param fileId the file id.
* @param workfileName the workfile name.
* @param response link to the client.
* @throws QVCSException if there is no enclosing transaction, or for a number of other problems.
*/
public synchronized void deleteFileFromOpaqueBranch(final String branchName, final int originDirectoryId, final int cemeteryDirectoryId,
final int fileId, final String workfileName,
ServerResponseFactoryInterface response) throws QVCSException {
deleteFileFromBranch(branchName, originDirectoryId, cemeteryDirectoryId, fileId, workfileName, response);
}
/**
* Delete a file from a translucent branch.
* @param branchName the branch name.
* @param originDirectoryId the directory id of the directory containing the file.
* @param cemeteryDirectoryId the cemetery directory id.
* @param fileId the file id.
* @param workfileName the workfile name.
* @param response link to the client.
* @throws QVCSException if there is no enclosing transaction, or for a number of other problems.
*/
public synchronized void deleteFileFromTranslucentBranch(final String branchName, final int originDirectoryId, final int cemeteryDirectoryId,
final int fileId, final String workfileName,
ServerResponseFactoryInterface response) throws QVCSException {
deleteFileFromBranch(branchName, originDirectoryId, cemeteryDirectoryId, fileId, workfileName, response);
}
private synchronized void deleteFileFromBranch(final String branchName, final int originDirectoryId, final int cemeteryDirectoryId,
final int fileId, final String workfileName,
ServerResponseFactoryInterface response) throws QVCSException {
checkForContainingTransaction("#### INTERNAL ERROR: Attempt to delete file on branch without a surrounding transaction.", response);
Branch branch = branchDAO.findByProjectIdAndBranchName(projectId, branchName);
if (branch != null) {
deleteBranchFileRecordWithIsDeletedFlag(branch.getBranchId(), fileId);
com.qumasoft.server.datamodel.File datamodelFile = fileDAO.findById(branch.getBranchId(), fileId);
if (datamodelFile != null) {
/*
* if (file record on branch exists)...
*/
// Verify that it's origin directory id matches what we found in the database.
if (!datamodelFile.getDirectoryId().equals(originDirectoryId)) {
throw new QVCSException("Original directory does not match: db record directory: [" + datamodelFile.getDirectoryId()
+ "], caller original filename: [" + originDirectoryId + "]");
}
/*
* if (file record exists on parent branch) mark the record deleted on the branch create an entry for the file in
* the branch's cemeter directory. else update the record to the cemeter directory on the branch.
*/
boolean fileExistsOnParentBranch = doesFileExistOnParentBranch(branch, fileId);
if (fileExistsOnParentBranch) {
datamodelFile.setDeletedFlag(true);
try {
// Update the branch record to show it is deleted from the directory so we won't show it in the
// current location.
fileDAO.update(datamodelFile, false);
} catch (SQLException e) {
StringBuilder message = new StringBuilder();
message.append("SQLException when setting deleted flag on branch: [").append(datamodelFile.getFileName()).append("].")
.append(" Exception: ").append(e.getLocalizedMessage());
LOGGER.log(Level.SEVERE, message.toString());
throw new QVCSException(message.toString());
}
datamodelFile.setDeletedFlag(false);
datamodelFile.setDirectoryId(-1);
try {
fileDAO.insert(datamodelFile);
} catch (SQLException e) {
StringBuilder message = new StringBuilder();
message.append("SQLException when creating cemetery record on branch file: [").append(datamodelFile.getFileName()).append("].").append(" Exception: ").
append(e.getLocalizedMessage());
LOGGER.log(Level.SEVERE, message.toString());
throw new QVCSException(message.toString());
}
} else {
datamodelFile.setDirectoryId(-1);
try {
// Move the file to the cemetery directory.
fileDAO.update(datamodelFile, false);
} catch (SQLException e) {
StringBuilder message = new StringBuilder();
message.append("SQLException when setting deleting file on branch: [").append(datamodelFile.getFileName()).append("].")
.append(" Exception: ").append(e.getLocalizedMessage());
LOGGER.log(Level.SEVERE, message.toString());
throw new QVCSException(message.toString());
}
}
} else {
/*
* else -- there is no file record for the branch, ergo it uses the parent branch's file record. create a record for
* the branch that has its deleted flag set to true. create an entry for the file in the branch's cemetery
* directory.
*/
// Walk up the parent branch tree until we find the file.
datamodelFile = findFile(branch, fileId);
if (!datamodelFile.getDirectoryId().equals(originDirectoryId)) {
throw new QVCSException("Original directory in parent branch does not match: db record directory: ["
+ datamodelFile.getDirectoryId() + "], caller original filename: ["
+ originDirectoryId + "]");
}
datamodelFile.setBranchId(branch.getBranchId());
datamodelFile.setDeletedFlag(true);
try {
fileDAO.insert(datamodelFile);
} catch (SQLException e) {
StringBuilder message = new StringBuilder();
message.append("SQLException when creating deleted flag record on branch file: [").append(datamodelFile.getFileName()).append("].").append(" Exception: ").
append(e.getLocalizedMessage());
LOGGER.log(Level.SEVERE, message.toString());
throw new QVCSException(message.toString());
}
// Create an entry for the file in the branch's destination directory.
datamodelFile.setDeletedFlag(false);
datamodelFile.setBranchId(branch.getBranchId());
datamodelFile.setDirectoryId(-1);
try {
fileDAO.insert(datamodelFile);
} catch (SQLException e) {
StringBuilder message = new StringBuilder();
message.append("SQLException when creating cemetery record for branch file: [").append(datamodelFile.getFileName()).append("].").append(" Exception: ").
append(e.getLocalizedMessage());
LOGGER.log(Level.SEVERE, message.toString());
throw new QVCSException(message.toString());
}
}
} else {
throw new QVCSException("Branch not found: [" + branchName + "]");
}
}
/**
* Add a directory.
*
* @param viewName the branch name.
* @param rootDirectoryId the root directory id for this project.
* @param parentAppendedPath the appended path of the parent directory.
* @param parentDirectoryID the parent directory id.
* @param childDirectoryID the child directory id -- i.e. the directory id of the directory that we are adding to the parent
* directory.
* @param childDirectoryName the child directory name.
* @param response the response object that identifies the client for this request.
* @throws QVCSException if there is no surrounding transaction or if the branch does not exist.
* @throws SQLException if the insert fails.
*/
public synchronized void addDirectory(final String viewName, final int rootDirectoryId, final String parentAppendedPath, final int parentDirectoryID,
final int childDirectoryID,
final String childDirectoryName, ServerResponseFactoryInterface response) throws QVCSException, SQLException {
checkForContainingTransaction("#### INTERNAL ERROR: Attempt to add a directory without a surrounding transaction.", response);
Branch branch = branchDAO.findByProjectIdAndBranchName(projectId, viewName);
if (branch != null) {
Directory directory = new Directory();
directory.setDirectoryId(childDirectoryID);
directory.setParentDirectoryId(parentDirectoryID);
if (parentAppendedPath != null && parentAppendedPath.length() > 0) {
directory.setAppendedPath(parentAppendedPath + QVCSConstants.QVCS_STANDARD_PATH_SEPARATOR_STRING + childDirectoryName);
} else {
directory.setAppendedPath(childDirectoryName);
}
directory.setBranchId(branch.getBranchId());
directory.setDeletedFlag(false);
directory.setRootDirectoryId(rootDirectoryId);
// Prevent insertion of duplicates.
if (null == directoryDAO.findById(directory.getBranchId(), directory.getDirectoryId())) {
directoryDAO.insert(directory);
}
} else {
throw new QVCSException("Did not find branch: [" + viewName + "]");
}
}
/**
* Delete a directory on the trunk. This does <b>not</b> check to see if the given directory is empty. This does not actually
* delete the directory record; it merely updates the parent and root directory id's to -1.
*
* @param directoryId the directory id.
* @param response the response object that identifies the client for this request.
* @throws QVCSException if there is no transaction, if an attempt is made to delete the root directory, or if the given
* directory is not found, or if there is a SQL problem on the update.
*/
public synchronized void deleteDirectoryOnTrunk(final int directoryId, ServerResponseFactoryInterface response)
throws QVCSException {
checkForContainingTransaction("#### INTERNAL ERROR: Attempt to delete a directory without a surrounding transaction.", response);
String viewName = QVCSConstants.QVCS_TRUNK_VIEW;
Branch branch = branchDAO.findByProjectIdAndBranchName(projectId, viewName);
if (branch != null) {
Directory directory = directoryDAO.findById(branch.getBranchId(), directoryId);
if (directory != null) {
// Make sure we're not trying to delete the root directory -- we do not allow that.
if (directory.getAppendedPath().equals("")) {
throw new QVCSException("Invalid attempt to delete the root directory.");
}
directory.setParentDirectoryId(-1);
try {
directoryDAO.update(directory, false);
} catch (SQLException e) {
StringBuilder message = new StringBuilder();
message.append("SQLException when updating directory for branch file: [").append(directory.getAppendedPath()).append("].").append(" Exception: ").
append(e.getLocalizedMessage());
LOGGER.log(Level.SEVERE, message.toString());
throw new QVCSException(message.toString());
}
} else {
throw new QVCSException("Did not find directory to delete on trunk. Directory id: [" + directoryId + "]");
}
} else {
throw new QVCSException("Did not find branch: [" + viewName + "]");
}
}
/**
* Delete a directory on an opaque branch.
* @param viewName the branch name.
* @param directoryId the directory id of the directory to delete.
* @param response link to the client.
* @throws QVCSException if there is no enclosing transaction, or for a number of other problems.
*/
public synchronized void deleteDirectoryOnOpaqueBranch(final String viewName, final int directoryId, ServerResponseFactoryInterface response) throws QVCSException {
deleteDirectoryOnBranch(viewName, directoryId, response);
}
/**
* Delete a directory on a translucent branch.
* @param viewName the branch name.
* @param directoryId the directory id of the directory to delete.
* @param response link to the client.
* @throws QVCSException if there is no enclosing transaction, or for a number of other problems.
*/
public synchronized void deleteDirectoryOnTranslucentBranch(final String viewName, final int directoryId, ServerResponseFactoryInterface response) throws QVCSException {
deleteDirectoryOnBranch(viewName, directoryId, response);
}
private void deleteDirectoryOnBranch(final String branchName, final int directoryId, ServerResponseFactoryInterface response) throws QVCSException {
checkForContainingTransaction("#### INTERNAL ERROR: Attempt to delete a directory on branch without a surrounding transaction.", response);
Branch branch = branchDAO.findByProjectIdAndBranchName(projectId, branchName);
if (branch != null) {
Directory datamodelDirectory = directoryDAO.findById(branch.getBranchId(), directoryId);
if (datamodelDirectory != null) {
// Make sure we're not trying to delete the root directory -- we do not allow that.
if (datamodelDirectory.getAppendedPath().equals("")) {
throw new QVCSException("Invalid attempt to delete the root directory.");
}
/*
* if (directory record exists on parent branch) mark the record deleted on the branch create an entry for the file
* in the branch's cemetery directory. else update the record to the cemetery directory on the branch.
*/
boolean directoryExistsOnParentBranch = doesDirectoryExistOnParentBranch(branch, directoryId);
if (directoryExistsOnParentBranch) {
datamodelDirectory.setDeletedFlag(true);
try {
// Update the branch record to show it is deleted so we won't show it in the current location.
directoryDAO.update(datamodelDirectory, false);
} catch (SQLException e) {
StringBuilder message = new StringBuilder();
message.append("SQLException when setting deleted flag on branch: [").append(datamodelDirectory.getAppendedPath()).append("].").append(" Exception: ").
append(e.getLocalizedMessage());
LOGGER.log(Level.SEVERE, message.toString());
throw new QVCSException(message.toString());
}
datamodelDirectory.setDeletedFlag(false);
datamodelDirectory.setParentDirectoryId(-1);
try {
directoryDAO.insert(datamodelDirectory);
} catch (SQLException e) {
StringBuilder message = new StringBuilder();
message.append("SQLException when creating cemetery record on branch: [").append(datamodelDirectory.getAppendedPath()).append("].").append(" Exception: ").
append(e.getLocalizedMessage());
LOGGER.log(Level.SEVERE, message.toString());
throw new QVCSException(message.toString());
}
} else {
datamodelDirectory.setParentDirectoryId(-1);
try {
// Move the file to the cemetery directory.
directoryDAO.update(datamodelDirectory, false);
} catch (SQLException e) {
StringBuilder message = new StringBuilder();
message.append("SQLException when setting deleting file on branch: [").append(datamodelDirectory.getAppendedPath()).append("].").
append(" Exception: ").append(e.getLocalizedMessage());
LOGGER.log(Level.SEVERE, message.toString());
throw new QVCSException(message.toString());
}
}
} else {
/*
* else -- there is no directory record for the branch, ergo it uses the parent branch's directory record. create a
* record for the branch that has its deleted flag set to true. create an entry for the directory in the branch's
* cemetery directory.
*/
// Walk up the parent branch tree until we find the file.
datamodelDirectory = findDirectory(branch, directoryId);
// Make sure we're not trying to delete the root directory -- we do not allow that.
if (datamodelDirectory.getAppendedPath().equals("")) {
throw new QVCSException("Invalid attempt to delete the root directory.");
}
datamodelDirectory.setBranchId(branch.getBranchId());
datamodelDirectory.setDeletedFlag(true);
try {
directoryDAO.insert(datamodelDirectory);
} catch (SQLException e) {
StringBuilder message = new StringBuilder();
message.append("SQLException when creating deleted flag record on branch: [").append(datamodelDirectory.getAppendedPath()).append("].").append(" Exception: ").
append(e.getLocalizedMessage());
LOGGER.log(Level.SEVERE, message.toString());
throw new QVCSException(message.toString());
}
// Create an entry for the directory in the branch's cemetery.
datamodelDirectory.setDeletedFlag(false);
datamodelDirectory.setBranchId(branch.getBranchId());
datamodelDirectory.setParentDirectoryId(-1);
try {
directoryDAO.insert(datamodelDirectory);
} catch (SQLException e) {
StringBuilder message = new StringBuilder();
message.append("SQLException when creating cemetery record for branch: [").append(datamodelDirectory.getAppendedPath()).append("].").append(" Exception: ").
append(e.getLocalizedMessage());
LOGGER.log(Level.SEVERE, message.toString());
throw new QVCSException(message.toString());
}
}
} else {
throw new QVCSException("Branch not found: [" + branchName + "]");
}
}
@Override
public synchronized void commitPendingChanges(ServerResponseFactoryInterface response, Date date) throws QVCSException {
try {
Connection connection = DatabaseManager.getInstance().getConnection();
if (!connection.getAutoCommit()) {
connection.commit();
connection.setAutoCommit(true);
}
} catch (IllegalStateException | SQLException e) {
throw new QVCSException(Utility.expandStackTraceToString(e));
}
}
/**
* Get the directory contents for a translucent (a.k.a. feature) branch.
*
* @param projectView the project view that describes the translucent branch.
* @param appendedPath the appended path for the directory.
* @param directoryId the directory id.
* @param response object to identify the client.
* @return the directory contents for the translucent branch.
* @throws QVCSException if we're not in a transaction.
*/
public synchronized DirectoryContents getDirectoryContentsForTranslucentBranch(ProjectView projectView, String appendedPath, int directoryId,
ServerResponseFactoryInterface response)
throws QVCSException {
checkForContainingTransaction("#### INTERNAL ERROR: Attempt to getDirectoryContentsForTranslucentBranch without a surrounding transaction.", response);
DirectoryContents directoryContents;
String parentBranch = projectView.getRemoteViewProperties().getBranchParent();
DirectoryContents parentDirectoryContents = getParentBranchDirectoryContents(parentBranch, appendedPath, directoryId);
Branch branch = branchDAO.findByProjectIdAndBranchName(projectId, projectView.getViewName());
// Find the files on this branch.
List<com.qumasoft.server.datamodel.File> fileList = fileDAO.findByBranchId(branch.getBranchId());
// Find the child directories on this branch.
List<com.qumasoft.server.datamodel.Directory> directoryList = directoryDAO.findByBranchId(branch.getBranchId());
directoryContents = mergeChildBranchToParentBranchDirectoryContentsForTranslucentBranch(parentDirectoryContents, directoryId, appendedPath, fileList, directoryList);
return directoryContents;
}
/**
* Get the directory contents for the trunk.
*
* @param appendedPath the appended path.
* @param directoryId the directory id.
* @param response object to identify the client.
* @return the directory contents for the trunk for the given directory id.
* @throws QVCSException if we're not in a transaction.
*/
public synchronized DirectoryContents getDirectoryContentsForTrunk(String appendedPath, int directoryId, ServerResponseFactoryInterface response) throws QVCSException {
checkForContainingTransaction("#### INTERNAL ERROR: Attempt to getDirectoryContentsForTrunk without a surrounding transaction.", response);
DirectoryContents directoryContents;
// Find the files on the trunk.
List<com.qumasoft.server.datamodel.File> fileList = fileDAO.findByBranchAndDirectoryId(getTrunkBranchId(), directoryId);
// Find the child directories on the trunk.
List<com.qumasoft.server.datamodel.Directory> directoryList = directoryDAO.findChildDirectories(getTrunkBranchId(), directoryId);
directoryContents = new DirectoryContents(getProjectName(), directoryId, appendedPath);
for (com.qumasoft.server.datamodel.File file : fileList) {
directoryContents.addFileID(file.getFileId(), file.getFileName());
}
for (com.qumasoft.server.datamodel.Directory directory : directoryList) {
directoryContents.addDirectoryID(directory.getDirectoryId(), Utility.getLastDirectorySegment(directory.getAppendedPath()));
}
return directoryContents;
}
/**
* Get the directory contents for the trunk cemetery. This can return null if the cemetery has not been created yet.
*
* @param response object to identify the client.
* @return the directory contents for the trunk's cemetery. This will be null if the cemetery has not been created yet (which
* will happen if no files have been deleted).
* @throws QVCSException if we're not in a transaction.
*/
public synchronized DirectoryContents getDirectoryContentsForTrunkCemetery(ServerResponseFactoryInterface response) throws QVCSException {
Integer cemeteryDirectoryId = null;
DirectoryContents cemeteryDirectoryContents = null;
Directory rootTrunkDirectory = directoryDAO.findByAppendedPath(getTrunkBranchId(), "");
DirectoryContents trunkDirectoryContents = getDirectoryContentsForTrunk("", rootTrunkDirectory.getDirectoryId(), response);
// Look in the root directory to see if there is a cemetery directory...
Set<Integer> keys = trunkDirectoryContents.getChildDirectories().keySet();
Map<Integer, String> map = trunkDirectoryContents.getChildDirectories();
for (Integer key : keys) {
String directoryName = map.get(key);
if (directoryName.equals(QVCSConstants.QVCS_CEMETERY_DIRECTORY)) {
cemeteryDirectoryId = key;
break;
}
}
if (cemeteryDirectoryId != null) {
cemeteryDirectoryContents = getDirectoryContentsForTrunk(QVCSConstants.QVCS_CEMETERY_DIRECTORY, cemeteryDirectoryId, response);
}
return cemeteryDirectoryContents;
}
/**
* Get the directory contents for a date based view.
*
* <p><b>This is currently coded to support the Trunk as the 'parent' of the date based view... i.e. we're only supporting a
* date based view of the Trunk at this time.</b></p>
*
* @param projectView the project view.
* @param appendedPath the appended path.
* @param directoryId the directory id.
* @param response object to identify the client.
* @return the directory contents for the given directory at the view's point in time.
* @throws QVCSException if we're not in a transaction, or if this is called for a non-date based view.
*/
public synchronized DirectoryContents getDirectoryContentsForDateBasedView(ProjectView projectView, String appendedPath, int directoryId,
ServerResponseFactoryInterface response)
throws QVCSException {
checkForContainingTransaction("#### INTERNAL ERROR: Attempt to getDirectoryContentsForDateBasedView without a surrounding transaction.", response);
DirectoryContents directoryContents;
if (!projectView.getRemoteViewProperties().getIsDateBasedViewFlag()) {
throw new QVCSException("Invalid call to getDirectoryContentsForDateBasedView for non-date based view.");
}
Date viewDate = projectView.getRemoteViewProperties().getDateBasedDate();
// Find the files on the trunk.
List<com.qumasoft.server.datamodel.File> fileList = fileDAO.findByBranchAndDirectoryIdAndViewDate(getTrunkBranchId(), directoryId, viewDate);
// Find the child directories that were created on or before the base date of the view.
List<com.qumasoft.server.datamodel.Directory> directoryList = directoryDAO.findChildDirectoriesOnOrBeforeViewDate(getTrunkBranchId(), directoryId, viewDate);
directoryContents = new DirectoryContents(getProjectName(), directoryId, appendedPath);
for (com.qumasoft.server.datamodel.File file : fileList) {
directoryContents.addFileID(file.getFileId(), file.getFileName());
}
for (com.qumasoft.server.datamodel.Directory directory : directoryList) {
directoryContents.addDirectoryID(directory.getDirectoryId(), Utility.getLastDirectorySegment(directory.getAppendedPath()));
}
// Look through the history tables to see if there are any records there that affect things:
// Look in file history for any records that we need to look at.
FileHistoryDAO fileHistoryDAO = new FileHistoryDAOImpl();
List<com.qumasoft.server.datamodel.FileHistory> fileHistoryList = fileHistoryDAO.findByBranchAndDirectoryIdAndViewDate(getTrunkBranchId(), directoryId, viewDate);
// Find all the child directories in history created on or before the base date of the view.
DirectoryHistoryDAO directoryHistoryDAO = new DirectoryHistoryDAOImpl();
List<com.qumasoft.server.datamodel.DirectoryHistory> directoryHistoryList = directoryHistoryDAO.findChildDirectoriesOnOrBeforeViewDate(getTrunkBranchId(),
directoryId, viewDate);
Integer mostRecentFileId = -1;
for (com.qumasoft.server.datamodel.FileHistory fileHistory : fileHistoryList) {
if (!fileHistory.getFileId().equals(mostRecentFileId)) {
mostRecentFileId = fileHistory.getFileId();
if (!fileHistory.isDeletedFlag()) {
directoryContents.addFileID(fileHistory.getFileId(), fileHistory.getFileName());
}
}
directoryContents.addFileID(fileHistory.getFileId(), fileHistory.getFileName());
}
Integer mostRecentDirectoryId = -1;
for (com.qumasoft.server.datamodel.DirectoryHistory directoryHistory : directoryHistoryList) {
if (!directoryHistory.getDirectoryId().equals(mostRecentDirectoryId)) {
mostRecentDirectoryId = directoryHistory.getDirectoryId();
if (!directoryHistory.isDeletedFlag()) {
directoryContents.addDirectoryID(directoryHistory.getDirectoryId(), Utility.getLastDirectorySegment(directoryHistory.getAppendedPath()));
}
}
}
return directoryContents;
}
/**
* Get the director contents for an opaque branch. <p><b>The current implementation only supports the Trunk as the parent
* branch. We should be able to lift this restriction, but the current code enforces this restriction, and the algorithm will
* only work for opaque branches that have the Trunk as their parent branch.</b></p>
*
* @param projectView the projectView that describes the opaque branch.
* @param appendedPath the appended path.
* @param directoryId the directory id.
* @param response object to identify the client.
* @return the directory contents for the given directory for the opaque branch.
* @throws QVCSException if we're not in a transaction, or if this is called for an opaque branch that does not have the Trunk
* as its parent branch.
*/
public synchronized DirectoryContents getDirectoryContentsForOpaqueBranch(ProjectView projectView, String appendedPath, int directoryId,
ServerResponseFactoryInterface response)
throws QVCSException {
checkForContainingTransaction("#### INTERNAL ERROR: Attempt to getDirectoryContentsForOpaqueBranch without a surrounding transaction.", response);
DirectoryContents directoryContents;
String parentBranchName = projectView.getRemoteViewProperties().getBranchParent();
if (!parentBranchName.equals(QVCSConstants.QVCS_TRUNK_VIEW)) {
throw new QVCSException("Opaque branch must have trunk as its parent branch.");
}
Branch branch = branchDAO.findByProjectIdAndBranchName(projectId, projectView.getViewName());
Date branchDate = projectView.getRemoteViewProperties().getBranchDate();
// Find the files on the trunk created on or before the branch creation time.
List<com.qumasoft.server.datamodel.File> fileList = fileDAO.findByBranchAndDirectoryIdAndViewDate(getTrunkBranchId(), directoryId, branchDate);
// Find the child directories that were created on or before the branch creation time.
List<com.qumasoft.server.datamodel.Directory> directoryList = directoryDAO.findChildDirectoriesOnOrBeforeViewDate(getTrunkBranchId(), directoryId, branchDate);
DirectoryContents parentBranchDirectoryContents = new DirectoryContents(getProjectName(), directoryId, appendedPath);
for (com.qumasoft.server.datamodel.File file : fileList) {
parentBranchDirectoryContents.addFileID(file.getFileId(), file.getFileName());
}
for (com.qumasoft.server.datamodel.Directory directory : directoryList) {
parentBranchDirectoryContents.addDirectoryID(directory.getDirectoryId(), Utility.getLastDirectorySegment(directory.getAppendedPath()));
}
// Find the files on this branch.
List<com.qumasoft.server.datamodel.File> branchFileList = fileDAO.findByBranchAndDirectoryId(branch.getBranchId(), directoryId);
// Find the child directories on this branch.
List<com.qumasoft.server.datamodel.Directory> branchDirectoryList = directoryDAO.findChildDirectories(branch.getBranchId(), directoryId);
directoryContents = mergeChildBranchToParentBranchDirectoryContents(parentBranchDirectoryContents, directoryId, appendedPath, branchFileList, branchDirectoryList);
return directoryContents;
}
/**
* Get the directory contents for the translucent branch cemetery for the given branch name.
* @param branchName the name of the branch.
* @param response link to the client.
* @return a directory contents object for the translucent branch cemetery for the given branch name.
* @throws QVCSException if there is no containing transaction, or if the branch cannot be found.
*/
public synchronized DirectoryContents getDirectoryContentsForTranslucentBranchCemetery(String branchName, ServerResponseFactoryInterface response)
throws QVCSException {
return getDirectoryContentsForBranchCemetery(branchName, response);
}
private DirectoryContents getDirectoryContentsForBranchCemetery(String branchName, ServerResponseFactoryInterface response) throws QVCSException {
checkForContainingTransaction("#### INTERNAL ERROR: Attempt to get directory contents for branch cemetery without a surrounding transaction.", response);
DirectoryContents directoryContents = null;
Branch branch = branchDAO.findByProjectIdAndBranchName(projectId, branchName);
if (branch != null) {
// Find the deleted files on this branch.
List<com.qumasoft.server.datamodel.File> fileList = fileDAO.findByBranchId(branch.getBranchId());
// Find the deleted child directories on this branch.
List<com.qumasoft.server.datamodel.Directory> directoryList = directoryDAO.findByBranchId(branch.getBranchId());
directoryContents = mergeChildBranchToParentBranchDirectoryContentsForTranslucentBranch(null, -1, QVCSConstants.QVCS_CEMETERY_DIRECTORY, fileList, directoryList);
} else {
throw new QVCSException("Branch not found: [" + branchName + "]");
}
return directoryContents;
}
@Override
public int getPriority() {
return TransactionParticipantInterface.HIGH_PRIORITY;
}
/**
* Check that there is a containing active transaction for the client identified by the response object.
*
* @param errorMessage the message to include in the exception if there is no transaction.
* @param response the response object that identifies the client for this request.
* @throws QVCSException if there is not an active transaction.
*/
private void checkForContainingTransaction(final String errorMessage, ServerResponseFactoryInterface response) throws QVCSException {
if (!ServerTransactionManager.getInstance().transactionIsInProgress(response)) {
LOGGER.log(Level.WARNING, errorMessage);
throw new QVCSException(errorMessage);
}
}
/**
* Does the directory exist on a parent branch. This is called only when the given directoryId is not found on a child branch.
* We want to determine if the directory exists on the branch's parent branches, so this method 'walks' up the branch tree until
* it gets to the Trunk. If it gets to the trunk without finding the directory then it returns false, otherwise, it will
* presumably have found the directory somewhere along the path to the trunk, and it will return true.
*
* @param branch the branch from which we begin the search... i.e. we begin looking in this branch's parent branch.
* @param directoryId the directory id to look for.
* @return true if we find the directory on the given branch's parent branches somewhere.
*/
private boolean doesDirectoryExistOnParentBranch(Branch branch, int directoryId) {
boolean foundDirectoryOnParentBranch = false;
ProjectView projectView = ViewManager.getInstance().getView(projectName, branch.getBranchName());
if (projectView.getRemoteViewProperties().getParentProjectName() != null) {
Branch parentBranch = branchDAO.findByProjectIdAndBranchName(projectId, projectView.getRemoteViewProperties().getBranchParent());
Directory directory = directoryDAO.findById(parentBranch.getBranchId(), directoryId);
if (directory != null) {
foundDirectoryOnParentBranch = true;
} else {
if (!parentBranch.getBranchName().equals(QVCSConstants.QVCS_TRUNK_VIEW)) {
foundDirectoryOnParentBranch = doesDirectoryExistOnParentBranch(parentBranch, directoryId);
}
}
}
return foundDirectoryOnParentBranch;
}
/**
* Does the file exist on a parent branch. This is called only when the given fileId is not found on a child branch. We want to
* determine if the file exists on the branch's parent branches, so this method 'walks' up the branch tree until it gets to the
* Trunk. If it gets to the trunk without finding the file then it returns false, otherwise, it will presumably have found the
* file somewhere along the path to the trunk, and it will return true.
*
* @param branch the branch from which we begin the search... i.e. we begin looking in this branch's parent branch.
* @param fileId the file id to look for.
* @return true if we find the file on the given branch's parent branches somewhere.
*/
private boolean doesFileExistOnParentBranch(Branch branch, int fileId) {
boolean foundFileOnParentBranch = false;
ProjectView projectView = ViewManager.getInstance().getView(projectName, branch.getBranchName());
if (projectView.getRemoteViewProperties().getParentProjectName() != null) {
Branch parentBranch = branchDAO.findByProjectIdAndBranchName(projectId, projectView.getRemoteViewProperties().getBranchParent());
com.qumasoft.server.datamodel.File file = fileDAO.findById(parentBranch.getBranchId(), fileId);
if (file != null) {
Directory containingDirectory = directoryDAO.findById(file.getBranchId(), file.getDirectoryId());
if (!containingDirectory.getAppendedPath().equals(QVCSConstants.QVCS_BRANCH_ARCHIVES_DIRECTORY)) {
foundFileOnParentBranch = true;
}
} else {
if (!parentBranch.getBranchName().equals(QVCSConstants.QVCS_TRUNK_VIEW)) {
foundFileOnParentBranch = doesFileExistOnParentBranch(parentBranch, fileId);
}
}
}
return foundFileOnParentBranch;
}
/**
* Walk up the branch tree until we find the directory.
*
* @param branch the branch where we'll look.
* @param directoryId the directory id.
* @return the directory (from the database).
*/
private Directory findDirectory(Branch branch, int directoryId) {
Directory directory = directoryDAO.findById(branch.getBranchId(), directoryId);
if (directory == null) {
ProjectView projectView = ViewManager.getInstance().getView(projectName, branch.getBranchName());
if (projectView.getRemoteViewProperties().getParentProjectName() != null) {
Branch parentBranch = branchDAO.findByProjectIdAndBranchName(projectId, projectView.getRemoteViewProperties().getBranchParent());
directory = findDirectory(parentBranch, directoryId);
}
}
return directory;
}
/**
* Walk up the branch tree until we find the file.
*
* @param branch the branch where we'll look.
* @param fileId the file id.
* @return the file (from the database).
*/
private com.qumasoft.server.datamodel.File findFile(Branch branch, int fileId) {
com.qumasoft.server.datamodel.File file = fileDAO.findById(branch.getBranchId(), fileId);
if (file == null) {
ProjectView projectView = ViewManager.getInstance().getView(projectName, branch.getBranchName());
if (projectView.getRemoteViewProperties().getParentProjectName() != null) {
Branch parentBranch = branchDAO.findByProjectIdAndBranchName(projectId, projectView.getRemoteViewProperties().getBranchParent());
file = findFile(parentBranch, fileId);
}
}
return file;
}
/**
* Get the database branch id for the project's trunk branch.
*
* @return the branch id for this project's trunk branch.
* @throws QVCSException if we could not find the trunk branch.
*/
private int getTrunkBranchId() throws QVCSException {
if (trunkBranchId == null) {
Branch trunk = branchDAO.findByProjectIdAndBranchName(projectId, QVCSConstants.QVCS_TRUNK_VIEW);
if (trunk != null) {
trunkBranchId = trunk.getBranchId();
} else {
throw new QVCSException("Did not find Trunk for project [" + getProjectName() + "]");
}
}
return trunkBranchId;
}
/**
* Recursive method to build the directory contents for a branch's parent branch. This method 'walks' its way to the trunk to
* build the directory contents for the given parent branch.
*
* @param parentBranch the name of the parent branch.
* @param appendedPath the appended path.
* @param directoryId the directory id.
* @return the directory contents of the given parent branch.
*/
private DirectoryContents getParentBranchDirectoryContents(String parentBranch, String appendedPath, int directoryId) {
DirectoryContents directoryContents;
DirectoryContents parentDirectoryContents = null;
if (!parentBranch.equals(QVCSConstants.QVCS_TRUNK_VIEW)) {
// Walk up to the next parent branch... we'll only stop when we reach the trunk.
ProjectView projectView = ViewManager.getInstance().getView(projectName, parentBranch);
String parentParentBranch = projectView.getRemoteViewProperties().getBranchParent();
parentDirectoryContents = getParentBranchDirectoryContents(parentParentBranch, appendedPath, directoryId);
}
Branch branch = branchDAO.findByProjectIdAndBranchName(projectId, parentBranch);
// Find the files on this branch.
List<com.qumasoft.server.datamodel.File> fileList = fileDAO.findByBranchAndDirectoryId(branch.getBranchId(), directoryId);
// Find the child directories on this branch.
List<com.qumasoft.server.datamodel.Directory> directoryList = directoryDAO.findChildDirectories(branch.getBranchId(), directoryId);
directoryContents = mergeChildBranchToParentBranchDirectoryContents(parentDirectoryContents, directoryId, appendedPath, fileList, directoryList);
return directoryContents;
}
/**
* Merge the directory contents of the parent with the directory contents supplied in the file list and directory list.
*
* @param parentDirectoryContents the parent directory contents (may be null).
* @param directoryId the directory id.
* @param appendedPath the appended path.
* @param fileList the list of files to update in the parent directory contents.
* @param directoryList the list of directories to update in the parent directory contents.
* @return
*/
private DirectoryContents mergeChildBranchToParentBranchDirectoryContents(DirectoryContents parentDirectoryContents, int directoryId, String appendedPath,
List<com.qumasoft.server.datamodel.File> fileList, List<Directory> directoryList) {
DirectoryContents directoryContents;
if (parentDirectoryContents == null) {
directoryContents = new DirectoryContents(projectName, directoryId, appendedPath);
} else {
directoryContents = parentDirectoryContents;
}
for (com.qumasoft.server.datamodel.File file : fileList) {
if (file.isDeletedFlag()) {
directoryContents.removeFileID(file.getFileId());
} else {
directoryContents.addFileID(file.getFileId(), file.getFileName());
}
}
for (com.qumasoft.server.datamodel.Directory directory : directoryList) {
if (directory.isDeletedFlag()) {
directoryContents.removeDirectoryID(directory.getDirectoryId());
} else {
directoryContents.addDirectoryID(directory.getDirectoryId(), Utility.getLastDirectorySegment(directory.getAppendedPath()));
}
}
return directoryContents;
}
/**
* Merge the directory contents of the parent with the directory contents supplied in the file list and directory list.
*
* @param parentDirectoryContents the parent directory contents (may be null).
* @param directoryId the directory id.
* @param appendedPath the appended path.
* @param branchFileList the list of files on the branch.
* @param branchDirectoryList the list of directories on the branch.
* @return
*/
private DirectoryContents mergeChildBranchToParentBranchDirectoryContentsForTranslucentBranch(DirectoryContents parentDirectoryContents, int directoryId, String appendedPath,
List<com.qumasoft.server.datamodel.File> branchFileList, List<Directory> branchDirectoryList) {
DirectoryContents directoryContents;
if (parentDirectoryContents == null) {
directoryContents = new DirectoryContents(projectName, directoryId, appendedPath);
} else {
directoryContents = parentDirectoryContents;
}
for (com.qumasoft.server.datamodel.File file : branchFileList) {
if (file.isDeletedFlag()) {
if (file.getDirectoryId().equals(directoryId)) {
directoryContents.removeFileID(file.getFileId());
}
} else {
if (file.getDirectoryId().equals(directoryId)) {
directoryContents.addFileID(file.getFileId(), file.getFileName());
} else {
// The file is in a different directory on the branch.
directoryContents.removeFileID(file.getFileId());
}
}
}
for (com.qumasoft.server.datamodel.Directory directory : branchDirectoryList) {
if (directory.isDeletedFlag()) {
if (directory.getParentDirectoryId().equals(directoryId)) {
directoryContents.removeDirectoryID(directory.getDirectoryId());
}
} else {
if (directory.getParentDirectoryId().equals(directoryId)) {
directoryContents.addDirectoryID(directory.getDirectoryId(), Utility.getLastDirectorySegment(directory.getAppendedPath()));
} else {
// The directory has a different parent directory on the branch.
directoryContents.removeDirectoryID(directory.getDirectoryId());
}
}
}
return directoryContents;
}
/**
* Delete any file record on the branch that has the is deleted flag set to true.
*
* @param branchId the branch id.
* @param fileId the file id.
*/
private void deleteBranchFileRecordWithIsDeletedFlag(final int branchId, final int fileId) {
com.qumasoft.server.datamodel.File markedForDeleteFile = fileDAO.findIsDeletedById(branchId, fileId);
if (markedForDeleteFile != null) {
fileDAO.deleteWithIsDeletedFlag(markedForDeleteFile);
}
}
}