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.SwingUtilities;
 
   35 import javax.swing.SwingWorker;
 
   36 import org.netbeans.api.progress.ProgressHandle;
 
   37 import org.openide.nodes.ChildFactory;
 
   38 import org.openide.nodes.Children;
 
   39 import org.openide.nodes.Node;
 
   40 import org.openide.util.Cancellable;
 
   41 import org.openide.util.NbBundle;
 
   42 import org.openide.util.lookup.Lookups;
 
   72 class AdHocSearchChildFactory 
extends ChildFactory<KeyValue> {
 
   74     private static final Logger logger = Logger.getLogger(AdHocSearchChildFactory.class.getName());
 
   77     static final List<String> COMMON_PROPERTIES
 
   83                             .map(BlackboardAttribute.ATTRIBUTE_TYPE::getDisplayName),
 
   84                     Arrays.stream(AbstractAbstractFileNode.AbstractFilePropertyType.values())
 
   85                             .map(Object::toString))
 
   86                     .collect(Collectors.toList());
 
   88     private final Collection<AdHocQueryRequest> queryRequests;
 
   89     private final boolean saveResults;
 
   98     AdHocSearchChildFactory(Collection<AdHocQueryRequest> queryRequests, 
boolean saveResults) {
 
   99         this.queryRequests = queryRequests;
 
  100         this.saveResults = saveResults;
 
  111     protected boolean createKeys(List<KeyValue> toPopulate) {
 
  113         for (AdHocQueryRequest queryRequest : queryRequests) {
 
  117             if (!queryRequest.getQuery().validate()) {
 
  123             Map<String, Object> map = queryRequest.getProperties();
 
  129             COMMON_PROPERTIES.stream()
 
  130                     .forEach((propertyType) -> map.put(propertyType, 
""));
 
  131             map.put(TSK_KEYWORD.getDisplayName(), queryRequest.getQueryString());
 
  132             map.put(TSK_KEYWORD_REGEXP.getDisplayName(), !queryRequest.getQuery().isLiteral());
 
  134             createFlatKeys(queryRequest.getQuery(), toPopulate);
 
  139         if (toPopulate.isEmpty()) {
 
  140             toPopulate.add(
new KeyValue(
"This KeyValue Is Empty", 0));
 
  153     @NbBundle.Messages({
"KeywordSearchResultFactory.query.exception.msg=Could not perform the query "})
 
  154     private boolean createFlatKeys(KeywordSearchQuery queryRequest, List<KeyValue> toPopulate) {
 
  159         QueryResults queryResults;
 
  161             queryResults = queryRequest.performQuery();
 
  162         } 
catch (KeywordSearchModuleException | NoOpenCoreException ex) {
 
  163             logger.log(Level.SEVERE, 
"Could not perform the query " + queryRequest.getQueryString(), ex); 
 
  164             MessageNotifyUtil.Notify.error(Bundle.KeywordSearchResultFactory_query_exception_msg() + queryRequest.getQueryString(), ex.getCause().getMessage());
 
  167         SleuthkitCase tskCase;
 
  169             tskCase = Case.getCurrentCaseThrows().getSleuthkitCase();
 
  170         } 
catch (NoCurrentCaseException ex) {
 
  171             logger.log(Level.SEVERE, 
"There was no case open.", ex); 
 
  176         List<KeywordHitKey> tempList = 
new ArrayList<>();
 
  177         for (KeywordHit hit : getOneHitPerObject(queryResults)) {
 
  182             Map<String, Object> properties = 
new LinkedHashMap<>();
 
  187             if (hit.hasSnippet()) {
 
  188                 properties.put(TSK_KEYWORD_PREVIEW.getDisplayName(), hit.getSnippet());
 
  194                 content = tskCase.getContentById(hit.getContentID());
 
  195                 if (content == null) {
 
  196                     logger.log(Level.SEVERE, 
"There was a error getting content by id."); 
 
  199             } 
catch (TskCoreException ex) {
 
  200                 logger.log(Level.SEVERE, 
"There was a error getting content by id.", ex); 
 
  204             contentName = content.getName();
 
  205             if (content instanceof AbstractFile) {
 
  206                 AbstractFsContentNode.fillPropertyMap(properties, (AbstractFile) content);
 
  208                 properties.put(LOCATION.toString(), contentName);
 
  212             BlackboardArtifact artifact = null;
 
  213             if (hit.isArtifactHit()) {
 
  215                     artifact = tskCase.getBlackboardArtifact(hit.getArtifactID().get());
 
  216                     hitName = artifact.getDisplayName() + 
" Artifact"; 
 
  217                 } 
catch (TskCoreException ex) {
 
  218                     logger.log(Level.SEVERE, 
"Error getting blckboard artifact by id", ex);
 
  222                 hitName = contentName;
 
  225             tempList.add(
new KeywordHitKey(hitName, properties, hitNumber, hit.getSolrObjectId(), content, artifact, queryRequest, queryResults));
 
  229         if (hitNumber != 0) {
 
  232             toPopulate.addAll(tempList);
 
  239         new BlackboardResultWriter(queryResults, queryRequest.getKeywordList().getName(), saveResults).execute();
 
  253     Collection<KeywordHit> getOneHitPerObject(QueryResults queryResults) {
 
  254         HashMap<Long, KeywordHit> hits = 
new HashMap<>();
 
  255         for (Keyword keyWord : queryResults.getKeywords()) {
 
  256             for (KeywordHit hit : queryResults.getResults(keyWord)) {
 
  258                 if (!hits.containsKey(hit.getSolrObjectId())) {
 
  259                     hits.put(hit.getSolrObjectId(), hit);
 
  260                 } 
else if (hit.getChunkId() < hits.get(hit.getSolrObjectId()).getChunkId()) {
 
  261                     hits.put(hit.getSolrObjectId(), hit);
 
  265         return hits.values();
 
  268     @NbBundle.Messages({
"KeywordSearchResultFactory.createNodeForKey.noResultsFound.text=No results found."})
 
  270     protected Node createNodeForKey(KeyValue key) {
 
  273         if (key instanceof KeywordHitKey) {
 
  274             AdHocQueryResult adHocQueryResult = 
new AdHocQueryResult((KeywordHitKey) key);
 
  280             ArrayList<Object> lookups = 
new ArrayList<>();
 
  281             lookups.add(adHocQueryResult);
 
  282             if (((KeywordHitKey) key).getContent() != null) {
 
  283                 lookups.add(((KeywordHitKey) key).getContent());
 
  285             if (((KeywordHitKey) key).getArtifact() != null) {
 
  286                 lookups.add(((KeywordHitKey) key).getArtifact());
 
  289             Node kvNode = 
new KeyValueNode(key, Children.LEAF, Lookups.fixed(lookups.toArray()));
 
  292             resultNode = 
new AdHocSearchFilterNode(kvNode);
 
  294             resultNode = 
new EmptyNode(
"This Node Is Empty");
 
  295             resultNode.setDisplayName(NbBundle.getMessage(
this.getClass(), 
"KeywordSearchResultFactory.createNodeForKey.noResultsFound.text"));
 
  306     final class AdHocQueryResult {
 
  308         private final long solrObjectId;
 
  309         private final QueryResults results;
 
  318         AdHocQueryResult(KeywordHitKey key) {
 
  319             this.solrObjectId = key.getSolrObjectId();
 
  320             this.results = key.getHits();
 
  329         long getSolrObjectId() {
 
  338         QueryResults getResults() {
 
  347     class KeywordHitKey 
extends KeyValue {
 
  349         private final long solrObjectId;
 
  351         private final Content content;
 
  352         private final BlackboardArtifact artifact;
 
  353         private final QueryResults hits;
 
  354         private final KeywordSearchQuery query;
 
  370         KeywordHitKey(String name, Map<String, Object> map, 
int id, 
long solrObjectId, Content content, BlackboardArtifact artifact, KeywordSearchQuery query, QueryResults hits) {
 
  371             super(name, map, 
id);
 
  372             this.solrObjectId = solrObjectId;
 
  373             this.content = content;
 
  374             this.artifact = artifact;
 
  380         Content getContent() {
 
  384         BlackboardArtifact getArtifact() {
 
  388         long getSolrObjectId() {
 
  392         QueryResults getHits() {
 
  396         KeywordSearchQuery getQuery() {
 
  405     static class BlackboardResultWriter 
extends SwingWorker<Void, Void> {
 
  407         private static final List<BlackboardResultWriter> WRITERS = 
new ArrayList<>();
 
  408         private ProgressHandle progress;
 
  409         private final KeywordSearchQuery query;
 
  410         private final QueryResults hits;
 
  411         private static final int QUERY_DISPLAY_LEN = 40;
 
  412         private final boolean saveResults;
 
  414         BlackboardResultWriter(QueryResults hits, String listName, 
boolean saveResults) {
 
  416             this.query = hits.getQuery();
 
  417             this.saveResults = saveResults;
 
  421         protected Void doInBackground() throws Exception {
 
  423                 if (RuntimeProperties.runningWithGUI()) {
 
  424                     final String queryStr = query.getQueryString();
 
  425                     final String queryDisp = queryStr.length() > QUERY_DISPLAY_LEN ? queryStr.substring(0, QUERY_DISPLAY_LEN - 1) + 
" ..." : queryStr;
 
  426                     SwingUtilities.invokeLater(() -> {
 
  427                         progress = ProgressHandle.createHandle(
 
  428                                 NbBundle.getMessage(
this.getClass(), 
"KeywordSearchResultFactory.progress.saving", queryDisp),
 
  431                             public boolean cancel() {
 
  433                                 logger.log(Level.INFO, 
"Ad hoc search cancelled by user"); 
 
  435                                     BlackboardResultWriter.this.cancel(
true);
 
  442                 registerWriter(
this); 
 
  443                 hits.process(
this, 
false, saveResults, null);
 
  445                 deregisterWriter(
this);
 
  446                 if (RuntimeProperties.runningWithGUI() && progress != null) {
 
  447                     EventQueue.invokeLater(progress::finish);
 
  454         protected void done() {
 
  457             } 
catch (InterruptedException | CancellationException ex) {
 
  458                 logger.log(Level.WARNING, 
"User cancelled writing of ad hoc search query results for '{0}' to the blackboard", query.getQueryString()); 
 
  459             } 
catch (ExecutionException ex) {
 
  460                 logger.log(Level.SEVERE, 
"Error writing of ad hoc search query results for " + query.getQueryString() + 
" to the blackboard", ex); 
 
  464         private static synchronized void registerWriter(BlackboardResultWriter writer) {
 
  468         private static synchronized void deregisterWriter(BlackboardResultWriter writer) {
 
  469             WRITERS.remove(writer);
 
  472         static synchronized void stopAllWriters() {
 
  473             for (BlackboardResultWriter w : WRITERS) {