// 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.qvcslib;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.Map;
import java.util.TreeMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.event.ChangeListener;
/**
* The DirectoryManagerFactory is used to create the DirectoryManager for a given project.
* <p>
* Note that DirectoryManager objects are only useful for the GUI, and/or other client programs that need to have a merged view of archives and workfiles. The server should not
* need to use DirectoryManager objects, and should use the ArchiveDirManager class instead.</p>
*
* @author Jim Voris
*/
public final class DirectoryManagerFactory {
// Create our logger object
private static final Logger LOGGER = Logger.getLogger("com.qumasoft.qvcslib");
// This is a singleton.
private static final DirectoryManagerFactory FACTORY = new DirectoryManagerFactory();
private final Map<String, DirectoryManagerInterface> directoryManagerMap;
private final Map<String, AbstractProjectProperties> projectPropertiesMap;
private final Map<String, String> serverPasswordsMap;
private final Map<String, String> serverUsersMap;
// This map holds a collection of Maps that contain the directory managers for a given project.
private final Map<String, Map<String, DirectoryManagerInterface>> directoryManagerProjectCollectionMap;
/**
* Creates a new instance of DirectoryManagerFactory.
*/
private DirectoryManagerFactory() {
directoryManagerMap = Collections.synchronizedMap(new TreeMap<String, DirectoryManagerInterface>());
projectPropertiesMap = Collections.synchronizedMap(new TreeMap<String, AbstractProjectProperties>());
serverPasswordsMap = Collections.synchronizedMap(new TreeMap<String, String>());
serverUsersMap = Collections.synchronizedMap(new TreeMap<String, String>());
directoryManagerProjectCollectionMap = Collections.synchronizedMap(new TreeMap<String, Map<String, DirectoryManagerInterface>>());
}
/**
* Get the directory manager factory singleton.
* @return the directory manager factory singleton.
*/
public static DirectoryManagerFactory getInstance() {
return FACTORY;
}
/**
* Build the directory manager for the given parameters. (This may just return the existing directory manager for the given parameters, since it may already have been built.).
* @param serverName the server name.
* @param directoryCoordinate the directory coordinate.
* @param projectType the type of project.
* @param projectProperties the project's project properties.
* @param workfileDirectory the workfile directory.
* @param listener a change listener.
* @param fastNotifyFlag the fast notify flag.
* @return the directory manager for the given parameters.
* @throws QVCSException if there are problems creating the archive dir manager.
*/
public DirectoryManagerInterface getDirectoryManager(String serverName, DirectoryCoordinate directoryCoordinate, String projectType,
AbstractProjectProperties projectProperties,
String workfileDirectory, ChangeListener listener, boolean fastNotifyFlag) throws QVCSException {
DirectoryManager directoryManager = null;
String projectName = directoryCoordinate.getProjectName();
String viewName = directoryCoordinate.getViewName();
String appendedPath = directoryCoordinate.getAppendedPath();
Map<String, DirectoryManagerInterface> directoryManagersForProjectMap;
try {
directoryManager = (DirectoryManager) lookupDirectoryManager(serverName, projectName, viewName, appendedPath, projectType);
if (directoryManager == null) {
// Create the directory manager that we'll return.
directoryManager = new DirectoryManager(getServerUsername(serverName), projectName, viewName);
// Create the archive directory manager that we need.
ArchiveDirManagerInterface archiveDirManager = ArchiveDirManagerFactory.getInstance().getDirectoryManager(serverName, directoryCoordinate,
projectType, projectProperties, getServerUsername(serverName), true);
archiveDirManager.setDirectoryManager(directoryManager);
// Create the workfile directory manager that we need.
WorkfileDirectoryManager workfileDirectoryManager = new WorkfileDirectoryManager(workfileDirectory, archiveDirManager, directoryManager);
directoryManager.setArchiveDirManager(archiveDirManager);
directoryManager.setWorkfileDirectoryManager(workfileDirectoryManager);
String keyValue = getProjectViewKey(serverName, projectName, viewName, projectProperties, appendedPath);
// Update the property map if we can or need to.
String propertiesKey = getPropertiesViewKey(serverName, projectName, viewName);
AbstractProjectProperties existingProjectProperties = projectPropertiesMap.get(propertiesKey);
if ((existingProjectProperties == null) && (projectProperties != null)) {
projectPropertiesMap.put(propertiesKey, projectProperties);
// Create the Map that we will use to contain the collection
// of directory managers for a given project.
directoryManagersForProjectMap = Collections.synchronizedMap(new TreeMap<String, DirectoryManagerInterface>());
directoryManagerProjectCollectionMap.put(propertiesKey, directoryManagersForProjectMap);
} else {
// Lookup the Map that we use to contain the collection of
// directory managers for this project.
directoryManagersForProjectMap = directoryManagerProjectCollectionMap.get(propertiesKey);
}
directoryManagerMap.put(keyValue, directoryManager);
directoryManagersForProjectMap.put(keyValue, directoryManager);
if (listener != null) {
directoryManager.addChangeListener(listener);
}
LOGGER.log(Level.FINE, "DirectoryManagerFactory created directoryManager for: " + keyValue);
// Things are now setup. It's okay to get started.
// (This is here so a remote won't deliver a response to us before
// we have made an entry in the map of directory managers....
// this did happen -- which is why I broke the initialization
// of the archiveDirProxy into two steps.
archiveDirManager.setFastNotify(fastNotifyFlag);
archiveDirManager.startDirectoryManager();
} else {
LOGGER.log(Level.FINE, "DirectoryManagerFactory found existing directoryManager for: " + getProjectViewKey(serverName, projectName, viewName,
projectProperties, appendedPath));
}
} catch (QVCSException e) {
LOGGER.log(Level.WARNING, e.getLocalizedMessage());
throw e;
}
return directoryManager;
}
/**
* Lookup an existing directory manager given these parameters.
* @param serverName the server name.
* @param projectName the project name.
* @param viewName the view name.
* @param appendedPath the appended path.
* @param projectType the project type.
* @return the associated directory manager (or null if it has not been created).
*/
public DirectoryManagerInterface lookupDirectoryManager(String serverName, String projectName, String viewName, String appendedPath, String projectType) {
DirectoryManagerInterface directoryManager = null;
String propertiesKey = getPropertiesViewKey(serverName, projectName, viewName);
AbstractProjectProperties projectProperties = projectPropertiesMap.get(propertiesKey);
if (projectProperties != null) {
String keyValue = getProjectViewKey(serverName, projectName, viewName, projectProperties, appendedPath);
directoryManager = directoryManagerMap.get(keyValue);
}
return directoryManager;
}
/**
* Remove the directory manager associated with the given parameters. The goal of this method is to remove any references to the directory manager so that it can get
* garbage collected.
* @param serverName the server name.
* @param projectName the project name.
* @param viewName the view name.
* @param projectType the project type.
* @param appendedPath the appended path.
*/
public void removeDirectoryManager(String serverName, String projectName, String viewName, String projectType, String appendedPath) {
String propertiesKey = getPropertiesViewKey(serverName, projectName, viewName);
AbstractProjectProperties projectProperties = projectPropertiesMap.get(propertiesKey);
if (projectProperties != null) {
String keyValue = getProjectViewKey(serverName, projectName, viewName, projectProperties, appendedPath);
LOGGER.log(Level.FINE, "DirectoryManagerFactory.removeDirectoryManager: removing directory manager for: " + keyValue);
directoryManagerMap.remove(keyValue);
if ((appendedPath.length() == 0) && (0 == viewName.compareTo(QVCSConstants.QVCS_TRUNK_VIEW))) {
serverPasswordsMap.remove(serverName);
serverUsersMap.remove(serverName);
}
// And remove the directory manager from the directory manager
// for project collection...
Map map = directoryManagerProjectCollectionMap.get(propertiesKey);
if (map != null) {
map.remove(keyValue);
}
}
ArchiveDirManagerFactory.getInstance().removeDirectoryManager(serverName, projectName, viewName, projectType, appendedPath);
}
/**
* Set the server password for the given server. This is within the context of a given user... i.e. Directory managers are client side objects, and as a result, necessarily
* have a user name associated with them.
* @param serverName the server name.
* @param password the password for that server.
*/
public void setServerPassword(String serverName, String password) {
serverPasswordsMap.put(serverName, password);
ArchiveDirManagerFactory.getInstance().setServerPassword(serverName, password);
}
/**
* Get the server password for a given server name.
* @param serverName the server name.
* @return the user's password for that server.
*/
public String getServerPassword(String serverName) {
return serverPasswordsMap.get(serverName);
}
/**
* Set the server user name. This allows us to capture the user name that is used to connect to the given server.
* @param serverName the server name.
* @param username the associated user name.
*/
public void setServerUsername(String serverName, String username) {
serverUsersMap.put(serverName, username);
ArchiveDirManagerFactory.getInstance().setServerUsername(serverName, username);
}
/**
* Get the user name that is used for the given server name.
* @param serverName the server name.
* @return the associated user name.
*/
public String getServerUsername(String serverName) {
return serverUsersMap.get(serverName);
}
/**
* Get the directory managers collection for the given project/view.
* @param serverName the server name.
* @param projectName the project name.
* @param viewName the view name.
* @return the collection of directory managers for the given project/view.
*/
public Collection<DirectoryManagerInterface> getDirectoryManagersForProject(String serverName, String projectName, String viewName) {
String key = getPropertiesViewKey(serverName, projectName, viewName);
Map<String, DirectoryManagerInterface> map = directoryManagerProjectCollectionMap.get(key);
if (map == null) {
Map<String, DirectoryManagerInterface> directoryManagersForProjectMap = Collections.synchronizedMap(new TreeMap<String, DirectoryManagerInterface>());
return directoryManagersForProjectMap.values();
} else {
return map.values();
}
}
/**
* Discard any directory managers that we have for the given project. The purpose is to discard any references that this factory class has to any directory managers associated
* with the given project. This includes any non-trunk view related directory managers as well as all trunk directory managers.
* @param serverName the server name.
* @param projectName the project name.
*/
public void discardDirectoryManagersForProject(String serverName, String projectName) {
// Get the iterator for the collection of project/view maps. This iterator
// iterates over the collection of maps that contain the directory managers
// for a given project/view. Since we are discarding all directory managers
// for a given project, we need to discard any project/view map for the
// given project, AND we must also discard all the directory managers
// contained in those maps. The caller does not know the view that we
// need to discard -- and in fact we need to discard any/all views associated
// with the given project.... which is why we have to do things the way
// we do in the code below, since we cannot construct the map keys until
// we know the view...
DirectoryManagerInterface directoryManager = null;
Iterator<Map<String, DirectoryManagerInterface>> mapIt = directoryManagerProjectCollectionMap.values().iterator();
while (mapIt.hasNext()) {
// Get the next map that contains directory managers for a given
// project/view.
boolean discardThisMap = false;
Map<String, DirectoryManagerInterface> map = mapIt.next();
Iterator<DirectoryManagerInterface> it = map.values().iterator();
while (it.hasNext()) {
// Get the first directory manager contained within a project/view
// map so that we can figure out whether this map contains
// directory managers for the given servername/project.
directoryManager = it.next();
ArchiveDirManagerProxy archiveDirManager = (ArchiveDirManagerProxy) directoryManager.getArchiveDirManager();
if (archiveDirManager != null) {
String archiveDirManagerServerName = archiveDirManager.getServerProperties().getServerName();
String dirManagerProjectName = directoryManager.getProjectName();
if (0 == archiveDirManagerServerName.compareTo(serverName)
&& 0 == dirManagerProjectName.compareTo(projectName)) {
discardThisMap = true;
}
break;
}
}
// If this map contains directoryManagers for the given servername/project...
if (discardThisMap && (directoryManager != null)) {
mapIt.remove();
projectPropertiesMap.remove(getPropertiesViewKey(serverName, projectName, directoryManager.getViewName()));
it = map.values().iterator();
while (it.hasNext()) {
directoryManager = it.next();
String keyValue = getProjectViewKey(serverName, projectName, directoryManager.getViewName(), directoryManager.getProjectProperties(),
directoryManager.getAppendedPath());
directoryManagerMap.remove(keyValue);
ArchiveDirManagerFactory.getInstance().removeDirectoryManager(serverName, projectName, directoryManager.getViewName(), QVCSConstants.QVCS_REMOTE_PROJECT_TYPE,
directoryManager.getAppendedPath());
}
}
}
}
private String getPropertiesViewKey(String serverName, String projectName, String viewName) {
String keyValue = serverName + "." + projectName + "." + viewName;
return keyValue;
}
private String getProjectViewKey(String serverName, String projectName, String viewName, AbstractProjectProperties projectProperties, String appendedPath) {
// Make this a standard appended path...
String standardAppendedPath = Utility.convertToStandardPath(appendedPath);
if (projectProperties.getIgnoreCaseFlag()) {
standardAppendedPath = standardAppendedPath.toLowerCase();
}
String keyValue = serverName + ":" + projectName + ":" + viewName + "//" + projectProperties.getProjectType() + ":" + standardAppendedPath;
return keyValue;
}
}