19 package org.sleuthkit.autopsy.keywordsearch;
 
   21 import java.util.ArrayList;
 
   22 import java.util.Collections;
 
   23 import java.util.HashMap;
 
   24 import java.util.HashSet;
 
   25 import java.util.List;
 
   27 import java.util.Map.Entry;
 
   29 import java.util.Timer;
 
   30 import java.util.TimerTask;
 
   31 import java.util.concurrent.CancellationException;
 
   32 import java.util.concurrent.ExecutionException;
 
   33 import java.util.concurrent.atomic.AtomicLong;
 
   34 import java.util.logging.Level;
 
   35 import javax.swing.SwingUtilities;
 
   36 import javax.swing.SwingWorker;
 
   37 import org.netbeans.api.progress.aggregate.AggregateProgressFactory;
 
   38 import org.netbeans.api.progress.aggregate.AggregateProgressHandle;
 
   39 import org.netbeans.api.progress.aggregate.ProgressContributor;
 
   40 import org.openide.util.Cancellable;
 
   41 import org.openide.util.NbBundle;
 
   42 import org.openide.util.NbBundle.Messages;
 
   63     private Map<Long, SearchJobInfo> 
jobs = 
new HashMap<>(); 
 
   66         ingester = Ingester.getDefault();
 
   67         updateTimer = 
