Converted DeletedContent, ExtractedContent, FileSize, FileTypesByExtension, FileTypesByMimeType, HashsetHits, InterestingHits and KeywordHits to new child factory approach that supports paging.

This commit is contained in:
esaunders 2019-04-17 13:05:50 -04:00
parent f54c9aa2ff
commit c4e81f2ea2
8 changed files with 273 additions and 270 deletions

View File

@ -1,7 +1,7 @@
/* /*
* Autopsy Forensic Browser * Autopsy Forensic Browser
* *
* Copyright 2011-2018 Basis Technology Corp. * Copyright 2011-2019 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org * Contact: carrier <at> sleuthkit <dot> org
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
@ -39,7 +39,6 @@ import org.openide.util.lookup.Lookups;
import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.casemodule.CasePreferences; import org.sleuthkit.autopsy.casemodule.CasePreferences;
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
import org.sleuthkit.autopsy.core.UserPreferences;
import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.ingest.IngestManager; import org.sleuthkit.autopsy.ingest.IngestManager;
import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.AbstractFile;
@ -358,7 +357,7 @@ public class DeletedContent implements AutopsyVisitableItem {
} }
} }
static class DeletedContentChildren extends ChildFactory.Detachable<AbstractFile> { static class DeletedContentChildren extends BaseChildFactory<AbstractFile> {
private final SleuthkitCase skCase; private final SleuthkitCase skCase;
private final DeletedContent.DeletedContentFilter filter; private final DeletedContent.DeletedContentFilter filter;
@ -368,6 +367,7 @@ public class DeletedContent implements AutopsyVisitableItem {
private final long datasourceObjId; private final long datasourceObjId;
DeletedContentChildren(DeletedContent.DeletedContentFilter filter, SleuthkitCase skCase, Observable o, long datasourceObjId) { DeletedContentChildren(DeletedContent.DeletedContentFilter filter, SleuthkitCase skCase, Observable o, long datasourceObjId) {
super(filter.getName());
this.skCase = skCase; this.skCase = skCase;
this.filter = filter; this.filter = filter;
this.notifier = o; this.notifier = o;
@ -376,6 +376,11 @@ public class DeletedContent implements AutopsyVisitableItem {
private final Observer observer = new DeletedContentChildrenObserver(); private final Observer observer = new DeletedContentChildrenObserver();
@Override
protected List<AbstractFile> makeKeys() {
return runFsQuery();
}
// Cause refresh of children if there are changes // Cause refresh of children if there are changes
private class DeletedContentChildrenObserver implements Observer { private class DeletedContentChildrenObserver implements Observer {
@ -386,25 +391,19 @@ public class DeletedContent implements AutopsyVisitableItem {
} }
@Override @Override
protected void addNotify() { protected void onAdd() {
if (notifier != null) { if (notifier != null) {
notifier.addObserver(observer); notifier.addObserver(observer);
} }
} }
@Override @Override
protected void removeNotify() { protected void onRemove() {
if (notifier != null) { if (notifier != null) {
notifier.deleteObserver(observer); notifier.deleteObserver(observer);
} }
} }
@Override
protected boolean createKeys(List<AbstractFile> list) {
list.addAll(runFsQuery());
return true;
}
static private String makeQuery(DeletedContent.DeletedContentFilter filter, long filteringDSObjId) { static private String makeQuery(DeletedContent.DeletedContentFilter filter, long filteringDSObjId) {
String query = ""; String query = "";
switch (filter) { switch (filter) {
@ -440,11 +439,6 @@ public class DeletedContent implements AutopsyVisitableItem {
} }
if (UserPreferences.hideKnownFilesInViewsTree()) {
query += " AND (known != " + TskData.FileKnown.KNOWN.getFileKnownValue() //NON-NLS
+ " OR known IS NULL)"; //NON-NLS
}
if (Objects.equals(CasePreferences.getGroupItemsInTreeByDataSource(), true)) { if (Objects.equals(CasePreferences.getGroupItemsInTreeByDataSource(), true)) {
query += " AND data_source_obj_id = " + filteringDSObjId; query += " AND data_source_obj_id = " + filteringDSObjId;
} }

View File

@ -423,12 +423,12 @@ public class ExtractedContent implements AutopsyVisitableItem {
/** /**
* Creates children for a given artifact type * Creates children for a given artifact type
*/ */
private class ArtifactFactory extends ChildFactory.Detachable<BlackboardArtifact> { private class ArtifactFactory extends BaseChildFactory<BlackboardArtifact> {
private BlackboardArtifact.Type type; private BlackboardArtifact.Type type;
public ArtifactFactory(BlackboardArtifact.Type type) { public ArtifactFactory(BlackboardArtifact.Type type) {
super(); super(type.getTypeName());
this.type = type; this.type = type;
} }
@ -481,36 +481,36 @@ public class ExtractedContent implements AutopsyVisitableItem {
}; };
@Override @Override
protected void addNotify() { protected void onAdd() {
IngestManager.getInstance().addIngestJobEventListener(pcl); IngestManager.getInstance().addIngestJobEventListener(pcl);
IngestManager.getInstance().addIngestModuleEventListener(pcl); IngestManager.getInstance().addIngestModuleEventListener(pcl);
} }
@Override @Override
protected void removeNotify() { protected void onRemove() {
IngestManager.getInstance().removeIngestJobEventListener(pcl); IngestManager.getInstance().removeIngestJobEventListener(pcl);
IngestManager.getInstance().removeIngestModuleEventListener(pcl); IngestManager.getInstance().removeIngestModuleEventListener(pcl);
} }
@Override
protected boolean createKeys(List<BlackboardArtifact> list) {
if (skCase != null) {
try {
List<BlackboardArtifact> arts =
Objects.equals(CasePreferences.getGroupItemsInTreeByDataSource(), true) ?
blackboard.getArtifacts(type.getTypeID(), datasourceObjId) :
skCase.getBlackboardArtifacts(type.getTypeID());
list.addAll(arts);
} catch (TskException ex) {
Logger.getLogger(ArtifactFactory.class.getName()).log(Level.SEVERE, "Couldn't get blackboard artifacts from database", ex); //NON-NLS
}
}
return true;
}
@Override @Override
protected Node createNodeForKey(BlackboardArtifact key) { protected Node createNodeForKey(BlackboardArtifact key) {
return new BlackboardArtifactNode(key); return new BlackboardArtifactNode(key);
} }
@Override
protected List<BlackboardArtifact> makeKeys() {
if (skCase != null) {
try {
List<BlackboardArtifact> arts =
Objects.equals(CasePreferences.getGroupItemsInTreeByDataSource(), true) ?
blackboard.getArtifacts(type.getTypeID(), datasourceObjId) :
skCase.getBlackboardArtifacts(type.getTypeID());
return arts;
} catch (TskException ex) {
Logger.getLogger(ArtifactFactory.class.getName()).log(Level.SEVERE, "Couldn't get blackboard artifacts from database", ex); //NON-NLS
}
}
return Collections.emptyList();
}
} }
} }

