19 package org.sleuthkit.autopsy.keywordsearch;
 
   21 import com.google.common.collect.SetMultimap;
 
   22 import com.google.common.collect.TreeMultimap;
 
   23 import java.awt.EventQueue;
 
   24 import java.util.ArrayList;
 
   25 import java.util.Arrays;
 
   26 import java.util.Collection;
 
   27 import java.util.Comparator;
 
   28 import java.util.HashMap;
 
   29 import java.util.LinkedHashMap;
 
   30 import java.util.List;
 
   32 import java.util.concurrent.CancellationException;
 
   33 import java.util.concurrent.ExecutionException;
 
   34 import java.util.logging.Level;
 
   35 import java.util.stream.Collectors;
 
   36 import java.util.stream.Stream;
 
   37 import javax.swing.SwingWorker;
 
   38 import org.netbeans.api.progress.ProgressHandle;
 
   39 import org.openide.nodes.ChildFactory;
 
   40 import org.openide.nodes.Children;
 
   41 import org.openide.nodes.Node;
 
   42 import org.openide.util.NbBundle;
 
   43 import org.openide.util.lookup.Lookups;
 
   67 class KeywordSearchResultFactory 
extends ChildFactory<KeyValueQueryContent> {
 
   69     private static final Logger logger = Logger.getLogger(KeywordSearchResultFactory.class.getName());
 
   72     static final List<String> COMMON_PROPERTIES
 
   78                     .map(BlackboardAttribute.ATTRIBUTE_TYPE::getDisplayName),
 
   79                     Arrays.stream(AbstractAbstractFileNode.AbstractFilePropertyType.values())
 
   80                     .map(Object::toString))
 
   81             .collect(Collectors.toList());
 
   83     private final Collection<QueryRequest> queryRequests;
 
   85     KeywordSearchResultFactory(Collection<QueryRequest> queryRequests) {
 
   86         this.queryRequests = queryRequests;
 
   97     protected boolean createKeys(List<KeyValueQueryContent> toPopulate) {
 
   99         for (QueryRequest queryRequest : queryRequests) {
 
  103             if (!queryRequest.getQuery().validate()) {
 
  109             Map<String, Object> map = queryRequest.getProperties();
 
  115             COMMON_PROPERTIES.stream()
 
  116                     .forEach((propertyType) -> map.put(propertyType, 
""));
 
  117             map.put(TSK_KEYWORD.getDisplayName(), queryRequest.getQueryString());
 
  118             map.put(TSK_KEYWORD_REGEXP.getDisplayName(), !queryRequest.getQuery().isLiteral());
 
  120             createFlatKeys(queryRequest.getQuery(), toPopulate);
 
  133     @NbBundle.Messages({
"KeywordSearchResultFactory.query.exception.msg=Could not perform the query "})
 
  134     private boolean createFlatKeys(KeywordSearchQuery queryRequest, List<KeyValueQueryContent> toPopulate) {
 
  139         QueryResults queryResults;
 
  141             queryResults = queryRequest.performQuery();
 
  142         } 
catch (KeywordSearchModuleException | NoOpenCoreException ex) {
 
  143             logger.log(Level.SEVERE, 
"Could not perform the query " + queryRequest.getQueryString(), ex); 
 
  144             MessageNotifyUtil.Notify.error(Bundle.KeywordSearchResultFactory_query_exception_msg() + queryRequest.getQueryString(), ex.getCause().getMessage());
 
  149         List<KeyValueQueryContent> tempList = 
new ArrayList<>();
 
  150         for (KeywordHit hit : getOneHitPerObject(queryResults)) {
 
  155             Map<String, Object> properties = 
new LinkedHashMap<>();
 
  156             Content content = hit.getContent();
 
  157             String contentName = content.
getName();
 
  158             if (content instanceof AbstractFile) {
 
  159                 AbstractFsContentNode.fillPropertyMap(properties, (AbstractFile) content);
 
  161                 properties.put(LOCATION.toString(), contentName);
 
  167             if (hit.hasSnippet()) {
 
  168                 properties.put(TSK_KEYWORD_PREVIEW.getDisplayName(), hit.getSnippet());
 
  171             String hitName = hit.isArtifactHit()
 
  172                     ? hit.getArtifact().getDisplayName() + 
" Artifact"  
  176             tempList.add(
new KeyValueQueryContent(hitName, properties, hitNumber, hit.getSolrObjectId(), content, queryRequest, queryResults));
 
  181         toPopulate.addAll(tempList);
 
  187         new BlackboardResultWriter(queryResults, queryRequest.getKeywordList().getName()).execute();
 
  201     Collection<KeywordHit> getOneHitPerObject(QueryResults queryResults) {
 
  202         HashMap<Long, KeywordHit> hits = 
new HashMap<>();
 
  203         for (Keyword keyWord : queryResults.getKeywords()) {
 
  204             for (KeywordHit hit : queryResults.getResults(keyWord)) {
 
  206                 if (!hits.containsKey(hit.getSolrObjectId())) {
 
  207                     hits.put(hit.getSolrObjectId(), hit);
 
  208                 } 
else if (hit.getChunkId() < hits.get(hit.getSolrObjectId()).getChunkId()) {
 
  209                     hits.put(hit.getSolrObjectId(), hit);
 
  213         return hits.values();
 
  217     protected Node createNodeForKey(KeyValueQueryContent key) {
 
  218         final Content content = key.getContent();
 
  219         QueryResults hits = key.getHits();
 
  221         Node kvNode = 
new KeyValueNode(key, Children.LEAF, Lookups.singleton(content));
 
  224         return new KeywordSearchFilterNode(hits, kvNode);
 
  231     class KeyValueQueryContent 
extends KeyValue {
 
  233         private final long solrObjectId;
 
  235         private final Content content;
 
  236         private final QueryResults hits;
 
  237         private final KeywordSearchQuery query;
 
  251         KeyValueQueryContent(String name, Map<String, Object> map, 
int id, 
long solrObjectId, Content content, KeywordSearchQuery query, QueryResults hits) {
 
  252             super(name, map, 
id);
 
  253             this.solrObjectId = solrObjectId;
 
  254             this.content = content;
 
  260         Content getContent() {
 
  264         long getSolrObjectId() {
 
  268         QueryResults getHits() {
 
  272         KeywordSearchQuery getQuery() {
 
  281     static class BlackboardResultWriter 
extends SwingWorker<Object, Void> {
 
  283         private static final List<BlackboardResultWriter> writers = 
new ArrayList<>();
 
  284         private ProgressHandle progress;
 
  285         private final KeywordSearchQuery query;
 
  286         private final QueryResults hits;
 
  287         private Collection<BlackboardArtifact> newArtifacts = 
new ArrayList<>();
 
  288         private static final int QUERY_DISPLAY_LEN = 40;
 
  290         BlackboardResultWriter(QueryResults hits, String listName) {
 
  292             this.query = hits.getQuery();
 
  295         protected void finalizeWorker() {
 
  296             deregisterWriter(
this);
 
  297             EventQueue.invokeLater(progress::finish);
 
  301         protected Object doInBackground() throws Exception {
 
  302             registerWriter(
this); 
 
  303             final String queryStr = query.getQueryString();
 
  304             final String queryDisp = queryStr.length() > QUERY_DISPLAY_LEN ? queryStr.substring(0, QUERY_DISPLAY_LEN - 1) + 
" ..." : queryStr;
 
  306                 progress = ProgressHandle.createHandle(NbBundle.getMessage(
this.getClass(), 
"KeywordSearchResultFactory.progress.saving", queryDisp), () -> BlackboardResultWriter.this.cancel(
true));
 
  307                 newArtifacts = hits.writeAllHitsToBlackBoard(progress, null, 
this, 
false);
 
  315         protected void done() {
 
  318             } 
catch (InterruptedException | CancellationException ex) {
 
  319                 logger.log(Level.WARNING, 
"User cancelled writing of ad hoc search query results for '{0}' to the blackboard", query.getQueryString()); 
 
  320             } 
catch (ExecutionException ex) {
 
  321                 logger.log(Level.SEVERE, 
"Error writing of ad hoc search query results for " + query.getQueryString() + 
" to the blackboard", ex); 
 
  325         private static synchronized void registerWriter(BlackboardResultWriter writer) {
 
  329         private static synchronized void deregisterWriter(BlackboardResultWriter writer) {
 
  330             writers.remove(writer);
 
  333         static synchronized void stopAllWriters() {
 
  334             for (BlackboardResultWriter w : writers) {