19 package org.sleuthkit.autopsy.ingest;
 
   21 import com.google.common.eventbus.Subscribe;
 
   22 import com.google.common.util.concurrent.ThreadFactoryBuilder;
 
   23 import java.awt.EventQueue;
 
   24 import java.awt.GraphicsEnvironment;
 
   25 import java.beans.PropertyChangeEvent;
 
   26 import java.beans.PropertyChangeListener;
 
   27 import java.io.Serializable;
 
   28 import java.lang.reflect.InvocationTargetException;
 
   29 import java.util.ArrayList;
 
   30 import java.util.Collection;
 
   31 import java.util.Collections;
 
   32 import java.util.Date;
 
   33 import java.util.EnumSet;
 
   34 import java.util.HashMap;
 
   35 import java.util.HashSet;
 
   36 import java.util.List;
 
   38 import java.util.Optional;
 
   40 import java.util.concurrent.Callable;
 
   41 import java.util.concurrent.ConcurrentHashMap;
 
   42 import java.util.concurrent.ExecutorService;
 
   43 import java.util.concurrent.Executors;
 
   44 import java.util.concurrent.Future;
 
   45 import java.util.concurrent.atomic.AtomicLong;
 
   46 import java.util.logging.Level;
 
   47 import java.util.stream.Collectors;
 
   48 import java.util.stream.Stream;
 
   49 import javax.annotation.concurrent.GuardedBy;
 
   50 import javax.annotation.concurrent.Immutable;
 
   51 import javax.annotation.concurrent.ThreadSafe;
 
   52 import javax.swing.JOptionPane;
 
   53 import javax.swing.SwingUtilities;
 
   54 import org.netbeans.api.progress.ProgressHandle;
 
   55 import org.openide.util.Cancellable;
 
   56 import org.openide.util.NbBundle;
 
   57 import org.openide.windows.WindowManager;
 
  130     @GuardedBy(
"IngestManager.class")
 
  134     private final ExecutorService 
startIngestJobsExecutor = Executors.newSingleThreadExecutor(new ThreadFactoryBuilder().setNameFormat("IM-start-ingest-jobs-%d").build()); 
 
  143     private final ExecutorService 
