Autopsy  4.5.0
Graphical digital forensics platform for The Sleuth Kit and other tools.
KeywordSearchResultFactory.java
Go to the documentation of this file.
1 /*
2  * Autopsy Forensic Browser
3  *
4  * Copyright 2011-2018 Basis Technology Corp.
5  * Contact: carrier <at> sleuthkit <dot> org
6  *
7  * Licensed under the Apache License, Version 2.0 (the "License");
8  * you may not use this content except in compliance with the License.
9  * You may obtain a copy of the License at
10  *
11  * http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing, software
14  * distributed under the License is distributed on an "AS IS" BASIS,
15  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16  * See the License for the specific language governing permissions and
17  * limitations under the License.
18  */
19 package org.sleuthkit.autopsy.keywordsearch;
20 
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;
28 import java.util.Map;
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;
50 import org.sleuthkit.datamodel.AbstractFile;
51 import org.sleuthkit.datamodel.BlackboardArtifact;
52 import org.sleuthkit.datamodel.BlackboardAttribute;
53 import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_KEYWORD;
54 import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_KEYWORD_PREVIEW;
55 import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_KEYWORD_REGEXP;
56 import org.sleuthkit.datamodel.Content;
57 import org.sleuthkit.datamodel.SleuthkitCase;
58 import org.sleuthkit.datamodel.TskCoreException;
59 
67 class KeywordSearchResultFactory extends ChildFactory<KeyValue> {
68 
69  private static final Logger logger = Logger.getLogger(KeywordSearchResultFactory.class.getName());
70 
71  //common properties (superset of all Node properties) to be displayed as columns
72  static final List<String> COMMON_PROPERTIES
73  = Stream.concat(
74  Stream.of(
75  TSK_KEYWORD,
76  TSK_KEYWORD_REGEXP,
77  TSK_KEYWORD_PREVIEW)
78  .map(BlackboardAttribute.ATTRIBUTE_TYPE::getDisplayName),
79  Arrays.stream(AbstractAbstractFileNode.AbstractFilePropertyType.values())
80  .map(Object::toString))
81  .collect(Collectors.toList());
82 
83  private final Collection<QueryRequest> queryRequests;
84 
85  KeywordSearchResultFactory(Collection<QueryRequest> queryRequests) {
86  this.queryRequests = queryRequests;
87  }
88 
96  @Override
97  protected boolean createKeys(List<KeyValue> toPopulate) {
98 
99  for (QueryRequest queryRequest : queryRequests) {
103  if (!queryRequest.getQuery().validate()) {
104  //TODO mark the particular query node RED
105  break;
106  }
107 
108  //JMTODO: It looks like this map is not actually used for anything...
109  Map<String, Object> map = queryRequest.getProperties();
110  /*
111  * make sure all common properties are displayed as columns (since
112  * we are doing lazy child Node load we need to preinitialize
113  * properties when sending parent Node)
114  */
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());
119 
120  createFlatKeys(queryRequest.getQuery(), toPopulate);
121  }
122 
123  return true;
124  }
125 
133  @NbBundle.Messages({"KeywordSearchResultFactory.query.exception.msg=Could not perform the query "})
134  private boolean createFlatKeys(KeywordSearchQuery queryRequest, List<KeyValue> toPopulate) {
135 
139  QueryResults queryResults;
140  try {
141  queryResults = queryRequest.performQuery();
142  } catch (KeywordSearchModuleException | NoOpenCoreException ex) {
143  logger.log(Level.SEVERE, "Could not perform the query " + queryRequest.getQueryString(), ex); //NON-NLS
144  MessageNotifyUtil.Notify.error(Bundle.KeywordSearchResultFactory_query_exception_msg() + queryRequest.getQueryString(), ex.getCause().getMessage());
145  return false;
146  }
147  SleuthkitCase tskCase;
148  try {
149  tskCase = Case.getCurrentCase().getSleuthkitCase();
150  } catch (IllegalStateException ex) {
151  logger.log(Level.SEVERE, "There was no case open.", ex); //NON-NLS
152  return false;
153  }
154 
155  int hitNumber = 0;
156  List<KeyValueQueryContent> tempList = new ArrayList<>();
157  for (KeywordHit hit : getOneHitPerObject(queryResults)) {
158 
162  Map<String, Object> properties = new LinkedHashMap<>();
163  Content content;
164  String contentName;
165  try {
166  content = tskCase.getContentById(hit.getContentID());
167  if (content == null) {
168  logger.log(Level.SEVERE, "There was a error getting content by id."); //NON-NLS
169  return false;
170  }
171  } catch (TskCoreException ex) {
172  logger.log(Level.SEVERE, "There was a error getting content by id.", ex); //NON-NLS
173  return false;
174  }
175 
176  contentName = content.getName();
177  if (content instanceof AbstractFile) {
178  AbstractFsContentNode.fillPropertyMap(properties, (AbstractFile) content);
179  } else {
180  properties.put(LOCATION.toString(), contentName);
181  }
182 
186  if (hit.hasSnippet()) {
187  properties.put(TSK_KEYWORD_PREVIEW.getDisplayName(), hit.getSnippet());
188  }
189 
190  String hitName;
191  BlackboardArtifact artifact = null;
192  if (hit.isArtifactHit()) {
193  try {
194  artifact = tskCase.getBlackboardArtifact(hit.getArtifactID().get());
195  hitName = artifact.getDisplayName() + " Artifact"; //NON-NLS
196  } catch (TskCoreException ex) {
197  logger.log(Level.SEVERE, "Error getting blckboard artifact by id", ex);
198  return false;
199  }
200  } else {
201  hitName = contentName;
202  }
203  hitNumber++;
204  tempList.add(new KeyValueQueryContent(hitName, properties, hitNumber, hit.getSolrObjectId(), content, artifact, queryRequest, queryResults));
205 
206  }
207 
208  if (hitNumber == 0) {
209  toPopulate.add(new KeyValue("This KeyValue Is Empty", 0));
210  } else {
211  // Add all the nodes to toPopulate at once. Minimizes node creation
212  // EDT threads, which can slow and/or hang the UI on large queries.
213  toPopulate.addAll(tempList);
214  }
215 
216  //write to bb
217  //cannot reuse snippet in BlackboardResultWriter
218  //because for regex searches in UI we compress results by showing a content per regex once (even if multiple term hits)
219  //whereas in bb we write every hit per content separately
220  new BlackboardResultWriter(queryResults, queryRequest.getKeywordList().getName()).execute();
221 
222  return true;
223  }
224 
234  Collection<KeywordHit> getOneHitPerObject(QueryResults queryResults) {
235  HashMap<Long, KeywordHit> hits = new HashMap<>();
236  for (Keyword keyWord : queryResults.getKeywords()) {
237  for (KeywordHit hit : queryResults.getResults(keyWord)) {
238  // add hit with lowest SolrObjectID-Chunk-ID combination.
239  if (!hits.containsKey(hit.getSolrObjectId())) {
240  hits.put(hit.getSolrObjectId(), hit);
241  } else if (hit.getChunkId() < hits.get(hit.getSolrObjectId()).getChunkId()) {
242  hits.put(hit.getSolrObjectId(), hit);
243  }
244  }
245  }
246  return hits.values();
247  }
248 
249  @NbBundle.Messages({"KeywordSearchResultFactory.createNodeForKey.noResultsFound.text=No results found."})
250  @Override
251  protected Node createNodeForKey(KeyValue key) {
252  Node resultNode;
253 
254  if (key instanceof KeyValueQueryContent) {
255  AdHocQueryResult adHocQueryResult = new AdHocQueryResult((KeyValueQueryContent) key);
256 
257  Node kvNode = new KeyValueNode(key, Children.LEAF);
258 
259  //wrap in KeywordSearchFilterNode for the markup content, might need to override FilterNode for more customization
260  resultNode = new KeywordSearchFilterNode(adHocQueryResult, kvNode);
261  } else {
262  resultNode = new EmptyNode("This Node Is Empty");
263  resultNode.setDisplayName(NbBundle.getMessage(this.getClass(), "KeywordSearchResultFactory.createNodeForKey.noResultsFound.text"));
264  }
265 
266  return resultNode;
267 
268  }
269 
274  final class AdHocQueryResult {
275 
276  private final long solrObjectId;
277  private final Content content;
278  private final BlackboardArtifact artifact;
279  private final QueryResults results;
280 
290  AdHocQueryResult(KeyValueQueryContent key) {
291  this.solrObjectId = key.getSolrObjectId();
292  this.content = key.getContent();
293  this.artifact = key.getArtifact();
294  this.results = key.getHits();
295  }
296 
303  long getSolrObjectId() {
304  return solrObjectId;
305  }
306 
316  Content getContent() {
317  return content;
318  }
319 
325  BlackboardArtifact getArtifact() {
326  return artifact;
327  }
328 
334  QueryResults getResults() {
335  return results;
336  }
337  }
338 
343  class KeyValueQueryContent extends KeyValue {
344 
345  private final long solrObjectId;
346 
347  private final Content content;
348  private final BlackboardArtifact artifact;
349  private final QueryResults hits;
350  private final KeywordSearchQuery query;
351 
366  KeyValueQueryContent(String name, Map<String, Object> map, int id, long solrObjectId, Content content, BlackboardArtifact artifact, KeywordSearchQuery query, QueryResults hits) {
367  super(name, map, id);
368  this.solrObjectId = solrObjectId;
369  this.content = content;
370  this.artifact = artifact;
371 
372  this.hits = hits;
373  this.query = query;
374  }
375 
376  Content getContent() {
377  return content;
378  }
379 
380  BlackboardArtifact getArtifact() {
381  return artifact;
382  }
383 
384  long getSolrObjectId() {
385  return solrObjectId;
386  }
387 
388  QueryResults getHits() {
389  return hits;
390  }
391 
392  KeywordSearchQuery getQuery() {
393  return query;
394  }
395  }
396 
401  static class BlackboardResultWriter extends SwingWorker<Void, Void> {
402 
403  private static final List<BlackboardResultWriter> WRITERS = new ArrayList<>();
404  private ProgressHandle progress;
405  private final KeywordSearchQuery query;
406  private final QueryResults hits;
407  private static final int QUERY_DISPLAY_LEN = 40;
408 
409  BlackboardResultWriter(QueryResults hits, String listName) {
410  this.hits = hits;
411  this.query = hits.getQuery();
412  }
413 
414  protected void finalizeWorker() {
415  deregisterWriter(this);
416  EventQueue.invokeLater(progress::finish);
417  }
418 
419  @Override
420  protected Void doInBackground() throws Exception {
421  registerWriter(this); //register (synchronized on class) outside of writerLock to prevent deadlock
422  final String queryStr = query.getQueryString();
423  final String queryDisp = queryStr.length() > QUERY_DISPLAY_LEN ? queryStr.substring(0, QUERY_DISPLAY_LEN - 1) + " ..." : queryStr;
424  try {
425  progress = ProgressHandle.createHandle(NbBundle.getMessage(this.getClass(), "KeywordSearchResultFactory.progress.saving", queryDisp), () -> BlackboardResultWriter.this.cancel(true));
426  hits.process(progress, null, this, false);
427  } finally {
428  finalizeWorker();
429  }
430  return null;
431  }
432 
433  @Override
434  protected void done() {
435  try {
436  get();
437  } catch (InterruptedException | CancellationException ex) {
438  logger.log(Level.WARNING, "User cancelled writing of ad hoc search query results for '{0}' to the blackboard", query.getQueryString()); //NON-NLS
439  } catch (ExecutionException ex) {
440  logger.log(Level.SEVERE, "Error writing of ad hoc search query results for " + query.getQueryString() + " to the blackboard", ex); //NON-NLS
441  }
442  }
443 
444  private static synchronized void registerWriter(BlackboardResultWriter writer) {
445  WRITERS.add(writer);
446  }
447 
448  private static synchronized void deregisterWriter(BlackboardResultWriter writer) {
449  WRITERS.remove(writer);
450  }
451 
452  static synchronized void stopAllWriters() {
453  for (BlackboardResultWriter w : WRITERS) {
454  w.cancel(true);
455  WRITERS.remove(w);
456  }
457  }
458  }
459 }

Copyright © 2012-2016 Basis Technology. Generated on: Tue Feb 20 2018
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.