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());