Autopsy  4.4.1
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-2017 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;
50 import org.sleuthkit.datamodel.AbstractFile;
51 import org.sleuthkit.datamodel.BlackboardAttribute;
52 import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_KEYWORD;
53 import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_KEYWORD_PREVIEW;
54 import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_KEYWORD_REGEXP;
55 import org.sleuthkit.datamodel.Content;
56 import org.sleuthkit.datamodel.SleuthkitCase;
57 import org.sleuthkit.datamodel.TskCoreException;
58 
66 class KeywordSearchResultFactory extends ChildFactory<KeyValueQueryContent> {
67 
68  private static final Logger logger = Logger.getLogger(KeywordSearchResultFactory.class.getName());
69 
70  //common properties (superset of all Node properties) to be displayed as columns
71  static final List<String> COMMON_PROPERTIES =
72  Stream.concat(
73  Stream.of(
74  TSK_KEYWORD,
75  TSK_KEYWORD_REGEXP,
76  TSK_KEYWORD_PREVIEW)
77  .map(BlackboardAttribute.ATTRIBUTE_TYPE::getDisplayName),
78  Arrays.stream(AbstractAbstractFileNode.AbstractFilePropertyType.values())
79  .map(Object::toString))
80  .collect(Collectors.toList());
81 
82  private final Collection<QueryRequest> queryRequests;
83 
84  KeywordSearchResultFactory(Collection<QueryRequest> queryRequests) {
85  this.queryRequests = queryRequests;
86  }
87 
95  @Override
96  protected boolean createKeys(List<KeyValueQueryContent> toPopulate) {
97 
98  for (QueryRequest queryRequest : queryRequests) {
102  if (!queryRequest.getQuery().validate()) {
103  //TODO mark the particular query node RED
104  break;
105  }
106 
107  //JMTODO: It looks like this map is not actually used for anything...
108  Map<String, Object> map = queryRequest.getProperties();
109  /*
110  * make sure all common properties are displayed as columns (since
111  * we are doing lazy child Node load we need to preinitialize
112  * properties when sending parent Node)
113  */
114  COMMON_PROPERTIES.stream()
115  .forEach((propertyType) -> map.put(propertyType, ""));
116  map.put(TSK_KEYWORD.getDisplayName(), queryRequest.getQueryString());
117  map.put(TSK_KEYWORD_REGEXP.getDisplayName(), !queryRequest.getQuery().isLiteral());
118 
119  createFlatKeys(queryRequest.getQuery(), toPopulate);
120  }
121 
122  return true;
123  }
124 
132  @NbBundle.Messages({"KeywordSearchResultFactory.query.exception.msg=Could not perform the query "})
133  private boolean createFlatKeys(KeywordSearchQuery queryRequest, List<KeyValueQueryContent> toPopulate) {
134 
138  QueryResults queryResults;
139  try {
140  queryResults = queryRequest.performQuery();
141  } catch (KeywordSearchModuleException | NoOpenCoreException ex) {
142  logger.log(Level.SEVERE, "Could not perform the query " + queryRequest.getQueryString(), ex); //NON-NLS
143  MessageNotifyUtil.Notify.error(Bundle.KeywordSearchResultFactory_query_exception_msg() + queryRequest.getQueryString(), ex.getCause().getMessage());
144  return false;
145  }
146  SleuthkitCase tskCase = null;
147  try {
148  tskCase = Case.getCurrentCase().getSleuthkitCase();
149  } catch (IllegalStateException ex) {
150  logger.log(Level.SEVERE, "There was no case open.", ex); //NON-NLS
151  return false;
152  }
153 
154  int hitNumber = 0;
155  List<KeyValueQueryContent> tempList = new ArrayList<>();
156  for (KeywordHit hit : getOneHitPerObject(queryResults)) {
157 
161  Map<String, Object> properties = new LinkedHashMap<>();
162  Content content = null;
163  String contentName = "";
164  try {
165  content = tskCase.getContentById(hit.getContentID());
166  if (content == null) {
167  logger.log(Level.SEVERE, "There was a error getting content by id."); //NON-NLS
168  return false;
169  }
170  } catch (TskCoreException ex) {
171  logger.log(Level.SEVERE, "There was a error getting content by id.", ex); //NON-NLS
172  return false;
173  }
174 
175  contentName = content.getName();
176  if (content instanceof AbstractFile) {
177  AbstractFsContentNode.fillPropertyMap(properties, (AbstractFile) content);
178  } else {
179  properties.put(LOCATION.toString(), contentName);
180  }
181 
185  if (hit.hasSnippet()) {
186  properties.put(TSK_KEYWORD_PREVIEW.getDisplayName(), hit.getSnippet());
187  }
188 
189  String hitName;
190  if (hit.isArtifactHit()) {
191  try {
192  hitName = tskCase.getBlackboardArtifact(hit.getArtifactID().get()).getDisplayName() + " Artifact"; //NON-NLS
193  } catch (TskCoreException ex) {
194  logger.log(Level.SEVERE, "Error getting blckboard artifact by id", ex);
195  return false;
196  }
197  } else {
198  hitName = contentName;
199  }
200  hitNumber++;
201  tempList.add(new KeyValueQueryContent(hitName, properties, hitNumber, hit.getSolrObjectId(), content, queryRequest, queryResults));
202 
203  }
204 
205  // Add all the nodes to toPopulate at once. Minimizes node creation
206  // EDT threads, which can slow and/or hang the UI on large queries.
207  toPopulate.addAll(tempList);
208 
209  //write to bb
210  //cannot reuse snippet in BlackboardResultWriter
211  //because for regex searches in UI we compress results by showing a content per regex once (even if multiple term hits)
212  //whereas in bb we write every hit per content separately
213  new BlackboardResultWriter(queryResults, queryRequest.getKeywordList().getName()).execute();
214 
215  return true;
216  }
217 
227  Collection<KeywordHit> getOneHitPerObject(QueryResults queryResults) {
228  HashMap<Long, KeywordHit> hits = new HashMap<>();
229  for (Keyword keyWord : queryResults.getKeywords()) {
230  for (KeywordHit hit : queryResults.getResults(keyWord)) {
231  // add hit with lowest SolrObjectID-Chunk-ID combination.
232  if (!hits.containsKey(hit.getSolrObjectId())) {
233  hits.put(hit.getSolrObjectId(), hit);
234  } else if (hit.getChunkId() < hits.get(hit.getSolrObjectId()).getChunkId()) {
235  hits.put(hit.getSolrObjectId(), hit);
236  }
237  }
238  }
239  return hits.values();
240  }
241 
242  @Override
243  protected Node createNodeForKey(KeyValueQueryContent key) {
244  final Content content = key.getContent();
245  QueryResults hits = key.getHits();
246 
247  Node kvNode = new KeyValueNode(key, Children.LEAF, Lookups.singleton(content));
248 
249  //wrap in KeywordSearchFilterNode for the markup content, might need to override FilterNode for more customization
250  return new KeywordSearchFilterNode(hits, kvNode);
251 
252  }
253 
258  class KeyValueQueryContent extends KeyValue {
259 
260  private final long solrObjectId;
261 
262  private final Content content;
263  private final QueryResults hits;
264  private final KeywordSearchQuery query;
265 
279  KeyValueQueryContent(String name, Map<String, Object> map, int id, long solrObjectId, Content content, KeywordSearchQuery query, QueryResults hits) {
280  super(name, map, id);
281  this.solrObjectId = solrObjectId;
282  this.content = content;
283 
284  this.hits = hits;
285  this.query = query;
286  }
287 
288  Content getContent() {
289  return content;
290  }
291 
292  long getSolrObjectId() {
293  return solrObjectId;
294  }
295 
296  QueryResults getHits() {
297  return hits;
298  }
299 
300  KeywordSearchQuery getQuery() {
301  return query;
302  }
303  }
304 
309  static class BlackboardResultWriter extends SwingWorker<Void, Void> {
310 
311  private static final List<BlackboardResultWriter> writers = new ArrayList<>();
312  private ProgressHandle progress;
313  private final KeywordSearchQuery query;
314  private final QueryResults hits;
315  private static final int QUERY_DISPLAY_LEN = 40;
316 
317  BlackboardResultWriter(QueryResults hits, String listName) {
318  this.hits = hits;
319  this.query = hits.getQuery();
320  }
321 
322  protected void finalizeWorker() {
323  deregisterWriter(this);
324  EventQueue.invokeLater(progress::finish);
325  }
326 
327  @Override
328  protected Void doInBackground() throws Exception {
329  registerWriter(this); //register (synchronized on class) outside of writerLock to prevent deadlock
330  final String queryStr = query.getQueryString();
331  final String queryDisp = queryStr.length() > QUERY_DISPLAY_LEN ? queryStr.substring(0, QUERY_DISPLAY_LEN - 1) + " ..." : queryStr;
332  try {
333  progress = ProgressHandle.createHandle(NbBundle.getMessage(this.getClass(), "KeywordSearchResultFactory.progress.saving", queryDisp), () -> BlackboardResultWriter.this.cancel(true));
334  hits.writeAllHitsToBlackBoard(progress, null, this, false);
335  } finally {
336  finalizeWorker();
337  }
338  return null;
339  }
340 
341  @Override
342  protected void done() {
343  try {
344  get();
345  } catch (InterruptedException | CancellationException ex) {
346  logger.log(Level.WARNING, "User cancelled writing of ad hoc search query results for '{0}' to the blackboard", query.getQueryString()); //NON-NLS
347  } catch (ExecutionException ex) {
348  logger.log(Level.SEVERE, "Error writing of ad hoc search query results for " + query.getQueryString() + " to the blackboard", ex); //NON-NLS
349  }
350  }
351 
352  private static synchronized void registerWriter(BlackboardResultWriter writer) {
353  writers.add(writer);
354  }
355 
356  private static synchronized void deregisterWriter(BlackboardResultWriter writer) {
357  writers.remove(writer);
358  }
359 
360  static synchronized void stopAllWriters() {
361  for (BlackboardResultWriter w : writers) {
362  w.cancel(true);
363  writers.remove(w);
364  }
365  }
366  }
367 }

Copyright © 2012-2016 Basis Technology. Generated on: Fri Sep 29 2017
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.