Autopsy  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 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;
31 import java.util.Map;
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;
59 
67 class KeywordSearchResultFactory extends ChildFactory<KeyValueQueryContent> {
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<KeyValueQueryContent> 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<KeyValueQueryContent> 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 
148  int hitNumber = 0;
149  List<KeyValueQueryContent> tempList = new ArrayList<>();
150  for (KeywordHit hit : getOneHitPerObject(queryResults)) {
151 
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);
160  } else {
161  properties.put(LOCATION.toString(), contentName);
162  }
163 
167  if (hit.hasSnippet()) {
168  properties.put(TSK_KEYWORD_PREVIEW.getDisplayName(), hit.getSnippet());
169  }
170 
171  String hitName = hit.isArtifactHit()
172  ? hit.getArtifact().getDisplayName() + " Artifact" //NON-NLS
173  : contentName;
174 
175  hitNumber++;
176  tempList.add(new KeyValueQueryContent(hitName, properties, hitNumber, hit.getSolrObjectId(), content, queryRequest, queryResults));
177  }
178 
179  // Add all the nodes to toPopulate at once. Minimizes node creation
180  // EDT threads, which can slow and/or hang the UI on large queries.
181  toPopulate.addAll(tempList);
182 
183  //write to bb
184  //cannot reuse snippet in BlackboardResultWriter
185  //because for regex searches in UI we compress results by showing a content per regex once (even if multiple term hits)
186  //whereas in bb we write every hit per content separately
187  new BlackboardResultWriter(queryResults, queryRequest.getKeywordList().getName()).execute();
188 
189  return true;
190  }
191 
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)) {
205  // add hit with lowest SolrObjectID-Chunk-ID combination.
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);
210  }
211  }
212  }
213  return hits.values();
214  }
215 
216  @Override
217  protected Node createNodeForKey(KeyValueQueryContent key) {
218  final Content content = key.getContent();
219  QueryResults hits = key.getHits();
220 
221  Node kvNode = new KeyValueNode(key, Children.LEAF, Lookups.singleton(content));
222 
223  //wrap in KeywordSearchFilterNode for the markup content, might need to override FilterNode for more customization
224  return new KeywordSearchFilterNode(hits, kvNode);
225  }
226 
231  class KeyValueQueryContent extends KeyValue {
232 
233  private final long solrObjectId;
234 
235  private final Content content;
236  private final QueryResults hits;
237  private final KeywordSearchQuery query;
238 
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;
255 
256  this.hits = hits;
257  this.query = query;
258  }
259 
260  Content getContent() {
261  return content;
262  }
263 
264  long getSolrObjectId() {
265  return solrObjectId;
266  }
267 
268  QueryResults getHits() {
269  return hits;
270  }
271 
272  KeywordSearchQuery getQuery() {
273  return query;
274  }
275  }
276 
281  static class BlackboardResultWriter extends SwingWorker<Object, Void> {
282 
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;
289 
290  BlackboardResultWriter(QueryResults hits, String listName) {
291  this.hits = hits;
292  this.query = hits.getQuery();
293  }
294 
295  protected void finalizeWorker() {
296  deregisterWriter(this);
297  EventQueue.invokeLater(progress::finish);
298  }
299 
300  @Override
301  protected Object doInBackground() throws Exception {
302  registerWriter(this); //register (synchronized on class) outside of writerLock to prevent deadlock
303  final String queryStr = query.getQueryString();
304  final String queryDisp = queryStr.length() > QUERY_DISPLAY_LEN ? queryStr.substring(0, QUERY_DISPLAY_LEN - 1) + " ..." : queryStr;
305  try {
306  progress = ProgressHandle.createHandle(NbBundle.getMessage(this.getClass(), "KeywordSearchResultFactory.progress.saving", queryDisp), () -> BlackboardResultWriter.this.cancel(true));
307  newArtifacts = hits.writeAllHitsToBlackBoard(progress, null, this, false);
308  } finally {
309  finalizeWorker();
310  }
311  return null;
312  }
313 
314  @Override
315  protected void done() {
316  try {
317  get();
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()); //NON-NLS
320  } catch (ExecutionException ex) {
321  logger.log(Level.SEVERE, "Error writing of ad hoc search query results for " + query.getQueryString() + " to the blackboard", ex); //NON-NLS
322  }
323  }
324 
325  private static synchronized void registerWriter(BlackboardResultWriter writer) {
326  writers.add(writer);
327  }
328 
329  private static synchronized void deregisterWriter(BlackboardResultWriter writer) {
330  writers.remove(writer);
331  }
332 
333  static synchronized void stopAllWriters() {
334  for (BlackboardResultWriter w : writers) {
335  w.cancel(true);
336  writers.remove(w);
337  }
338  }
339  }
340 }

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