/* 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.RemoteViewProperties;
import com.qumasoft.qvcslib.ServerResponseFactoryInterface;
import com.qumasoft.qvcslib.Utility;
import com.qumasoft.qvcslib.commandargs.UnLabelDirectoryCommandArgs;
import com.qumasoft.qvcslib.requestdata.ClientRequestUnLabelDirectoryData;
import com.qumasoft.server.clientrequest.ClientRequestUnLabelDirectory;
import com.qumasoft.server.dataaccess.BranchDAO;
import com.qumasoft.server.dataaccess.ProjectDAO;
import com.qumasoft.server.dataaccess.impl.BranchDAOImpl;
import com.qumasoft.server.dataaccess.impl.ProjectDAOImpl;
import com.qumasoft.server.datamodel.Branch;
import com.qumasoft.server.datamodel.Project;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.sql.SQLException;
import java.util.Collection;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* View Manager. Manage the views defined on the server. This is a singleton.
* @author Jim Voris
*/
public final class ViewManager {
// Create our logger object
private static final Logger LOGGER = Logger.getLogger("com.qumasoft.server");
private static final ViewManager VIEW_MANAGER = new ViewManager();
private boolean isInitializedFlagMember = false;
private String viewStoreNameMember = null;
private String viewStoreNameOldMember = null;
private ViewStore viewStoreMember;
/**
* Creates a new instance of ViewManager.
*/
private ViewManager() {
}
/**
* Get the ViewManager singleton.
* @return the ViewManager singleton.
*/
public static ViewManager getInstance() {
return VIEW_MANAGER;
}
/**
* Initialize the view manager.
* @return true if initialization succeeded; false otherwise.
*/
public synchronized boolean initialize() {
if (!isInitializedFlagMember) {
viewStoreNameOldMember = getViewStoreName() + ".old";
loadViewStore();
isInitializedFlagMember = true;
}
return isInitializedFlagMember;
}
/**
* Reset the view store so it is empty.
*/
synchronized void resetStore() {
File viewStoreFile = new File(getViewStoreName());
if (viewStoreFile.exists()) {
viewStoreFile.delete();
}
}
private String getViewStoreName() {
if (viewStoreNameMember == null) {
viewStoreNameMember = System.getProperty("user.dir")
+ File.separator
+ QVCSConstants.QVCS_ADMIN_DATA_DIRECTORY
+ File.separator
+ QVCSConstants.QVCS_VIEW_STORE_NAME + "dat";
}
return viewStoreNameMember;
}
private synchronized void loadViewStore() {
File viewStoreFile;
FileInputStream fileStream = null;
try {
viewStoreFile = new File(getViewStoreName());
fileStream = new FileInputStream(viewStoreFile);
ObjectInputStream inStream = new ObjectInputStream(fileStream);
viewStoreMember = (ViewStore) inStream.readObject();
} catch (FileNotFoundException e) {
// The file doesn't exist yet. Create a default store.
viewStoreMember = new ViewStore();
writeViewStore();
} catch (IOException | ClassNotFoundException e) {
LOGGER.log(Level.WARNING, Utility.expandStackTraceToString(e));
if (fileStream != null) {
try {
fileStream.close();
fileStream = null;
} catch (IOException ex) {
LOGGER.log(Level.WARNING, Utility.expandStackTraceToString(ex));
}
}
// Serialization failed. Create a default store.
viewStoreMember = new ViewStore();
writeViewStore();
} finally {
viewStoreMember.initProjectViewMap();
if (fileStream != null) {
try {
fileStream.close();
} catch (IOException e) {
LOGGER.log(Level.WARNING, Utility.expandStackTraceToString(e));
}
}
viewStoreMember.dump();
}
}
/**
* Write the view store to disk.
*/
public synchronized void writeViewStore() {
FileOutputStream fileStream = null;
try {
File storeFile = new File(getViewStoreName());
File oldStoreFile = new File(viewStoreNameOldMember);
if (oldStoreFile.exists()) {
oldStoreFile.delete();
}
if (storeFile.exists()) {
storeFile.renameTo(oldStoreFile);
}
File newStoreFile = new File(getViewStoreName());
// Make sure the needed directories exists
if (!newStoreFile.getParentFile().exists()) {
newStoreFile.getParentFile().mkdirs();
}
fileStream = new FileOutputStream(newStoreFile);
ObjectOutputStream outStream = new ObjectOutputStream(fileStream);
outStream.writeObject(viewStoreMember);
} catch (IOException e) {
LOGGER.log(Level.WARNING, Utility.expandStackTraceToString(e));
} finally {
if (fileStream != null) {
try {
fileStream.close();
} catch (IOException e) {
LOGGER.log(Level.WARNING, Utility.expandStackTraceToString(e));
}
}
}
}
private synchronized ViewStore getViewStore() {
return viewStoreMember;
}
/**
* Get the views associated with a given project.
* @param projectName the project name.
* @return the views associated with the given project.
*/
public synchronized Collection<ProjectView> getViews(final String projectName) {
return getViewStore().getViews(projectName);
}
/**
* Get the ProjectView object for the given project and view.
* @param projectName the project name.
* @param viewName the view name.
* @return the ProjectView object that describes the given view.
*/
public synchronized ProjectView getView(final String projectName, final String viewName) {
return getViewStore().getView(projectName, viewName);
}
/**
* Add a view.
* @param projectView the object that describes the view.
* @throws QVCSException if the branch type is not known, or if we cannot store the view information onto the database.
*/
public synchronized void addView(ProjectView projectView) throws QVCSException {
getViewStore().addView(projectView);
writeViewStore();
ProjectDAO projectDAO = new ProjectDAOImpl();
Project project = projectDAO.findByProjectName(projectView.getProjectName());
BranchDAO branchDAO = new BranchDAOImpl();
Branch branch = new Branch();
branch.setBranchName(projectView.getViewName());
branch.setProjectId(project.getProjectId());
int branchType = -1;
if (projectView.getRemoteViewProperties().getIsOpaqueBranchFlag()) {
branchType = DatabaseManager.OPAQUE_BRANCH_TYPE;
} else if (projectView.getRemoteViewProperties().getIsTranslucentBranchFlag()) {
branchType = DatabaseManager.TRANSLUCENT_BRANCH_TYPE;
} else if (projectView.getRemoteViewProperties().getIsDateBasedViewFlag()) {
branchType = DatabaseManager.DATE_BASED_BRANCH_TYPE;
} else {
throw new QVCSException("Unknown branch type");
}
branch.setBranchTypeId(branchType);
try {
branchDAO.insert(branch);
} catch (SQLException e) {
LOGGER.log(Level.SEVERE, null, e);
throw new QVCSException("Failed to insert view: [" + projectView.getViewName() + "]");
}
}
/**
* Remove a view. This removes the view from the view store, removes any view based file labels, and gets rid of any file id
* dictionary entries for the view that we're removing.
*
* @param projectView the view that we are to remove.
* @param response an object that identifies the client.
*/
public synchronized void removeView(ProjectView projectView, ServerResponseFactoryInterface response) {
// Discard any directory managers for the view. Use an empty string for the
// server name so we create a useful key prefix string since we're running
// on the server.
ArchiveDirManagerFactoryForServer.getInstance().discardViewDirectoryManagers("", projectView.getProjectName(), projectView.getViewName());
getViewStore().removeView(projectView);
writeViewStore();
// Remove all file labels used for the view.
if (!projectView.getRemoteViewProperties().getIsReadOnlyViewFlag()
|| projectView.getRemoteViewProperties().getIsOpaqueBranchFlag()
|| projectView.getRemoteViewProperties().getIsTranslucentBranchFlag()) {
removeViewLabel(projectView, response);
}
// Remove any file id's associated with the view.
FileIDDictionary.getInstance().removeIDsForView(projectView.getProjectName(), projectView.getViewName());
// TODO -- Would be a good idea to perform a cascading delete of records from FileHistory, File, Directory, and DirectoryHistory...
// though strictly speaking, it is not required since there won't be any way to get to the records.
}
private synchronized void removeViewLabel(ProjectView projectView, ServerResponseFactoryInterface response) {
// Use existing code to do the heavy lifting...
UnLabelDirectoryCommandArgs commandArgs = new UnLabelDirectoryCommandArgs();
commandArgs.setLabelString(deduceViewLabel(projectView));
commandArgs.setRecurseFlag(true);
commandArgs.setUserName(QVCSConstants.QVCS_SERVER_USER);
ClientRequestUnLabelDirectoryData clientRequestUnLabelDirectoryData = new ClientRequestUnLabelDirectoryData();
clientRequestUnLabelDirectoryData.setAppendedPath("");
clientRequestUnLabelDirectoryData.setCommandArgs(commandArgs);
clientRequestUnLabelDirectoryData.setProjectName(projectView.getProjectName());
clientRequestUnLabelDirectoryData.setViewName(QVCSConstants.QVCS_TRUNK_VIEW);
ClientRequestUnLabelDirectory clientRequestUnLabelDirectory = new ClientRequestUnLabelDirectory(clientRequestUnLabelDirectoryData);
clientRequestUnLabelDirectory.execute(QVCSConstants.QVCS_SERVER_USER, response);
}
private synchronized String deduceViewLabel(ProjectView projectView) {
String label = null;
RemoteViewProperties remoteViewProperties = projectView.getRemoteViewProperties();
if (remoteViewProperties.getIsOpaqueBranchFlag()) {
label = projectView.getOpaqueBranchLabel();
} else if (remoteViewProperties.getIsTranslucentBranchFlag()) {
label = projectView.getTranslucentBranchLabel();
}
return label;
}
}