diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/DeletedContent.java b/Core/src/org/sleuthkit/autopsy/datamodel/DeletedContent.java index 4e81d4b428..435364803d 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/DeletedContent.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/DeletedContent.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2018 Basis Technology Corp. + * Copyright 2011-2019 Basis Technology Corp. * Contact: carrier sleuthkit org * * 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.CasePreferences; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; -import org.sleuthkit.autopsy.core.UserPreferences; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.ingest.IngestManager; import org.sleuthkit.datamodel.AbstractFile; @@ -358,7 +357,7 @@ public class DeletedContent implements AutopsyVisitableItem { } } - static class DeletedContentChildren extends ChildFactory.Detachable { + static class DeletedContentChildren extends BaseChildFactory { private final SleuthkitCase skCase; private final DeletedContent.DeletedContentFilter filter; @@ -368,6 +367,7 @@ public class DeletedContent implements AutopsyVisitableItem { private final long datasourceObjId; DeletedContentChildren(DeletedContent.DeletedContentFilter filter, SleuthkitCase skCase, Observable o, long datasourceObjId) { + super(filter.getName()); this.skCase = skCase; this.filter = filter; this.notifier = o; @@ -376,6 +376,11 @@ public class DeletedContent implements AutopsyVisitableItem { private final Observer observer = new DeletedContentChildrenObserver(); + @Override + protected List makeKeys() { + return runFsQuery(); + } + // Cause refresh of children if there are changes private class DeletedContentChildrenObserver implements Observer { @@ -386,25 +391,19 @@ public class DeletedContent implements AutopsyVisitableItem { } @Override - protected void addNotify() { + protected void onAdd() { if (notifier != null) { notifier.addObserver(observer); } } @Override - protected void removeNotify() { + protected void onRemove() { if (notifier != null) { notifier.deleteObserver(observer); } } - @Override - protected boolean createKeys(List list) { - list.addAll(runFsQuery()); - return true; - } - static private String makeQuery(DeletedContent.DeletedContentFilter filter, long filteringDSObjId) { String query = ""; 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)) { query += " AND data_source_obj_id = " + filteringDSObjId; } diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/ExtractedContent.java b/Core/src/org/sleuthkit/autopsy/datamodel/ExtractedContent.java index 4b3694b35f..9844811a02 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/ExtractedContent.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/ExtractedContent.java @@ -423,12 +423,12 @@ public class ExtractedContent implements AutopsyVisitableItem { /** * Creates children for a given artifact type */ - private class ArtifactFactory extends ChildFactory.Detachable { + private class ArtifactFactory extends BaseChildFactory { private BlackboardArtifact.Type type; public ArtifactFactory(BlackboardArtifact.Type type) { - super(); + super(type.getTypeName()); this.type = type; } @@ -481,36 +481,36 @@ public class ExtractedContent implements AutopsyVisitableItem { }; @Override - protected void addNotify() { + protected void onAdd() { IngestManager.getInstance().addIngestJobEventListener(pcl); IngestManager.getInstance().addIngestModuleEventListener(pcl); } @Override - protected void removeNotify() { + protected void onRemove() { IngestManager.getInstance().removeIngestJobEventListener(pcl); IngestManager.getInstance().removeIngestModuleEventListener(pcl); } - @Override - protected boolean createKeys(List list) { - if (skCase != null) { - try { - List 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 protected Node createNodeForKey(BlackboardArtifact key) { return new BlackboardArtifactNode(key); } + + @Override + protected List makeKeys() { + if (skCase != null) { + try { + List 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(); + } } } diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/FileSize.java b/Core/src/org/sleuthkit/autopsy/datamodel/FileSize.java index 53628326a2..78d38259f3 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/FileSize.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/FileSize.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2013-2018 Basis Technology Corp. + * Copyright 2013-2019 Basis Technology Corp. * Contact: carrier sleuthkit org * * 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 */ - static class FileSizeChildren extends ChildFactory.Detachable { + static class FileSizeChildren extends BaseChildFactory { private final SleuthkitCase skCase; private final FileSizeFilter filter; @@ -377,6 +377,7 @@ public class FileSize implements AutopsyVisitableItem { * added to case */ FileSizeChildren(FileSizeFilter filter, SleuthkitCase skCase, Observable o, long dsObjId) { + super(filter.getName()); this.skCase = skCase; this.filter = filter; this.notifier = o; @@ -385,14 +386,14 @@ public class FileSize implements AutopsyVisitableItem { } @Override - protected void addNotify() { + protected void onAdd() { if (notifier != null) { notifier.addObserver(observer); } } @Override - protected void removeNotify() { + protected void onRemove() { if (notifier != null) { notifier.deleteObserver(observer); } @@ -400,6 +401,11 @@ public class FileSize implements AutopsyVisitableItem { private final Observer observer = new FileSizeChildrenObserver(); + @Override + protected List makeKeys() { + return runFsQuery(); + } + // Cause refresh of children if there are changes private class FileSizeChildrenObserver implements Observer { @@ -409,12 +415,6 @@ public class FileSize implements AutopsyVisitableItem { } } - @Override - protected boolean createKeys(List list) { - list.addAll(runFsQuery()); - return true; - } - private static String makeQuery(FileSizeFilter filter, long filteringDSObjId) { String query; switch (filter) { @@ -436,17 +436,6 @@ public class FileSize implements AutopsyVisitableItem { // Ignore unallocated block files. 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 if (Objects.equals(CasePreferences.getGroupItemsInTreeByDataSource(), true)) { query += " AND data_source_obj_id = " + filteringDSObjId; diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/FileTypesByExtension.java b/Core/src/org/sleuthkit/autopsy/datamodel/FileTypesByExtension.java index 5f1f33607d..27e0705d08 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/FileTypesByExtension.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/FileTypesByExtension.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2018 Basis Technology Corp. + * Copyright 2011-2019 Basis Technology Corp. * Contact: carrier sleuthkit org * * 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.PropertyChangeListener; import java.util.Arrays; +import java.util.Collections; import java.util.EnumSet; import java.util.List; import java.util.Objects; @@ -292,7 +293,7 @@ public final class FileTypesByExtension implements AutopsyVisitableItem { * should refresh */ 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())); this.filter = filter; 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. */ - private class FileExtensionNodeChildren extends ChildFactory.Detachable implements Observer { + private class FileExtensionNodeChildren extends BaseChildFactory implements Observer { private final SleuthkitCase skCase; 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 * data to display */ - private FileExtensionNodeChildren(FileTypesByExtension.SearchFilterInterface filter, SleuthkitCase skCase, Observable o) { - super(); + private FileExtensionNodeChildren(FileTypesByExtension.SearchFilterInterface filter, SleuthkitCase skCase, Observable o, String nodeName) { + super(nodeName); this.filter = filter; this.skCase = skCase; notifier = o; } @Override - protected void addNotify() { + protected void onAdd() { if (notifier != null) { notifier.addObserver(this); } } @Override - protected void removeNotify() { + protected void onRemove() { if (notifier != null) { notifier.deleteObserver(this); } @@ -417,19 +418,19 @@ public final class FileTypesByExtension implements AutopsyVisitableItem { } @Override - protected boolean createKeys(List list) { - try { - 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; + protected Node createNodeForKey(FileTypesKey key) { + return key.accept(new FileTypes.FileNodeCreationVisitor()); } @Override - protected Node createNodeForKey(FileTypesKey key) { - return key.accept(new FileTypes.FileNodeCreationVisitor()); + protected List makeKeys() { + 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(); } } diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/FileTypesByMimeType.java b/Core/src/org/sleuthkit/autopsy/datamodel/FileTypesByMimeType.java index 54bb2e7285..49f586c7e8 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/FileTypesByMimeType.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/FileTypesByMimeType.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2018 Basis Technology Corp. + * Copyright 2011-2019 Basis Technology Corp. * Contact: carrier sleuthkit org * * 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 * tree. */ - private class MediaSubTypeNodeChildren extends ChildFactory.Detachable implements Observer { + private class MediaSubTypeNodeChildren extends BaseChildFactory implements Observer { private final String mimeType; private MediaSubTypeNodeChildren(String mimeType) { - super(); + super(mimeType); addObserver(this); this.mimeType = mimeType; } - @Override - protected boolean createKeys(List 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 public void update(Observable o, Object arg) { refresh(true); @@ -475,5 +464,26 @@ public final class FileTypesByMimeType extends Observable implements AutopsyVisi protected Node createNodeForKey(FileTypesKey key) { return key.accept(new FileTypes.FileNodeCreationVisitor()); } + + @Override + protected List 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 + } } } diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/HashsetHits.java b/Core/src/org/sleuthkit/autopsy/datamodel/HashsetHits.java index 3262087487..f970585455 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/HashsetHits.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/HashsetHits.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2018 Basis Technology Corp. + * Copyright 2011-2019 Basis Technology Corp. * Contact: carrier sleuthkit org * * 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.Set; import java.util.logging.Level; +import java.util.stream.Collectors; import org.openide.nodes.ChildFactory; import org.openide.nodes.Children; 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.CasePreferences; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; -import org.sleuthkit.autopsy.core.UserPreferences; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.ingest.IngestManager; import org.sleuthkit.autopsy.ingest.ModuleDataEvent; @@ -378,60 +378,53 @@ public class HashsetHits implements AutopsyVisitableItem { /** * Creates the nodes for the hits in a given set. */ - private class HitFactory extends ChildFactory.Detachable implements Observer { + private class HitFactory extends BaseChildFactory implements Observer { private String hashsetName; private Map artifactHits = new HashMap<>(); private HitFactory(String hashsetName) { - super(); + super(hashsetName); this.hashsetName = hashsetName; } @Override - protected void addNotify() { + protected void onAdd() { hashsetResults.addObserver(this); } @Override - protected void removeNotify() { + protected void onRemove() { hashsetResults.deleteObserver(this); } @Override - protected boolean createKeys(List list) { - - if (skCase == null) { - return true; - } - - hashsetResults.getArtifactIds(hashsetName).forEach((id) -> { - try { - if (!artifactHits.containsKey(id)) { - BlackboardArtifact art = skCase.getBlackboardArtifact(id); - artifactHits.put(id, art); - } - } catch (TskException ex) { - logger.log(Level.SEVERE, "TSK Exception occurred", ex); //NON-NLS - } - }); - - // 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; - } - - @Override - protected Node createNodeForKey(Long id) { - BlackboardArtifact art = artifactHits.get(id); - return (null == art) ? null : new BlackboardArtifactNode(art); + protected Node createNodeForKey(BlackboardArtifact key) { + return new BlackboardArtifactNode(key); } @Override public void update(Observable o, Object arg) { refresh(true); } + + @Override + protected List makeKeys() { + if (skCase != null) { + + hashsetResults.getArtifactIds(hashsetName).forEach((id) -> { + try { + if (!artifactHits.containsKey(id)) { + BlackboardArtifact art = skCase.getBlackboardArtifact(id); + artifactHits.put(id, art); + } + } catch (TskException ex) { + logger.log(Level.SEVERE, "TSK Exception occurred", ex); //NON-NLS + } + }); + return new ArrayList<>(artifactHits.values()); + } + return Collections.emptyList(); + } } } diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/InterestingHits.java b/Core/src/org/sleuthkit/autopsy/datamodel/InterestingHits.java index 8782b0fb89..c36833992c 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/InterestingHits.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/InterestingHits.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2018 Basis Technology Corp. + * Copyright 2011-2019 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -449,51 +449,57 @@ public class InterestingHits implements AutopsyVisitableItem { } } - private class HitFactory extends ChildFactory implements Observer { + private class HitFactory extends BaseChildFactory implements Observer { private final String setName; private final String typeName; private final Map artifactHits = new HashMap<>(); private HitFactory(String setName, String typeName) { - super(); + super(typeName); this.setName = setName; this.typeName = typeName; interestingResults.addObserver(this); } @Override - protected boolean createKeys(List list) { + protected List makeKeys() { - if (skCase == null) { - return true; - } - - interestingResults.getArtifactIds(setName, typeName).forEach((id) -> { - try { - if (!artifactHits.containsKey(id)) { - BlackboardArtifact art = skCase.getBlackboardArtifact(id); - artifactHits.put(id, art); + if (skCase != null) { + interestingResults.getArtifactIds(setName, typeName).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 } - } catch (TskCoreException ex) { - logger.log(Level.SEVERE, "TSK Exception occurred", ex); //NON-NLS - } - }); + }); - list.addAll(artifactHits.keySet()); - - return true; + return new ArrayList<>(artifactHits.values()); + } + return Collections.emptyList(); } @Override - protected Node createNodeForKey(Long l) { - BlackboardArtifact art = artifactHits.get(l); - return (null == art) ? null : new BlackboardArtifactNode(art); + protected Node createNodeForKey(BlackboardArtifact art) { + return new BlackboardArtifactNode(art); } @Override public void update(Observable o, Object arg) { refresh(true); } + + @Override + protected void onAdd() { + // No-op + } + + @Override + protected void onRemove() { + // No-op + } } } diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/KeywordHits.java b/Core/src/org/sleuthkit/autopsy/datamodel/KeywordHits.java index c80069c7ef..cc6d88da29 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/KeywordHits.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/KeywordHits.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2018 Basis Technology Corp. + * Copyright 2011-2019 Basis Technology Corp. * Contact: carrier sleuthkit org * * 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.Collections; import java.util.EnumSet; +import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; @@ -34,7 +35,6 @@ import java.util.Observable; import java.util.Observer; import java.util.Set; import java.util.logging.Level; -import java.util.stream.Collectors; import org.apache.commons.lang3.StringUtils; import org.openide.nodes.ChildFactory; 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.CasePreferences; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; -import org.sleuthkit.autopsy.core.UserPreferences; import org.sleuthkit.autopsy.coreutils.Logger; import static org.sleuthkit.autopsy.datamodel.Bundle.*; 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"; - /** * query attributes table for the ones that we need for the tree */ @@ -108,20 +106,20 @@ public class KeywordHits implements AutopsyVisitableItem { /** * Constructor - * - * @param skCase Case DB - */ + * + * @param skCase Case DB + */ KeywordHits(SleuthkitCase skCase) { this(skCase, 0); } - + /** * Constructor - * - * @param skCase Case DB - * @param objId Object id of the data source - * - */ + * + * @param skCase Case DB + * @param objId Object id of the data source + * + */ public KeywordHits(SleuthkitCase skCase, long objId) { this.skCase = skCase; this.datasourceObjId = objId; @@ -324,9 +322,9 @@ public class KeywordHits implements AutopsyVisitableItem { String queryStr = KEYWORD_HIT_ATTRIBUTES_QUERY; if (Objects.equals(CasePreferences.getGroupItemsInTreeByDataSource(), true)) { - queryStr += " AND blackboard_artifacts.data_source_obj_id = " + datasourceObjId; + queryStr += " AND blackboard_artifacts.data_source_obj_id = " + datasourceObjId; } - + try (CaseDbQuery dbQuery = skCase.executeQuery(queryStr)) { ResultSet resultSet = dbQuery.getResultSet(); while (resultSet.next()) { @@ -530,7 +528,7 @@ public class KeywordHits implements AutopsyVisitableItem { } final void updateDisplayName() { - super.setDisplayName(getName() + " (" + countTotalDescendants() + ")"); + super.setDisplayName(getDisplayName() + " (" + 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 */ @@ -640,8 +656,17 @@ public class KeywordHits implements AutopsyVisitableItem { private final String keyword; private TermNode(String setName, String keyword) { - super(Children.create(new RegExpInstancesFactory(setName, keyword), true), Lookups.singleton(keyword)); - super.setName(keyword); + super(Children.create(createChildFactory(setName, keyword), true), Lookups.singleton(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.keyword = keyword; 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 * that were found */ - private class RegExpInstancesFactory extends DetachableObserverChildFactory { + private class RegExpInstancesFactory extends DetachableObserverChildFactory { private final String keyword; private final String setName; @@ -744,33 +735,15 @@ public class KeywordHits implements AutopsyVisitableItem { } @Override - protected boolean createKeys(List list) { - List instances = 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())); - } + protected boolean createKeys(List list) { + list.addAll(keywordResults.getKeywordInstances(setName, keyword)); return true; } @Override - protected Node createNodeForKey(RegExpInstanceKey key) { - if (key.isRegExp()) { - 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()); - } + protected Node createNodeForKey(String key) { + return new RegExpInstanceNode(setName, keyword, key); } - } /** @@ -784,7 +757,15 @@ public class KeywordHits implements AutopsyVisitableItem { private RegExpInstanceNode(String setName, String keyword, String 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.keyword = keyword; this.instance = instance; @@ -837,7 +818,7 @@ public class KeywordHits implements AutopsyVisitableItem { /** * Create a blackboard node for the given Keyword Hit artifact * - * @param artifactId + * @param art * * @return Node or null on error */ @@ -850,81 +831,110 @@ public class KeywordHits implements AutopsyVisitableItem { "KeywordHits.createNodeForKey.chgTime.name=ChangeTime", "KeywordHits.createNodeForKey.chgTime.displayName=Change Time", "KeywordHits.createNodeForKey.chgTime.desc=Change Time"}) - private BlackboardArtifactNode createBlackboardArtifactNode(Long artifactId) { + private BlackboardArtifactNode createBlackboardArtifactNode(BlackboardArtifact art) { if (skCase == null) { return null; } - try { - BlackboardArtifact art = skCase.getBlackboardArtifact(artifactId); - BlackboardArtifactNode n = new BlackboardArtifactNode(art); - // The associated file should be available through the Lookup that - // gets created when the BlackboardArtifactNode is constructed. - AbstractFile file = n.getLookup().lookup(AbstractFile.class); - if (file == null) { - try { - file = skCase.getAbstractFileById(art.getObjectID()); - } catch (TskCoreException ex) { - logger.log(Level.SEVERE, "TskCoreException while constructing BlackboardArtifact Node from KeywordHitsKeywordChildren", ex); //NON-NLS - return n; - } - } - /* - * It is possible to get a keyword hit on artifacts generated for - * the underlying image in which case MAC times are not - * available/applicable/useful. - */ - if (file == null) { + BlackboardArtifactNode n = new BlackboardArtifactNode(art); //NON-NLS + + // The associated file should be available through the Lookup that + // gets created when the BlackboardArtifactNode is constructed. + AbstractFile file = n.getLookup().lookup(AbstractFile.class); + if (file == null) { + try { + file = skCase.getAbstractFileById(art.getObjectID()); + } catch (TskCoreException ex) { + logger.log(Level.SEVERE, "TskCoreException while constructing BlackboardArtifact Node from KeywordHitsKeywordChildren", ex); //NON-NLS return n; } - - n.addNodeProperty(new NodeProperty<>( - KeywordHits_createNodeForKey_modTime_name(), - KeywordHits_createNodeForKey_modTime_displayName(), - KeywordHits_createNodeForKey_modTime_desc(), - ContentUtils.getStringTime(file.getMtime(), file))); - n.addNodeProperty(new NodeProperty<>( - KeywordHits_createNodeForKey_accessTime_name(), - KeywordHits_createNodeForKey_accessTime_displayName(), - KeywordHits_createNodeForKey_accessTime_desc(), - ContentUtils.getStringTime(file.getAtime(), file))); - n.addNodeProperty(new NodeProperty<>( - KeywordHits_createNodeForKey_chgTime_name(), - KeywordHits_createNodeForKey_chgTime_displayName(), - KeywordHits_createNodeForKey_chgTime_desc(), - ContentUtils.getStringTime(file.getCtime(), file))); - return n; - } catch (TskCoreException ex) { - logger.log(Level.WARNING, "TSK Exception occurred", ex); //NON-NLS } - return null; + /* + * It is possible to get a keyword hit on artifacts generated for the + * underlying image in which case MAC times are not + * available/applicable/useful. + */ + if (file == null) { + return n; + } + n.addNodeProperty(new NodeProperty<>( + KeywordHits_createNodeForKey_modTime_name(), + KeywordHits_createNodeForKey_modTime_displayName(), + KeywordHits_createNodeForKey_modTime_desc(), + ContentUtils.getStringTime(file.getMtime(), file))); + n.addNodeProperty(new NodeProperty<>( + KeywordHits_createNodeForKey_accessTime_name(), + KeywordHits_createNodeForKey_accessTime_displayName(), + KeywordHits_createNodeForKey_accessTime_desc(), + ContentUtils.getStringTime(file.getAtime(), file))); + n.addNodeProperty(new NodeProperty<>( + KeywordHits_createNodeForKey_chgTime_name(), + KeywordHits_createNodeForKey_chgTime_displayName(), + KeywordHits_createNodeForKey_chgTime_desc(), + ContentUtils.getStringTime(file.getCtime(), file))); + return n; } /** * Creates nodes for individual files that had hits */ - private class HitsFactory extends DetachableObserverChildFactory { + private class HitsFactory extends BaseChildFactory implements Observer { private final String keyword; private final String setName; private final String instance; + private final Map artifactHits = new HashMap<>(); 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.keyword = keyword; this.instance = instance; } @Override - protected boolean createKeys(List list) { - list.addAll(keywordResults.getArtifactIds(setName, keyword, instance)); - return true; + protected List makeKeys() { + if (skCase != null) { + 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 - protected Node createNodeForKey(Long artifactId) { - return createBlackboardArtifactNode(artifactId); + protected Node createNodeForKey(BlackboardArtifact art) { + 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); } } }