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-2015 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.Collection;
24 import java.util.HashMap;
25 import java.util.LinkedHashMap;
26 import java.util.List;
27 import java.util.Map;
28 import java.util.concurrent.ExecutionException;
29 import java.util.concurrent.locks.ReentrantReadWriteLock;
30 import java.util.logging.Level;
31 import javax.swing.SwingWorker;
32 import org.netbeans.api.progress.ProgressHandle;
33 import org.openide.nodes.ChildFactory;
34 import org.openide.nodes.Children;
35 import org.openide.nodes.Node;
36 import org.openide.util.Cancellable;
37 import org.openide.util.NbBundle;
38 import org.openide.util.lookup.Lookups;
46 import org.sleuthkit.datamodel.AbstractFile;
47 import org.sleuthkit.datamodel.BlackboardArtifact;
48 import org.sleuthkit.datamodel.BlackboardAttribute;
49 import org.sleuthkit.datamodel.Content;
50 
58 class KeywordSearchResultFactory extends ChildFactory<KeyValueQueryContent> {
59 
60  //common properties (superset of all Node properties) to be displayed as columns
61  //these are merged with FsContentPropertyType defined properties
62  public static enum CommonPropertyTypes {
63 
64  KEYWORD {
65  @Override
66  public String toString() {
67  return BlackboardAttribute.ATTRIBUTE_TYPE.TSK_KEYWORD.getDisplayName();
68  }
69  },
70  REGEX {
71  @Override
72  public String toString() {
73  return BlackboardAttribute.ATTRIBUTE_TYPE.TSK_KEYWORD_REGEXP.getDisplayName();
74  }
75  },
76  CONTEXT {
77  @Override
78  public String toString() {
79  return BlackboardAttribute.ATTRIBUTE_TYPE.TSK_KEYWORD_PREVIEW.getDisplayName();
80  }
81  },
82  }
83  private Collection<QueryRequest> queryRequests;
84  private final DataResultTopComponent viewer; //viewer driving this child node factory
85  private static final Logger logger = Logger.getLogger(KeywordSearchResultFactory.class.getName());
86 
87  KeywordSearchResultFactory(Collection<QueryRequest> queryRequests, DataResultTopComponent viewer) {
88  this.queryRequests = queryRequests;
89  this.viewer = viewer;
90  }
91 
99  public static void initCommonProperties(Map<String, Object> toSet) {
100  CommonPropertyTypes[] commonTypes = CommonPropertyTypes.values();
101  final int COMMON_PROPS_LEN = commonTypes.length;
102  for (int i = 0; i < COMMON_PROPS_LEN; ++i) {
103  toSet.put(commonTypes[i].toString(), "");
104  }
105 
106  AbstractAbstractFileNode.AbstractFilePropertyType[] fsTypes = AbstractAbstractFileNode.AbstractFilePropertyType.values();
107  final int FS_PROPS_LEN = fsTypes.length;
108  for (int i = 0; i < FS_PROPS_LEN; ++i) {
109  toSet.put(fsTypes[i].toString(), "");
110  }
111  }
112 
113  public static void setCommonProperty(Map<String, Object> toSet, CommonPropertyTypes type, String value) {
114  final String typeStr = type.toString();
115  toSet.put(typeStr, value);
116  }
117 
118  public static void setCommonProperty(Map<String, Object> toSet, CommonPropertyTypes type, Boolean value) {
119  final String typeStr = type.toString();
120  toSet.put(typeStr, value);
121  }
122 
123  @Override
124  protected boolean createKeys(List<KeyValueQueryContent> toPopulate) {
125 
126  for (QueryRequest queryRequest : queryRequests) {
127  Map<String, Object> map = queryRequest.getProperties();
128  initCommonProperties(map);
129  final String query = queryRequest.getQueryString();
130  setCommonProperty(map, CommonPropertyTypes.KEYWORD, query);
131  setCommonProperty(map, CommonPropertyTypes.REGEX, !queryRequest.getQuery().isLiteral());
132  createFlatKeys(queryRequest, toPopulate);
133  }
134 
135  return true;
136  }
137 
145  private boolean createFlatKeys(QueryRequest queryRequest, List<KeyValueQueryContent> toPopulate) {
149  final KeywordSearchQuery keywordSearchQuery = queryRequest.getQuery();
150  if (!keywordSearchQuery.validate()) {
151  //TODO mark the particular query node RED
152  return false;
153  }
154 
158  QueryResults queryResults;
159  try {
160  queryResults = keywordSearchQuery.performQuery();
161  } catch (NoOpenCoreException ex) {
162  logger.log(Level.SEVERE, "Could not perform the query " + keywordSearchQuery.getQueryString(), ex); //NON-NLS
163  return false;
164  }
165 
166  int id = 0;
167  List<KeyValueQueryContent> tempList = new ArrayList<>();
168  for (KeywordHit hit : getOneHitPerObject(queryResults)) {
172  Map<String, Object> properties = new LinkedHashMap<>();
173  Content content = hit.getContent();
174  if (content instanceof AbstractFile) {
175  AbstractFsContentNode.fillPropertyMap(properties, (AbstractFile) content);
176  } else {
177  properties.put(AbstractAbstractFileNode.AbstractFilePropertyType.LOCATION.toString(), content.getName());
178  }
179 
183  if (hit.hasSnippet()) {
184  setCommonProperty(properties, CommonPropertyTypes.CONTEXT, hit.getSnippet());
185  }
186 
187  //@@@ USE ConentHit in UniqueFileMap instead of the below search
188  //get unique match result files
189  // BC: @@@ THis is really ineffecient. We should keep track of this when
190  // we flattened the list of files to the unique files.
191  final String highlightQueryEscaped = getHighlightQuery(keywordSearchQuery, keywordSearchQuery.isLiteral(), queryResults, content);
192 
193  String name = content.getName();
194  if (hit.isArtifactHit()) {
195  name = hit.getArtifact().getDisplayName() + " Artifact"; // NON-NLS
196  }
197  tempList.add(new KeyValueQueryContent(name, properties, ++id, hit.getSolrObjectId(), content, highlightQueryEscaped, keywordSearchQuery, queryResults));
198  }
199 
200  // Add all the nodes to toPopulate at once. Minimizes node creation
201  // EDT threads, which can slow and/or hang the UI on large queries.
202  toPopulate.addAll(tempList);
203 
204  //write to bb
205  //cannot reuse snippet in BlackboardResultWriter
206  //because for regex searches in UI we compress results by showing a content per regex once (even if multiple term hits)
207  //whereas in bb we write every hit per content separately
208  new BlackboardResultWriter(queryResults, queryRequest.getQuery().getKeywordList().getName()).execute();
209 
210  return true;
211  }
212 
222  Collection<KeywordHit> getOneHitPerObject(QueryResults queryResults) {
223  HashMap<Long, KeywordHit> hits = new HashMap<Long, KeywordHit>();
224  for (Keyword keyWord : queryResults.getKeywords()) {
225  for (KeywordHit hit : queryResults.getResults(keyWord)) {
226  // add hit with lowest SolrObjectID-Chunk-ID combination.
227  if (!hits.containsKey(hit.getSolrObjectId())) {
228  hits.put(hit.getSolrObjectId(), hit);
229  } else {
230  if (hit.getChunkId() < hits.get(hit.getSolrObjectId()).getChunkId()) {
231  hits.put(hit.getSolrObjectId(), hit);
232  }
233  }
234  }
235  }
236  return hits.values();
237  }
238 
249  private String getHighlightQuery(KeywordSearchQuery query, boolean literal_query, QueryResults queryResults, Content content) {
250  if (literal_query) {
251  //literal, treat as non-regex, non-term component query
252  return constructEscapedSolrQuery(query.getQueryString(), literal_query);
253  } else {
254  //construct a Solr query using aggregated terms to get highlighting
255  //the query is executed later on demand
256  if (queryResults.getKeywords().size() == 1) {
257  //simple case, no need to process subqueries and do special escaping
258  Keyword keyword = queryResults.getKeywords().iterator().next();
259  return constructEscapedSolrQuery(keyword.getSearchTerm(), literal_query);
260  } else {
261  //find terms for this content hit
262  List<Keyword> hitTerms = new ArrayList<>();
263  for (Keyword keyword : queryResults.getKeywords()) {
264  for (KeywordHit hit : queryResults.getResults(keyword)) {
265  if (hit.getContent().equals(content)) {
266  hitTerms.add(keyword);
267  break; //go to next term
268  }
269  }
270  }
271 
272  StringBuilder highlightQuery = new StringBuilder();
273  final int lastTerm = hitTerms.size() - 1;
274  int curTerm = 0;
275  for (Keyword term : hitTerms) {
276  //escape subqueries, MAKE SURE they are not escaped again later
277  highlightQuery.append(constructEscapedSolrQuery(term.getSearchTerm(), literal_query));
278  if (lastTerm != curTerm) {
279  highlightQuery.append(" "); //acts as OR ||
280  }
281 
282  ++curTerm;
283  }
284  return highlightQuery.toString();
285  }
286  }
287  }
288 
296  private String constructEscapedSolrQuery(String query, boolean literal_query) {
297  StringBuilder highlightQuery = new StringBuilder();
298  String highLightField;
299  if (literal_query) {
300  highLightField = LuceneQuery.HIGHLIGHT_FIELD_LITERAL;
301  } else {
302  highLightField = LuceneQuery.HIGHLIGHT_FIELD_REGEX;
303  }
304  highlightQuery.append(highLightField).append(":").append("\"").append(KeywordSearchUtil.escapeLuceneQuery(query)).append("\"");
305  return highlightQuery.toString();
306  }
307 
308  @Override
309  protected Node createNodeForKey(KeyValueQueryContent key) {
310  final Content content = key.getContent();
311  final String queryStr = key.getQueryStr();;
312  QueryResults hits = key.getHits();
313 
314  Node kvNode = new KeyValueNode(key, Children.LEAF, Lookups.singleton(content));
315 
316  //wrap in KeywordSearchFilterNode for the markup content, might need to override FilterNode for more customization
317  // store the data in HighlightedMatchesSource so that it can be looked up (in content viewer)
318  HighlightedText highlights = new HighlightedText(key.solrObjectId, queryStr, !key.getQuery().isLiteral(), false, hits);
319  return new KeywordSearchFilterNode(highlights, kvNode);
320  }
321 
326  class KeyValueQueryContent extends KeyValue {
327 
328  private long solrObjectId;
329  private Content content;
330  private String queryStr;
331  private QueryResults hits;
332  private KeywordSearchQuery query;
333 
349  public KeyValueQueryContent(String name, Map<String, Object> map, int id, long solrObjectId, Content content, String queryStr, KeywordSearchQuery query, QueryResults hits) {
350  super(name, map, id);
351  this.solrObjectId = solrObjectId;
352  this.content = content;
353  this.queryStr = queryStr;
354  this.hits = hits;
355  this.query = query;
356  }
357 
358  Content getContent() {
359  return content;
360  }
361 
362  String getQueryStr() {
363  return queryStr;
364  }
365 
366  QueryResults getHits() {
367  return hits;
368  }
369 
370  KeywordSearchQuery getQuery() {
371  return query;
372  }
373  }
374 
379  static class BlackboardResultWriter extends SwingWorker<Object, Void> {
380 
381  private static List<BlackboardResultWriter> writers = new ArrayList<>();
382  //lock utilized to enqueue writers and limit execution to 1 at a time
383  private static final ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock(true); //use fairness policy
384  //private static final Lock writerLock = rwLock.writeLock();
385  private ProgressHandle progress;
386  private KeywordSearchQuery query;
387  private String listName;
388  private QueryResults hits;
389  private Collection<BlackboardArtifact> newArtifacts = new ArrayList<>();
390  private static final int QUERY_DISPLAY_LEN = 40;
391 
392  BlackboardResultWriter(QueryResults hits, String listName) {
393  this.hits = hits;
394  this.query = hits.getQuery();
395  this.listName = listName;
396  }
397 
398  protected void finalizeWorker() {
399  deregisterWriter(this);
400 
401  EventQueue.invokeLater(new Runnable() {
402  @Override
403  public void run() {
404  progress.finish();
405  }
406  });
407  }
408 
409  @Override
410  protected Object doInBackground() throws Exception {
411  registerWriter(this); //register (synchronized on class) outside of writerLock to prevent deadlock
412  final String queryStr = query.getQueryString();
413  final String queryDisp = queryStr.length() > QUERY_DISPLAY_LEN ? queryStr.substring(0, QUERY_DISPLAY_LEN - 1) + " ..." : queryStr;
414  //block until previous writer is done
415  //writerLock.lock();
416 
417  try {
418  progress = ProgressHandle.createHandle(
419  NbBundle.getMessage(this.getClass(), "KeywordSearchResultFactory.progress.saving", queryDisp), new Cancellable() {
420  @Override
421  public boolean cancel() {
422  return BlackboardResultWriter.this.cancel(true);
423  }
424  });
425 
426  // Create blackboard artifacts
427  newArtifacts = hits.writeAllHitsToBlackBoard(progress, null, this, false);
428  } finally {
429  finalizeWorker();
430  }
431 
432  return null;
433  }
434 
435  @Override
436  protected void done() {
437  try {
438  // test if any exceptions were thrown
439  get();
440  } catch (InterruptedException | ExecutionException ex) {
441  logger.log(Level.SEVERE, "Error querying ", ex); //NON-NLS
442  }
443  }
444 
445  private static synchronized void registerWriter(BlackboardResultWriter writer) {
446  writers.add(writer);
447  }
448 
449  private static synchronized void deregisterWriter(BlackboardResultWriter writer) {
450  writers.remove(writer);
451  }
452 
453  static synchronized void stopAllWriters() {
454  for (BlackboardResultWriter w : writers) {
455  w.cancel(true);
456  writers.remove(w);
457  }
458  }
459  }
460 }
synchronized static Logger getLogger(String name)
Definition: Logger.java:161

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