Fixes for Case operation cancellation infrastructure

This commit is contained in:
Richard Cordovano 2017-01-26 09:17:37 -05:00
parent 3ada14c54d
commit c6c45288f4
3 changed files with 309 additions and 279 deletions

View File

@ -43,6 +43,7 @@ import java.util.MissingResourceException;
import java.util.Set;
import java.util.TimeZone;
import java.util.UUID;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@ -95,7 +96,6 @@ import org.sleuthkit.autopsy.framework.ModalDialogProgressIndicator;
import org.sleuthkit.autopsy.framework.ProgressIndicator;
import org.sleuthkit.autopsy.ingest.IngestJob;
import org.sleuthkit.autopsy.ingest.IngestManager;
import org.sleuthkit.autopsy.ingest.RunIngestAction;
import org.sleuthkit.autopsy.keywordsearchservice.KeywordSearchService;
import org.sleuthkit.autopsy.timeline.OpenTimelineAction;
import org.sleuthkit.datamodel.BlackboardArtifactTag;
@ -443,11 +443,11 @@ public class Case {
* exception.
*/
@Messages({
"# {0} - exception message", "Case.creationException.couldNotCreateCase=Could not create case: {0}",
"# {0} - exception message", "Case.exceptionMessage.wrapperMessage={0}",
"Case.exceptionMessage.illegalCaseName=Case name contains illegal characters.",
"Case.exceptionMessage.lockAcquisitionInterrupted=Acquiring locks was interrupted.",
"Case.exceptionMessage.cancelled=Cancelled by user.",
"Case.progressIndicatorTitle.creatingCase=Creating Case",
"Case.progressIndicatorCancelButton.label=Cancel",
"Case.progressIndicatorCancelButton.label=Cancelled",
"Case.progressMessage.preparing=Preparing...",
"Case.progressMessage.acquiringLocks=Acquiring locks...",
"Case.progressMessage.finshing=Finishing..."
@ -482,7 +482,6 @@ public class Case {
} catch (IllegalCaseNameException ex) {
throw new CaseActionException(Bundle.Case_creationException_couldNotCreateCase(Bundle.Case_exceptionMessage_illegalCaseName()), ex);
}
logger.log(Level.INFO, "Attempting to create case {0} (display name = {1}) in directory = {2}", new Object[]{caseName, caseDisplayName, caseDir}); //NON-NLS
/*
* Set up either a GUI progress indicator or a logging progress
@ -507,7 +506,6 @@ public class Case {
* open is released in the same thread in which it was acquired, as is
* required by the coordination service.
*/
try {
Future<Case> future = getCaseLockingExecutor().submit(() -> {
if (CaseType.SINGLE_USER_CASE == caseType) {
newCase.open(caseDir, caseName, caseDisplayName, caseNumber, examiner, caseType, progressIndicator);
@ -520,9 +518,9 @@ public class Case {
try (CoordinationService.Lock nameLock = Case.acquireExclusiveCaseNameLock(caseName)) {
assert (null != nameLock);
/*
* Next, acquire a shared case directory lock that will
* be held as long as this node has this case open. This
* will prevent deletion of the case by another node.
* Next, acquire a shared case directory lock that will be
* held as long as this node has this case open. This will
* prevent deletion of the case by another node.
*/
acquireSharedCaseDirLock(caseDir);
/*
@ -536,8 +534,8 @@ public class Case {
newCase.open(caseDir, caseName, caseDisplayName, caseNumber, examiner, caseType, progressIndicator);
} catch (CaseActionException ex) {
/*
* Release the case directory lock immediately
* if there was a problem opening the case.
* Release the case directory lock immediately if
* there was a problem opening the case.
*/
if (CaseType.MULTI_USER_CASE == caseType) {
releaseSharedCaseDirLock(caseName);
@ -549,10 +547,22 @@ public class Case {
}
return newCase;
});
/*
* If running with a GUI, give the future for the case creation task to
* the cancel button listener for the GUI progress indicator and make
* the progress indicator visible to the user.
*/
if (RuntimeProperties.runningWithGUI()) {
listener.setCaseActionFuture(future);
SwingUtilities.invokeLater(() -> ((ModalDialogProgressIndicator) progressIndicator).setVisible(true));
}
/*
* Wait for the case creation task to finish.
*/
try {
logger.log(Level.INFO, "Attempting to create case {0} (display name = {1}) in directory = {2}", new Object[]{caseName, caseDisplayName, caseDir}); //NON-NLS
currentCase = future.get();
logger.log(Level.INFO, "Created case {0} in directory = {1}", new Object[]{caseName, caseDir}); //NON-NLS
if (RuntimeProperties.runningWithGUI()) {
@ -560,12 +570,12 @@ public class Case {
}
eventPublisher.publishLocally(new AutopsyEvent(Events.CURRENT_CASE.toString(), null, currentCase));
} catch (InterruptedException | ExecutionException ex) {
if (ex instanceof InterruptedException) {
throw new CaseActionException(Bundle.Case_creationException_couldNotCreateCase(Bundle.Case_exceptionMessage_lockAcquisitionInterrupted()), ex);
} else {
throw new CaseActionException(Bundle.Case_creationException_couldNotCreateCase(ex.getCause().getMessage()), ex);
}
} catch (InterruptedException ex) {
throw new CaseActionException(Bundle.Case_exceptionMessage_wrapperMessage(ex.getMessage()), ex);
} catch (ExecutionException ex) {
throw new CaseActionException(Bundle.Case_exceptionMessage_wrapperMessage(ex.getCause().getMessage()), ex);
} catch (CancellationException ex) {
throw new CaseActionException(Bundle.Case_exceptionMessage_cancelled(), ex);
} finally {
progressIndicator.finish(Bundle.Case_progressMessage_finshing());
if (RuntimeProperties.runningWithGUI()) {
@ -589,6 +599,7 @@ public class Case {
"Case.progressIndicatorTitle.openingCase=Opening Case",
"Case.exceptionMessage.failedToReadMetadata=Failed to read metadata."
})
public static void openAsCurrentCase(String caseMetadataFilePath) throws CaseActionException {
/*
* If running with the desktop GUI, this needs to be done before any
@ -609,9 +620,12 @@ public class Case {
closeCurrentCase();
}
logger.log(Level.INFO, "Opening case with metadata file path {0}", caseMetadataFilePath); //NON-NLS
CaseMetadata metadata;
try {
CaseMetadata metadata = new CaseMetadata(Paths.get(caseMetadataFilePath));
metadata = new CaseMetadata(Paths.get(caseMetadataFilePath));
} catch (CaseMetadataException ex) {
throw new CaseActionException(Bundle.Case_openException_couldNotOpenCase(Bundle.Case_exceptionMessage_failedToReadMetadata()), ex);
}
/*
* Set up either a GUI progress indicator or a logging progress
@ -630,31 +644,29 @@ public class Case {
progressIndicator.start(Bundle.Case_progressMessage_preparing());
/*
* Opening a case is always done in the same non-UI thread that will
* be used later to close the case. If the case is a multi-user
* case, this ensures that case directory lock that is held as long
* as the case is open is released in the same thread in which it
* was acquired, as is required by the coordination service.
* Opening a case is always done in the same non-UI thread that will be
* used later to close the case. If the case is a multi-user case, this
* ensures that case directory lock that is held as long as the case is
* open is released in the same thread in which it was acquired, as is
* required by the coordination service.
*/
CaseType caseType = metadata.getCaseType();
String caseName = metadata.getCaseName();
try {
Future<Case> future = getCaseLockingExecutor().submit(() -> {
if (CaseType.SINGLE_USER_CASE == caseType) {
caseToOpen.open(metadata, progressIndicator);
} else {
/*
* First, acquire a shared case directory lock that will
* be held as long as this node has this case open, in
* order to prevent deletion of the case by another
* node.
* First, acquire a shared case directory lock that will be held
* as long as this node has this case open, in order to prevent
* deletion of the case by another node.
*/
progressIndicator.start(Bundle.Case_progressMessage_acquiringLocks());
acquireSharedCaseDirLock(metadata.getCaseDirectory());
/*
* Next, acquire an exclusive case resources lock to
* ensure only one node at a time can
* create/open/upgrade/close case resources.
* Next, acquire an exclusive case resources lock to ensure only
* one node at a time can create/open/upgrade/close case
* resources.
*/
try (CoordinationService.Lock resourcesLock = acquireExclusiveCaseResourcesLock(metadata.getCaseName())) {
assert (null != resourcesLock);
@ -662,8 +674,8 @@ public class Case {
caseToOpen.open(metadata, progressIndicator);
} catch (CaseActionException ex) {
/*
* Release the case directory lock immediately
* if there was a problem opening the case.
* Release the case directory lock immediately if there
* was a problem opening the case.
*/
if (CaseType.MULTI_USER_CASE == caseType) {
releaseSharedCaseDirLock(caseName);
@ -674,32 +686,41 @@ public class Case {
}
return caseToOpen;
});
/*
* If running with a GUI, give the future for the case opening task to
* the cancel button listener for the GUI progress indicator and make
* the progress indicator visible to the user.
*/
if (RuntimeProperties.runningWithGUI()) {
listener.setCaseActionFuture(future);
SwingUtilities.invokeLater(() -> ((ModalDialogProgressIndicator) progressIndicator).setVisible(true));
}
future.get();
/*
* Wait for the case opening task to finish.
*/
try {
logger.log(Level.INFO, "Opening case with metadata file path {0}", caseMetadataFilePath); //NON-NLS
currentCase = future.get();
logger.log(Level.INFO, "Opened case with metadata file path {0}", caseMetadataFilePath); //NON-NLS
if (RuntimeProperties.runningWithGUI()) {
updateGUIForCaseOpened();
}
eventPublisher.publishLocally(new AutopsyEvent(Events.CURRENT_CASE.toString(), null, currentCase));
} catch (InterruptedException | ExecutionException ex) {
if (ex instanceof ExecutionException) {
throw new CaseActionException(Bundle.Case_openException_couldNotOpenCase(ex.getCause().getMessage()), ex);
} else {
throw new CaseActionException(Bundle.Case_openException_couldNotOpenCase(Bundle.Case_exceptionMessage_lockAcquisitionInterrupted()), ex);
}
} catch (InterruptedException ex) {
throw new CaseActionException(Bundle.Case_exceptionMessage_wrapperMessage(ex.getMessage()), ex);
} catch (ExecutionException ex) {
throw new CaseActionException(Bundle.Case_exceptionMessage_wrapperMessage(ex.getCause().getMessage()), ex);
} catch (CancellationException ex) {
throw new CaseActionException(Bundle.Case_exceptionMessage_cancelled(), ex);
} finally {
progressIndicator.finish(Bundle.Case_progressMessage_finshing());
if (RuntimeProperties.runningWithGUI()) {
SwingUtilities.invokeLater(() -> ((ModalDialogProgressIndicator) progressIndicator).setVisible(false));
}
}
} catch (CaseMetadataException ex) {
throw new CaseActionException(Bundle.Case_openException_couldNotOpenCase(Bundle.Case_exceptionMessage_failedToReadMetadata()), ex);
}
}
/**
@ -763,14 +784,12 @@ public class Case {
listener.setCaseContext(caseContext);
progressIndicator.start(Bundle.Case_progressMessage_preparing());
logger.log(Level.INFO, "Closing case with metadata file path {0}", currentCase.getCaseMetadata().getFilePath()); //NON-NLS
try {
/*
* Closing a case is always done in the same non-UI thread that
* opened/created the case. If the case is a multi-user case, this
* ensures that case directory lock that is held as long as the case
* is open is released in the same thread in which it was acquired,
* as is required by the coordination service.
* ensures that case directory lock that is held as long as the case is
* open is released in the same thread in which it was acquired, as is
* required by the coordination service.
*/
Future<Void> future = getCaseLockingExecutor().submit(() -> {
if (CaseType.SINGLE_USER_CASE == currentCase.getCaseType()) {
@ -778,8 +797,8 @@ public class Case {
} else {
String caseName = currentCase.getCaseMetadata().getCaseName();
/*
* Acquire an exclusive case resources lock to ensure only
* one node at a time can create/open/upgrade/close the case
* Acquire an exclusive case resources lock to ensure only one
* node at a time can create/open/upgrade/close the case
* resources.
*/
progressIndicator.start(Bundle.Case_progressMessage_acquiringLocks());
@ -788,25 +807,34 @@ public class Case {
currentCase.close(progressIndicator);
} finally {
/*
* Always release the case directory lock that was
* acquired when the case was opened.
* Always release the case directory lock that was acquired
* when the case was opened.
*/
releaseSharedCaseDirLock(caseName);
}
}
return null;
});
/*
* If running with a GUI, give the future for the case closing task to
* the cancel button listener for the GUI progress indicator and make
* the progress indicator visible to the user.
*/
if (RuntimeProperties.runningWithGUI()) {
listener.setCaseActionFuture(future);
SwingUtilities.invokeLater(() -> ((ModalDialogProgressIndicator) progressIndicator).setVisible(true));
}
try {
logger.log(Level.INFO, "Closing case with metadata file path {0}", currentCase.getCaseMetadata().getFilePath()); //NON-NLS
future.get();
} catch (InterruptedException | ExecutionException ex) {
if (ex instanceof ExecutionException) {
throw new CaseActionException(Bundle.Case_closeException_couldNotCloseCase(ex.getCause().getMessage()), ex);
} else {
throw new CaseActionException(Bundle.Case_closeException_couldNotCloseCase(Bundle.Case_exceptionMessage_lockAcquisitionInterrupted()), ex);
}
} catch (InterruptedException ex) {
throw new CaseActionException(Bundle.Case_exceptionMessage_wrapperMessage(ex.getMessage()), ex);
} catch (ExecutionException ex) {
throw new CaseActionException(Bundle.Case_exceptionMessage_wrapperMessage(ex.getCause().getMessage()), ex);
} catch (CancellationException ex) {
throw new CaseActionException(Bundle.Case_exceptionMessage_cancelled(), ex);
} finally {
/*
* The case is no longer the current case, even if an exception was
@ -865,8 +893,6 @@ public class Case {
throw new CaseActionException(Bundle.Case_deleteException_couldNotDeleteCase(Bundle.Case_exceptionMessage_cannotDeleteCurrentCase()));
}
logger.log(Level.INFO, "Deleting case with metadata file path {0}", metadata.getFilePath()); //NON-NLS
try {
/*
* Set up either a GUI progress indicator or a logging progress
* indicator.
@ -884,6 +910,7 @@ public class Case {
progressIndicator = new LoggingProgressIndicator();
}
progressIndicator.start(Bundle.Case_progressMessage_preparing());
Future<Void> future = getCaseLockingExecutor().submit(() -> {
if (CaseType.SINGLE_USER_CASE == metadata.getCaseType()) {
cleanupDeletedCase(metadata, progressIndicator);
@ -906,10 +933,9 @@ public class Case {
if (CaseType.MULTI_USER_CASE == metadata.getCaseType()) {
/*
* Delete the case database from the database
* server. The case database for a single-user case
* is in the case directory and will be deleted whe
* it is deleted.
* Delete the case database from the database server.
* The case database for a single-user case is in the
* case directory and will be deleted whe it is deleted.
*/
progressIndicator.start(Bundle.Case_progressMessage_deletingCaseDatabase());
CaseDbConnectionInfo db = UserPreferences.getDatabaseConnectionInfo();
@ -926,14 +952,17 @@ public class Case {
}
return null;
});
logger.log(Level.INFO, "Deleted case with metadata file path {0}", metadata.getFilePath()); //NON-NLS
try {
logger.log(Level.INFO, "Deleting case with metadata file path {0}", metadata.getFilePath()); //NON-NLS
future.get();
} catch (InterruptedException | ExecutionException ex) {
if (ex instanceof ExecutionException) {
throw new CaseActionException(Bundle.Case_deleteException_couldNotDeleteCase(ex.getCause().getMessage()), ex);
} else {
throw new CaseActionException(Bundle.Case_deleteException_couldNotDeleteCase(Bundle.Case_exceptionMessage_lockAcquisitionInterrupted()), ex);
}
logger.log(Level.INFO, "Deleted case with metadata file path {0}", metadata.getFilePath()); //NON-NLS
} catch (InterruptedException ex) {
throw new CaseActionException(Bundle.Case_exceptionMessage_wrapperMessage(ex.getMessage()), ex);
} catch (ExecutionException ex) {
throw new CaseActionException(Bundle.Case_exceptionMessage_wrapperMessage(ex.getCause().getMessage()), ex);
} catch (CancellationException ex) {
throw new CaseActionException(Bundle.Case_exceptionMessage_cancelled(), ex);
}
}

View File

@ -24,7 +24,6 @@ import javax.swing.SwingUtilities;
import org.openide.DialogDescriptor;
import org.openide.DialogDisplayer;
import org.openide.util.HelpCtx;
import org.sleuthkit.autopsy.framework.ProgressIndicator;
/**
* A progress indicator that displays progress using a modal dialog with a
@ -51,6 +50,18 @@ public final class ModalDialogProgressIndicator implements ProgressIndicator {
dialog = DialogDisplayer.getDefault().createDialog(dialogDescriptor);
}
public ModalDialogProgressIndicator(String title, HelpCtx helpCtx) {
progressPanel = new ProgressPanel();
DialogDescriptor dialogDescriptor = new DialogDescriptor(
progressPanel,
title,
true,
DialogDescriptor.NO_OPTION,
null,
null);
dialog = DialogDisplayer.getDefault().createDialog(dialogDescriptor);
}
public void setVisible(boolean isVisible) {
this.dialog.setVisible(isVisible);
}

View File

@ -27,10 +27,10 @@ import org.sleuthkit.autopsy.framework.ProgressIndicator;
/**
* An implementation of the Autopsy service interface used for test purposes.
*/
//@ServiceProvider(service = AutopsyService.class)
@ServiceProvider(service = AutopsyService.class)
public class TestAutopsyService implements AutopsyService {
private static final Logger LOGGER = Logger.getLogger(TestAutopsyService.class.getName());
private static final Logger logger = Logger.getLogger(TestAutopsyService.class.getName());
@Override
public String getServiceName() {
@ -41,41 +41,31 @@ public class TestAutopsyService implements AutopsyService {
public void openCaseResources(CaseContext context) throws AutopsyServiceException {
ProgressIndicator progressIndicator = context.getProgressIndicator();
try {
progressIndicator.start("Doing first task...", 100);
logger.log(Level.INFO, "Test Autopsy Service started first task");
progressIndicator.start("Test Autopsy Service doing first task...", 100);
Thread.sleep(1000L);
progressIndicator.progress(10);
progressIndicator.progress(20);
Thread.sleep(1000L);
progressIndicator.progress(10);
progressIndicator.progress(40);
Thread.sleep(1000L);
progressIndicator.progress(10);
progressIndicator.progress(60);
Thread.sleep(1000L);
progressIndicator.progress(10);
progressIndicator.progress(80);
Thread.sleep(1000L);
progressIndicator.progress(10);
Thread.sleep(1000L);
progressIndicator.progress(10);
Thread.sleep(1000L);
progressIndicator.progress(10);
Thread.sleep(1000L);
progressIndicator.progress(10);
Thread.sleep(1000L);
progressIndicator.progress(10);
Thread.sleep(1000L);
progressIndicator.progress(10);
progressIndicator.finish("First task completed.");
progressIndicator.start("Doing second task...");
Thread.sleep(10000L);
progressIndicator.finish("Second task completed.");
} catch (InterruptedException ex) {
LOGGER.log(Level.INFO, "Autopsy Test Service caught interrupt while working");
progressIndicator.progress(100);
progressIndicator.finish("First task completed by Test Autopsy Service.");
logger.log(Level.INFO, "Test Autopsy Service completed first task");
logger.log(Level.INFO, "Test Autopsy Service started second task");
progressIndicator.start("Test Autopsy Service doing second task...");
for (int i = 0; i < 10000; ++i) {
if (context.cancelRequested()) {
progressIndicator.finish("Cancelling...");
try {
Thread.sleep(1000L);
} catch (InterruptedException ex1) {
LOGGER.log(Level.INFO, "Autopsy Test Service caught interrupt while working");
logger.log(Level.INFO, "Autopsy Test Service cancelled while doing second task, cancel requested = {0}", context.cancelRequested());
}
}
progressIndicator.finish("Second task completed by Test Autopsy Service.");
logger.log(Level.INFO, "Second task completed by Test Autopsy Service");
} catch (InterruptedException ex) {
logger.log(Level.INFO, "Autopsy Test Service interrupted (cancelled) while doing first task, cancel requested = {0}", context.cancelRequested());
}
}