View File

@ -1,7 +1,7 @@
/* /*
* Autopsy Forensic Browser * Autopsy Forensic Browser
* *
* Copyright 2013-2018 Basis Technology Corp. * Copyright 2013-2019 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org * Contact: carrier <at> sleuthkit <dot> org
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
@ -361,7 +361,7 @@ public class FileSize implements AutopsyVisitableItem {
/* /*
* Makes children, which are nodes for files of a given range * Makes children, which are nodes for files of a given range
*/ */
static class FileSizeChildren extends ChildFactory.Detachable<AbstractFile> { static class FileSizeChildren extends BaseChildFactory<AbstractFile> {
private final SleuthkitCase skCase; private final SleuthkitCase skCase;
private final FileSizeFilter filter; private final FileSizeFilter filter;
@ -377,6 +377,7 @@ public class FileSize implements AutopsyVisitableItem {
* added to case * added to case
*/ */
FileSizeChildren(FileSizeFilter filter, SleuthkitCase skCase, Observable o, long dsObjId) { FileSizeChildren(FileSizeFilter filter, SleuthkitCase skCase, Observable o, long dsObjId) {
super(filter.getName());
this.skCase = skCase; this.skCase = skCase;
this.filter = filter; this.filter = filter;
this.notifier = o; this.notifier = o;
@ -385,14 +386,14 @@ public class FileSize implements AutopsyVisitableItem {
} }
@Override @Override
protected void addNotify() { protected void onAdd() {
if (notifier != null) { if (notifier != null) {
notifier.addObserver(observer); notifier.addObserver(observer);
} }
} }
@Override @Override
protected void removeNotify() { protected void onRemove() {
if (notifier != null) { if (notifier != null) {
notifier.deleteObserver(observer); notifier.deleteObserver(observer);
} }
@ -400,6 +401,11 @@ public class FileSize implements AutopsyVisitableItem {
private final Observer observer = new FileSizeChildrenObserver(); private final Observer observer = new FileSizeChildrenObserver();
@Override
protected List<AbstractFile> makeKeys() {
return runFsQuery();
}
// Cause refresh of children if there are changes // Cause refresh of children if there are changes
private class FileSizeChildrenObserver implements Observer { private class FileSizeChildrenObserver implements Observer {
@ -409,12 +415,6 @@ public class FileSize implements AutopsyVisitableItem {
} }
} }
@Override
protected boolean createKeys(List<AbstractFile> list) {
list.addAll(runFsQuery());
return true;
}
private static String makeQuery(FileSizeFilter filter, long filteringDSObjId) { private static String makeQuery(FileSizeFilter filter, long filteringDSObjId) {
String query; String query;
switch (filter) { switch (filter) {
@ -436,17 +436,6 @@ public class FileSize implements AutopsyVisitableItem {
// Ignore unallocated block files. // Ignore unallocated block files.
query = query + " AND (type != " + TskData.TSK_DB_FILES_TYPE_ENUM.UNALLOC_BLOCKS.getFileType() + ")"; //NON-NLS query = query + " AND (type != " + TskData.TSK_DB_FILES_TYPE_ENUM.UNALLOC_BLOCKS.getFileType() + ")"; //NON-NLS
// Hide known files if indicated in the user preferences.
if(UserPreferences.hideKnownFilesInViewsTree()) {
query += " AND (known != " + TskData.FileKnown.KNOWN.getFileKnownValue() //NON-NLS
+ " OR known IS NULL)"; //NON-NLS
}
// Hide slack files if indicated in the user preferences.
if(UserPreferences.hideSlackFilesInViewsTree()) {
query += " AND (type != " + TskData.TSK_DB_FILES_TYPE_ENUM.SLACK.getFileType() + ")"; //NON-NLS
}
// filter by datasource if indicated in case preferences // filter by datasource if indicated in case preferences
if (Objects.equals(CasePreferences.getGroupItemsInTreeByDataSource(), true)) { if (Objects.equals(CasePreferences.getGroupItemsInTreeByDataSource(), true)) {
query += " AND data_source_obj_id = " + filteringDSObjId; query += " AND data_source_obj_id = " + filteringDSObjId;

View File

@ -1,7 +1,7 @@
/* /*
* Autopsy Forensic Browser * Autopsy Forensic Browser
* *
* Copyright 2011-2018 Basis Technology Corp. * Copyright 2011-2019 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org * Contact: carrier <at> sleuthkit <dot> org
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
@ -21,6 +21,7 @@ package org.sleuthkit.autopsy.datamodel;
import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener; import java.beans.PropertyChangeListener;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections;
import java.util.EnumSet; import java.util.EnumSet;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
@ -292,7 +293,7 @@ public final class FileTypesByExtension implements AutopsyVisitableItem {
* should refresh * should refresh
*/ */
FileExtensionNode(FileTypesByExtension.SearchFilterInterface filter, SleuthkitCase skCase, FileTypesByExtObservable o) { FileExtensionNode(FileTypesByExtension.SearchFilterInterface filter, SleuthkitCase skCase, FileTypesByExtObservable o) {
super(typesRoot, Children.create(new FileExtensionNodeChildren(filter, skCase, o), true), super(typesRoot, Children.create(new FileExtensionNodeChildren(filter, skCase, o, filter.getDisplayName()), true),
Lookups.singleton(filter.getDisplayName())); Lookups.singleton(filter.getDisplayName()));
this.filter = filter; this.filter = filter;
super.setName(filter.getDisplayName()); super.setName(filter.getDisplayName());
@ -377,7 +378,7 @@ public final class FileTypesByExtension implements AutopsyVisitableItem {
/** /**
* Child node factory for a specific file type - does the database query. * Child node factory for a specific file type - does the database query.
*/ */
private class FileExtensionNodeChildren extends ChildFactory.Detachable<FileTypesKey> implements Observer { private class FileExtensionNodeChildren extends BaseChildFactory<FileTypesKey> implements Observer {
private final SleuthkitCase skCase; private final SleuthkitCase skCase;
private final FileTypesByExtension.SearchFilterInterface filter; private final FileTypesByExtension.SearchFilterInterface filter;
@ -390,22 +391,22 @@ public final class FileTypesByExtension implements AutopsyVisitableItem {
* @param o Observable that will notify when there could be new * @param o Observable that will notify when there could be new
* data to display * data to display
*/ */
private FileExtensionNodeChildren(FileTypesByExtension.SearchFilterInterface filter, SleuthkitCase skCase, Observable o) { private FileExtensionNodeChildren(FileTypesByExtension.SearchFilterInterface filter, SleuthkitCase skCase, Observable o, String nodeName) {
super(); super(nodeName);
this.filter = filter; this.filter = filter;
this.skCase = skCase; this.skCase = skCase;
notifier = o; notifier = o;
} }
@Override @Override
protected void addNotify() { protected void onAdd() {
if (notifier != null) { if (notifier != null) {
notifier.addObserver(this); notifier.addObserver(this);
} }
} }
@Override @Override
protected void removeNotify() { protected void onRemove() {
if (notifier != null) { if (notifier != null) {
notifier.deleteObserver(this); notifier.deleteObserver(this);
} }
@ -417,19 +418,19 @@ public final class FileTypesByExtension implements AutopsyVisitableItem {
} }
@Override @Override
protected boolean createKeys(List<FileTypesKey> list) { protected Node createNodeForKey(FileTypesKey key) {
try { return key.accept(new FileTypes.FileNodeCreationVisitor());
list.addAll(skCase.findAllFilesWhere(createQuery(filter))
.stream().map(f -> new FileTypesKey(f)).collect(Collectors.toList()));
} catch (TskCoreException ex) {
logger.log(Level.SEVERE, "Couldn't get search results", ex); //NON-NLS
}
return true;
} }
@Override @Override
protected Node createNodeForKey(FileTypesKey key) { protected List<FileTypesKey> makeKeys() {
return key.accept(new FileTypes.FileNodeCreationVisitor()); try {
return skCase.findAllFilesWhere(createQuery(filter))
.stream().map(f -> new FileTypesKey(f)).collect(Collectors.toList());
} catch (TskCoreException ex) {
logger.log(Level.SEVERE, "Couldn't get search results", ex); //NON-NLS
}
return Collections.emptyList();
} }
} }

View File

@ -1,7 +1,7 @@
/* /*
* Autopsy Forensic Browser * Autopsy Forensic Browser
* *
* Copyright 2011-2018 Basis Technology Corp. * Copyright 2011-2019 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org * Contact: carrier <at> sleuthkit <dot> org
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
@ -445,27 +445,16 @@ public final class FileTypesByMimeType extends Observable implements AutopsyVisi
* files that match MimeType which is represented by this position in the * files that match MimeType which is represented by this position in the
* tree. * tree.
*/ */
private class MediaSubTypeNodeChildren extends ChildFactory.Detachable<FileTypesKey> implements Observer { private class MediaSubTypeNodeChildren extends BaseChildFactory<FileTypesKey> implements Observer {
private final String mimeType; private final String mimeType;
private MediaSubTypeNodeChildren(String mimeType) { private MediaSubTypeNodeChildren(String mimeType) {
super(); super(mimeType);
addObserver(this); addObserver(this);
this.mimeType = mimeType; this.mimeType = mimeType;
} }
@Override
protected boolean createKeys(List<FileTypesKey> list) {
try {
list.addAll(skCase.findAllFilesWhere(createBaseWhereExpr() + " AND mime_type = '" + mimeType + "'")
.stream().map(f -> new FileTypesKey(f)).collect(Collectors.toList())); //NON-NLS
} catch (TskCoreException ex) {
logger.log(Level.SEVERE, "Couldn't get search results", ex); //NON-NLS
}
return true;
}
@Override @Override
public void update(Observable o, Object arg) { public void update(Observable o, Object arg) {
refresh(true); refresh(true);
@ -475,5 +464,26 @@ public final class FileTypesByMimeType extends Observable implements AutopsyVisi
protected Node createNodeForKey(FileTypesKey key) { protected Node createNodeForKey(FileTypesKey key) {
return key.accept(new FileTypes.FileNodeCreationVisitor()); return key.accept(new FileTypes.FileNodeCreationVisitor());
} }
@Override
protected List<FileTypesKey> makeKeys() {
try {
return skCase.findAllFilesWhere(createBaseWhereExpr() + " AND mime_type = '" + mimeType + "'")
.stream().map(f -> new FileTypesKey(f)).collect(Collectors.toList()); //NON-NLS
} catch (TskCoreException ex) {
logger.log(Level.SEVERE, "Couldn't get search results", ex); //NON-NLS
}
return Collections.emptyList();
}
@Override
protected void onAdd() {
// No-op
}
@Override
protected void onRemove() {
// No-op
}
} }
} }

View File

@ -1,7 +1,7 @@
/* /*
* Autopsy Forensic Browser * Autopsy Forensic Browser
* *
* Copyright 2011-2018 Basis Technology Corp. * Copyright 2011-2019 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org * Contact: carrier <at> sleuthkit <dot> org
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
@ -35,6 +35,7 @@ import java.util.Observable;
import java.util.Observer; import java.util.Observer;
import java.util.Set; import java.util.Set;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.stream.Collectors;
import org.openide.nodes.ChildFactory; import org.openide.nodes.ChildFactory;
import org.openide.nodes.Children; import org.openide.nodes.Children;
import org.openide.nodes.Node; import org.openide.nodes.Node;
@ -44,7 +45,6 @@ import org.openide.util.lookup.Lookups;
import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.casemodule.CasePreferences; import org.sleuthkit.autopsy.casemodule.CasePreferences;
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
import org.sleuthkit.autopsy.core.UserPreferences;
import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.ingest.IngestManager; import org.sleuthkit.autopsy.ingest.IngestManager;
import org.sleuthkit.autopsy.ingest.ModuleDataEvent; import org.sleuthkit.autopsy.ingest.ModuleDataEvent;
@ -378,33 +378,40 @@ public class HashsetHits implements AutopsyVisitableItem {
/** /**
* Creates the nodes for the hits in a given set. * Creates the nodes for the hits in a given set.
*/ */
private class HitFactory extends ChildFactory.Detachable<Long> implements Observer { private class HitFactory extends BaseChildFactory<BlackboardArtifact> implements Observer {
private String hashsetName; private String hashsetName;
private Map<Long, BlackboardArtifact> artifactHits = new HashMap<>(); private Map<Long, BlackboardArtifact> artifactHits = new HashMap<>();
private HitFactory(String hashsetName) { private HitFactory(String hashsetName) {
super(); super(hashsetName);
this.hashsetName = hashsetName; this.hashsetName = hashsetName;
} }
@Override @Override
protected void addNotify() { protected void onAdd() {
hashsetResults.addObserver(this); hashsetResults.addObserver(this);
} }
@Override @Override
protected void removeNotify() { protected void onRemove() {
hashsetResults.deleteObserver(this); hashsetResults.deleteObserver(this);
} }
@Override @Override
protected boolean createKeys(List<Long> list) { protected Node createNodeForKey(BlackboardArtifact key) {
return new BlackboardArtifactNode(key);
if (skCase == null) {
return true;
} }
@Override
public void update(Observable o, Object arg) {
refresh(true);
}
@Override
protected List<BlackboardArtifact> makeKeys() {
if (skCase != null) {
hashsetResults.getArtifactIds(hashsetName).forEach((id) -> { hashsetResults.getArtifactIds(hashsetName).forEach((id) -> {
try { try {
if (!artifactHits.containsKey(id)) { if (!artifactHits.containsKey(id)) {
@ -415,23 +422,9 @@ public class HashsetHits implements AutopsyVisitableItem {
logger.log(Level.SEVERE, "TSK Exception occurred", ex); //NON-NLS logger.log(Level.SEVERE, "TSK Exception occurred", ex); //NON-NLS
} }
}); });
return new ArrayList<>(artifactHits.values());
// Adding all keys at once is more efficient than adding one at a
// time because Netbeans triggers internal processing each time an
// element is added to the list.
list.addAll(artifactHits.keySet());
return true;
} }
return Collections.emptyList();
@Override
protected Node createNodeForKey(Long id) {
BlackboardArtifact art = artifactHits.get(id);
return (null == art) ? null : new BlackboardArtifactNode(art);
}
@Override
public void update(Observable o, Object arg) {
refresh(true);
} }
} }
} }

View File

@ -1,7 +1,7 @@
/* /*
* Autopsy Forensic Browser * Autopsy Forensic Browser
* *
* Copyright 2011-2018 Basis Technology Corp. * Copyright 2011-2019 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org * Contact: carrier <at> sleuthkit <dot> org
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
@ -449,26 +449,23 @@ public class InterestingHits implements AutopsyVisitableItem {
} }
} }
private class HitFactory extends ChildFactory<Long> implements Observer { private class HitFactory extends BaseChildFactory<BlackboardArtifact> implements Observer {
private final String setName; private final String setName;
private final String typeName; private final String typeName;
private final Map<Long, BlackboardArtifact> artifactHits = new HashMap<>(); private final Map<Long, BlackboardArtifact> artifactHits = new HashMap<>();
private HitFactory(String setName, String typeName) { private HitFactory(String setName, String typeName) {
super(); super(typeName);
this.setName = setName; this.setName = setName;
this.typeName = typeName; this.typeName = typeName;
interestingResults.addObserver(this); interestingResults.addObserver(this);
} }
@Override @Override
protected boolean createKeys(List<Long> list) { protected List<BlackboardArtifact> makeKeys() {
if (skCase == null) {
return true;
}
if (skCase != null) {
interestingResults.getArtifactIds(setName, typeName).forEach((id) -> { interestingResults.getArtifactIds(setName, typeName).forEach((id) -> {
try { try {
if (!artifactHits.containsKey(id)) { if (!artifactHits.containsKey(id)) {
@ -480,20 +477,29 @@ public class InterestingHits implements AutopsyVisitableItem {
} }
}); });
list.addAll(artifactHits.keySet()); return new ArrayList<>(artifactHits.values());
}
return true; return Collections.emptyList();
} }
@Override @Override
protected Node createNodeForKey(Long l) { protected Node createNodeForKey(BlackboardArtifact art) {
BlackboardArtifact art = artifactHits.get(l); return new BlackboardArtifactNode(art);
return (null == art) ? null : new BlackboardArtifactNode(art);
} }
@Override @Override
public void update(Observable o, Object arg) { public void update(Observable o, Object arg) {
refresh(true); refresh(true);
} }
@Override
protected void onAdd() {
// No-op
}
@Override
protected void onRemove() {
// No-op
}
} }
} }

View File

@ -1,7 +1,7 @@
/* /*
* Autopsy Forensic Browser * Autopsy Forensic Browser
* *
* Copyright 2011-2018 Basis Technology Corp. * Copyright 2011-2019 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org * Contact: carrier <at> sleuthkit <dot> org
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
@ -25,6 +25,7 @@ import java.sql.SQLException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.EnumSet; import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
@ -34,7 +35,6 @@ import java.util.Observable;
import java.util.Observer; import java.util.Observer;
import java.util.Set; import java.util.Set;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.stream.Collectors;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.openide.nodes.ChildFactory; import org.openide.nodes.ChildFactory;
import org.openide.nodes.Children; import org.openide.nodes.Children;
@ -46,7 +46,6 @@ import org.openide.util.lookup.Lookups;
import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.casemodule.CasePreferences; import org.sleuthkit.autopsy.casemodule.CasePreferences;
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
import org.sleuthkit.autopsy.core.UserPreferences;
import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.Logger;
import static org.sleuthkit.autopsy.datamodel.Bundle.*; import static org.sleuthkit.autopsy.datamodel.Bundle.*;
import org.sleuthkit.autopsy.ingest.IngestManager; import org.sleuthkit.autopsy.ingest.IngestManager;
@ -85,7 +84,6 @@ public class KeywordHits implements AutopsyVisitableItem {
*/ */
private static final String DEFAULT_INSTANCE_NAME = "DEFAULT_INSTANCE_NAME"; private static final String DEFAULT_INSTANCE_NAME = "DEFAULT_INSTANCE_NAME";
/** /**
* query attributes table for the ones that we need for the tree * query attributes table for the ones that we need for the tree
*/ */
@ -530,7 +528,7 @@ public class KeywordHits implements AutopsyVisitableItem {
} }
final void updateDisplayName() { final void updateDisplayName() {
super.setDisplayName(getName() + " (" + countTotalDescendants() + ")"); super.setDisplayName(getDisplayName() + " (" + countTotalDescendants() + ")");
} }
abstract int countTotalDescendants(); abstract int countTotalDescendants();
@ -631,6 +629,24 @@ public class KeywordHits implements AutopsyVisitableItem {
} }
} }
/**
* Create a ChildFactory object for the given set name and keyword.
*
* The type of ChildFactory we create is based on whether the node
* represents a regular expression keyword search or not. For regular
* expression keyword searches there will be an extra layer in the tree that
* represents each of the individual terms found by the regular expression.
* E.g., for an email regular expression search there will be a node in the
* tree for every email address hit.
*/
ChildFactory<?> createChildFactory(String setName, String keyword) {
if (isOnlyDefaultInstance(keywordResults.getKeywordInstances(setName, keyword))) {
return new HitsFactory(setName, keyword, DEFAULT_INSTANCE_NAME);
} else {
return new RegExpInstancesFactory(setName, keyword);
}
}
/** /**
* Represents the search term or regexp that user searched for * Represents the search term or regexp that user searched for
*/ */
@ -640,8 +656,17 @@ public class KeywordHits implements AutopsyVisitableItem {
private final String keyword; private final String keyword;
private TermNode(String setName, String keyword) { private TermNode(String setName, String keyword) {
super(Children.create(new RegExpInstancesFactory(setName, keyword), true), Lookups.singleton(keyword)); super(Children.create(createChildFactory(setName, keyword), true), Lookups.singleton(keyword));
super.setName(keyword);
super.setDisplayName(keyword);
/**
* We differentiate between the programmatic name and the display
* name. The programmatic name is used to create an association with
* an event bus and must be the same as the node name passed by our
* ChildFactory to it's parent constructor. See the HitsFactory
* constructor for an example.
*/
super.setName(setName + "_" + keyword);
this.setName = setName; this.setName = setName;
this.keyword = keyword; this.keyword = keyword;
this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/keyword_hits.png"); //NON-NLS this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/keyword_hits.png"); //NON-NLS
@ -694,45 +719,11 @@ public class KeywordHits implements AutopsyVisitableItem {
} }
} }
/**
* Allows us to pass in either longs or strings as they keys for different
* types of nodes at the same level. Probably a better way to do this, but
* it works.
*/
private class RegExpInstanceKey {
private final boolean isRegExp;
private String strKey;
private Long longKey;
RegExpInstanceKey(String key) {
isRegExp = true;
strKey = key;
}
RegExpInstanceKey(Long key) {
isRegExp = false;
longKey = key;
}
boolean isRegExp() {
return isRegExp;
}
Long getIdKey() {
return longKey;
}
String getRegExpKey() {
return strKey;
}
}
/** /**
* Creates the nodes for a given regexp that represent the specific terms * Creates the nodes for a given regexp that represent the specific terms
* that were found * that were found
*/ */
private class RegExpInstancesFactory extends DetachableObserverChildFactory<RegExpInstanceKey> { private class RegExpInstancesFactory extends DetachableObserverChildFactory<String> {
private final String keyword; private final String keyword;
private final String setName; private final String setName;
@ -744,35 +735,17 @@ public class KeywordHits implements AutopsyVisitableItem {
} }
@Override @Override
protected boolean createKeys(List<RegExpInstanceKey> list) { protected boolean createKeys(List<String> list) {
List<String> instances = keywordResults.getKeywordInstances(setName, keyword); list.addAll(keywordResults.getKeywordInstances(setName, keyword));
// The keys are different depending on what we are displaying.
// regexp get another layer to show instances.
// Exact/substring matches don't.
if (isOnlyDefaultInstance(instances)) {
list.addAll(keywordResults.getArtifactIds(setName, keyword, DEFAULT_INSTANCE_NAME).stream()
.map(RegExpInstanceKey::new)
.collect(Collectors.toList()));
} else {
list.addAll(instances.stream()
.map(RegExpInstanceKey::new)
.collect(Collectors.toList()));
}
return true; return true;
} }
@Override @Override
protected Node createNodeForKey(RegExpInstanceKey key) { protected Node createNodeForKey(String key) {
if (key.isRegExp()) { return new RegExpInstanceNode(setName, keyword, key);
return new RegExpInstanceNode(setName, keyword, key.getRegExpKey());
} else {
// if it isn't a regexp, then skip the 'instance' layer of the tree
return createBlackboardArtifactNode(key.getIdKey());
} }
} }
}
/** /**
* Represents a specific term that was found from a regexp * Represents a specific term that was found from a regexp
*/ */
@ -784,7 +757,15 @@ public class KeywordHits implements AutopsyVisitableItem {
private RegExpInstanceNode(String setName, String keyword, String instance) { private RegExpInstanceNode(String setName, String keyword, String instance) {
super(Children.create(new HitsFactory(setName, keyword, instance), true), Lookups.singleton(instance)); super(Children.create(new HitsFactory(setName, keyword, instance), true), Lookups.singleton(instance));
super.setName(instance); //the instance represents the name of the keyword hit at this point as the keyword is the regex super.setDisplayName(instance); //the instance represents the name of the keyword hit at this point as the keyword is the regex
/**
* We differentiate between the programmatic name and the display
* name. The programmatic name is used to create an association with
* an event bus and must be the same as the node name passed by our
* ChildFactory to it's parent constructor. See the HitsFactory
* constructor for an example.
*/
super.setName(setName + "_" + keyword + "_" + instance);
this.setName = setName; this.setName = setName;
this.keyword = keyword; this.keyword = keyword;
this.instance = instance; this.instance = instance;
@ -837,7 +818,7 @@ public class KeywordHits implements AutopsyVisitableItem {
/** /**
* Create a blackboard node for the given Keyword Hit artifact * Create a blackboard node for the given Keyword Hit artifact
* *
* @param artifactId * @param art
* *
* @return Node or null on error * @return Node or null on error
*/ */
@ -850,14 +831,13 @@ public class KeywordHits implements AutopsyVisitableItem {
"KeywordHits.createNodeForKey.chgTime.name=ChangeTime", "KeywordHits.createNodeForKey.chgTime.name=ChangeTime",
"KeywordHits.createNodeForKey.chgTime.displayName=Change Time", "KeywordHits.createNodeForKey.chgTime.displayName=Change Time",
"KeywordHits.createNodeForKey.chgTime.desc=Change Time"}) "KeywordHits.createNodeForKey.chgTime.desc=Change Time"})
private BlackboardArtifactNode createBlackboardArtifactNode(Long artifactId) { private BlackboardArtifactNode createBlackboardArtifactNode(BlackboardArtifact art) {
if (skCase == null) { if (skCase == null) {
return null; return null;
} }
try { BlackboardArtifactNode n = new BlackboardArtifactNode(art); //NON-NLS
BlackboardArtifact art = skCase.getBlackboardArtifact(artifactId);
BlackboardArtifactNode n = new BlackboardArtifactNode(art);
// The associated file should be available through the Lookup that // The associated file should be available through the Lookup that
// gets created when the BlackboardArtifactNode is constructed. // gets created when the BlackboardArtifactNode is constructed.
AbstractFile file = n.getLookup().lookup(AbstractFile.class); AbstractFile file = n.getLookup().lookup(AbstractFile.class);
@ -870,14 +850,13 @@ public class KeywordHits implements AutopsyVisitableItem {
} }
} }
/* /*
* It is possible to get a keyword hit on artifacts generated for * It is possible to get a keyword hit on artifacts generated for the
* the underlying image in which case MAC times are not * underlying image in which case MAC times are not
* available/applicable/useful. * available/applicable/useful.
*/ */
if (file == null) { if (file == null) {
return n; return n;
} }
n.addNodeProperty(new NodeProperty<>( n.addNodeProperty(new NodeProperty<>(
KeywordHits_createNodeForKey_modTime_name(), KeywordHits_createNodeForKey_modTime_name(),
KeywordHits_createNodeForKey_modTime_displayName(), KeywordHits_createNodeForKey_modTime_displayName(),
@ -894,37 +873,68 @@ public class KeywordHits implements AutopsyVisitableItem {
KeywordHits_createNodeForKey_chgTime_desc(), KeywordHits_createNodeForKey_chgTime_desc(),
ContentUtils.getStringTime(file.getCtime(), file))); ContentUtils.getStringTime(file.getCtime(), file)));
return n; return n;
} catch (TskCoreException ex) {
logger.log(Level.WARNING, "TSK Exception occurred", ex); //NON-NLS
}
return null;
} }
/** /**
* Creates nodes for individual files that had hits * Creates nodes for individual files that had hits
*/ */
private class HitsFactory extends DetachableObserverChildFactory<Long> { private class HitsFactory extends BaseChildFactory<BlackboardArtifact> implements Observer {
private final String keyword; private final String keyword;
private final String setName; private final String setName;
private final String instance; private final String instance;
private final Map<Long, BlackboardArtifact> artifactHits = new HashMap<>();
private HitsFactory(String setName, String keyword, String instance) { private HitsFactory(String setName, String keyword, String instance) {
super(); /**
* The node name passed to the parent constructor will consist of
* the set name, keyword and optionally the instance name (in the
* case of regular expression hits. This name must match the name
* set in the TermNode or RegExpInstanceNode constructors.
*/
super(setName + "_" + keyword + (DEFAULT_INSTANCE_NAME.equals(instance) ? "" : "_" + instance));
this.setName = setName; this.setName = setName;
this.keyword = keyword; this.keyword = keyword;
this.instance = instance; this.instance = instance;
} }
@Override @Override
protected boolean createKeys(List<Long> list) { protected List<BlackboardArtifact> makeKeys() {
list.addAll(keywordResults.getArtifactIds(setName, keyword, instance)); if (skCase != null) {
return true; keywordResults.getArtifactIds(setName, keyword, instance).forEach((id) -> {
try {
if (!artifactHits.containsKey(id)) {
BlackboardArtifact art = skCase.getBlackboardArtifact(id);
artifactHits.put(id, art);
}
} catch (TskCoreException ex) {
logger.log(Level.SEVERE, "TSK Exception occurred", ex); //NON-NLS
}
});
return new ArrayList<>(artifactHits.values());
}
return Collections.emptyList();
} }
@Override @Override
protected Node createNodeForKey(Long artifactId) { protected Node createNodeForKey(BlackboardArtifact art) {
return createBlackboardArtifactNode(artifactId); return createBlackboardArtifactNode(art);
}
@Override
protected void onAdd() {
keywordResults.addObserver(this);
}
@Override
protected void onRemove() {
keywordResults.deleteObserver(this);
}
@Override
public void update(Observable o, Object arg) {
refresh(true);
} }
} }
} }