// 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.ArchiveDirManagerFactory; import com.qumasoft.qvcslib.ArchiveDirManagerInterface; import com.qumasoft.qvcslib.LogFile; import com.qumasoft.qvcslib.QVCSException; import com.qumasoft.qvcslib.QVCSServedProjectNamesFilter; import com.qumasoft.qvcslib.ServedProjectProperties; import com.qumasoft.qvcslib.ServerResponseFactoryInterface; import com.qumasoft.qvcslib.Utility; import java.io.File; import java.io.IOException; import java.net.ServerSocket; import java.nio.charset.Charset; import java.util.Set; import javax.net.ssl.SSLServerSocket; import javax.net.ServerSocketFactory; import javax.net.ssl.SSLServerSocketFactory; import java.net.Socket; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Vector; import java.util.logging.Logger; import java.util.logging.Level; import java.util.logging.LogManager; import com.qumasoft.qvcslib.ArchiveDigestManager; import com.qumasoft.qvcslib.ArchiveDirManager; import com.qumasoft.qvcslib.QVCSConstants; import com.qumasoft.qvcslib.ServerResponseFactory; import com.qumasoft.qvcslib.ServerResponseFactoryBaseClass; import com.qumasoft.qvcslib.SerializableObjectInterface; import com.qumasoft.qvcslib.ServerResponseLogin; import com.qumasoft.qvcslib.ServerResponseMessage; import com.qumasoft.qvcslib.VanillaServerResponseFactory; import com.qumasoft.webserver.WebServer; /** * * @author $Author$ */ public class QVCSEnterpriseServer { // Capture the version of the server. private static final String m_Version = "$Label$"; static final int DEFAULT_VANILLA_LISTEN_PORT = 9887; static final int DEFAULT_NON_SECURE_LISTEN_PORT = 9888; static final int DEFAULT_SECURE_LISTEN_PORT = 9889; static final int DEFAULT_ADMIN_LISTEN_PORT = 9890; static final String WEB_SERVER_PORT = "9080"; static final int m_workerThreads = 50; private int m_VanillaPort = DEFAULT_VANILLA_LISTEN_PORT; private int m_NonSecurePort = DEFAULT_NON_SECURE_LISTEN_PORT; private int m_SecurePort = DEFAULT_SECURE_LISTEN_PORT; private int m_AdminPort = DEFAULT_ADMIN_LISTEN_PORT; private String m_QVCSHomeDirectory = null; private String m_args[]; /* Where worker threads stand idle */ final Vector<Runnable> m_Threads = new Vector<Runnable>(); final Vector<Runnable> m_VanillaThreads = new Vector<Runnable>(); int m_ThreadCount = 1; VanillaServer m_VanillaServer = null; NonSecureServer m_nonSecureServer = null; SecureServer m_secureServer = null; SecureServer m_adminServer = null; QVCSWebServer m_webServer = null; // Server socket listener threads. Thread m_VanillaThread = null; Thread m_NonSecureThread = null; Thread m_SecureThread = null; Thread m_AdminThread = null; // Web server thread. Thread m_WebServerThread = null; // Change on trunk. static private QVCSEnterpriseServer m_Server; // Create our logger object private static Logger m_logger = Logger.getLogger("com.qumasoft.server"); static final List<ServerResponseFactoryInterface> connectedUsersCollection = Collections.synchronizedList(new ArrayList<ServerResponseFactoryInterface>()); public static void main (String args[]) { QVCSEnterpriseServer server = new QVCSEnterpriseServer(args); server.startServer(); } public static Collection getConnectedUsers() { Vector<ServerResponseFactoryInterface> collection = null; synchronized (connectedUsersCollection) { collection = new Vector<ServerResponseFactoryInterface>(); Iterator<ServerResponseFactoryInterface> it = connectedUsersCollection.iterator(); while (it.hasNext()) { collection.add(it.next()); } } return collection; } private Collection<ServerResponseFactoryInterface> getConnectedUsersCollection() { return connectedUsersCollection; } public static void setShutdownInProgress(boolean flag) { if (flag) { m_logger.log(Level.INFO, "QVCS Enterprise Server is exiting."); if (m_Server.m_VanillaThread != null) { m_Server.m_VanillaServer.closeServerSocket(); } if (m_Server.m_NonSecureThread != null) { m_Server.m_nonSecureServer.closeServerSocket(); } if (m_Server.m_SecureThread != null) { m_Server.m_secureServer.closeServerSocket(); } if (m_Server.m_AdminThread != null) { m_Server.m_adminServer.closeServerSocket(); } } } /** Creates a new instance of QVCSEnterpriseServer * @param args command line arguments. */ public QVCSEnterpriseServer(String[] args) { m_args = args; m_Server = this; if (m_args.length > 0) { System.setProperty("user.dir", m_args[0]); m_QVCSHomeDirectory = m_args[0]; } } public void startServer() { try { if (m_args.length > 1) { m_VanillaPort = Integer.parseInt(m_args[1]); } } catch (NumberFormatException e) { m_VanillaPort = DEFAULT_VANILLA_LISTEN_PORT; } try { if (m_args.length > 2) { m_NonSecurePort = Integer.parseInt(m_args[2]); } } catch (NumberFormatException e) { m_NonSecurePort = DEFAULT_NON_SECURE_LISTEN_PORT; } try { if (m_args.length > 3) { m_SecurePort = Integer.parseInt(m_args[3]); } } catch (NumberFormatException e) { m_SecurePort = DEFAULT_SECURE_LISTEN_PORT; } try { if (m_args.length > 4) { m_AdminPort = Integer.parseInt(m_args[4]); } } catch (NumberFormatException e) { m_AdminPort = DEFAULT_ADMIN_LISTEN_PORT; } // Init the logging properties. initLoggingProperties(); // Report the System info. reportSystemInfo(); m_logger.log(Level.INFO, "QVCS Enterprise Server Version: '" + m_Version + "'."); m_logger.log(Level.INFO, "QVCS Enterprise Server running with " + Runtime.getRuntime().availableProcessors() + " available processors."); // Initialize the role privileges manager RolePrivilegesManager.getInstance().initialize(); // Initialize the role manager. RoleManager.getRoleManager().initialize(); // Initialize the authentication manager. AuthenticationManager.getAuthenticationManager().initialize(); // Initialize the archive digest manager. ArchiveDigestManager.getInstance().initialize(QVCSConstants.QVCS_SERVED_PROJECT_TYPE); // Initialize the file ID manager. FileIDManager.getInstance().initialize(); // See if we need to scan for fileID, etc. if (FileIDManager.getInstance().getFileIDResetRequiredFlag()) { // Remove the label manager store. DirectoryContentsLabelManager.getInstance().resetStore(); DirectoryContentsLabelManager.getInstance().initialize(); // Reset the directory id dictionary store. DirectoryIDDictionary.getInstance().resetStore(); DirectoryIDDictionary.getInstance().initialize(); // Reset the directoryID store. DirectoryIDManager.getInstance().resetStore(); DirectoryIDManager.getInstance().initialize(); // Reset the file id dictionary store. FileIDDictionary.getInstance().resetStore(); FileIDDictionary.getInstance().initialize(); // Remove the view manager store. ViewManager.getInstance().resetStore(); ViewManager.getInstance().initialize(); // We need to reset all file IDs. resetFileIDs(); FileIDManager.getInstance().setFileIDResetRequiredFlag(false); // Reset the directory IDs. Note that directory ID's are // per project, so there are separate maximum values for each separate // project. resetDirectoryIDs(); // Initialize our DirectoryContents objects. initializeDirectoryContentsObjects(); } else { // Initialize the DirectoryIDDictionary. DirectoryIDDictionary.getInstance().initialize(); // Initialize the FileIDDictionary. FileIDDictionary.getInstance().initialize(); // Initialize the directoryID manager. DirectoryIDManager.getInstance().initialize(); // Initialize the LabelHistory manager. DirectoryContentsLabelManager.getInstance().initialize(); // Initialize the ViewManager. ViewManager.getInstance().initialize(); } // Register our shutdown thread. Runtime.getRuntime().addShutdownHook(new QVCSEnterpriseServer.ShutdownThread()); // Initialize the Activity Journal Manager. ActivityJournalManager.getInstance().initialize(); ActivityJournalManager.getInstance().addJournalEntry("QVCS-Enterprise Server is starting. Server Version: " + m_Version + "."); // Launch four separate listener threads; one for vanilla connections, // one for non-secure requests, // one for secure requests, and a fourth for admin messages. m_VanillaServer = new VanillaServer(m_VanillaPort); m_nonSecureServer = new NonSecureServer(m_NonSecurePort); m_secureServer = new SecureServer(m_SecurePort); m_adminServer = new SecureServer(m_AdminPort); m_webServer = new QVCSWebServer(m_args); m_VanillaThread = new Thread(m_VanillaServer, "vanilla server"); m_NonSecureThread = new Thread(m_nonSecureServer, "non secure server"); m_SecureThread = new Thread(m_secureServer, "secure server"); m_AdminThread = new Thread(m_adminServer, "admin server"); m_WebServerThread = new Thread(m_webServer, "web server"); m_VanillaThread.start(); m_NonSecureThread.start(); m_SecureThread.start(); m_AdminThread.start(); m_WebServerThread.start(); try { // Wait for all the worker threads to exit. When a worker thread // exits, it calls notify on the m_Threads object. synchronized (m_Threads) { while (m_Threads.size() > 0) { m_Threads.wait(); } } synchronized (m_VanillaThreads) { while (m_VanillaThreads.size() > 0) { m_VanillaThreads.wait(); } } m_VanillaThread.join(); m_NonSecureThread.join(); m_SecureThread.join(); m_AdminThread.join(); // Kill the web server. m_WebServerThread.interrupt(); } catch (Exception e) { m_logger.log(Level.WARNING, Utility.expandStackTraceToString(e)); } finally { ActivityJournalManager.getInstance().addJournalEntry("QVCS-Enterprise Server is shutting down."); ArchiveDigestManager.getInstance().writeStore(); DirectoryIDManager.getInstance().writeStore(); FileIDManager.getInstance().writeStore(); DirectoryContentsLabelManager.getInstance().writeStore(); DirectoryIDDictionary.getInstance().writeStore(); FileIDDictionary.getInstance().writeStore(); ActivityJournalManager.getInstance().closeJournal(); m_logger.log(Level.INFO, "QVCS Enterprise Server exit complete."); System.out.println("QVCS Enterprise Server exit complete."); System.exit(0); } } public static void stopServer(String args[]) { m_logger.log(Level.INFO, "QVCS Enterprise Server Windows Service beginning shutdown."); ServerResponseFactoryBaseClass.setShutdownInProgress(true); } private void initLoggingProperties() { try { String logConfigFile = m_QVCSHomeDirectory + File.separator + "serverLogging.properties"; System.setProperty("java.util.logging.config.file", logConfigFile); LogManager.getLogManager().readConfiguration(); } catch (Exception e) { m_logger.log(Level.SEVERE, "Caught exception: " + e.getClass().toString() + " : " + e.getLocalizedMessage()); System.out.println("Caught exception: " + e.getClass().toString() + " : " + e.getLocalizedMessage()); } } private void reportSystemInfo() { java.util.Properties systemProperties = System.getProperties(); java.util.Set keys = systemProperties.keySet(); java.util.Iterator it = keys.iterator(); m_logger.log(Level.FINE, "System properties:"); while(it.hasNext()) { String key = (String)it.next(); String message = key + " = " + System.getProperty(key); m_logger.log(Level.FINE, message); } // Log what charset is the platform default m_logger.log(Level.FINE, "Default charset: " + Charset.defaultCharset().displayName()); } /** This method is called <i>before</i> the server opens any ports to listen * for client connections... The goal is to reset the file ids for all files in all * existing projects. We do <i>not</i> use the ArchiveDirManager class here * as that is heavier-weight than what we want/need to do here. */ private void resetFileIDs() { m_logger.log(Level.INFO, "QVCSEnterpriseServer: reseting all file id's for all projects."); try { // Get a list of all the projects that this server serves... ServedProjectProperties[] projectPropertiesList = getServedProjectPropertiesList(); // Iterate over the list of projects... for (int i = 0; i < projectPropertiesList.length; i++) { String archiveLocation = projectPropertiesList[i].getArchiveLocation(); File projectBaseDirectory = new File(archiveLocation); resetFileIDsForProjectDirectoryTree(projectBaseDirectory); } } catch (Exception e) { m_logger.log(Level.WARNING, Utility.expandStackTraceToString(e)); } } /** For use before the server accepts clients connections only!! */ private ServedProjectProperties[] getServedProjectPropertiesList() { ServedProjectProperties[] servedProjectsProperties = new ServedProjectProperties[0]; // Where all the property files can be found... File propertiesDirectory = new File(System.getProperty("user.dir") + System.getProperty("file.separator") + QVCSConstants.QVCS_PROPERTIES_DIRECTORY); QVCSServedProjectNamesFilter servedProjectNamesFilter = new QVCSServedProjectNamesFilter(); File[] servedProjectFiles = propertiesDirectory.listFiles(servedProjectNamesFilter); if (servedProjectFiles != null) { servedProjectsProperties = new ServedProjectProperties[servedProjectFiles.length]; for (int i = 0; i < servedProjectFiles.length; i++) { String projectName = servedProjectNamesFilter.getProjectName(servedProjectFiles[i].getName()); try { ServedProjectProperties projectProperties = new ServedProjectProperties(projectName); servedProjectsProperties[i] = new ServedProjectProperties(projectName); } catch (Exception e) { m_logger.log(Level.WARNING, "Error finding served project names for project: '" + projectName + "'."); } } } return servedProjectsProperties; } /** * For use by the resetFileIDs() method only!! Reset fileIDs for the given directory tree. */ private void resetFileIDsForProjectDirectoryTree(File directory) { m_logger.log(Level.INFO, "Reseting file id's for directory: " + directory.getAbsolutePath()); File[] fileList = directory.listFiles(); if (fileList != null) { for (int i = 0; i < fileList.length; i++) { if (fileList[i].getName().compareToIgnoreCase(QVCSConstants.QVCS_CACHE_NAME) == 0) { // Get rid of the cache file, since we may be changing things here that // would make the cache out of date. if (fileList[i].delete()) { m_logger.log(Level.INFO, "Deleting " + QVCSConstants.QVCS_CACHE_NAME + " file from directory: " + directory.getAbsolutePath()); } continue; } if (fileList[i].getName().compareToIgnoreCase(QVCSConstants.QVCS_JOURNAL_NAME) == 0) { continue; } if (fileList[i].isDirectory()) { // Recurse through the directory tree... resetFileIDsForProjectDirectoryTree(fileList[i]); continue; } if (fileList[i].getName().compareToIgnoreCase(QVCSConstants.QVCS_DIRECTORYID_FILENAME) == 0) { continue; } if (fileList[i].getName().endsWith(QVCSConstants.QVCS_ARCHIVE_TEMPFILE_SUFFIX)) { continue; } if (fileList[i].getName().endsWith(QVCSConstants.QVCS_ARCHIVE_OLDFILE_SUFFIX)) { continue; } LogFile logfile = new LogFile(fileList[i].getPath()); if (logfile.readInformation()) { // Update the file's file ID. This will cause a re-write of the archive file with an updated // header (supplemental info) that contains the assigned file id. logfile.setFileID(FileIDManager.getInstance().getNewFileID()); } else { m_logger.log(Level.WARNING, "Failed to read logfile information for: " + fileList[i].getPath()); } } } } /** * This method is called <i>before</i> the server opens any ports to listen * for client connections... The goal is to reset the directory ids for all existing projects. */ private void resetDirectoryIDs() { m_logger.log(Level.INFO, "QVCSEnterpriseServer: resetting all directory ids."); try { // Get a list of all the projects that this server serves... ServedProjectProperties[] projectPropertiesList = getServedProjectPropertiesList(); // Iterate over the list of projects... for (int i = 0; i < projectPropertiesList.length; i++) { String archiveLocation = projectPropertiesList[i].getArchiveLocation(); File projectBaseDirectory = new File(archiveLocation); resetDirectoryIDsForDirectoryTree(projectBaseDirectory); DirectoryIDManager.getInstance().setMaximumDirectoryID(projectPropertiesList[i].getProjectName(), 0); } } catch (Exception e) { m_logger.log(Level.WARNING, Utility.expandStackTraceToString(e)); } } /** * For use by the resetDirectoryIDs() method only!! Reset the given directory ids * for the given directory tree. */ private void resetDirectoryIDsForDirectoryTree(File directory) { m_logger.log(Level.INFO, "Scanning directory: " + directory.getAbsolutePath()); File[] fileList = directory.listFiles(); if (fileList != null) { for (int i = 0; i < fileList.length; i++) { if (fileList[i].isDirectory()) { // Recurse through the directory tree... resetDirectoryIDsForDirectoryTree(fileList[i]); continue; } if (fileList[i].getName().compareToIgnoreCase(QVCSConstants.QVCS_DIRECTORYID_FILENAME) == 0) { try { // Delete the directory ID file... It will get re-created when we create the directory // contents object for this directory... fileList[i].delete(); } catch (Exception e) { m_logger.log(Level.WARNING, Utility.expandStackTraceToString(e)); } continue; } } } } /** * Used once only when upgrading, or running the server for the very first * time. This method should only be run before the server accepts requests * from client. */ private void initializeDirectoryContentsObjects() { m_logger.log(Level.INFO, "QVCSEnterpriseServer: Initializing DirectoryContents objects..."); // Delete any/all existing directory contents objects... we're starting // from scratch here. deleteExistingDirectoryContentsObjects(); try { // Get a list of all the projects that this server serves... ServedProjectProperties[] projectPropertiesList = getServedProjectPropertiesList(); // Iterate over the list of projects... for (int i = 0; i < projectPropertiesList.length; i++) { // Wrap this work in a server transaction so the DirectoryContents // stuff will behave in a useful way... ServerResponseFactoryInterface bogusResponseObject = new BogusResponseObject(); // Keep track that we're in a transaction. ServerTransactionManager.getInstance().clientBeginTransaction(bogusResponseObject); String archiveLocation = projectPropertiesList[i].getArchiveLocation(); File projectBaseDirectory = new File(archiveLocation); // And initialize the directory contents objects for this project tree. initializeDirectoryContentsObjectForDirectory(projectBaseDirectory, projectPropertiesList[i], bogusResponseObject); // Keep track that we ended this transaction. ServerTransactionManager.getInstance().clientEndTransaction(bogusResponseObject); } // Throw away any archive dir managers we built since we now need // to built them again so they will discard (i.e. move) any obsolete // files. ArchiveDirManagerFactory.getInstance().resetDirectoryMap(); } catch (Exception e) { m_logger.log(Level.WARNING, Utility.expandStackTraceToString(e)); } } /** * Used only during an upgrade or when product is run for the 1st time. */ private void deleteExistingDirectoryContentsObjects() { try { // Get a list of all the projects that this server serves... ServedProjectProperties[] projectPropertiesList = getServedProjectPropertiesList(); // Iterate over the list of projects... for (int i = 0; i < projectPropertiesList.length; i++) { String projectName = projectPropertiesList[i].getProjectName(); String fullArchiveDirectory = System.getProperties().getProperty("user.dir") + File.separator + QVCSConstants.QVCS_PROJECTS_DIRECTORY + File.separator + projectName + File.separator + QVCSConstants.QVCS_DIRECTORY_METADATA_DIRECTORY; File directoryFile = new File(fullArchiveDirectory); if (!directoryFile.exists()) { if (!directoryFile.mkdirs()) { continue; } } // Delete all the files in the QVCS_DIRECTORY_METADATA_DIRECTORY File[] fileList = directoryFile.listFiles(); if (fileList != null) { for (int j = 0; j < fileList.length; j++) { if (fileList[j].isDirectory()) { continue; } fileList[j].delete(); } } } } catch (Exception e) { m_logger.log(Level.WARNING, Utility.expandStackTraceToString(e)); } } /** * Used only during an upgrade or when product is run for the 1st time. */ private void initializeDirectoryContentsObjectForDirectory(File directory, ServedProjectProperties servedProjectProperties, ServerResponseFactoryInterface bogusResponseObject) { m_logger.log(Level.INFO, "Creating directory contents for: " + directory.getAbsolutePath()); String projectName = servedProjectProperties.getProjectName(); String viewName = QVCSConstants.QVCS_TRUNK_VIEW; String appendedPath = deduceAppendedPath(directory, servedProjectProperties); File[] fileList = directory.listFiles(); if (fileList != null) { try { // Create the archiveDirManager for this directory... ArchiveDirManager archiveDirManager = (ArchiveDirManager)ArchiveDirManagerFactory.getInstance().getDirectoryManager(QVCSConstants.QVCS_SERVER_SERVER_NAME, projectName, viewName, appendedPath, QVCSConstants.QVCS_SERVED_PROJECT_TYPE, QVCSConstants.QVCS_SERVER_USER, bogusResponseObject, false); DirectoryContentsManager directoryContentsManager = DirectoryContentsManagerFactory.getInstance().getDirectoryContentsManager(projectName); directoryContentsManager.createDirectoryContentsFromArchiveDirManager(archiveDirManager, bogusResponseObject); } catch (QVCSException e) { m_logger.log(Level.WARNING, Utility.expandStackTraceToString(e)); } catch (IOException e) { m_logger.log(Level.WARNING, Utility.expandStackTraceToString(e)); } // Build all child directories only after creating the DirectoryContents // for this directory. for (int i = 0; i < fileList.length; i++) { if (fileList[i].isDirectory()) { // Recurse through the directory tree... initializeDirectoryContentsObjectForDirectory(fileList[i], servedProjectProperties, bogusResponseObject); } } } } private String deduceAppendedPath(File directory, ServedProjectProperties servedProjectProperties) { String appendedPath = null; String projectBaseDirectory = servedProjectProperties.getArchiveLocation(); String directoryPath = null; String standardDirectoryPath = null; try { directoryPath = directory.getCanonicalPath(); standardDirectoryPath = Utility.getInstance().convertToStandardPath(directoryPath); if (projectBaseDirectory.length() == standardDirectoryPath.length()) { appendedPath = ""; } else { appendedPath = standardDirectoryPath.substring(1 + projectBaseDirectory.length()); } } catch (IOException e) { m_logger.log(Level.WARNING, Utility.expandStackTraceToString(e)); } return appendedPath; } /** This is the class that runs at server exit time. */ static class ShutdownThread extends Thread { @Override public void run() { try { ActivityJournalManager.getInstance().addJournalEntry("QVCS-Enterprise Server: shutdown thread called to shutdown."); ArchiveDigestManager.getInstance().writeStore(); DirectoryIDManager.getInstance().writeStore(); FileIDManager.getInstance().writeStore(); DirectoryContentsLabelManager.getInstance().writeStore(); DirectoryIDDictionary.getInstance().writeStore(); FileIDDictionary.getInstance().writeStore(); ActivityJournalManager.getInstance().closeJournal(); m_logger.log(Level.INFO, "QVCS Enterprise Server exit complete."); System.out.println("QVCS Enterprise Server exit complete."); } catch (Exception e) { m_logger.log(Level.WARNING, Utility.expandStackTraceToString(e)); } } } class NonSecureServer implements Runnable { int localPort = 0; ServerSocket m_ServerSocket = null; NonSecureServer(int port) { localPort = port; } void closeServerSocket() { if (m_ServerSocket != null) { try { m_ServerSocket.close(); } catch (Exception e) { } } } public void run() { try { m_ServerSocket = new ServerSocket(localPort); m_logger.log(Level.INFO, "Non secure server is listening on port: " + localPort); while (true &&!ServerResponseFactoryBaseClass.getShutdownInProgress()) { Socket socket = m_ServerSocket.accept(); socket.setTcpNoDelay(true); socket.setKeepAlive(true); m_logger.log(Level.INFO, "QVCSEnterpriseServer: got non-secure connect"); m_logger.log(Level.INFO, "local socket port: " + socket.getLocalPort()); m_logger.log(Level.INFO, "remote socket port: " + socket.getPort()); Worker w = null; synchronized (m_Threads) { if (m_Threads.isEmpty()) { m_logger.log(Level.INFO, "creating new worker thread for non-secure connection"); Worker ws = new Worker(); ws.setSocket(socket); (new Thread(ws, "worker thread " + m_ThreadCount++)).start(); } else { m_logger.log(Level.INFO, "re-using worker thread for non-secure connection"); w = (Worker) m_Threads.elementAt(0); m_Threads.removeElementAt(0); w.setSocket(socket); } } } } catch (java.net.SocketException e) { m_logger.log(Level.INFO, "Server non-secure accept thread is exiting."); } catch (java.io.IOException e) { m_logger.log(Level.WARNING, Utility.expandStackTraceToString(e)); } catch (Exception e) { m_logger.log(Level.WARNING, Utility.expandStackTraceToString(e)); } finally { if (m_ServerSocket != null) { try { m_ServerSocket.close(); } catch (java.io.IOException e) { } } m_logger.log(Level.INFO, "QVCSEnterpriseServer: closing listener thread for port: " + localPort); } } } // This server is for connections from clients where the data is in raw // data format, instead of being handled as Java serialized objects. // The original motivation for this class was to add support for connections // from Microsoft SCC compliant clients. class VanillaServer implements Runnable { int localPort = 0; ServerSocket m_ServerSocket = null; VanillaServer(int port) { localPort = port; } void closeServerSocket() { if (m_ServerSocket != null) { try { m_ServerSocket.close(); } catch (Exception e) { } } } public void run() { try { m_ServerSocket = new ServerSocket(localPort); m_logger.log(Level.INFO, "Server is listening on port: " + localPort); while (true &&!ServerResponseFactoryBaseClass.getShutdownInProgress()) { Socket socket = m_ServerSocket.accept(); socket.setTcpNoDelay(true); socket.setKeepAlive(true); m_logger.log(Level.INFO, "QVCSEnterpriseServer: got connect"); m_logger.log(Level.INFO, "local socket port: " + socket.getLocalPort()); m_logger.log(Level.INFO, "remote socket port: " + socket.getPort()); m_logger.log(Level.INFO, "socket timeout: " + socket.getSoTimeout()); m_logger.log(Level.INFO, "receive buffer size: " + socket.getReceiveBufferSize()); m_logger.log(Level.INFO, "send buffer size: " + socket.getSendBufferSize()); VanillaWorker w = null; synchronized (m_VanillaThreads) { if (m_VanillaThreads.isEmpty()) { m_logger.log(Level.INFO, "creating new worker thread for connection"); VanillaWorker ws = new VanillaWorker(); ws.setSocket(socket); (new Thread(ws, "vanilla worker thread " + m_ThreadCount++)).start(); } else { m_logger.log(Level.INFO, "re-using worker thread for connection"); w = (VanillaWorker) m_VanillaThreads.elementAt(0); m_VanillaThreads.removeElementAt(0); w.setSocket(socket); } } } } catch (java.net.SocketException e) { m_logger.log(Level.INFO, "Server accept thread is exiting."); } catch (java.io.IOException e) { m_logger.log(Level.WARNING, Utility.expandStackTraceToString(e)); } catch (Exception e) { m_logger.log(Level.WARNING, Utility.expandStackTraceToString(e)); } finally { if (m_ServerSocket != null) { try { m_ServerSocket.close(); } catch (java.io.IOException e) { } } m_logger.log(Level.INFO, "QVCSEnterpriseServer: closing listener thread for port: " + localPort); } } } class SecureServer implements Runnable { int localPort = 0; SSLServerSocket m_ServerSocket = null; SecureServer(int port) { localPort = port; } void closeServerSocket() { if (m_ServerSocket != null) { try { m_ServerSocket.close(); } catch (Exception e) { } } } public void run() { try { ServerSocketFactory socketFactory = SSLServerSocketFactory.getDefault(); m_ServerSocket = (SSLServerSocket)socketFactory.createServerSocket(localPort); // Select an appropriate cipher suite. String [] useSuites = new String[1]; useSuites[0] = "SSL_DH_anon_WITH_3DES_EDE_CBC_SHA"; m_ServerSocket.setEnabledCipherSuites(useSuites); m_logger.log(Level.INFO, "Secure server is listening on port: " + localPort); while (true &&!ServerResponseFactoryBaseClass.getShutdownInProgress()) { Socket socket = m_ServerSocket.accept(); socket.setTcpNoDelay(true); socket.setKeepAlive(true); m_logger.log(Level.INFO, "QVCSEnterpriseServer: got secure connection"); m_logger.log(Level.INFO, "local socket port: " + socket.getLocalPort()); m_logger.log(Level.INFO, "remote socket port: " + socket.getPort()); Worker w = null; synchronized (m_Threads) { if (m_Threads.isEmpty()) { m_logger.log(Level.INFO, "creating new worker thread for secure connection"); Worker ws = new Worker(); ws.setSocket(socket); (new Thread(ws, "worker thread " + m_ThreadCount++)).start(); } else { m_logger.log(Level.INFO, "re-using worker thread for secure connection"); w = (Worker) m_Threads.elementAt(0); m_Threads.removeElementAt(0); w.setSocket(socket); } } } } catch (java.net.SocketException e) { m_logger.log(Level.INFO, "Server secure accept thread is exiting."); } catch (java.io.IOException e) { m_logger.log(Level.WARNING, Utility.expandStackTraceToString(e)); } catch (Exception e) { m_logger.log(Level.WARNING, Utility.expandStackTraceToString(e)); } finally { if (m_ServerSocket != null) { try { m_ServerSocket.close(); } catch (java.io.IOException e) { } } m_logger.log(Level.INFO, "QVCSEnterpriseServer: closing listener thread for secure port: " + localPort); } } } class VanillaWorker implements Runnable { /* Socket to client we're handling */ private Socket m_socket; synchronized void setSocket(Socket s) { this.m_socket = s; notifyAll(); } public synchronized void run() { while(true && !ServerResponseFactoryBaseClass.getShutdownInProgress()) { if (m_socket == null) { /* nothing to do */ try { wait(); } catch (InterruptedException e) { /* should not happen */ continue; } } handleVanillaClientRequests(); /* go back in wait queue if there's fewer * than numHandler connections. */ m_socket = null; synchronized (m_VanillaThreads) { // If shutdown is not in progress... if (!ServerResponseFactoryBaseClass.getShutdownInProgress()) { if (m_VanillaThreads.size() >= m_workerThreads) { // too many threads, exit this one return; } else { m_VanillaThreads.addElement(this); } } else { // Let the server thread know that this thread is done. m_VanillaThreads.notifyAll(); } } } } private void handleVanillaClientRequests() { String connectedTo = null; VanillaClientRequestFactory requestFactory = null; VanillaServerResponseFactory responseFactory = null; try { requestFactory = new VanillaClientRequestFactory(m_socket.getInputStream()); responseFactory = new VanillaServerResponseFactory(m_socket.getOutputStream(), m_socket.getPort(), m_socket.getInetAddress().getHostAddress()); connectedTo = m_socket.getInetAddress().getHostAddress(); m_logger.log(Level.INFO, "Connected to: " + connectedTo); while (!ServerResponseFactoryBaseClass.getShutdownInProgress() && responseFactory.getConnectionAliveFlag()) { try { ClientRequestInterface clientRequest = requestFactory.createClientRequest(responseFactory); if (clientRequest != null) { clientRequest.moveArguments(); SerializableObjectInterface returnObject = clientRequest.execute(requestFactory.getUserName(), responseFactory); if (clientRequest instanceof ClientRequestLogin) { ServerResponseLogin serverResponseLogin = (ServerResponseLogin) returnObject; if (serverResponseLogin.getLoginResult()) { requestFactory.setIsUserLoggedIn(true); requestFactory.setUserName(serverResponseLogin.getUserName()); responseFactory.setIsUserLoggedIn(true); responseFactory.setUserName(serverResponseLogin.getUserName()); ClientRequestLogin loginRequest = (ClientRequestLogin) clientRequest; responseFactory.setServerName(loginRequest.getServerName()); getConnectedUsersCollection().add(responseFactory); } } // Send the response back to the client. responseFactory.createServerResponse(returnObject); // If this was a login request that succeeded, we also // need to send the list of projects for this user. if (clientRequest instanceof ClientRequestLogin) { ClientRequestLogin clientRequestLogin = (ClientRequestLogin)clientRequest; ServerResponseMessage message = null; if (responseFactory.getIsUserLoggedIn() == false) { // The user failed to login. Report the problem to the user. if (clientRequestLogin.getAuthenticationFailedFlag()) { message = new ServerResponseMessage("Invalid username/password", null, null, null, ServerResponseMessage.HIGH_PRIORITY); responseFactory.createServerResponse(message); } } // Report any status information back to the user. if (clientRequestLogin.getMessage() != null) { message = new ServerResponseMessage(clientRequestLogin.getMessage(), null, null, null, ServerResponseMessage.HIGH_PRIORITY); responseFactory.createServerResponse(message); } } } else { m_logger.log(Level.INFO, "clientRequest is null!!"); m_logger.log(Level.INFO, "Breaking connection to: " + connectedTo); break; } } catch (QVCSShutdownException e) { // We are shutting down this server. m_logger.log(Level.INFO, "Shutting down server at request from: " + connectedTo); break; } catch (RuntimeException e) { m_logger.log(Level.INFO, "Breaking connection to: " + connectedTo); m_logger.log(Level.WARNING, Utility.expandStackTraceToString(e)); break; } catch (Exception e) { m_logger.log(Level.INFO, "Breaking connection to: " + connectedTo); m_logger.log(Level.WARNING, Utility.expandStackTraceToString(e)); break; } } } catch (Exception e) { m_logger.log(Level.INFO, "Breaking connection to: " + connectedTo); m_logger.log(Level.WARNING, Utility.expandStackTraceToString(e)); } finally { try { m_socket.close(); // The connection to the client is gone. Remove the response // factory as a listener for any archive directory managers // so we don't waste time trying to inform a client that we // can no longer talk to. if (responseFactory != null) { Set<ArchiveDirManagerInterface> directoryManagers = responseFactory.getDirectoryManagers(); Iterator<ArchiveDirManagerInterface> it = directoryManagers.iterator(); while (it.hasNext()) { ArchiveDirManagerInterface directoryManagerInterface = it.next(); directoryManagerInterface.removeLogFileListener(responseFactory); } getConnectedUsersCollection().remove(responseFactory); // Decrement the number of logged on users with the // license manager. if (responseFactory.getIsUserLoggedIn()) { ServerTransactionManager.getInstance().flushClientTransaction(responseFactory); LicenseManager.getInstance().logoutUser(responseFactory.getUserName(), responseFactory.getClientIPAddress()); } } } catch (Exception e) { m_logger.log(Level.WARNING, Utility.expandStackTraceToString(e)); } } } } class Worker implements Runnable { /* Socket to client we're handling */ private Socket m_socket; synchronized void setSocket(Socket s) { this.m_socket = s; notifyAll(); } public synchronized void run() { while(true && !ServerResponseFactoryBaseClass.getShutdownInProgress()) { if (m_socket == null) { /* nothing to do */ try { wait(); } catch (InterruptedException e) { /* should not happen */ continue; } } handleClientRequests(); /* go back in wait queue if there's fewer * than numHandler connections. */ m_socket = null; synchronized (m_Threads) { // If shutdown is not in progress... if (!ServerResponseFactoryBaseClass.getShutdownInProgress()) { if (m_Threads.size() >= m_workerThreads) { // too many threads, exit this one return; } else { m_Threads.addElement(this); } } else { // Let the server thread know that this thread is done. m_Threads.notifyAll(); } } } } private void handleClientRequests() { String connectedTo = null; ServerResponseFactory responseFactory = null; ClientRequestFactory requestFactory = null; try { requestFactory = new ClientRequestFactory(m_socket.getInputStream()); responseFactory = new ServerResponseFactory(m_socket.getOutputStream(), m_socket.getPort(), m_socket.getInetAddress().getHostAddress()); connectedTo = m_socket.getInetAddress().getHostAddress(); m_logger.log(Level.INFO, "Connected to: " + connectedTo); while (!ServerResponseFactoryBaseClass.getShutdownInProgress() && responseFactory.getConnectionAliveFlag()) { try { ClientRequestInterface clientRequest = requestFactory.createClientRequest(responseFactory); if (clientRequest != null) { clientRequest.moveArguments(); SerializableObjectInterface returnObject = clientRequest.execute(requestFactory.getUserName(), responseFactory); if (clientRequest instanceof ClientRequestLogin) { ServerResponseLogin serverResponseLogin = (ServerResponseLogin) returnObject; if (serverResponseLogin.getLoginResult()) { requestFactory.setIsUserLoggedIn(true); requestFactory.setUserName(serverResponseLogin.getUserName()); responseFactory.setIsUserLoggedIn(true); responseFactory.setUserName(serverResponseLogin.getUserName()); ClientRequestLogin loginRequest = (ClientRequestLogin) clientRequest; responseFactory.setServerName(loginRequest.getServerName()); getConnectedUsersCollection().add(responseFactory); } } // Send the response back to the client. responseFactory.createServerResponse(returnObject); // If this was a login request that succeeded, we also // need to send the list of projects for this user. if (clientRequest instanceof ClientRequestLogin) { ClientRequestLogin clientRequestLogin = (ClientRequestLogin)clientRequest; ServerResponseMessage message = null; if (responseFactory.getIsUserLoggedIn() == false) { // The user failed to login. Report the problem to the user. if (clientRequestLogin.getAuthenticationFailedFlag()) { message = new ServerResponseMessage("Invalid username/password", null, null, null, ServerResponseMessage.HIGH_PRIORITY); responseFactory.createServerResponse(message); } } else { // Report any status information back to the user. if (clientRequestLogin.getMessage() != null) { message = new ServerResponseMessage(clientRequestLogin.getMessage(), null, null, null, ServerResponseMessage.HIGH_PRIORITY); responseFactory.createServerResponse(message); } } } } else { m_logger.log(Level.INFO, "clientRequest is null!!"); m_logger.log(Level.INFO, "Breaking connection to: " + connectedTo); break; } } catch (QVCSShutdownException e) { // We are shutting down this server. m_logger.log(Level.INFO, "Shutting down server at request from: " + connectedTo); break; } catch (RuntimeException e) { m_logger.log(Level.INFO, "Breaking connection to: " + connectedTo); m_logger.log(Level.WARNING, Utility.expandStackTraceToString(e)); break; } catch (Exception e) { m_logger.log(Level.INFO, "Breaking connection to: " + connectedTo); m_logger.log(Level.WARNING, Utility.expandStackTraceToString(e)); break; } } } catch (Exception e) { m_logger.log(Level.INFO, "Breaking connection to: " + connectedTo); m_logger.log(Level.WARNING, Utility.expandStackTraceToString(e)); } finally { try { m_socket.close(); // The connection to the client is gone. Remove the response // factory as a listener for any archive directory managers // so we don't waste time trying to inform a client that we // can no longer talk to. if (responseFactory != null) { Set<ArchiveDirManagerInterface> directoryManagers = responseFactory.getDirectoryManagers(); Iterator<ArchiveDirManagerInterface> it = directoryManagers.iterator(); while (it.hasNext()) { ArchiveDirManagerInterface directoryManagerInterface = it.next(); directoryManagerInterface.removeLogFileListener(responseFactory); } getConnectedUsersCollection().remove(responseFactory); // Decrement the number of logged on users with the // license manager. if (responseFactory.getIsUserLoggedIn()) { ServerTransactionManager.getInstance().flushClientTransaction(responseFactory); LicenseManager.getInstance().logoutUser(responseFactory.getUserName(), responseFactory.getClientIPAddress()); } } } catch (Exception e) { m_logger.log(Level.WARNING, Utility.expandStackTraceToString(e)); } } } } static class QVCSWebServer implements Runnable { String[] m_Args; QVCSWebServer(String[] args) { if (args != null && args.length > 0) { m_Args = new String[2]; m_Args[0] = args[0]; if (args.length > 5) { m_Args[1] = args[5]; } else { m_Args[1] = WEB_SERVER_PORT; } } } /** When an object implementing interface <code>Runnable</code> is used * to create a thread, starting the thread causes the object's * <code>run</code> method to be called in that separately executing * thread. * <p> * The general contract of the method <code>run</code> is that it may * take any action whatsoever. * * @see java.lang.Thread#run() * */ public void run() { try { WebServer.main(m_Args); } catch (InterruptedException e) { m_logger.log(Level.INFO, "Web server exiting."); } catch (Exception e) { m_logger.log(Level.INFO, "Web server exiting due to exception: " + e.toString()); } } } }