eventPublishingExecutor = Executors.newSingleThreadExecutor(new ThreadFactoryBuilder().setNameFormat("IM-ingest-events-%d").build()); 
 
  162         if (null == instance) {
 
  164             instance.subscribeToServiceMonitorEvents();
 
  165             instance.subscribeToCaseEvents();
 
  204         PropertyChangeListener propChangeListener = (PropertyChangeEvent evt) -> {
 
  219                 logger.log(Level.SEVERE, 
"Service {0} is down, cancelling all running ingest jobs", serviceDisplayName); 
 
  221                     EventQueue.invokeLater(
new Runnable() {
 
  224                             JOptionPane.showMessageDialog(WindowManager.getDefault().getMainWindow(),
 
  225                                     NbBundle.getMessage(this.getClass(), 
"IngestManager.cancellingIngest.msgDlg.text"),
 
  226                                     NbBundle.getMessage(this.getClass(), 
"IngestManager.serviceIsDown.msgDlg.text", serviceDisplayName),
 
  227                                     JOptionPane.ERROR_MESSAGE);
 
  241         Set<String> servicesList = 
new HashSet<>();
 
  253             if (event.getNewValue() != null) {
 
  269     void handleCaseOpened() {
 
  274             String channelPrefix = openedCase.
getName();
 
  280         } 
catch (NoCurrentCaseException | AutopsyEventException ex) {
 
  281             logger.log(Level.SEVERE, 
"Failed to open remote events channel", ex); 
 
  282             MessageNotifyUtil.Notify.error(NbBundle.getMessage(
IngestManager.class, 
"IngestManager.OpenEventChannel.Fail.Title"),
 
  283                     NbBundle.getMessage(
IngestManager.class, 
"IngestManager.OpenEventChannel.Fail.ErrMsg"));
 
  294     void handleArtifactsPosted(Blackboard.ArtifactsPostedEvent tskEvent) {
 
  299         List<DataArtifact> newDataArtifacts = 
new ArrayList<>();
 
  300         List<AnalysisResult> newAnalysisResults = 
new ArrayList<>();
 
  301         Collection<BlackboardArtifact> newArtifacts = tskEvent.getArtifacts();
 
  302         for (BlackboardArtifact artifact : newArtifacts) {
 
  303             if (artifact instanceof DataArtifact) {
 
  304                 newDataArtifacts.add((DataArtifact) artifact);
 
  306                 newAnalysisResults.add((AnalysisResult) artifact);
 
  309         if (!newDataArtifacts.isEmpty() || !newAnalysisResults.isEmpty()) {
 
  310             IngestJob ingestJob = null;
 
  311             Optional<Long> ingestJobId = tskEvent.getIngestJobId();
 
  312             if (ingestJobId.isPresent()) {
 
  371                 BlackboardArtifact artifact = newArtifacts.iterator().next();
 
  372                 if (artifact != null) {
 
  374                         Content artifactDataSource = artifact.getDataSource();
 
  377                                 Content dataSource = job.getDataSource();
 
  378                                 if (artifactDataSource.getId() == dataSource.getId()) {
 
  384                     } 
catch (TskCoreException ex) {
 
  385                         logger.log(Level.SEVERE, String.format(
"Failed to get data source for blackboard artifact (object ID = %d)", artifact.getId()), ex); 
 
  389             if (ingestJob != null) {
 
  390                 if (!newDataArtifacts.isEmpty()) {
 
  391                     ingestJob.addDataArtifacts(newDataArtifacts);
 
  393                 if (!newAnalysisResults.isEmpty()) {
 
  394                     ingestJob.addAnalysisResults(newAnalysisResults);
 
  403         for (BlackboardArtifact.Type artifactType : tskEvent.getArtifactTypes()) {
 
  404             ModuleDataEvent legacyEvent = 
new ModuleDataEvent(tskEvent.getModuleName(), artifactType, tskEvent.getArtifacts(artifactType));
 
  405             AutopsyEvent autopsyEvent = 
new BlackboardPostEvent(legacyEvent);
 
  419     void handleCaseClosed() {
 
  425         Case.getCurrentCase().getSleuthkitCase().unregisterForEvents(
this);
 
  445             throw new IllegalArgumentException(
"dataSource argument does not implement the DataSource interface"); 
 
  448         IngestJobInputStream stream = 
new IngestJobInputStream(job);
 
  449         if (stream.getIngestJobStartResult().getJob() != null) {
 
  451         } 
else if (stream.getIngestJobStartResult().getModuleErrors().isEmpty()) {
 
  452             for (
IngestModuleError error : stream.getIngestJobStartResult().getModuleErrors()) {
 
  453                 logger.log(Level.SEVERE, String.format(
"%s ingest module startup error for %s", error.getModuleDisplayName(), dataSource.getName()), error.getThrowable());
 
  457             throw new TskCoreException(
"Error starting ingest modules", stream.getIngestJobStartResult().getStartupException());
 
  479             List<AbstractFile> emptyFilesSubset = 
new ArrayList<>();
 
  480             for (
Content dataSource : dataSources) {
 
  497             throw new IllegalArgumentException(
"dataSource argument does not implement the DataSource interface"); 
 
  501             if (job.hasIngestPipeline()) {
 
  526         List<DataSource> verifiedDataSources = 
new ArrayList<>();
 
  527         for (
Content content : dataSources) {
 
  529                 throw new IllegalArgumentException(
"Content object in dataSources argument does not implement the DataSource interface"); 
 
  531             DataSource verifiedDataSource = (DataSource) content;
 
  532             verifiedDataSources.add(verifiedDataSource);
 
  536             for (
DataSource dataSource : verifiedDataSources) {
 
  537                 List<IngestJob> startedJobs = 
new ArrayList<>();
 
  539                 if (job.hasIngestPipeline()) {
 
  540                     startResult = startIngestJob(job);
 
  542                         startedJobs.add(job);
 
  544                         for (
IngestJob jobToCancel : startedJobs) {
 
  569         "IngestManager.startupErr.dlgTitle=Ingest Module Startup Failure",
 
  570         "IngestManager.startupErr.dlgMsg=Unable to start up one or more ingest modules, ingest cancelled.",
 
  571         "IngestManager.startupErr.dlgSolution=Please disable the failed modules or fix the errors before restarting ingest.",
 
  572         "IngestManager.startupErr.dlgErrorList=Errors:" 
  574     IngestJobStartResult startIngestJob(IngestJob job) {
 
  578         if (!GraphicsEnvironment.isHeadless()) {
 
  579             if (SwingUtilities.isEventDispatchThread()) {
 
  580                 initIngestMessageInbox();
 
  583                     SwingUtilities.invokeAndWait(() -> initIngestMessageInbox());
 
  584                 } 
catch (InterruptedException ex) {
 
  586                 } 
catch (InvocationTargetException ex) {
 
  587                     logger.log(Level.WARNING, 
"There was an error starting ingest message inbox", ex);
 
  592         List<IngestModuleError> errors = null;
 
  595             openCase = Case.getCurrentCaseThrows();
 
  596         } 
catch (NoCurrentCaseException ex) {
 
  597             return new IngestJobStartResult(null, 
new IngestManagerException(
"Exception while getting open case.", ex), Collections.<IngestModuleError>emptyList()); 
 
  599         if (openCase.getCaseType() == Case.CaseType.MULTI_USER_CASE) {
 
  602                     if (RuntimeProperties.runningWithGUI()) {
 
  603                         EventQueue.invokeLater(
new Runnable() {
 
  606                                 String serviceDisplayName = ServicesMonitor.Service.REMOTE_CASE_DATABASE.getDisplayName();
 
  607                                 JOptionPane.showMessageDialog(WindowManager.getDefault().getMainWindow(),
 
  608                                         NbBundle.getMessage(this.getClass(), 
"IngestManager.cancellingIngest.msgDlg.text"),
 
  609                                         NbBundle.getMessage(this.getClass(), 
"IngestManager.serviceIsDown.msgDlg.text", serviceDisplayName),
 
  610                                         JOptionPane.ERROR_MESSAGE);
 
  614                     return new IngestJobStartResult(null, 
new IngestManagerException(
"Ingest aborted. Remote database is down"), Collections.<IngestModuleError>emptyList()); 
 
  616             } 
catch (ServicesMonitor.ServicesMonitorException ex) {
 
  617                 return new IngestJobStartResult(null, 
new IngestManagerException(
"Database server is down", ex), Collections.<IngestModuleError>emptyList()); 
 
  628         IngestManager.logger.log(Level.INFO, String.format(
"Starting ingest job %d at %s", job.getId(), 
new Date().getTime())); 
 
  630             errors = job.start();
 
  631         } 
catch (InterruptedException ex) {
 
  632             return new IngestJobStartResult(null, 
new IngestManagerException(
"Interrupted while starting ingest", ex), errors); 
 
  634         if (errors.isEmpty()) {
 
  635             this.fireIngestJobStarted(job.getId());
 
  640             for (IngestModuleError error : errors) {
 
  641                 logger.log(Level.SEVERE, String.format(
"Error starting %s ingest module for job %d", error.getModuleDisplayName(), job.getId()), error.getThrowable()); 
 
  643             IngestManager.logger.log(Level.SEVERE, 
"Ingest job {0} could not be started", job.getId()); 
 
  644             if (RuntimeProperties.runningWithGUI()) {
 
  645                 final StringBuilder message = 
new StringBuilder(1024);
 
  646                 message.append(Bundle.IngestManager_startupErr_dlgMsg()).append(
"\n"); 
 
  647                 message.append(Bundle.IngestManager_startupErr_dlgSolution()).append(
"\n\n"); 
 
  648                 message.append(Bundle.IngestManager_startupErr_dlgErrorList()).append(
"\n"); 
 
  649                 for (IngestModuleError error : errors) {
 
  650                     String moduleName = error.getModuleDisplayName();
 
  651                     String errorMessage = error.getThrowable().getLocalizedMessage();
 
  652                     message.append(moduleName).append(
": ").append(errorMessage).append(
"\n"); 
 
  654                 message.append(
"\n\n");
 
  655                 EventQueue.invokeLater(() -> {
 
  656                     JOptionPane.showMessageDialog(WindowManager.getDefault().getMainWindow(), message, Bundle.IngestManager_startupErr_dlgTitle(), JOptionPane.ERROR_MESSAGE);
 
  659             return new IngestJobStartResult(null, 
new IngestManagerException(
"Errors occurred while starting ingest"), errors); 
 
  662         return new IngestJobStartResult(job, null, errors);
 
  670     void finishIngestJob(IngestJob job) {
 
  671         long jobId = job.getId();
 
  675         if (!job.isCancelled()) {
 
  676             IngestManager.logger.log(Level.INFO, String.format(
"Ingest job %d completed at %s", job.getId(), 
new Date().getTime())); 
 
  677             fireIngestJobCompleted(jobId);
 
  679             IngestManager.logger.log(Level.INFO, String.format(
"Ingest job %d cancelled at %s", job.getId(), 
new Date().getTime())); 
 
  680             fireIngestJobCancelled(jobId);
 
  803     void fireIngestJobStarted(
long ingestJobId) {
 
  813     void fireIngestJobCompleted(
long ingestJobId) {
 
  814         AutopsyEvent 
event = 
new AutopsyEvent(IngestJobEvent.COMPLETED.toString(), ingestJobId, null);
 
  823     void fireIngestJobCancelled(
long ingestJobId) {
 
  824         AutopsyEvent 
event = 
new AutopsyEvent(IngestJobEvent.CANCELLED.toString(), ingestJobId, null);
 
  835     void fireDataSourceAnalysisStarted(
long ingestJobId, Content dataSource) {
 
  836         AutopsyEvent 
event = 
new DataSourceAnalysisStartedEvent(ingestJobId, dataSource);
 
  847     void fireDataSourceAnalysisCompleted(
long ingestJobId, Content dataSource) {
 
  848         AutopsyEvent 
event = 
new DataSourceAnalysisCompletedEvent(ingestJobId, dataSource, DataSourceAnalysisCompletedEvent.Reason.ANALYSIS_COMPLETED);
 
  859     void fireDataSourceAnalysisCancelled(
long ingestJobId, Content dataSource) {
 
  860         AutopsyEvent 
event = 
new DataSourceAnalysisCompletedEvent(ingestJobId, dataSource, DataSourceAnalysisCompletedEvent.Reason.ANALYSIS_CANCELLED);
 
  870     void fireFileIngestDone(AbstractFile file) {
 
  871         AutopsyEvent 
event = 
new FileAnalyzedEvent(file);
 
  882     void fireIngestModuleContentEvent(ModuleContentEvent moduleContentEvent) {
 
  883         AutopsyEvent 
event = 
new ContentChangedEvent(moduleContentEvent);
 
  895     void initIngestMessageInbox() {
 
  906     void postIngestMessage(IngestMessage message) {
 
  909                 if (message.getMessageType() != IngestMessage.MessageType.ERROR && message.getMessageType() != IngestMessage.MessageType.WARNING) {
 
  913                     if (errorPosts <= MAX_ERROR_MESSAGE_POSTS) {
 
  915                     } 
else if (errorPosts == MAX_ERROR_MESSAGE_POSTS + 1) {
 
  916                         IngestMessage errorMessageLimitReachedMessage = IngestMessage.createErrorMessage(
 
  917                                 NbBundle.getMessage(
this.getClass(), 
"IngestManager.IngestMessage.ErrorMessageLimitReached.title"),
 
  918                                 NbBundle.getMessage(
this.getClass(), 
"IngestManager.IngestMessage.ErrorMessageLimitReached.subject"),
 
  919                                 NbBundle.getMessage(
this.getClass(), 
"IngestManager.IngestMessage.ErrorMessageLimitReached.msg", 
MAX_ERROR_MESSAGE_POSTS));
 
  949     void setIngestTaskProgress(IngestTask task, String currentModuleName) {
 
  951         IngestThreadActivitySnapshot newSnap = 
new IngestThreadActivitySnapshot(task.getThreadId(), task.getIngestJobExecutor().getIngestJobId(), currentModuleName, task.getDataSource(), task.getContentName());
 
  953         incrementModuleRunTime(prevSnap.getModuleDisplayName(), newSnap.getStartTime().getTime() - prevSnap.getStartTime().getTime());
 
  963     void setIngestTaskProgressCompleted(IngestTask task) {
 
  965         IngestThreadActivitySnapshot newSnap = 
new IngestThreadActivitySnapshot(task.getThreadId());
 
  967         incrementModuleRunTime(prevSnap.getModuleDisplayName(), newSnap.getStartTime().getTime() - prevSnap.getStartTime().getTime());
 
  976     void incrementModuleRunTime(String moduleDisplayName, Long duration) {
 
  977         if (moduleDisplayName.equals(
"IDLE")) { 
 
  984             if (prevTimeL != null) {
 
  985                 prevTime = prevTimeL;
 
  987             prevTime += duration;
 
 1023         List<IngestJobProgressSnapshot> snapShots = 
new ArrayList<>();
 
 1027                 if (snapshot != null) {
 
 1028                     snapShots.add(snapshot);
 
 1041     long getFreeDiskSpace() {
 
 1066                 if (Thread.currentThread().isInterrupted()) {
 
 1074                     final String displayName = NbBundle.getMessage(this.getClass(), 
"IngestManager.StartIngestJobsTask.run.displayName");
 
 1075                     this.progress = ProgressHandle.createHandle(displayName, 
new Cancellable() {
 
 1077                         public boolean cancel() {
 
 1078                             if (progress != null) {
 
 1079                                 progress.setDisplayName(NbBundle.getMessage(
this.getClass(), 
"IngestManager.StartIngestJobsTask.run.cancelling", displayName));
 
 1083                                 handle.cancel(
true);
 
 1091                 startIngestJob(job);
 
 1095                 if (null != progress) {
 
 1112         private final BlockingIngestTaskQueue 
tasks;
 
 1123                     IngestTask task = tasks.getNextTask(); 
 
 1124                     task.execute(threadId);
 
 1125                 } 
catch (InterruptedException ex) {
 
 1128                 if (Thread.currentThread().isInterrupted()) {
 
 1184             startTime = 
new Date();
 
 1185             this.moduleDisplayName = NbBundle.getMessage(this.getClass(), 
"IngestManager.IngestThreadActivitySnapshot.idleThread");
 
 1186             this.dataSourceName = 
"";
 
 1207             startTime = 
new Date();
 
 1209             this.dataSourceName = dataSource.
getName();
 
 1228         IngestThreadActivitySnapshot(
long threadId, 
long jobId, String moduleDisplayName, 
Content dataSource, String fileName) {
 
 1231             startTime = 
new Date();
 
 1233             this.dataSourceName = dataSource.
getName();
 
 1243         long getIngestJobId() {
 
 1252         long getThreadId() {
 
 1261         Date getStartTime() {
 
 1262             return new Date(startTime.getTime());
 
 1270         String getModuleDisplayName() {
 
 1280         String getDataSourceName() {
 
 1289         String getFileName() {
 
 1371         private static final long serialVersionUID = 1L;
 
 1389             super(message, cause);
 
final ConcurrentHashMap< String, Long > ingestModuleRunTimes
void addIngestModuleEventListener(Set< IngestModuleEvent > eventTypes, final PropertyChangeListener listener)
final Map< Long, Future< Void > > startIngestJobFutures
List< IngestJobProgressSnapshot > getIngestJobSnapshots()
String getServiceStatus(String service)
void removeIngestModuleEventListener(final PropertyChangeListener listener)
IngestStream openIngestStream(DataSource dataSource, IngestJobSettings settings)
final String moduleDisplayName
static final String INGEST_MODULE_EVENT_CHANNEL_NAME
List< IngestThreadActivitySnapshot > getIngestThreadActivitySnapshots()
void queueIngestJob(Content dataSource, List< AbstractFile > files, IngestJobSettings settings)
IngestManagerException(String message, Throwable cause)
static synchronized IngestManager getInstance()
static IngestManager instance
final String dataSourceName
void removeIngestModuleEventListener(Set< IngestModuleEvent > eventTypes, final PropertyChangeListener listener)
final ExecutorService dataSourceLevelIngestJobTasksExecutor
static boolean runningWithGUI
void cancelAllIngestJobs()
void publish(AutopsyEvent event)
final ExecutorService dataArtifactIngestTasksExecutor
static void addPropertyChangeListener(final PropertyChangeListener listener)
IngestJobStartResult beginIngestJob(Collection< Content > dataSources, IngestJobSettings settings)
static final Logger logger
final ExecutorService eventPublishingExecutor
void clearIngestMessageBox()
void addSubscriber(Set< String > eventNames, PropertyChangeListener subscriber)
void subscribeToServiceMonitorEvents()
boolean isIngestRunning()
DATA_SOURCE_ANALYSIS_COMPLETED
static void removePropertyChangeListener(final PropertyChangeListener listener)
static final Set< String > INGEST_MODULE_EVENT_NAMES
volatile IngestMessageTopComponent ingestMessageBox
void addSubscriber(PropertyChangeListener subscriber)
final AutopsyEventPublisher publisher
synchronized void closeRemoteEventChannel()
Map< String, Long > getModuleRunTimes()
final ServicesMonitor servicesMonitor
void removeIngestJobEventListener(final PropertyChangeListener listener)
void addIngestJobEventListener(Set< IngestJobEvent > eventTypes, final PropertyChangeListener listener)
final BlockingIngestTaskQueue tasks
static final String INGEST_JOB_EVENT_CHANNEL_NAME
List< IngestModuleError > getModuleErrors()
final ExecutorService analysisResultIngestTasksExecutor
static final Set< String > INGEST_JOB_EVENT_NAMES
final AutopsyEventPublisher moduleEventPublisher
static int numberOfFileIngestThreads()
synchronized void openRemoteEventChannel(String channelName)
void addIngestJobEventListener(final PropertyChangeListener listener)
final Object ingestMessageBoxLock
INGEST_MODULES_STARTUP_FAILED
IngestManagerException(String message)
SleuthkitCase getSleuthkitCase()
void queueIngestJob(Collection< Content > dataSources, IngestJobSettings settings)
void removeSubscriber(Set< String > eventNames, PropertyChangeListener subscriber)
static final int MAX_ERROR_MESSAGE_POSTS
final AtomicLong ingestErrorMessagePosts
volatile boolean caseIsOpen
final AtomicLong nextIngestManagerTaskId
int getNumberOfFileIngestThreads()
void addIngestModuleEventListener(final PropertyChangeListener listener)
synchronized static Logger getLogger(String name)
DATA_SOURCE_ANALYSIS_STARTED
static Case getCurrentCaseThrows()
static void addEventTypeSubscriber(Set< Events > eventTypes, PropertyChangeListener subscriber)
synchronized IngestJob startIngestJob(Collection< Content > dataSources, IngestJobSettings settings)
final ConcurrentHashMap< Long, IngestThreadActivitySnapshot > ingestThreadActivitySnapshots
void cancelAllIngestJobs(IngestJob.CancellationReason reason)
final IngestMonitor ingestMonitor
IngestManager.IngestManagerException getStartupException()
final ExecutorService fileLevelIngestJobTasksExecutor
final Map< Long, IngestJob > ingestJobsById
void subscribeToCaseEvents()
final ExecutorService startIngestJobsExecutor
void removeIngestJobEventListener(Set< IngestJobEvent > eventTypes, final PropertyChangeListener listener)
final int numberOfFileIngestThreads
void registerForEvents(Object listener)
static final long serialVersionUID
final AutopsyEventPublisher jobEventPublisher