19 package org.sleuthkit.autopsy.keywordsearch;
 
   21 import java.awt.EventQueue;
 
   22 import java.util.ArrayList;
 
   23 import java.util.Arrays;
 
   24 import java.util.Collection;
 
   25 import java.util.HashMap;
 
   26 import java.util.LinkedHashMap;
 
   27 import java.util.List;
 
   29 import java.util.concurrent.CancellationException;
 
   30 import java.util.concurrent.ExecutionException;
 
   31 import java.util.logging.Level;
 
   32 import java.util.stream.Collectors;
 
   33 import java.util.stream.Stream;
 
   34 import javax.swing.SwingWorker;
 
   35 import org.netbeans.api.progress.ProgressHandle;
 
   36 import org.openide.nodes.ChildFactory;
 
   37 import org.openide.nodes.Children;
 
   38 import org.openide.nodes.Node;
 
   39 import org.openide.util.NbBundle;
 
   40 import org.openide.util.lookup.Lookups;
 
   55 import static org.
sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_KEYWORD;
 
   56 import static org.
sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_KEYWORD_PREVIEW;
 
   57 import static org.
sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_KEYWORD_REGEXP;
 
   69 class AdHocSearchChildFactory 
extends ChildFactory<KeyValue> {
 
   71     private static final Logger logger = Logger.getLogger(AdHocSearchChildFactory.class.getName());
 
   74     static final List<String> COMMON_PROPERTIES
 
   80                             .map(BlackboardAttribute.ATTRIBUTE_TYPE::getDisplayName),
 
   81                     Arrays.stream(AbstractAbstractFileNode.AbstractFilePropertyType.values())
 
   82                             .map(Object::toString))
 
   83                     .collect(Collectors.toList());
 
   85     private final Collection<AdHocQueryRequest> queryRequests;
 
   86     private final boolean saveResults;
 
   94     AdHocSearchChildFactory(Collection<AdHocQueryRequest> queryRequests, 
boolean saveResults) {
 
   95         this.queryRequests = queryRequests;
 
   96         this.saveResults = saveResults;
 
  107     protected boolean createKeys(List<KeyValue> toPopulate) {
 
  109         for (AdHocQueryRequest queryRequest : queryRequests) {
 
  113             if (!queryRequest.getQuery().validate()) {
 
  119             Map<String, Object> map = queryRequest.getProperties();
 
  125             COMMON_PROPERTIES.stream()
 
  126                     .forEach((propertyType) -> map.put(propertyType, 
""));
 
  127             map.put(TSK_KEYWORD.getDisplayName(), queryRequest.getQueryString());
 
  128             map.put(TSK_KEYWORD_REGEXP.getDisplayName(), !queryRequest.getQuery().isLiteral());
 
  130             createFlatKeys(queryRequest.getQuery(), toPopulate);
 
  135         if (toPopulate.isEmpty()) {
 
  136             toPopulate.add(
new KeyValue(
"This KeyValue Is Empty", 0));
 
  149     @NbBundle.Messages({
"KeywordSearchResultFactory.query.exception.msg=Could not perform the query "})
 
  150     private boolean createFlatKeys(KeywordSearchQuery queryRequest, List<KeyValue> toPopulate) {
 
  155         QueryResults queryResults;
 
  157             queryResults = queryRequest.performQuery();
 
  158         } 
catch (KeywordSearchModuleException | NoOpenCoreException ex) {
 
  159             logger.log(Level.SEVERE, 
"Could not perform the query " + queryRequest.getQueryString(), ex); 
 
  160             MessageNotifyUtil.Notify.error(Bundle.KeywordSearchResultFactory_query_exception_msg() + queryRequest.getQueryString(), ex.getCause().getMessage());
 
  163         SleuthkitCase tskCase;
 
  165             tskCase = Case.getCurrentCaseThrows().getSleuthkitCase();
 
  166         } 
catch (NoCurrentCaseException ex) {
 
  167             logger.log(Level.SEVERE, 
"There was no case open.", ex); 
 
  172         List<KeywordHitKey> tempList = 
new ArrayList<>();
 
  173         for (KeywordHit hit : getOneHitPerObject(queryResults)) {
 
  178             Map<String, Object> properties = 
new LinkedHashMap<>();
 
  183             if (hit.hasSnippet()) {
 
  184                 properties.put(TSK_KEYWORD_PREVIEW.getDisplayName(), hit.getSnippet());
 
  190                 content = tskCase.getContentById(hit.getContentID());
 
  191                 if (content == null) {
 
  192                     logger.log(Level.SEVERE, 
"There was a error getting content by id."); 
 
  195             } 
catch (TskCoreException ex) {
 
  196                 logger.log(Level.SEVERE, 
"There was a error getting content by id.", ex); 
 
  200             contentName = content.getName();
 
  201             if (content instanceof AbstractFile) {
 
  202                 AbstractFsContentNode.fillPropertyMap(properties, (AbstractFile) content);
 
  204                 properties.put(LOCATION.toString(), contentName);
 
  209             BlackboardArtifact artifact = null;
 
  210             if (hit.isArtifactHit()) {
 
  212                     artifact = tskCase.getBlackboardArtifact(hit.getArtifactID().get());
 
  213                     hitName = artifact.getDisplayName() + 
" Artifact"; 
 
  214                 } 
catch (TskCoreException ex) {
 
  215                     logger.log(Level.SEVERE, 
"Error getting blckboard artifact by id", ex);
 
  219                 hitName = contentName;
 
  222             tempList.add(
new KeywordHitKey(hitName, properties, hitNumber, hit.getSolrObjectId(), content, artifact, queryRequest, queryResults));
 
  226         if (hitNumber != 0) {
 
  229             toPopulate.addAll(tempList);
 
  236         new BlackboardResultWriter(queryResults, queryRequest.getKeywordList().getName(), saveResults).execute();
 
  250     Collection<KeywordHit> getOneHitPerObject(QueryResults queryResults) {
 
  251         HashMap<Long, KeywordHit> hits = 
new HashMap<>();
 
  252         for (Keyword keyWord : queryResults.getKeywords()) {
 
  253             for (KeywordHit hit : queryResults.getResults(keyWord)) {
 
  255                 if (!hits.containsKey(hit.getSolrObjectId())) {
 
  256                     hits.put(hit.getSolrObjectId(), hit);
 
  257                 } 
else if (hit.getChunkId() < hits.get(hit.getSolrObjectId()).getChunkId()) {
 
  258                     hits.put(hit.getSolrObjectId(), hit);
 
  262         return hits.values();
 
  265     @NbBundle.Messages({
"KeywordSearchResultFactory.createNodeForKey.noResultsFound.text=No results found."})
 
  267     protected Node createNodeForKey(KeyValue key) {
 
  270         if (key instanceof KeywordHitKey) {
 
  271             AdHocQueryResult adHocQueryResult = 
new AdHocQueryResult((KeywordHitKey) key);
 
  277             ArrayList<Object> lookups = 
new ArrayList<>();
 
  278             lookups.add(adHocQueryResult);
 
  279             if (((KeywordHitKey) key).getContent() != null) {
 
  280                 lookups.add(((KeywordHitKey) key).getContent());
 
  282             if (((KeywordHitKey) key).getArtifact() != null) {
 
  283                 lookups.add(((KeywordHitKey) key).getArtifact());
 
  286             Node kvNode = 
new KeyValueNode(key, Children.LEAF, Lookups.fixed(lookups.toArray()));
 
  289             resultNode = 
new AdHocSearchFilterNode(kvNode);
 
  291             resultNode = 
new EmptyNode(
"This Node Is Empty");
 
  292             resultNode.setDisplayName(NbBundle.getMessage(
this.getClass(), 
"KeywordSearchResultFactory.createNodeForKey.noResultsFound.text"));
 
  303     final class AdHocQueryResult {
 
  305         private final long solrObjectId;
 
  306         private final QueryResults results;
 
  315         AdHocQueryResult(KeywordHitKey key) {
 
  316             this.solrObjectId = key.getSolrObjectId();
 
  317             this.results = key.getHits();
 
  326         long getSolrObjectId() {
 
  335         QueryResults getResults() {
 
  344     class KeywordHitKey 
extends KeyValue {
 
  346         private final long solrObjectId;
 
  348         private final Content content;
 
  349         private final BlackboardArtifact artifact;
 
  350         private final QueryResults hits;
 
  351         private final KeywordSearchQuery query;
 
  367         KeywordHitKey(String name, Map<String, Object> map, 
int id, 
long solrObjectId, Content content, BlackboardArtifact artifact, KeywordSearchQuery query, QueryResults hits) {
 
  368             super(name, map, 
id);
 
  369             this.solrObjectId = solrObjectId;
 
  370             this.content = content;
 
  371             this.artifact = artifact;
 
  377         Content getContent() {
 
  381         BlackboardArtifact getArtifact() {
 
  385         long getSolrObjectId() {
 
  389         QueryResults getHits() {
 
  393         KeywordSearchQuery getQuery() {
 
  402     static class BlackboardResultWriter 
extends SwingWorker<Void, Void> {
 
  404         private static final List<BlackboardResultWriter> WRITERS = 
new ArrayList<>();
 
  405         private ProgressHandle progress;
 
  406         private final KeywordSearchQuery query;
 
  407         private final QueryResults hits;
 
  408         private static final int QUERY_DISPLAY_LEN = 40;
 
  409         private final boolean saveResults;
 
  411         BlackboardResultWriter(QueryResults hits, String listName, 
boolean saveResults) {
 
  413             this.query = hits.getQuery();
 
  414             this.saveResults = saveResults;
 
  417         protected void finalizeWorker() {
 
  418             deregisterWriter(
this);
 
  419             EventQueue.invokeLater(progress::finish);
 
  423         protected Void doInBackground() throws Exception {
 
  424             registerWriter(
this); 
 
  425             final String queryStr = query.getQueryString();
 
  426             final String queryDisp = queryStr.length() > QUERY_DISPLAY_LEN ? queryStr.substring(0, QUERY_DISPLAY_LEN - 1) + 
" ..." : queryStr;
 
  428                 progress = ProgressHandle.createHandle(NbBundle.getMessage(
this.getClass(), 
"KeywordSearchResultFactory.progress.saving", queryDisp), () -> BlackboardResultWriter.this.cancel(
true));
 
  429                 hits.process(progress, null, 
this, 
false, saveResults);
 
  437         protected void done() {
 
  440             } 
catch (InterruptedException | CancellationException ex) {
 
  441                 logger.log(Level.WARNING, 
"User cancelled writing of ad hoc search query results for '{0}' to the blackboard", query.getQueryString()); 
 
  442             } 
catch (ExecutionException ex) {
 
  443                 logger.log(Level.SEVERE, 
"Error writing of ad hoc search query results for " + query.getQueryString() + 
" to the blackboard", ex); 
 
  447         private static synchronized void registerWriter(BlackboardResultWriter writer) {
 
  451         private static synchronized void deregisterWriter(BlackboardResultWriter writer) {
 
  452             WRITERS.remove(writer);
 
  455         static synchronized void stopAllWriters() {
 
  456             for (BlackboardResultWriter w : WRITERS) {