diff --git a/Core/nbproject/project.properties b/Core/nbproject/project.properties index c3a40f5a77..e3895cc49d 100644 --- a/Core/nbproject/project.properties +++ b/Core/nbproject/project.properties @@ -15,6 +15,10 @@ file.reference.StixLib.jar=release/modules/ext/StixLib.jar file.reference.tika-core-1.5.jar=release/modules/ext/tika-core-1.5.jar file.reference.Tsk_DataModel_PostgreSQL.jar=release/modules/ext/Tsk_DataModel_PostgreSQL.jar file.reference.xmpcore-5.1.2.jar=release/modules/ext/xmpcore-5.1.2.jar +file.reference.curator-client-2.8.0.jar=release/modules/ext/curator-client-2.8.0.jar +file.reference.curator-framework-2.8.0.jar=release/modules/ext/curator-framework-2.8.0.jar +file.reference.curator-recipes-2.8.0.jar=release/modules/ext/curator-recipes-2.8.0.jar +file.reference.zookeeper-3.4.6.jar=release/modules/ext/zookeeper-3.4.6.jar javac.source=1.8 javac.compilerargs=-Xlint -Xlint:-serial javadoc.reference.metadata-extractor-2.8.1.jar=release/modules/ext/metadata-extractor-2.8.1-src.zip @@ -23,5 +27,6 @@ nbm.homepage=http://www.sleuthkit.org/ nbm.module.author=Brian Carrier nbm.needs.restart=true source.reference.metadata-extractor-2.8.1.jar=release/modules/ext/metadata-extractor-2.8.1-src.zip!/Source/ +source.reference.curator-recipes-2.8.0.jar=release/modules/ext/curator-recipes-2.8.0-sources.jar spec.version.base=10.7 diff --git a/Core/nbproject/project.xml b/Core/nbproject/project.xml index 99ae49a9df..28a8815bca 100644 --- a/Core/nbproject/project.xml +++ b/Core/nbproject/project.xml @@ -231,6 +231,7 @@ org.sleuthkit.autopsy.casemodule.events org.sleuthkit.autopsy.casemodule.services org.sleuthkit.autopsy.contentviewers + org.sleuthkit.autopsy.coordinationservice org.sleuthkit.autopsy.core org.sleuthkit.autopsy.core.events org.sleuthkit.autopsy.corecomponentinterfaces @@ -326,6 +327,22 @@ ext/Tsk_DataModel_PostgreSQL.jar release/modules/ext/Tsk_DataModel_PostgreSQL.jar + + ext/zookeeper-3.4.6.jar + release/modules/ext/zookeeper-3.4.6.jar + + + ext/curator-client-2.8.0.jar + release/modules/ext/curator-client-2.8.0.jar + + + ext/curator-recipes-2.8.0.jar + release/modules/ext/curator-recipes-2.8.0.jar + + + ext/curator-framework-2.8.0.jar + release/modules/ext/curator-framework-2.8.0.jar + diff --git a/Experimental/release/modules/ext/curator-client-2.8.0.jar b/Core/release/modules/ext/curator-client-2.8.0.jar similarity index 100% rename from Experimental/release/modules/ext/curator-client-2.8.0.jar rename to Core/release/modules/ext/curator-client-2.8.0.jar diff --git a/Experimental/release/modules/ext/curator-framework-2.8.0.jar b/Core/release/modules/ext/curator-framework-2.8.0.jar similarity index 100% rename from Experimental/release/modules/ext/curator-framework-2.8.0.jar rename to Core/release/modules/ext/curator-framework-2.8.0.jar diff --git a/Experimental/release/modules/ext/curator-recipes-2.8.0-sources.jar b/Core/release/modules/ext/curator-recipes-2.8.0-sources.jar similarity index 100% rename from Experimental/release/modules/ext/curator-recipes-2.8.0-sources.jar rename to Core/release/modules/ext/curator-recipes-2.8.0-sources.jar diff --git a/Experimental/release/modules/ext/curator-recipes-2.8.0.jar b/Core/release/modules/ext/curator-recipes-2.8.0.jar similarity index 100% rename from Experimental/release/modules/ext/curator-recipes-2.8.0.jar rename to Core/release/modules/ext/curator-recipes-2.8.0.jar diff --git a/Experimental/release/modules/ext/zookeeper-3.4.6.jar b/Core/release/modules/ext/zookeeper-3.4.6.jar similarity index 100% rename from Experimental/release/modules/ext/zookeeper-3.4.6.jar rename to Core/release/modules/ext/zookeeper-3.4.6.jar diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties b/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties index cbf9804b48..12184925e9 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties @@ -133,6 +133,7 @@ Case.GetCaseTypeGivenPath.Failure=Unable to get case type Case.metaDataFileCorrupt.exception.msg=The case metadata file (.aut) is corrupted. Case.deleteReports.deleteFromDiskException.log.msg=Unable to delete the report from the disk. Case.deleteReports.deleteFromDiskException.msg=Unable to delete the report {0} from the disk.\nYou may manually delete it from {1} +Case.exception.errorLocking=Unable to open case being updated by another user. CaseDeleteAction.closeConfMsg.text=Are you sure want to close and delete this case? \n\ Case Name\: {0}\n\ Case Directory\: {1} diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java index d879bf33b2..6410a269ce 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java @@ -39,6 +39,12 @@ import java.util.UUID; import java.util.logging.Level; import java.util.stream.Collectors; import java.util.stream.Stream; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Executors; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; import javax.swing.JOptionPane; import javax.swing.SwingUtilities; import org.openide.util.Exceptions; @@ -57,6 +63,8 @@ import org.sleuthkit.autopsy.casemodule.events.ContentTagDeletedEvent; import org.sleuthkit.autopsy.casemodule.events.DataSourceAddedEvent; import org.sleuthkit.autopsy.casemodule.events.ReportAddedEvent; import org.sleuthkit.autopsy.casemodule.services.Services; +import org.sleuthkit.autopsy.coordinationservice.CoordinationService; +import org.sleuthkit.autopsy.coordinationservice.CoordinationServiceNamespace; import org.sleuthkit.autopsy.core.RuntimeProperties; import org.sleuthkit.autopsy.core.UserPreferences; import org.sleuthkit.autopsy.core.UserPreferencesException; @@ -283,6 +291,8 @@ public class Case implements SleuthkitCase.ErrorObserver { private static final AutopsyEventPublisher eventPublisher = new AutopsyEventPublisher(); private static String appName; private static Case currentCase; + private static CoordinationService.Lock currentCaseLock; + private static ExecutorService currentCaseExecutor; private final CaseMetadata caseMetadata; private final SleuthkitCase db; private final Services services; @@ -835,10 +845,30 @@ public class Case implements SleuthkitCase.ErrorObserver { * exception. */ public void closeCase() throws CaseActionException { + + // The unlock must happen on the same thread that created the lock + Future future = getCurrentCaseExecutor().submit(() -> { + try{ + if (currentCaseLock != null) { + currentCaseLock.release(); + currentCaseLock = null; + } + } catch (CoordinationService.CoordinationServiceException exx) { + logger.log(Level.SEVERE, String.format("Error releasing shared lock"), exx); + } + return null; + }); + try{ + future.get(); + } catch (InterruptedException | ExecutionException ex){ + logger.log(Level.SEVERE, String.format("Interrupted while releasing shared lock"), ex); + } + changeCurrentCase(null); + try { services.close(); - this.db.close(); + this.db.close(); } catch (Exception e) { throw new CaseActionException(NbBundle.getMessage(this.getClass(), "Case.closeCase.exception.msg"), e); } @@ -943,6 +973,9 @@ public class Case implements SleuthkitCase.ErrorObserver { public static void create(String caseDir, String caseName, String caseNumber, String examiner, CaseType caseType) throws CaseActionException { logger.log(Level.INFO, "Attempting to create case {0} in directory = {1}", new Object[]{caseName, caseDir}); //NON-NLS + CoordinationService.Lock exclusiveResourceLock = null; + boolean caseCreatedSuccessfully = false; + /* * Create case directory if it doesn't already exist. */ @@ -965,49 +998,113 @@ public class Case implements SleuthkitCase.ErrorObserver { } else if (caseType == CaseType.MULTI_USER_CASE) { dbName = indexName; } + + try{ + /* If this is a multi-user case, acquire two locks: + * - a shared lock on the case to prevent it from being deleted while open + * - an exclusive lock to prevent multiple clients from executing openCase simultaneously + */ + if(caseType == CaseType.MULTI_USER_CASE){ + try{ - /* - * Create the case metadata (.aut) file. - */ - CaseMetadata metadata; - try { - metadata = new CaseMetadata(caseDir, caseType, caseName, caseNumber, examiner, dbName, indexName); - } catch (CaseMetadataException ex) { - throw new CaseActionException(Bundle.Case_creationException(), ex); - } + // The shared lock uses case directory + // The shared lock needs to be created on a special thread so it can be released + // from the same thread. + Future future = getCurrentCaseExecutor().submit(() -> { + currentCaseLock = CoordinationService.getServiceForNamespace(CoordinationServiceNamespace.getRoot()).tryGetSharedLock(CoordinationService.CategoryNode.CASES, caseDir); + if (null == currentCaseLock) { + throw new CaseActionException(NbBundle.getMessage(Case.class, "Case.exception.errorLocking", CaseMetadata.getFileExtension())); + } + return null; + }); + future.get(); - /* - * Create the case database. - */ - SleuthkitCase db = null; - try { - if (caseType == CaseType.SINGLE_USER_CASE) { - db = SleuthkitCase.newCase(dbName); - } else if (caseType == CaseType.MULTI_USER_CASE) { - db = SleuthkitCase.newCase(dbName, UserPreferences.getDatabaseConnectionInfo(), caseDir); + // The exclusive lock uses the unique case name. + // This lock does not need to be on a special thread since it will be released before + // leaving this method + exclusiveResourceLock = CoordinationService.getServiceForNamespace(CoordinationServiceNamespace.getRoot()).tryGetExclusiveLock(CoordinationService.CategoryNode.RESOURCE, + dbName, 12, TimeUnit.HOURS); + if (null == exclusiveResourceLock) { + throw new CaseActionException(NbBundle.getMessage(Case.class, "Case.exception.errorLocking", CaseMetadata.getFileExtension())); + } + } catch (Exception ex){ + throw new CaseActionException(NbBundle.getMessage(Case.class, "Case.exception.errorLocking", CaseMetadata.getFileExtension())); + } } - } catch (TskCoreException ex) { - logger.log(Level.SEVERE, String.format("Error creating a case %s in %s ", caseName, caseDir), ex); //NON-NLS - SwingUtilities.invokeLater(() -> { - WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); - }); + /* - * SleuthkitCase.newCase throws TskCoreExceptions with user-friendly - * messages, so propagate the exception message. + * Create the case metadata (.aut) file. */ - throw new CaseActionException(ex.getMessage(), ex); //NON-NLS - } catch (UserPreferencesException ex) { - logger.log(Level.SEVERE, "Error accessing case database connection info", ex); //NON-NLS - SwingUtilities.invokeLater(() -> { - WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); - }); - throw new CaseActionException(NbBundle.getMessage(Case.class, "Case.databaseConnectionInfo.error.msg"), ex); + CaseMetadata metadata; + try { + metadata = new CaseMetadata(caseDir, caseType, caseName, caseNumber, examiner, dbName, indexName); + } catch (CaseMetadataException ex) { + throw new CaseActionException(Bundle.Case_creationException(), ex); + } + + /* + * Create the case database. + */ + SleuthkitCase db = null; + try { + if (caseType == CaseType.SINGLE_USER_CASE) { + db = SleuthkitCase.newCase(dbName); + } else if (caseType == CaseType.MULTI_USER_CASE) { + db = SleuthkitCase.newCase(dbName, UserPreferences.getDatabaseConnectionInfo(), caseDir); + } + } catch (TskCoreException ex) { + logger.log(Level.SEVERE, String.format("Error creating a case %s in %s ", caseName, caseDir), ex); //NON-NLS + SwingUtilities.invokeLater(() -> { + WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); + }); + /* + * SleuthkitCase.newCase throws TskCoreExceptions with user-friendly + * messages, so propagate the exception message. + */ + throw new CaseActionException(ex.getMessage(), ex); //NON-NLS + } catch (UserPreferencesException ex) { + logger.log(Level.SEVERE, "Error accessing case database connection info", ex); //NON-NLS + SwingUtilities.invokeLater(() -> { + WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); + }); + throw new CaseActionException(NbBundle.getMessage(Case.class, "Case.databaseConnectionInfo.error.msg"), ex); + } + + Case newCase = new Case(metadata, db); + changeCurrentCase(newCase); + caseCreatedSuccessfully = true; + + logger.log(Level.INFO, "Created case {0} in directory = {1}", new Object[]{caseName, caseDir}); //NON-NLS + } finally { + // Release the exclusive resource lock + try { + if (exclusiveResourceLock != null) { + exclusiveResourceLock.release(); + } + } catch (CoordinationService.CoordinationServiceException exx) { + logger.log(Level.SEVERE, String.format("Error releasing resource lock for case {0}", caseName), exx); + } + + // If an error occurred while opening the case, release the shared case lock as well + if(! caseCreatedSuccessfully){ + Future future = getCurrentCaseExecutor().submit(() -> { + try { + if (currentCaseLock != null) { + currentCaseLock.release(); + currentCaseLock = null; + } + } catch (CoordinationService.CoordinationServiceException exx) { + logger.log(Level.SEVERE, String.format("Error releasing shared lock for case {0}", caseName), exx); + } + return null; + }); + try{ + future.get(); + } catch (InterruptedException | ExecutionException ex){ + logger.log(Level.SEVERE, String.format("Interrupted while releasing shared lock"), ex); + } + } } - - Case newCase = new Case(metadata, db); - changeCurrentCase(newCase); - - logger.log(Level.INFO, "Created case {0} in directory = {1}", new Object[]{caseName, caseDir}); //NON-NLS } /** @@ -1146,6 +1243,8 @@ public class Case implements SleuthkitCase.ErrorObserver { */ public static void open(String caseMetadataFilePath) throws CaseActionException { logger.log(Level.INFO, "Opening case with metadata file path {0}", caseMetadataFilePath); //NON-NLS + CoordinationService.Lock exclusiveResourceLock = null; + boolean caseOpenedSuccessfully = false; /* * Verify the extension of the case metadata file. @@ -1161,6 +1260,39 @@ public class Case implements SleuthkitCase.ErrorObserver { CaseMetadata metadata = new CaseMetadata(Paths.get(caseMetadataFilePath)); CaseType caseType = metadata.getCaseType(); + /* + * If this is a multi-user case, acquire two locks: + * - a shared lock on the case to prevent it from being deleted while open + * - an exclusive lock to prevent multiple clients from executing openCase simultaneously + */ + if(caseType == CaseType.MULTI_USER_CASE){ + try{ + + // The shared lock uses the case directory + // The shared lock needs to be created on a special thread so it can be released + // from the same thread. + Future future = getCurrentCaseExecutor().submit(() -> { + currentCaseLock = CoordinationService.getServiceForNamespace(CoordinationServiceNamespace.getRoot()).tryGetSharedLock(CoordinationService.CategoryNode.CASES, metadata.getCaseDirectory()); + if (null == currentCaseLock) { + throw new CaseActionException(NbBundle.getMessage(Case.class, "Case.exception.errorLocking", CaseMetadata.getFileExtension())); + } + return null; + }); + future.get(); + + // The exclusive lock uses the unique case name + // This lock does not need to be on a special thread since it will be released before + // leaving this method + exclusiveResourceLock = CoordinationService.getServiceForNamespace(CoordinationServiceNamespace.getRoot()).tryGetExclusiveLock(CoordinationService.CategoryNode.RESOURCE, + metadata.getCaseDatabaseName(), 12, TimeUnit.HOURS); + if (null == exclusiveResourceLock) { + throw new CaseActionException(NbBundle.getMessage(Case.class, "Case.exception.errorLocking", CaseMetadata.getFileExtension())); + } + } catch (Exception ex){ + throw new CaseActionException(NbBundle.getMessage(Case.class, "Case.exception.errorLocking", CaseMetadata.getFileExtension())); + } + } + /* * Open the case database. */ @@ -1224,6 +1356,7 @@ public class Case implements SleuthkitCase.ErrorObserver { } Case openedCase = new Case(metadata, db); changeCurrentCase(openedCase); + caseOpenedSuccessfully = true; } catch (CaseMetadataException ex) { throw new CaseActionException(NbBundle.getMessage(Case.class, "Case.metaDataFileCorrupt.exception.msg"), ex); //NON-NLS @@ -1236,6 +1369,36 @@ public class Case implements SleuthkitCase.ErrorObserver { * user-friendly messages, so propagate the exception message. */ throw new CaseActionException(ex.getMessage(), ex); + } finally { + // Release the exclusive resource lock + try { + if (exclusiveResourceLock != null) { + exclusiveResourceLock.release(); + exclusiveResourceLock = null; + } + } catch (CoordinationService.CoordinationServiceException exx) { + logger.log(Level.SEVERE, String.format("Error releasing resource lock for case {0}", caseMetadataFilePath), exx); + } + + // If an error occurred while opening the case, release the shared case lock as well + if(! caseOpenedSuccessfully){ + Future future = getCurrentCaseExecutor().submit(() -> { + try { + if (currentCaseLock != null) { + currentCaseLock.release(); + currentCaseLock = null; + } + } catch (CoordinationService.CoordinationServiceException exx) { + logger.log(Level.SEVERE, String.format("Error releasing shared lock for case {0}", caseMetadataFilePath), exx); + } + return null; + }); + try{ + future.get(); + } catch (InterruptedException | ExecutionException ex){ + logger.log(Level.SEVERE, String.format("Interrupted while releasing shared lock"), ex); + } + } } } @@ -1320,18 +1483,20 @@ public class Case implements SleuthkitCase.ErrorObserver { MessageNotifyUtil.Notify.error(NbBundle.getMessage(Case.class, "Case.CollaborationSetup.FailNotify.Title"), NbBundle.getMessage(Case.class, "Case.CollaborationSetup.FailNotify.ErrMsg")); } } - + AutopsyService.CaseContext context = new AutopsyService.CaseContext(Case.currentCase, new LoggingProgressIndicator()); String serviceName = ""; for (AutopsyService service : Lookup.getDefault().lookupAll(AutopsyService.class)) { try { serviceName = service.getServiceName(); - service.openCaseResources(context); + if (!serviceName.equals("Solr Keyword Search Service")) { + service.openCaseResources(context); + } } catch (AutopsyService.AutopsyServiceException ex) { Case.logger.log(Level.SEVERE, String.format("%s service failed to open case resources", serviceName), ex); } - } - + } + eventPublisher.publishLocally(new AutopsyEvent(Events.CURRENT_CASE.toString(), null, currentCase)); } else { @@ -1448,12 +1613,23 @@ public class Case implements SleuthkitCase.ErrorObserver { * * @param casePath A case directory path. * - * @return True if the deleteion succeeded, false otherwise. + * @return True if the deletion succeeded, false otherwise. */ static boolean deleteCaseDirectory(File casePath) { logger.log(Level.INFO, "Deleting case directory: {0}", casePath.getAbsolutePath()); //NON-NLS return FileUtil.deleteDir(casePath); } + + /** + * Get the single thread executor for the current case, creating it if necessary. + * @return The executor + */ + private static ExecutorService getCurrentCaseExecutor(){ + if(currentCaseExecutor == null){ + currentCaseExecutor = Executors.newSingleThreadExecutor(); + } + return currentCaseExecutor; + } /** * Gets the time zone(s) of the image data source(s) in this case. diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/coordinationservice/CoordinationService.java b/Core/src/org/sleuthkit/autopsy/coordinationservice/CoordinationService.java similarity index 86% rename from Experimental/src/org/sleuthkit/autopsy/experimental/coordinationservice/CoordinationService.java rename to Core/src/org/sleuthkit/autopsy/coordinationservice/CoordinationService.java index 8b0b293165..adc89d4afa 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/coordinationservice/CoordinationService.java +++ b/Core/src/org/sleuthkit/autopsy/coordinationservice/CoordinationService.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2015 Basis Technology Corp. + * Copyright 2011-2017 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -16,130 +16,91 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.sleuthkit.autopsy.experimental.coordinationservice; +package org.sleuthkit.autopsy.coordinationservice; +import java.io.IOException; import java.util.HashMap; import java.util.Map; import java.util.concurrent.TimeUnit; -import org.apache.zookeeper.CreateMode; -import org.apache.zookeeper.ZooDefs; import org.apache.curator.RetryPolicy; -import org.apache.curator.retry.ExponentialBackoffRetry; import org.apache.curator.framework.CuratorFramework; import org.apache.curator.framework.CuratorFrameworkFactory; import org.apache.curator.framework.recipes.locks.InterProcessMutex; import org.apache.curator.framework.recipes.locks.InterProcessReadWriteLock; +import org.apache.curator.retry.ExponentialBackoffRetry; +import org.apache.zookeeper.CreateMode; import org.apache.zookeeper.KeeperException; -import org.sleuthkit.autopsy.core.UserPreferences; -import java.io.IOException; -import org.apache.zookeeper.WatchedEvent; -import org.apache.zookeeper.ZooKeeper; import org.apache.zookeeper.KeeperException.NoNodeException; +import org.apache.zookeeper.WatchedEvent; +import org.apache.zookeeper.ZooDefs; +import org.apache.zookeeper.ZooKeeper; +import org.sleuthkit.autopsy.core.UserPreferences; /** - * A centralized service for maintaining configuration information and providing - * distributed synchronization using a shared hierarchical namespace of nodes. + * A coordination service for maintaining configuration information and + * providing distributed synchronization using a shared hierarchical namespace + * of nodes. + * + * TODO (JIRA 2205): Simple refactoring for general use. */ public final class CoordinationService { - /** - * Category nodes are the immediate children of the root node of a shared - * hierarchical namespace managed by the coordination service. - */ - public enum CategoryNode { // RJCTODO: Move this to CoordinationServiceNamespace - - CASES("cases"), - MANIFESTS("manifests"), - CONFIG("config"); - - private final String displayName; - - private CategoryNode(String displayName) { - this.displayName = displayName; - } - - public String getDisplayName() { - return displayName; - } - } - - /** - * Exception type thrown by the coordination service. - */ - public final static class CoordinationServiceException extends Exception { - - private static final long serialVersionUID = 1L; - - private CoordinationServiceException(String message) { - super(message); - } - - private CoordinationServiceException(String message, Throwable cause) { - super(message, cause); - } - } - - /** - * An opaque encapsulation of a lock for use in distributed synchronization. - * Instances are obtained by calling a get lock method and must be passed to - * a release lock method. - */ - public static class Lock implements AutoCloseable { - - /** - * This implementation uses the Curator read/write lock. see - * http://curator.apache.org/curator-recipes/shared-reentrant-read-write-lock.html - */ - private final InterProcessMutex interProcessLock; - private final String nodePath; - - private Lock(String nodePath, InterProcessMutex lock) { - this.nodePath = nodePath; - this.interProcessLock = lock; - } - - public String getNodePath() { - return nodePath; - } - - public void release() throws CoordinationServiceException { - try { - this.interProcessLock.release(); - } catch (Exception ex) { - throw new CoordinationServiceException(String.format("Failed to release the lock on %s", nodePath), ex); - } - } - - @Override - public void close() throws CoordinationServiceException { - release(); - } - } - private static CuratorFramework curator = null; private static final Map rootNodesToServices = new HashMap<>(); - private final Map categoryNodeToPath = new HashMap<>(); private static final int SESSION_TIMEOUT_MILLISECONDS = 300000; private static final int CONNECTION_TIMEOUT_MILLISECONDS = 300000; private static final int ZOOKEEPER_SESSION_TIMEOUT_MILLIS = 3000; private static final int ZOOKEEPER_CONNECTION_TIMEOUT_MILLIS = 15000; - private static final int PORT_OFFSET = 1000; + private static final int PORT_OFFSET = 1000; // When run in Solr, ZooKeeper defaults to Solr port + 1000 + private final Map categoryNodeToPath = new HashMap<>(); /** - * Gets an instance of the centralized coordination service for a specific - * namespace. + * Determines if ZooKeeper is accessible with the current settings. Closes + * the connection prior to returning. + * + * @return true if a connection was achieved, false otherwise + * + * @throws InterruptedException + * @throws IOException + */ + private static boolean isZooKeeperAccessible() throws InterruptedException, IOException { + boolean result = false; + Object workerThreadWaitNotifyLock = new Object(); + int zooKeeperServerPort = Integer.valueOf(UserPreferences.getIndexingServerPort()) + PORT_OFFSET; + String connectString = UserPreferences.getIndexingServerHost() + ":" + zooKeeperServerPort; + ZooKeeper zooKeeper = new ZooKeeper(connectString, ZOOKEEPER_SESSION_TIMEOUT_MILLIS, + (WatchedEvent event) -> { + synchronized (workerThreadWaitNotifyLock) { + workerThreadWaitNotifyLock.notify(); + } + }); + synchronized (workerThreadWaitNotifyLock) { + workerThreadWaitNotifyLock.wait(ZOOKEEPER_CONNECTION_TIMEOUT_MILLIS); + } + ZooKeeper.States state = zooKeeper.getState(); + if (state == ZooKeeper.States.CONNECTED || state == ZooKeeper.States.CONNECTEDREADONLY) { + result = true; + } + zooKeeper.close(); + return result; + } + + /** + * Gets a coordination service for a specific namespace. * * @param rootNode The name of the root node that defines the namespace. * - * @return The service for the namespace defined by the root node name. + * @return The coordination service. * - * @throws CoordinationServiceException If an instaNce of the coordination + * @throws CoordinationServiceException If an instance of the coordination * service cannot be created. */ - public static synchronized CoordinationService getInstance(String rootNode) throws CoordinationServiceException { + public static synchronized CoordinationService getServiceForNamespace(String rootNode) throws CoordinationServiceException { + /* + * Connect to ZooKeeper via Curator. + */ if (null == curator) { RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 3); - // When run in Solr, ZooKeeper defaults to Solr port + 1000 int zooKeeperServerPort = Integer.valueOf(UserPreferences.getIndexingServerPort()) + PORT_OFFSET; String connectString = UserPreferences.getIndexingServerHost() + ":" + zooKeeperServerPort; curator = CuratorFrameworkFactory.newClient(connectString, SESSION_TIMEOUT_MILLISECONDS, CONNECTION_TIMEOUT_MILLISECONDS, retryPolicy); @@ -156,7 +117,7 @@ public final class CoordinationService { CoordinationService service; try { service = new CoordinationService(rootNode); - } catch (Exception ex) { + } catch (IOException | InterruptedException | KeeperException | CoordinationServiceException ex) { curator = null; throw new CoordinationServiceException("Failed to create coordination service", ex); } @@ -166,15 +127,18 @@ public final class CoordinationService { } /** - * Constructs an instance of the centralized coordination service for a - * specific namespace. + * Constructs an instance of the coordination service for a specific + * namespace. * * @param rootNodeName The name of the root node that defines the namespace. + * + * @throws Exception (calls Curator methods that throw Exception instead of + * more specific exceptions) */ - private CoordinationService(String rootNodeName) throws Exception { + private CoordinationService(String rootNodeName) throws InterruptedException, IOException, KeeperException, CoordinationServiceException { if (false == isZooKeeperAccessible()) { - throw new Exception("Unable to access ZooKeeper"); + throw new CoordinationServiceException("Unable to access ZooKeeper"); } String rootNode = rootNodeName; @@ -190,6 +154,8 @@ public final class CoordinationService { if (ex.code() != KeeperException.Code.NODEEXISTS) { throw ex; } + } catch (Exception ex) { + throw new CoordinationServiceException("Curator experienced an error", ex); } categoryNodeToPath.put(node.getDisplayName(), nodePath); } @@ -384,35 +350,77 @@ public final class CoordinationService { } /** - * Determines if ZooKeeper is accessible with the current settings. Closes - * the connection prior to returning. - * - * @return true if a connection was achieved, false otherwise + * Exception type thrown by the coordination service. */ - private static boolean isZooKeeperAccessible() { - boolean result = false; - Object workerThreadWaitNotifyLock = new Object(); - int zooKeeperServerPort = Integer.valueOf(UserPreferences.getIndexingServerPort()) + PORT_OFFSET; - String connectString = UserPreferences.getIndexingServerHost() + ":" + zooKeeperServerPort; + public final static class CoordinationServiceException extends Exception { - try { - ZooKeeper zooKeeper = new ZooKeeper(connectString, ZOOKEEPER_SESSION_TIMEOUT_MILLIS, - (WatchedEvent event) -> { + private static final long serialVersionUID = 1L; - synchronized (workerThreadWaitNotifyLock) { - workerThreadWaitNotifyLock.notify(); - } - }); - synchronized (workerThreadWaitNotifyLock) { - workerThreadWaitNotifyLock.wait(ZOOKEEPER_CONNECTION_TIMEOUT_MILLIS); - } - ZooKeeper.States state = zooKeeper.getState(); - if (state == ZooKeeper.States.CONNECTED || state == ZooKeeper.States.CONNECTEDREADONLY) { - result = true; - } - zooKeeper.close(); - } catch (InterruptedException | IOException ignored) { + private CoordinationServiceException(String message) { + super(message); + } + + private CoordinationServiceException(String message, Throwable cause) { + super(message, cause); + } + } + + /** + * An opaque encapsulation of a lock for use in distributed synchronization. + * Instances are obtained by calling a get lock method and must be passed to + * a release lock method. + */ + public static class Lock implements AutoCloseable { + + /** + * This implementation uses the Curator read/write lock. see + * http://curator.apache.org/curator-recipes/shared-reentrant-read-write-lock.html + */ + private final InterProcessMutex interProcessLock; + private final String nodePath; + + private Lock(String nodePath, InterProcessMutex lock) { + this.nodePath = nodePath; + this.interProcessLock = lock; + } + + public String getNodePath() { + return nodePath; + } + + public void release() throws CoordinationServiceException { + try { + this.interProcessLock.release(); + } catch (Exception ex) { + throw new CoordinationServiceException(String.format("Failed to release the lock on %s", nodePath), ex); + } + } + + @Override + public void close() throws CoordinationServiceException { + release(); + } + } + + /** + * Category nodes are the immediate children of the root node of a shared + * hierarchical namespace managed by a coordination service. + */ + public enum CategoryNode { + + CASES("cases"), + MANIFESTS("manifests"), + CONFIG("config"), + RESOURCE("resource"); + + private final String displayName; + + private CategoryNode(String displayName) { + this.displayName = displayName; + } + + public String getDisplayName() { + return displayName; } - return result; } } diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/CoordinationServiceNamespace.java b/Core/src/org/sleuthkit/autopsy/coordinationservice/CoordinationServiceNamespace.java similarity index 76% rename from Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/CoordinationServiceNamespace.java rename to Core/src/org/sleuthkit/autopsy/coordinationservice/CoordinationServiceNamespace.java index 1b1c4a1227..567dd38bc6 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/CoordinationServiceNamespace.java +++ b/Core/src/org/sleuthkit/autopsy/coordinationservice/CoordinationServiceNamespace.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2015 Basis Technology Corp. + * Copyright 2016-2017 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -16,15 +16,15 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.sleuthkit.autopsy.experimental.autoingest; +package org.sleuthkit.autopsy.coordinationservice; /** - * Namespace elements for auto ingest coordination service nodes. + * Root node for Autopsy coordination service namespace. */ -final class CoordinationServiceNamespace { +public final class CoordinationServiceNamespace { private static final String ROOT = "autopsy"; - static String getRoot() { + public static String getRoot() { return ROOT; } diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/AutopsyOptionsPanel.java b/Core/src/org/sleuthkit/autopsy/corecomponents/AutopsyOptionsPanel.java index de9a6e5429..95d9419f7c 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/AutopsyOptionsPanel.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/AutopsyOptionsPanel.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2013-2014 Basis Technology Corp. + * Copyright 2011-2017 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -32,39 +32,29 @@ import org.sleuthkit.autopsy.core.UserPreferences; */ final class AutopsyOptionsPanel extends javax.swing.JPanel { + private static final long serialVersionUID = 1L; + AutopsyOptionsPanel() { initComponents(); + + /* + * Profiling has shown that contention for I/O resources and for the + * case database limits the number of threads that can do meaningful + * work during ingest. If Autopsy was compute-bound, adding more threads + * on machines with enough processors might help, but currently, after + * four threads, performance actually stays flat and then starts to + * degrade. + */ int availableProcessors = Runtime.getRuntime().availableProcessors(); Integer fileIngestThreadCountChoices[]; int recommendedFileIngestThreadCount; - if (availableProcessors >= 16) { - fileIngestThreadCountChoices = new Integer[]{1, 2, 4, 6, 8, 12, 16}; - if (availableProcessors >= 18) { - recommendedFileIngestThreadCount = 16; - } else { - recommendedFileIngestThreadCount = 12; - } - } else if (availableProcessors >= 12 && availableProcessors <= 15) { - fileIngestThreadCountChoices = new Integer[]{1, 2, 4, 6, 8, 12}; - if (availableProcessors >= 14) { - recommendedFileIngestThreadCount = 12; - } else { - recommendedFileIngestThreadCount = 8; - } - } else if (availableProcessors >= 8 && availableProcessors <= 11) { - fileIngestThreadCountChoices = new Integer[]{1, 2, 4, 6, 8}; - if (availableProcessors >= 10) { - recommendedFileIngestThreadCount = 8; - } else { - recommendedFileIngestThreadCount = 6; - } - } else if (availableProcessors >= 6 && availableProcessors <= 7) { - fileIngestThreadCountChoices = new Integer[]{1, 2, 4, 6}; + if (availableProcessors >= 6) { + fileIngestThreadCountChoices = new Integer[]{1, 2, 4}; recommendedFileIngestThreadCount = 4; - } else if (availableProcessors >= 4 && availableProcessors <= 5) { + } else if (availableProcessors >= 4 && availableProcessors < 6) { fileIngestThreadCountChoices = new Integer[]{1, 2, 4}; recommendedFileIngestThreadCount = 2; - } else if (availableProcessors >= 2 && availableProcessors <= 3) { + } else if (availableProcessors >= 2 && availableProcessors < 4) { fileIngestThreadCountChoices = new Integer[]{1, 2}; recommendedFileIngestThreadCount = 1; } else { @@ -72,7 +62,9 @@ final class AutopsyOptionsPanel extends javax.swing.JPanel { recommendedFileIngestThreadCount = 1; } numberOfFileIngestThreadsComboBox.setModel(new DefaultComboBoxModel<>(fileIngestThreadCountChoices)); + restartRequiredLabel.setText(NbBundle.getMessage(AutopsyOptionsPanel.class, "AutopsyOptionsPanel.restartRequiredLabel.text", recommendedFileIngestThreadCount)); + // TODO listen to changes in form fields and call controller.changed() DocumentListener docListener = new DocumentListener() { @@ -92,7 +84,7 @@ final class AutopsyOptionsPanel extends javax.swing.JPanel { } }; this.jFormattedTextFieldProcTimeOutHrs.getDocument().addDocumentListener(docListener); - + } void load() { @@ -138,7 +130,7 @@ final class AutopsyOptionsPanel extends javax.swing.JPanel { UserPreferences.setProcessTimeOutHrs((int) timeOutHrs); } } - + boolean valid() { return true; } diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/ImageNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/ImageNode.java index aee8368831..8b2f3b9ae7 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/ImageNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/ImageNode.java @@ -77,9 +77,9 @@ public class ImageNode extends AbstractContentNode { String imgName = nameForImage(img); this.setDisplayName(imgName); this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/hard-drive-icon.jpg"); //NON-NLS - + // Listen for ingest events so that we can detect new added files (e.g. carved) - IngestManager.getInstance().addIngestModuleEventListener(pcl); + IngestManager.getInstance().addIngestModuleEventListener(pcl); // Listen for case events so that we can detect when case is closed Case.addPropertyChangeListener(pcl); } @@ -101,8 +101,6 @@ public class ImageNode extends AbstractContentNode { "ImageNode.getActions.openFileSearchByAttr.text=Open File Search by Attributes",}) public Action[] getActions(boolean context) { - - List actionsList = new ArrayList(); for (Action a : super.getActions(true)) { actionsList.add(a); @@ -215,7 +213,12 @@ public class ImageNode extends AbstractContentNode { public String getItemType() { return getClass().getName(); } - + + /* + * This property change listener refreshes the tree when a new file is + * carved out of this image (i.e, the image is being treated as raw bytes + * and was ingested by the RawDSProcessor). + */ private final PropertyChangeListener pcl = (PropertyChangeEvent evt) -> { String eventType = evt.getPropertyName(); @@ -255,6 +258,5 @@ public class ImageNode extends AbstractContentNode { } } }; - - + } diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/VolumeNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/VolumeNode.java index 161a972743..ba37cd4f09 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/VolumeNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/VolumeNode.java @@ -80,6 +80,10 @@ public class VolumeNode extends AbstractContentNode { Case.removePropertyChangeListener(pcl); } + /* + * This property change listener refreshes the tree when a new file is + * carved out of the unallocated space of this volume. + */ private final PropertyChangeListener pcl = (PropertyChangeEvent evt) -> { String eventType = evt.getPropertyName(); diff --git a/Experimental/nbproject/project.properties b/Experimental/nbproject/project.properties index 140b9f1b5c..49be6268a5 100644 --- a/Experimental/nbproject/project.properties +++ b/Experimental/nbproject/project.properties @@ -1,17 +1,12 @@ file.reference.c3p0-0.9.5.jar=release/modules/ext/c3p0-0.9.5.jar -file.reference.curator-client-2.8.0.jar=release/modules/ext/curator-client-2.8.0.jar -file.reference.curator-framework-2.8.0.jar=release/modules/ext/curator-framework-2.8.0.jar -file.reference.curator-recipes-2.8.0.jar=release/modules/ext/curator-recipes-2.8.0.jar file.reference.jackson-core-2.7.0.jar=release/modules/ext/jackson-core-2.7.0.jar file.reference.LGoodDatePicker-4.3.1.jar=release/modules/ext/LGoodDatePicker-4.3.1.jar file.reference.mchange-commons-java-0.2.9.jar=release/modules/ext/mchange-commons-java-0.2.9.jar file.reference.solr-solrj-4.9.1.jar=release/modules/ext/solr-solrj-4.9.1.jar file.reference.tika-core-1.5.jar=release/modules/ext/tika-core-1.5.jar -file.reference.zookeeper-3.4.6.jar=release/modules/ext/zookeeper-3.4.6.jar javac.source=1.8 javac.compilerargs=-Xlint -Xlint:-serial javadoc.reference.LGoodDatePicker-4.3.1.jar=release/modules/ext/LGoodDatePicker-4.3.1-javadoc.jar javadoc.reference.solr-solrj-4.9.1.jar=release/modules/ext/solr-solrj-4.9.1-javadoc.jar -source.reference.curator-recipes-2.8.0.jar=release/modules/ext/curator-recipes-2.8.0-sources.jar source.reference.LGoodDatePicker-4.3.1.jar=release/modules/ext/LGoodDatePicker-4.3.1-sources.jar source.reference.solr-solrj-4.9.1.jar=release/modules/ext/solr-solrj-4.9.1-sources.jar diff --git a/Experimental/nbproject/project.xml b/Experimental/nbproject/project.xml index 8ab88c13b5..63cedca0de 100644 --- a/Experimental/nbproject/project.xml +++ b/Experimental/nbproject/project.xml @@ -119,14 +119,6 @@ org.sleuthkit.autopsy.experimental.autoingest org.sleuthkit.autopsy.experimental.configuration - - ext/zookeeper-3.4.6.jar - release/modules/ext/zookeeper-3.4.6.jar - - - ext/curator-client-2.8.0.jar - release/modules/ext/curator-client-2.8.0.jar - ext/mchange-commons-java-0.2.9.jar release/modules/ext/mchange-commons-java-0.2.9.jar @@ -139,10 +131,6 @@ ext/tika-core-1.5.jar release/modules/ext/tika-core-1.5.jar - - ext/curator-recipes-2.8.0.jar - release/modules/ext/curator-recipes-2.8.0.jar - ext/jackson-core-2.7.0.jar release/modules/ext/jackson-core-2.7.0.jar @@ -151,10 +139,6 @@ ext/c3p0-0.9.5.jar release/modules/ext/c3p0-0.9.5.jar - - ext/curator-framework-2.8.0.jar - release/modules/ext/curator-framework-2.8.0.jar - ext/solr-solrj-4.9.1.jar release/modules/ext/solr-solrj-4.9.1.jar diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobLogger.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobLogger.java index f3828d5594..e9406950fd 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobLogger.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobLogger.java @@ -18,6 +18,7 @@ */ package org.sleuthkit.autopsy.experimental.autoingest; +import org.sleuthkit.autopsy.coordinationservice.CoordinationServiceNamespace; import java.io.BufferedWriter; import java.io.File; import java.io.FileWriter; @@ -29,9 +30,9 @@ import java.text.SimpleDateFormat; import java.time.Instant; import java.util.Date; import org.sleuthkit.autopsy.coreutils.NetworkUtils; -import org.sleuthkit.autopsy.experimental.coordinationservice.CoordinationService; -import org.sleuthkit.autopsy.experimental.coordinationservice.CoordinationService.Lock; -import org.sleuthkit.autopsy.experimental.coordinationservice.CoordinationService.CoordinationServiceException; +import org.sleuthkit.autopsy.coordinationservice.CoordinationService; +import org.sleuthkit.autopsy.coordinationservice.CoordinationService.Lock; +import org.sleuthkit.autopsy.coordinationservice.CoordinationService.CoordinationServiceException; import java.util.concurrent.TimeUnit; import java.util.List; import javax.annotation.concurrent.Immutable; @@ -430,7 +431,7 @@ final class AutoIngestJobLogger { * log file. */ private void log(MessageCategory category, String message) throws AutoIngestJobLoggerException, InterruptedException { - try (Lock lock = CoordinationService.getInstance(CoordinationServiceNamespace.getRoot()).tryGetExclusiveLock(CoordinationService.CategoryNode.CASES, getLogPath(caseDirectoryPath).toString(), LOCK_TIME_OUT, LOCK_TIME_OUT_UNIT)) { + try (Lock lock = CoordinationService.getServiceForNamespace(CoordinationServiceNamespace.getRoot()).tryGetExclusiveLock(CoordinationService.CategoryNode.CASES, getLogPath(caseDirectoryPath).toString(), LOCK_TIME_OUT, LOCK_TIME_OUT_UNIT)) { if (null != lock) { File logFile = getLogPath(caseDirectoryPath).toFile(); try (PrintWriter writer = new PrintWriter(new BufferedWriter(new FileWriter(logFile, logFile.exists())), true)) { diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestManager.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestManager.java index e213633e07..bb55281227 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestManager.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestManager.java @@ -18,6 +18,7 @@ */ package org.sleuthkit.autopsy.experimental.autoingest; +import org.sleuthkit.autopsy.coordinationservice.CoordinationServiceNamespace; import com.google.common.util.concurrent.ThreadFactoryBuilder; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; @@ -98,9 +99,9 @@ import org.sleuthkit.autopsy.events.AutopsyEventPublisher; import org.sleuthkit.autopsy.ingest.IngestJob; import org.sleuthkit.autopsy.ingest.IngestJobSettings; import org.sleuthkit.datamodel.Content; -import org.sleuthkit.autopsy.experimental.coordinationservice.CoordinationService; -import org.sleuthkit.autopsy.experimental.coordinationservice.CoordinationService.CoordinationServiceException; -import org.sleuthkit.autopsy.experimental.coordinationservice.CoordinationService.Lock; +import org.sleuthkit.autopsy.coordinationservice.CoordinationService; +import org.sleuthkit.autopsy.coordinationservice.CoordinationService.CoordinationServiceException; +import org.sleuthkit.autopsy.coordinationservice.CoordinationService.Lock; import org.sleuthkit.autopsy.experimental.configuration.SharedConfiguration; import org.apache.solr.client.solrj.impl.HttpSolrServer; import org.openide.util.Lookup; @@ -231,7 +232,7 @@ public final class AutoIngestManager extends Observable implements PropertyChang void startUp() throws AutoIngestManagerStartupException { SYS_LOGGER.log(Level.INFO, "Auto ingest starting"); try { - coordinationService = CoordinationService.getInstance(CoordinationServiceNamespace.getRoot()); + coordinationService = CoordinationService.getServiceForNamespace(CoordinationServiceNamespace.getRoot()); } catch (CoordinationServiceException ex) { throw new AutoIngestManagerStartupException("Failed to get coordination service", ex); } diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/ReviewModeCaseManager.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/ReviewModeCaseManager.java index 099ef36088..2cae50ef1a 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/ReviewModeCaseManager.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/ReviewModeCaseManager.java @@ -18,10 +18,7 @@ */ package org.sleuthkit.autopsy.experimental.autoingest; -import java.beans.PropertyChangeEvent; -import java.beans.PropertyChangeListener; import java.io.IOException; -import java.lang.reflect.InvocationTargetException; import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; @@ -37,8 +34,7 @@ import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.CaseActionException; import org.sleuthkit.autopsy.casemodule.CaseNewAction; import org.sleuthkit.autopsy.experimental.configuration.AutoIngestUserPreferences; -import org.sleuthkit.autopsy.experimental.coordinationservice.CoordinationService; -import org.sleuthkit.autopsy.experimental.coordinationservice.CoordinationService.CoordinationServiceException; +import org.sleuthkit.autopsy.coordinationservice.CoordinationService; /** * Handles opening, locking, and unlocking cases in review mode. Instances of @@ -47,7 +43,7 @@ import org.sleuthkit.autopsy.experimental.coordinationservice.CoordinationServic * dispatch thread (EDT). Because of the tight coupling to the UI, exception * messages are deliberately user-friendly. */ -final class ReviewModeCaseManager implements PropertyChangeListener { +final class ReviewModeCaseManager { /* * Provides uniform exceptions with user-friendly error messages. @@ -77,14 +73,7 @@ final class ReviewModeCaseManager implements PropertyChangeListener { */ synchronized static ReviewModeCaseManager getInstance() { if (instance == null) { - /* - * Two stage construction is used here to avoid allowing "this" - * reference to escape from the constructor via registering as an - * PropertyChangeListener. This is to ensure that a partially - * constructed manager is not published to other threads. - */ instance = new ReviewModeCaseManager(); - Case.addPropertyChangeListener(instance); } return instance; } @@ -149,17 +138,7 @@ final class ReviewModeCaseManager implements PropertyChangeListener { * requirement */ synchronized void openCaseInEDT(Path caseMetadataFilePath) throws ReviewModeCaseManagerException { - Path caseFolderPath = caseMetadataFilePath.getParent(); try { - /* - * Acquire a lock on the case folder. If the lock cannot be - * acquired, the case cannot be opened. - */ - currentCaseLock = CoordinationService.getInstance(CoordinationServiceNamespace.getRoot()).tryGetSharedLock(CoordinationService.CategoryNode.CASES, caseFolderPath.toString()); - if (null == currentCaseLock) { - throw new ReviewModeCaseManagerException("Could not get shared access to multi-user case folder"); - } - /* * Open the case. */ @@ -177,64 +156,8 @@ final class ReviewModeCaseManager implements PropertyChangeListener { CallableSystemAction.get(AddImageAction.class).setEnabled(false); }); - } catch (CoordinationServiceException | ReviewModeCaseManagerException | CaseActionException ex) { - /* - * Release the coordination service lock on the case folder. - */ - try { - if (currentCaseLock != null) { - currentCaseLock.release(); - currentCaseLock = null; - } - } catch (CoordinationService.CoordinationServiceException exx) { - logger.log(Level.SEVERE, String.format("Error deleting legacy LOCKED state file for case at %s", caseFolderPath), exx); - } - - if (ex instanceof CoordinationServiceException) { - throw new ReviewModeCaseManagerException("Could not get access to the case folder from the coordination service, contact administrator", ex); - } else if (ex instanceof IOException) { - throw new ReviewModeCaseManagerException("Could not write to the case folder, contact adminstrator", ex); - } else if (ex instanceof CaseActionException) { - /* - * CaseActionExceptions have user friendly error messages. - */ - throw new ReviewModeCaseManagerException(String.format("Could not open the case (%s), contract administrator", ex.getMessage()), ex); - } else if (ex instanceof ReviewModeCaseManagerException) { - throw (ReviewModeCaseManagerException) ex; - } + } catch (CaseActionException ex) { + throw new ReviewModeCaseManagerException(String.format("Could not open the case (%s), contract administrator", ex.getMessage()), ex); } } - - /** - * @inheritDoc - */ - @Override - public void propertyChange(PropertyChangeEvent evt) { - if (evt.getPropertyName().equals(Case.Events.CURRENT_CASE.toString()) - && null != evt.getOldValue() - && null == evt.getNewValue()) { - /* - * When a case is closed, release the coordination service lock on - * the case folder. This must be done in the EDT because it was - * acquired in the EDT via openCase(). - */ - if (null != currentCaseLock) { - try { - SwingUtilities.invokeAndWait(() -> { - try { - currentCaseLock.release(); - currentCaseLock = null; - } catch (CoordinationService.CoordinationServiceException ex) { - logger.log(Level.SEVERE, String.format("Failed to release the coordination service lock with path %s", currentCaseLock.getNodePath()), ex); - currentCaseLock = null; - } - }); - } catch (InterruptedException | InvocationTargetException ex) { - logger.log(Level.SEVERE, String.format("Failed to release the coordination service lock with path %s", currentCaseLock.getNodePath()), ex); - currentCaseLock = null; - } - } - } - } - } diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/configuration/SharedConfiguration.java b/Experimental/src/org/sleuthkit/autopsy/experimental/configuration/SharedConfiguration.java index 0e6d38c903..653fc65807 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/configuration/SharedConfiguration.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/configuration/SharedConfiguration.java @@ -49,9 +49,9 @@ import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.autopsy.core.ServicesMonitor; import org.sleuthkit.autopsy.modules.hashdatabase.HashDbManager.HashDb; import org.sleuthkit.autopsy.experimental.configuration.AutoIngestSettingsPanel.UpdateConfigSwingWorker; -import org.sleuthkit.autopsy.experimental.coordinationservice.CoordinationService; -import org.sleuthkit.autopsy.experimental.coordinationservice.CoordinationService.Lock; -import org.sleuthkit.autopsy.experimental.coordinationservice.CoordinationService.CoordinationServiceException; +import org.sleuthkit.autopsy.coordinationservice.CoordinationService; +import org.sleuthkit.autopsy.coordinationservice.CoordinationService.Lock; +import org.sleuthkit.autopsy.coordinationservice.CoordinationService.CoordinationServiceException; /* * A utility class for loading and saving shared configuration data @@ -160,7 +160,7 @@ public class SharedConfiguration { File remoteFolder = getSharedFolder(); - try (Lock writeLock = CoordinationService.getInstance(LOCK_ROOT).tryGetExclusiveLock(CoordinationService.CategoryNode.CONFIG, remoteFolder.getAbsolutePath(), 30, TimeUnit.MINUTES)) { + try (Lock writeLock = CoordinationService.getServiceForNamespace(LOCK_ROOT).tryGetExclusiveLock(CoordinationService.CategoryNode.CONFIG, remoteFolder.getAbsolutePath(), 30, TimeUnit.MINUTES)) { if (writeLock == null) { logger.log(Level.INFO, String.format("Failed to lock %s - another node is currently uploading or downloading configuration", remoteFolder.getAbsolutePath())); return SharedConfigResult.LOCKED; @@ -230,7 +230,7 @@ public class SharedConfiguration { File remoteFolder = getSharedFolder(); - try (Lock readLock = CoordinationService.getInstance(LOCK_ROOT).tryGetSharedLock(CoordinationService.CategoryNode.CONFIG, remoteFolder.getAbsolutePath(), 30, TimeUnit.MINUTES)) { + try (Lock readLock = CoordinationService.getServiceForNamespace(LOCK_ROOT).tryGetSharedLock(CoordinationService.CategoryNode.CONFIG, remoteFolder.getAbsolutePath(), 30, TimeUnit.MINUTES)) { if (readLock == null) { return SharedConfigResult.LOCKED; } diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/TikaTextExtractor.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/TikaTextExtractor.java index 052cb5e2e1..2e0e1117e7 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/TikaTextExtractor.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/TikaTextExtractor.java @@ -51,7 +51,7 @@ class TikaTextExtractor extends FileTextExtractor { private static final List TIKA_SUPPORTED_TYPES = new Tika().getParser().getSupportedTypes(new ParseContext()) - .parallelStream() + .stream() .map(mt -> mt.getType() + "/" + mt.getSubtype()) .collect(Collectors.toList());