new Timer(NbBundle.getMessage(
this.getClass(), 
"SearchRunner.updateTimer.title.text"), 
true); 
 
   75         if (instance == null) {
 
   91     public synchronized void startJob(
long jobId, 
long dataSourceId, List<String> keywordListNames) {
 
   92         if (jobs.containsKey(jobId) == 
false) {
 
   93             logger.log(Level.INFO, 
"Adding job {0}", jobId); 
 
   95             jobs.put(jobId, jobData);
 
   99         jobs.get(jobId).incrementModuleReferenceCount();
 
  102         if ((jobs.size() > 0) && (updateTimerRunning == 
false)) {
 
  103             final long updateIntervalMs = ((long) KeywordSearchSettings.getUpdateFrequency().getTime()) * 60 * 1000;
 
  104             updateTimer.scheduleAtFixedRate(
new UpdateTimerTask(), updateIntervalMs, updateIntervalMs);
 
  105             updateTimerRunning = 
true;
 
  117         boolean readyForFinalSearch = 
false;
 
  118         synchronized (
this) {
 
  119             job = jobs.get(jobId);
 
  127                 readyForFinalSearch = 
true;
 
  131         if (readyForFinalSearch) {
 
  132             logger.log(Level.INFO, 
"Commiting search index before final search for search job {0}", job.
getJobId()); 
 
  145         logger.log(Level.INFO, 
"Stopping job {0}", jobId); 
 
  149         synchronized (
this) {
 
  150             job = jobs.get(jobId);
 
  157             if ((currentSearcher != null) && (!currentSearcher.isDone())) {
 
  158                 currentSearcher.cancel(
true);
 
  172         for (String listName : keywordListNames) {
 
  173             logger.log(Level.INFO, 
"Adding keyword list {0} to all jobs", listName); 
 
  175                 j.addKeywordListName(listName);
 
  191             logger.log(Level.SEVERE, 
"Error executing Solr query to check number of indexed files", ex); 
 
  203         logger.log(Level.INFO, 
"Starting final search for search job {0}", job.
getJobId());         
 
  207                 logger.log(Level.INFO, 
"Checking for previous search for search job {0} before executing final search", job.
getJobId()); 
 
  212                 logger.log(Level.INFO, 
"Kicking off final search for search job {0}", job.
getJobId()); 
 
  213                 finalSearcher.execute(); 
 
  216                 logger.log(Level.INFO, 
"Waiting for final search for search job {0}", job.
getJobId()); 
 
  218                 logger.log(Level.INFO, 
"Final search for search job {0} completed", job.
getJobId()); 
 
  220             } 
catch (InterruptedException | CancellationException ex) {
 
  221                 logger.log(Level.INFO, 
"Final search for search job {0} interrupted or cancelled", job.
getJobId()); 
 
  222             } 
catch (ExecutionException ex) {
 
  223                 logger.log(Level.SEVERE, String.format(
"Final search for search job %d failed", job.
getJobId()), ex); 
 
  238             if (jobs.isEmpty()) {
 
  240                 updateTimerRunning = 
false;
 
  248                 for (Entry<Long, SearchJobInfo> j : jobs.entrySet()) {
 
  252                         logger.log(Level.INFO, 
"Executing periodic search for search job {0}", job.
getJobId());
 
  281         private SearchJobInfo(
long jobId, 
long dataSourceId, List<String> keywordListNames) {
 
  285             currentResults = 
new HashMap<>();
 
  286             workerRunning = 
false;
 
  303             if (!keywordListNames.contains(keywordListName)) {
 
  304                 keywordListNames.add(keywordListName);
 
  309             return currentResults.get(k);
 
  313             currentResults.put(k, resultsIDs);
 
  321             workerRunning = flag;
 
  333             moduleReferenceCount.incrementAndGet();
 
  337             return moduleReferenceCount.decrementAndGet();
 
  347                 while (workerRunning) {
 
  348                     logger.log(Level.INFO, 
"Waiting for previous worker to finish"); 
 
  349                     finalSearchLock.wait(); 
 
  350                     logger.log(Level.INFO, 
"Notified previous worker finished"); 
 
  360                 logger.log(Level.INFO, 
"Notifying after finishing search"); 
 
  361                 workerRunning = 
false;
 
  362                 finalSearchLock.notify();
 
  373     private final class Searcher extends SwingWorker<Object, Void> {
 
  390             keywords = 
new ArrayList<>();
 
  391             keywordToList = 
new HashMap<>();
 
  392             keywordLists = 
new ArrayList<>();
 
  402         @Messages(
"SearchRunner.query.exception.msg=Error performing query:")
 
  404             final String displayName = NbBundle.getMessage(this.getClass(), 
"KeywordSearchIngestModule.doInBackGround.displayName")
 
  405                     + (finalRun ? (
" - " + NbBundle.getMessage(this.getClass(), 
"KeywordSearchIngestModule.doInBackGround.finalizeMsg")) : 
"");
 
  406             final String pgDisplayName = displayName + (
" (" + NbBundle.getMessage(this.getClass(), 
"KeywordSearchIngestModule.doInBackGround.pendingMsg") + 
")");
 
  407             progressGroup = AggregateProgressFactory.createSystemHandle(pgDisplayName, null, 
new Cancellable() {
 
  409                 public boolean cancel() {
 
  410                     logger.log(Level.INFO, 
"Cancelling the searcher by user."); 
 
  411                     if (progressGroup != null) {
 
  412                         progressGroup.setDisplayName(displayName + 
" " + NbBundle.getMessage(
this.getClass(), 
"SearchRunner.doInBackGround.cancelMsg"));
 
  420             ProgressContributor[] subProgresses = 
new ProgressContributor[keywords.size()];
 
  422             for (Keyword keywordQuery : keywords) {
 
  423                 subProgresses[i] = AggregateProgressFactory.createProgressContributor(keywordQuery.getSearchTerm());
 
  424                 progressGroup.addContributor(subProgresses[i]);
 
  428             progressGroup.start();
 
  433                 progressGroup.setDisplayName(displayName);
 
  435                 int keywordsSearched = 0;
 
  437                 for (Keyword keyword : keywords) {
 
  438                     if (this.isCancelled()) {
 
  439                         logger.log(Level.INFO, 
"Cancel detected, bailing before new keyword processed: {0}", keyword.getSearchTerm()); 
 
  443                     final KeywordList keywordList = keywordToList.get(keyword);
 
  447                     if (keywordsSearched > 0) {
 
  448                         subProgresses[keywordsSearched - 1].finish();
 
  451                     KeywordSearchQuery keywordSearchQuery = KeywordSearchUtil.getQueryForKeyword(keyword, keywordList);
 
  456                     final KeywordQueryFilter dataSourceFilter = 
new KeywordQueryFilter(KeywordQueryFilter.FilterType.DATA_SOURCE, job.
getDataSourceId());
 
  457                     keywordSearchQuery.addFilter(dataSourceFilter);
 
  459                     QueryResults queryResults;
 
  463                         queryResults = keywordSearchQuery.performQuery();
 
  465                         logger.log(Level.SEVERE, 
"Error performing query: " + keyword.getSearchTerm(), ex); 
 
  471                     } 
catch (CancellationException e) {
 
  472                         logger.log(Level.INFO, 
"Cancel detected, bailing during keyword query: {0}", keyword.getSearchTerm()); 
 
  480                     if (!newResults.getKeywords().isEmpty()) {
 
  485                         int totalUnits = newResults.getKeywords().size();
 
  486                         subProgresses[keywordsSearched].start(totalUnits);
 
  487                         int unitProgress = 0;
 
  488                         String queryDisplayStr = keyword.getSearchTerm();
 
  489                         if (queryDisplayStr.length() > 50) {
 
  490                             queryDisplayStr = queryDisplayStr.substring(0, 49) + 
"...";
 
  492                         subProgresses[keywordsSearched].progress(keywordList.getName() + 
": " + queryDisplayStr, unitProgress);
 
  495                         newResults.writeAllHitsToBlackBoard(null, subProgresses[keywordsSearched], 
this, keywordList.getIngestMessages());
 
  500                     subProgresses[keywordsSearched].progress(
"");
 
  507             catch (Exception ex) {
 
  508                 logger.log(Level.WARNING, 
"searcher exception occurred", ex); 
 
  513                     logger.log(Level.INFO, 
"Searcher took {0} secs to run (final = {1})", 
new Object[]{stopWatch.getElapsedTimeSecs(), this.finalRun}); 
 
  527                 logger.log(Level.INFO, 
"Searcher calling get() on itself in done()"); 
 
  529                 logger.log(Level.INFO, 
"Searcher finished calling get() on itself in done()"); 
 
  530             } 
catch (InterruptedException | ExecutionException e) {
 
  531                 logger.log(Level.SEVERE, 
"Error performing keyword search: " + e.getMessage()); 
 
  533                         NbBundle.getMessage(this.getClass(),
 
  534                                 "SearchRunner.Searcher.done.err.msg"), e.getMessage()));
 
  536             catch (java.util.concurrent.CancellationException ex) {
 
  544             XmlKeywordSearchList loader = XmlKeywordSearchList.getCurrent();
 
  547             keywordToList.clear();
 
  548             keywordLists.clear();
 
  550             for (String name : keywordListNames) {
 
  552                 keywordLists.add(list);
 
  553                 for (Keyword k : list.getKeywords()) {
 
  555                     keywordToList.put(k, list);
 
  566             SwingUtilities.invokeLater(
new Runnable() {
 
  569                     progressGroup.finish();
 
  593             QueryResults newResults = 
new QueryResults(queryResult.getQuery());
 
  596             for (Keyword keyword : queryResult.getKeywords()) {
 
  599                 List<KeywordHit> queryTermResults = queryResult.getResults(keyword);
 
  603                 Collections.sort(queryTermResults);
 
  607                 List<KeywordHit> newUniqueHits = 
new ArrayList<>();
 
  612                 if (curTermResults == null) {
 
  615                     curTermResults = 
new HashSet<>();
 
  619                 for (KeywordHit hit : queryTermResults) {
 
  620                     if (curTermResults.contains(hit.getSolrObjectId())) {
 
  628                     newUniqueHits.add(hit);
 
  632                     curTermResults.add(hit.getSolrObjectId());
 
  641                 newResults.addResult(keyword, newUniqueHits);
 
Map< Keyword, Set< Long > > currentResults
 
synchronized List< String > getKeywordListNames()
 
int queryNumIndexedFiles()
 
SearchJobInfo(long jobId, long dataSourceId, List< String > keywordListNames)
 
AggregateProgressHandle progressGroup
 
static IngestMessage createErrorMessage(String source, String subject, String detailsHtml)
 
static void fireNumIndexedFilesChange(Integer oldNum, Integer newNum)
 
synchronized void addKeywordListName(String keywordListName)
 
volatile boolean workerRunning
 
List< KeywordList > keywordLists
 
long decrementModuleReferenceCount()
 
AtomicLong moduleReferenceCount
 
synchronized void startJob(long jobId, long dataSourceId, List< String > keywordListNames)
 
static synchronized Server getServer()
 
synchronized Set< Long > currentKeywordResults(Keyword k)
 
boolean isWorkerRunning()
 
void doFinalSearch(SearchJobInfo job)
 
static final Logger logger
 
synchronized void setCurrentSearcher(SearchRunner.Searcher searchRunner)
 
final Object finalSearchLock
 
List< String > keywordListNames
 
static synchronized SearchRunner getInstance()
 
synchronized void addKeywordListsToAllJobs(List< String > keywordListNames)
 
void postMessage(final IngestMessage message)
 
List< String > keywordListNames
 
Map< Long, SearchJobInfo > jobs
 
static SearchRunner instance
 
void incrementModuleReferenceCount()
 
void waitForCurrentWorker()
 
static void error(String title, String message)
 
synchronized static Logger getLogger(String name)
 
QueryResults filterResults(QueryResults queryResult)
 
synchronized void addKeywordResults(Keyword k, Set< Long > resultsIDs)
 
SearchRunner.Searcher currentSearcher
 
volatile boolean updateTimerRunning
 
synchronized SearchRunner.Searcher getCurrentSearcher()
 
Map< Keyword, KeywordList > keywordToList
 
void setWorkerRunning(boolean flag)
 
static synchronized IngestServices getInstance()