19 package org.sleuthkit.autopsy.keywordsearch;
21 import com.google.common.util.concurrent.ThreadFactoryBuilder;
22 import java.util.ArrayList;
23 import java.util.Collections;
24 import java.util.HashMap;
25 import java.util.HashSet;
26 import java.util.Iterator;
27 import java.util.List;
29 import java.util.Map.Entry;
31 import java.util.concurrent.CancellationException;
32 import java.util.concurrent.ConcurrentHashMap;
33 import java.util.concurrent.ExecutionException;
34 import java.util.concurrent.Future;
35 import java.util.concurrent.ScheduledThreadPoolExecutor;
36 import static java.util.concurrent.TimeUnit.MILLISECONDS;
37 import java.util.concurrent.atomic.AtomicLong;
38 import java.util.logging.Level;
39 import javax.annotation.concurrent.GuardedBy;
40 import javax.swing.SwingUtilities;
41 import javax.swing.SwingWorker;
42 import org.netbeans.api.progress.ProgressHandle;
43 import org.openide.util.Cancellable;
44 import org.openide.util.NbBundle;
45 import org.openide.util.NbBundle.Messages;
62 final class IngestSearchRunner {
64 private static final Logger logger = Logger.
getLogger(IngestSearchRunner.class.getName());
65 private static IngestSearchRunner instance = null;
66 private final IngestServices services = IngestServices.getInstance();
67 private Ingester ingester = null;
68 private long currentUpdateIntervalMs;
69 private volatile boolean periodicSearchTaskRunning;
70 private volatile Future<?> periodicSearchTaskHandle;
71 private final ScheduledThreadPoolExecutor periodicSearchTaskExecutor;
72 private static final int NUM_SEARCH_SCHEDULING_THREADS = 1;
73 private static final String SEARCH_SCHEDULER_THREAD_NAME =
"periodic-search-scheduling-%d";
74 private final Map<Long, SearchJobInfo> jobs =
new ConcurrentHashMap<>();
75 private final boolean usingNetBeansGUI = RuntimeProperties.runningWithGUI();
84 private IngestSearchRunner() {
85 currentUpdateIntervalMs = ((long) KeywordSearchSettings.getUpdateFrequency().getTime()) * 60 * 1000;
86 ingester = Ingester.getDefault();
87 periodicSearchTaskExecutor =
new ScheduledThreadPoolExecutor(NUM_SEARCH_SCHEDULING_THREADS,
new ThreadFactoryBuilder().setNameFormat(SEARCH_SCHEDULER_THREAD_NAME).build());
95 public static synchronized IngestSearchRunner getInstance() {
96 if (instance == null) {
97 instance =
new IngestSearchRunner();
109 public synchronized void startJob(IngestJobContext jobContext, List<String> keywordListNames) {
110 long jobId = jobContext.getJobId();
111 if (jobs.containsKey(jobId) ==
false) {
112 SearchJobInfo jobData =
new SearchJobInfo(jobContext, keywordListNames);
113 jobs.put(jobId, jobData);
121 jobs.get(jobId).incrementModuleReferenceCount();
126 if ((jobs.size() > 0) && (periodicSearchTaskRunning ==
false)) {
127 currentUpdateIntervalMs = ((long) KeywordSearchSettings.getUpdateFrequency().getTime()) * 60 * 1000;
128 periodicSearchTaskHandle = periodicSearchTaskExecutor.schedule(
new PeriodicSearchTask(), currentUpdateIntervalMs, MILLISECONDS);
129 periodicSearchTaskRunning =
true;
138 public synchronized void endJob(
long jobId) {
144 job = jobs.get(jobId);
148 if (job.decrementModuleReferenceCount() != 0) {
157 logger.log(Level.INFO,
"Commiting search index before final search for search job {0}", job.getJobId());
159 logger.log(Level.INFO,
"Starting final search for search job {0}", job.getJobId());
161 logger.log(Level.INFO,
"Final search for search job {0} completed", job.getJobId());
163 if (jobs.isEmpty()) {
164 cancelPeriodicSearchSchedulingTask();
173 public synchronized void stopJob(
long jobId) {
174 logger.log(Level.INFO,
"Stopping search job {0}", jobId);
178 job = jobs.get(jobId);
187 IngestSearchRunner.Searcher currentSearcher = job.getCurrentSearcher();
188 if ((currentSearcher != null) && (!currentSearcher.isDone())) {
189 logger.log(Level.INFO,
"Cancelling search job {0}", jobId);
190 currentSearcher.cancel(
true);
195 if (jobs.isEmpty()) {
196 cancelPeriodicSearchSchedulingTask();
207 public synchronized void addKeywordListsToAllJobs(List<String> keywordListNames) {
208 for (String listName : keywordListNames) {
209 logger.log(Level.INFO,
"Adding keyword list {0} to all jobs", listName);
210 for (SearchJobInfo j : jobs.values()) {
211 j.addKeywordListName(listName);
221 private void commit() {
230 final int numIndexedFiles = KeywordSearch.getServer().queryNumIndexedFiles();
231 KeywordSearch.fireNumIndexedFilesChange(null, numIndexedFiles);
232 }
catch (NoOpenCoreException | KeywordSearchModuleException ex) {
233 logger.log(Level.SEVERE,
"Error executing Solr query for number of indexed files", ex);
243 private void doFinalSearch(SearchJobInfo job) {
244 if (!job.getKeywordListNames().isEmpty()) {
250 job.waitForCurrentWorker();
251 IngestSearchRunner.Searcher finalSearcher =
new IngestSearchRunner.Searcher(job,
true);
252 job.setCurrentSearcher(finalSearcher);
257 finalSearcher.doInBackground();
258 }
catch (InterruptedException | CancellationException ex) {
259 logger.log(Level.INFO,
"Final search for search job {0} interrupted or cancelled", job.getJobId());
260 }
catch (Exception ex) {
261 logger.log(Level.SEVERE, String.format(
"Final search for search job %d failed", job.getJobId()), ex);
269 private synchronized void cancelPeriodicSearchSchedulingTask() {
270 if (periodicSearchTaskHandle != null) {
271 logger.log(Level.INFO,
"No more search jobs, stopping periodic search scheduling");
272 periodicSearchTaskHandle.cancel(
true);
273 periodicSearchTaskRunning =
false;
290 if (jobs.isEmpty() || periodicSearchTaskHandle.isCancelled()) {
291 logger.log(Level.INFO,
"Periodic search scheduling task has been cancelled, exiting");
292 periodicSearchTaskRunning =
false;
310 for (Iterator<Entry<Long, SearchJobInfo>> iterator = jobs.entrySet().iterator(); iterator.hasNext();) {
313 if (periodicSearchTaskHandle.isCancelled()) {
314 logger.log(Level.INFO,
"Periodic search scheduling task has been cancelled, exiting");
315 periodicSearchTaskRunning =
false;
320 logger.log(Level.INFO,
"Starting periodic search for search job {0}", job.
getJobId());
327 }
catch (InterruptedException | ExecutionException ex) {
328 logger.log(Level.SEVERE, String.format(
"Error performing keyword search for ingest job %d", job.
getJobId()), ex);
331 NbBundle.getMessage(this.getClass(),
"SearchRunner.Searcher.done.err.msg"), ex.getMessage()));
332 }
catch (java.util.concurrent.CancellationException ex) {
333 logger.log(Level.SEVERE, String.format(
"Keyword search for ingest job %d cancelled", job.
getJobId()), ex);
338 logger.log(Level.INFO,
"Periodic searches for all ingest jobs cumulatively took {0} secs", stopWatch.
getElapsedTimeSecs());
340 periodicSearchTaskHandle = periodicSearchTaskExecutor.schedule(
new PeriodicSearchTask(), currentUpdateIntervalMs, MILLISECONDS);
352 if (lastSerchTimeSec * 1000 < currentUpdateIntervalMs / 4) {
355 currentUpdateIntervalMs *= 2;
356 logger.log(Level.WARNING,
"Last periodic search took {0} sec. Increasing search interval to {1} sec",
new Object[]{lastSerchTimeSec, currentUpdateIntervalMs / 1000});
371 private final List<String> keywordListNames;
381 dataSourceId = jobContext.getDataSource().getId();
383 currentResults =
new HashMap<>();
384 workerRunning =
false;
385 currentSearcher = null;
405 if (!keywordListNames.contains(keywordListName)) {
406 keywordListNames.add(keywordListName);
423 workerRunning = flag;
449 while (workerRunning) {
450 logger.log(Level.INFO, String.format(
"Waiting for previous search task for job %d to finish", jobId));
452 logger.log(Level.INFO, String.format(
"Notified previous search task for job %d to finish", jobId));
462 workerRunning =
false;
474 private final class Searcher extends SwingWorker<Object, Void> {
491 keywordListNames = job.getKeywordListNames();
492 keywords =
new ArrayList<>();
493 keywordToList =
new HashMap<>();
494 keywordLists =
new ArrayList<>();
498 @Messages(
"SearchRunner.query.exception.msg=Error performing query:")
501 if (usingNetBeansGUI) {
515 SwingUtilities.invokeAndWait(() -> {
516 final String displayName = NbBundle.getMessage(this.getClass(),
"KeywordSearchIngestModule.doInBackGround.displayName")
517 + (
finalRun ? (
" - " + NbBundle.getMessage(this.getClass(),
"KeywordSearchIngestModule.doInBackGround.finalizeMsg")) :
"");
520 public boolean cancel() {
522 progressIndicator.setDisplayName(displayName +
" " + NbBundle.getMessage(
this.getClass(),
"SearchRunner.doInBackGround.cancelMsg"));
524 logger.log(Level.INFO,
"Search cancelled by user");
526 IngestSearchRunner.Searcher.this.cancel(
true);
537 for (
Keyword keyword : keywords) {
539 logger.log(Level.INFO,
"Cancellation requested, exiting before new keyword processed: {0}", keyword.getSearchTerm());
543 KeywordList keywordList = keywordToList.get(keyword);
544 if (usingNetBeansGUI) {
545 String searchTermStr = keyword.getSearchTerm();
546 if (searchTermStr.length() > 50) {
547 searchTermStr = searchTermStr.substring(0, 49) +
"...";
549 final String progressMessage = keywordList.getName() +
": " + searchTermStr;
550 SwingUtilities.invokeLater(() -> {
558 KeywordSearchQuery keywordSearchQuery = KeywordSearchUtil.getQueryForKeyword(keyword, keywordList);
559 KeywordQueryFilter dataSourceFilter =
new KeywordQueryFilter(KeywordQueryFilter.FilterType.DATA_SOURCE, job.
getDataSourceId());
560 keywordSearchQuery.addFilter(dataSourceFilter);
563 QueryResults queryResults;
565 queryResults = keywordSearchQuery.performQuery();
567 logger.log(Level.SEVERE,
"Error performing query: " + keyword.getSearchTerm(), ex);
568 if (usingNetBeansGUI) {
569 final String userMessage = Bundle.SearchRunner_query_exception_msg() + keyword.getSearchTerm();
570 SwingUtilities.invokeLater(() -> {
578 }
catch (CancellationException e) {
579 logger.log(Level.INFO,
"Cancellation requested, exiting during keyword query: {0}", keyword.getSearchTerm());
587 if (!newResults.getKeywords().isEmpty()) {
589 newResults.process(
this, keywordList.getIngestMessages(),
true, job.
getJobId());
592 }
catch (Exception ex) {
593 logger.log(Level.SEVERE, String.format(
"Error performing keyword search for ingest job %d", job.
getJobId()), ex);
596 SwingUtilities.invokeLater(
new Runnable() {
615 XmlKeywordSearchList loader = XmlKeywordSearchList.getCurrent();
618 keywordToList.clear();
619 keywordLists.clear();
621 for (String name : keywordListNames) {
623 keywordLists.add(list);
626 keywordToList.put(k, list);
650 QueryResults newResults =
new QueryResults(queryResult.getQuery());
653 for (
Keyword keyword : queryResult.getKeywords()) {
656 List<KeywordHit> queryTermResults = queryResult.getResults(keyword);
660 Collections.sort(queryTermResults);
664 List<KeywordHit> newUniqueHits =
new ArrayList<>();
669 if (curTermResults == null) {
672 curTermResults =
new HashSet<>();
676 for (KeywordHit hit : queryTermResults) {
677 if (curTermResults.contains(hit.getSolrObjectId())) {
685 newUniqueHits.add(hit);
689 curTermResults.add(hit.getSolrObjectId());
698 newResults.addResult(keyword, newUniqueHits);
final List< String > keywordListNames
long getElapsedTimeSecs()
synchronized IngestSearchRunner.Searcher getCurrentSearcher()
static IngestMessage createErrorMessage(String source, String subject, String detailsHtml)
synchronized void addKeywordListName(String keywordListName)
final List< String > keywordListNames
void incrementModuleReferenceCount()
synchronized void setCurrentSearcher(IngestSearchRunner.Searcher searchRunner)
synchronized List< String > getKeywordListNames()
Logger getLogger(String moduleDisplayName)
final Map< Keyword, KeywordList > keywordToList
long decrementModuleReferenceCount()
boolean isWorkerRunning()
IngestJobContext getJobContext()
void setWorkerRunning(boolean flag)
final AtomicLong moduleReferenceCount
void waitForCurrentWorker()
List< Keyword > getKeywords()
final List< Keyword > keywords
synchronized void addKeywordResults(Keyword k, Set< Long > resultsIDs)
final IngestJobContext jobContext
boolean fileIngestIsCancelled()
ProgressHandle progressIndicator
QueryResults filterResults(QueryResults queryResult)
final Object finalSearchLock
synchronized Set< Long > currentKeywordResults(Keyword k)
static void error(String title, String message)
final List< KeywordList > keywordLists
void recalculateUpdateIntervalTime(long lastSerchTimeSec)
IngestSearchRunner.Searcher currentSearcher
volatile boolean workerRunning
final Map< Keyword, Set< Long > > currentResults