Autopsy  4.19.1
Graphical digital forensics platform for The Sleuth Kit and other tools.
AdHocSearchChildFactory.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;
40 import org.openide.util.lookup.Lookups;
52 import org.sleuthkit.datamodel.AbstractFile;
53 import org.sleuthkit.datamodel.BlackboardArtifact;
54 import org.sleuthkit.datamodel.BlackboardAttribute;
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;
58 import org.sleuthkit.datamodel.Content;
59 import org.sleuthkit.datamodel.SleuthkitCase;
60 import org.sleuthkit.datamodel.TskCoreException;
61 
69 class AdHocSearchChildFactory extends ChildFactory<KeyValue> {
70 
71  private static final Logger logger = Logger.getLogger(AdHocSearchChildFactory.class.getName());
72 
73  //common properties (superset of all Node properties) to be displayed as columns
74  static final List<String> COMMON_PROPERTIES
75  = Stream.concat(
76  Stream.of(
77  TSK_KEYWORD,
78  TSK_KEYWORD_REGEXP,
79  TSK_KEYWORD_PREVIEW)
80  .map(BlackboardAttribute.ATTRIBUTE_TYPE::getDisplayName),
81  Arrays.stream(AbstractAbstractFileNode.AbstractFilePropertyType.values())
82  .map(Object::toString))
83  .collect(Collectors.toList());
84 
85  private final Collection<AdHocQueryRequest> queryRequests;
86  private final boolean saveResults;
87 
94  AdHocSearchChildFactory(Collection<AdHocQueryRequest> queryRequests, boolean saveResults) {
95  this.queryRequests = queryRequests;
96  this.saveResults = saveResults;
97  }
98 
106  @Override
107  protected boolean createKeys(List<KeyValue> toPopulate) {
108 
109  for (AdHocQueryRequest queryRequest : queryRequests) {
113  if (!queryRequest.getQuery().validate()) {
114  //TODO mark the particular query node RED
115  break;
116  }
117 
118  //JMTODO: It looks like this map is not actually used for anything...
119  Map<String, Object> map = queryRequest.getProperties();
120  /*
121  * make sure all common properties are displayed as columns (since
122  * we are doing lazy child Node load we need to preinitialize
123  * properties when sending parent Node)
124  */
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());
129 
130  createFlatKeys(queryRequest.getQuery(), toPopulate);
131  }
132 
133  // If there were no hits, make a single Node that will display that
134  // no results were found.
135  if (toPopulate.isEmpty()) {
136  toPopulate.add(new KeyValue("This KeyValue Is Empty", 0));
137  }
138 
139  return true;
140  }
141 
149  @NbBundle.Messages({"KeywordSearchResultFactory.query.exception.msg=Could not perform the query "})
150  private boolean createFlatKeys(KeywordSearchQuery queryRequest, List<KeyValue> toPopulate) {
151 
155  QueryResults queryResults;
156  try {
157  queryResults = queryRequest.performQuery();
158  } catch (KeywordSearchModuleException | NoOpenCoreException ex) {
159  logger.log(Level.SEVERE, "Could not perform the query " + queryRequest.getQueryString(), ex); //NON-NLS
160  MessageNotifyUtil.Notify.error(Bundle.KeywordSearchResultFactory_query_exception_msg() + queryRequest.getQueryString(), ex.getCause().getMessage());
161  return false;
162  }
163  SleuthkitCase tskCase;
164  try {
165  tskCase = Case.getCurrentCaseThrows().getSleuthkitCase();
166  } catch (NoCurrentCaseException ex) {
167  logger.log(Level.SEVERE, "There was no case open.", ex); //NON-NLS
168  return false;
169  }
170 
171  int hitNumber = 0;
172  List<KeywordHitKey> tempList = new ArrayList<>();
173  for (KeywordHit hit : getOneHitPerObject(queryResults)) {
174 
178  Map<String, Object> properties = new LinkedHashMap<>();
179 
183  if (hit.hasSnippet()) {
184  properties.put(TSK_KEYWORD_PREVIEW.getDisplayName(), hit.getSnippet());
185  }
186 
187  Content content;
188  String contentName;
189  try {
190  content = tskCase.getContentById(hit.getContentID());
191  if (content == null) {
192  logger.log(Level.SEVERE, "There was a error getting content by id."); //NON-NLS
193  return false;
194  }
195  } catch (TskCoreException ex) {
196  logger.log(Level.SEVERE, "There was a error getting content by id.", ex); //NON-NLS
197  return false;
198  }
199 
200  contentName = content.getName();
201  if (content instanceof AbstractFile) {
202  AbstractFsContentNode.fillPropertyMap(properties, (AbstractFile) content);
203  } else {
204  properties.put(LOCATION.toString(), contentName);
205  }
206 
207 
208  String hitName;
209  BlackboardArtifact artifact = null;
210  if (hit.isArtifactHit()) {
211  try {
212  artifact = tskCase.getBlackboardArtifact(hit.getArtifactID().get());
213  hitName = artifact.getDisplayName() + " Artifact"; //NON-NLS
214  } catch (TskCoreException ex) {
215  logger.log(Level.SEVERE, "Error getting blckboard artifact by id", ex);
216  return false;
217  }
218  } else {
219  hitName = contentName;
220  }
221  hitNumber++;
222  tempList.add(new KeywordHitKey(hitName, properties, hitNumber, hit.getSolrObjectId(), content, artifact, queryRequest, queryResults));
223 
224  }
225 
226  if (hitNumber != 0) {
227  // Add all the nodes to toPopulate at once. Minimizes node creation
228  // EDT threads, which can slow and/or hang the UI on large queries.
229  toPopulate.addAll(tempList);
230  }
231 
232  //write to bb
233  //cannot reuse snippet in BlackboardResultWriter
234  //because for regex searches in UI we compress results by showing a content per regex once (even if multiple term hits)
235  //whereas in bb we write every hit per content separately
236  new BlackboardResultWriter(queryResults, queryRequest.getKeywordList().getName(), saveResults).execute();
237 
238  return true;
239  }
240 
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)) {
254  // add hit with lowest SolrObjectID-Chunk-ID combination.
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);
259  }
260  }
261  }
262  return hits.values();
263  }
264 
265  @NbBundle.Messages({"KeywordSearchResultFactory.createNodeForKey.noResultsFound.text=No results found."})
266  @Override
267  protected Node createNodeForKey(KeyValue key) {
268  Node resultNode;
269 
270  if (key instanceof KeywordHitKey) {
271  AdHocQueryResult adHocQueryResult = new AdHocQueryResult((KeywordHitKey) key);
272 
277  ArrayList<Object> lookups = new ArrayList<>();
278  lookups.add(adHocQueryResult);
279  if (((KeywordHitKey) key).getContent() != null) {
280  lookups.add(((KeywordHitKey) key).getContent());
281  }
282  if (((KeywordHitKey) key).getArtifact() != null) {
283  lookups.add(((KeywordHitKey) key).getArtifact());
284  }
285 
286  Node kvNode = new KeyValueNode(key, Children.LEAF, Lookups.fixed(lookups.toArray()));
287 
288  //wrap in KeywordSearchFilterNode for the markup content, might need to override FilterNode for more customization
289  resultNode = new AdHocSearchFilterNode(kvNode);
290  } else {
291  resultNode = new EmptyNode("This Node Is Empty");
292  resultNode.setDisplayName(NbBundle.getMessage(this.getClass(), "KeywordSearchResultFactory.createNodeForKey.noResultsFound.text"));
293  }
294 
295  return resultNode;
296 
297  }
298 
303  final class AdHocQueryResult {
304 
305  private final long solrObjectId;
306  private final QueryResults results;
307 
315  AdHocQueryResult(KeywordHitKey key) {
316  this.solrObjectId = key.getSolrObjectId();
317  this.results = key.getHits();
318  }
319 
326  long getSolrObjectId() {
327  return solrObjectId;
328  }
329 
335  QueryResults getResults() {
336  return results;
337  }
338  }
339 
344  class KeywordHitKey extends KeyValue {
345 
346  private final long solrObjectId;
347 
348  private final Content content;
349  private final BlackboardArtifact artifact;
350  private final QueryResults hits;
351  private final KeywordSearchQuery query;
352 
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;
372 
373  this.hits = hits;
374  this.query = query;
375  }
376 
377  Content getContent() {
378  return content;
379  }
380 
381  BlackboardArtifact getArtifact() {
382  return artifact;
383  }
384 
385  long getSolrObjectId() {
386  return solrObjectId;
387  }
388 
389  QueryResults getHits() {
390  return hits;
391  }
392 
393  KeywordSearchQuery getQuery() {
394  return query;
395  }
396  }
397 
402  static class BlackboardResultWriter extends SwingWorker<Void, Void> {
403 
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;
410 
411  BlackboardResultWriter(QueryResults hits, String listName, boolean saveResults) {
412  this.hits = hits;
413  this.query = hits.getQuery();
414  this.saveResults = saveResults;
415  }
416 
417  protected void finalizeWorker() {
418  deregisterWriter(this);
419  EventQueue.invokeLater(progress::finish);
420  }
421 
422  @Override
423  protected Void doInBackground() throws Exception {
424  registerWriter(this); //register (synchronized on class) outside of writerLock to prevent deadlock
425  final String queryStr = query.getQueryString();
426  final String queryDisp = queryStr.length() > QUERY_DISPLAY_LEN ? queryStr.substring(0, QUERY_DISPLAY_LEN - 1) + " ..." : queryStr;
427  try {
428  progress = ProgressHandle.createHandle(NbBundle.getMessage(this.getClass(), "KeywordSearchResultFactory.progress.saving", queryDisp), () -> BlackboardResultWriter.this.cancel(true));
429  hits.process(progress, null, this, false, saveResults);
430  } finally {
431  finalizeWorker();
432  }
433  return null;
434  }
435 
436  @Override
437  protected void done() {
438  try {
439  get();
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()); //NON-NLS
442  } catch (ExecutionException ex) {
443  logger.log(Level.SEVERE, "Error writing of ad hoc search query results for " + query.getQueryString() + " to the blackboard", ex); //NON-NLS
444  }
445  }
446 
447  private static synchronized void registerWriter(BlackboardResultWriter writer) {
448  WRITERS.add(writer);
449  }
450 
451  private static synchronized void deregisterWriter(BlackboardResultWriter writer) {
452  WRITERS.remove(writer);
453  }
454 
455  static synchronized void stopAllWriters() {
456  for (BlackboardResultWriter w : WRITERS) {
457  w.cancel(true);
458  WRITERS.remove(w);
459  }
460  }
461  }
462 }

Copyright © 2012-2021 Basis Technology. Generated on: Thu Sep 30 2021
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.