From 37696a5a198ccf2945d41769290a8685ff4135cf Mon Sep 17 00:00:00 2001 From: Greg DiCristofaro Date: Mon, 25 Oct 2021 12:28:16 -0400 Subject: [PATCH 01/38] changes to counts row --- .../sleuthkit/autopsy/mainui/datamodel/CountsRowDTO.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/CountsRowDTO.java b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/CountsRowDTO.java index 7ec560f105..b6690b06df 100644 --- a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/CountsRowDTO.java +++ b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/CountsRowDTO.java @@ -35,7 +35,7 @@ import org.openide.util.NbBundle.Messages; "CountsRowResultDTO_columns_count_displayName=Name", "CountsRowResultDTO_columns_count_description=Name" }) -public class CountsRowDTO implements RowDTO { +public class CountsRowDTO implements RowDTO { private static final String DEFAULT_TYPE_ID = "COUNTS"; @@ -55,11 +55,11 @@ public class CountsRowDTO implements RowDTO { private final List cellValues; private final String typeId; - public CountsRowDTO(long id, String displayName, long count) { - this(DEFAULT_TYPE_ID, id, displayName, count); + public CountsRowDTO(T itemType, long id, String displayName, long count) { + this(DEFAULT_TYPE_ID, itemType, id, displayName, count); } - public CountsRowDTO(String typeId, long id, String displayName, long count) { + public CountsRowDTO(String typeId, T itemType, long id, String displayName, long count) { this.typeId = typeId; this.id = id; this.displayName = displayName; From 6c1461df9dae645509de1d0ca0c308678a02a5a5 Mon Sep 17 00:00:00 2001 From: Greg DiCristofaro Date: Tue, 26 Oct 2021 07:22:27 -0400 Subject: [PATCH 02/38] dao changes --- .../mainui/datamodel/CountsRowDTO.java | 14 ++++++++-- .../mainui/datamodel/DataArtifactDAO.java | 28 +++++++++++++++++++ 2 files changed, 39 insertions(+), 3 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/CountsRowDTO.java b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/CountsRowDTO.java index b6690b06df..ee640d4af8 100644 --- a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/CountsRowDTO.java +++ b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/CountsRowDTO.java @@ -54,17 +54,19 @@ public class CountsRowDTO implements RowDTO { private final long count; private final List cellValues; private final String typeId; + private final T typeData; - public CountsRowDTO(T itemType, long id, String displayName, long count) { - this(DEFAULT_TYPE_ID, itemType, id, displayName, count); + public CountsRowDTO(T typeData, long id, String displayName, long count) { + this(DEFAULT_TYPE_ID, typeData, id, displayName, count); } - public CountsRowDTO(String typeId, T itemType, long id, String displayName, long count) { + public CountsRowDTO(String typeId, T typeData, long id, String displayName, long count) { this.typeId = typeId; this.id = id; this.displayName = displayName; this.count = count; this.cellValues = ImmutableList.of(Arrays.asList(displayName, count)); + this.typeData = typeData; } @Override @@ -89,4 +91,10 @@ public class CountsRowDTO implements RowDTO { public String getTypeId() { return typeId; } + + public T getTypeData() { + return typeData; + } + + } diff --git a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/DataArtifactDAO.java b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/DataArtifactDAO.java index 9083519bd4..73e57da6eb 100644 --- a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/DataArtifactDAO.java +++ b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/DataArtifactDAO.java @@ -27,6 +27,7 @@ import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Map.Entry; import java.util.concurrent.ExecutionException; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -168,4 +169,31 @@ public class DataArtifactDAO extends BlackboardArtifactDAO { public void dropDataArtifactCache() { dataArtifactCache.invalidateAll(); } + + + public SearchResultsDTO getDataArtifactsCounts() throws ExecutionException { + SleuthkitCase skCase = getCase(); + String query = "SELECT artifact_type_id, COUNT(*) AS count FROM blackboard_artifacts GROUP BY artifact_type_id"; + Map typeCounts = new HashMap<>(); + skCase.getCaseDbAccessManager().select(query, (resultSet) -> { + while (resultSet.next()) { + typeCounts. + typeIdCounts.put(resultSet.getInt("artifact_type_id"), resultSet.getLong("count")); + } + }); + + + Map typeCounts = new HashMap<>(); + for (Entry typeIdCount : typeIdCounts.entrySet()) { + typeCounts.put(skCase.getArtifactType(typeIdCount.getKey()), typeIdCount.getValue()); + } + + typeCounts.entrySet().stream() + .map(entry -> new CountsRowDTO<>( + BlackboardArtifact.Type.Category.DATA_ARTIFACT.name(), + entry.getKey(), + entry.getKey().getTypeId(), + entry.getKey().getDisplayName(), + entry.getValue())); + } } From 55b5737af17d475750cf553bd0ad192baa566652 Mon Sep 17 00:00:00 2001 From: Greg DiCristofaro Date: Tue, 26 Oct 2021 10:49:59 -0400 Subject: [PATCH 03/38] tree count node --- .../DirectoryTreeTopComponent.java | 25 ++--- .../mainui/datamodel/CountsRowDTO.java | 46 ++++++--- .../mainui/datamodel/DataArtifactDAO.java | 98 +++++++++++++------ .../nodes/DataArtifactTypeTreeNode.java | 37 +++++++ .../mainui/nodes/SelectionResponder.java | 33 +++++++ .../mainui/nodes/TreeChildFactory.java | 57 +++++++++++ .../autopsy/mainui/nodes/TreeCountNode.java | 84 ++++++++++++++++ 7 files changed, 320 insertions(+), 60 deletions(-) create mode 100644 Core/src/org/sleuthkit/autopsy/mainui/nodes/DataArtifactTypeTreeNode.java create mode 100755 Core/src/org/sleuthkit/autopsy/mainui/nodes/SelectionResponder.java create mode 100644 Core/src/org/sleuthkit/autopsy/mainui/nodes/TreeChildFactory.java create mode 100644 Core/src/org/sleuthkit/autopsy/mainui/nodes/TreeCountNode.java diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.java b/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.java index 8579cc9108..20eea63b3c 100644 --- a/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.java +++ b/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.java @@ -82,17 +82,14 @@ import org.sleuthkit.autopsy.datamodel.EmptyNode; import org.sleuthkit.autopsy.datamodel.FileTypesByMimeType; import org.sleuthkit.autopsy.datamodel.KeywordHits; import org.sleuthkit.autopsy.datamodel.AutopsyTreeChildFactory; -import org.sleuthkit.autopsy.mainui.datamodel.DataArtifactSearchParam; import org.sleuthkit.autopsy.datamodel.DataArtifacts; -import org.sleuthkit.autopsy.mainui.datamodel.FileTypeExtensionsSearchParams; import org.sleuthkit.autopsy.datamodel.OsAccounts; import org.sleuthkit.autopsy.datamodel.PersonNode; import org.sleuthkit.autopsy.datamodel.Tags; import org.sleuthkit.autopsy.datamodel.ViewsNode; import org.sleuthkit.autopsy.datamodel.accounts.Accounts; import org.sleuthkit.autopsy.datamodel.accounts.BINRange; -import org.sleuthkit.autopsy.mainui.datamodel.AnalysisResultSearchParam; -import org.sleuthkit.autopsy.mainui.datamodel.FileTypeMimeSearchParams; +import org.sleuthkit.autopsy.mainui.nodes.SelectionResponder; import org.sleuthkit.datamodel.Account; import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.BlackboardArtifact.Category; @@ -872,20 +869,12 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat Node originNode = ((DirectoryTreeFilterNode) treeNode).getOriginal(); //set node, wrap in filter node first to filter out children Node drfn = new DataResultFilterNode(originNode, DirectoryTreeTopComponent.this.em); - // Create a TableFilterNode with knowledge of the node's type to allow for column order settings - DataArtifactSearchParam dataArtifactKey = originNode.getLookup().lookup(DataArtifactSearchParam.class); - FileTypeExtensionsSearchParams fileExtensionsKey = originNode.getLookup().lookup(FileTypeExtensionsSearchParams.class); - AnalysisResultSearchParam analysisResultKey = originNode.getLookup().lookup(AnalysisResultSearchParam.class); - FileTypeMimeSearchParams fileMimeKey = originNode.getLookup().lookup(FileTypeMimeSearchParams.class); - if (dataArtifactKey != null) { - dataResult.displayDataArtifact(dataArtifactKey); - } else if(analysisResultKey != null) { - dataResult.displayAnalysisResult(analysisResultKey); - } else if (fileExtensionsKey != null) { - dataResult.displayFileExtensions(fileExtensionsKey); - } else if(fileMimeKey != null) { - dataResult.displayFileMimes(fileMimeKey); - } else if (FileTypesByMimeType.isEmptyMimeTypeNode(originNode)) { + + if(originNode instanceof SelectionResponder) { + ((SelectionResponder) originNode).respondSelection(dataResult); + } + + if (FileTypesByMimeType.isEmptyMimeTypeNode(originNode)) { //Special case for when File Type Identification has not yet been run and //there are no mime types to populate Files by Mime Type Tree EmptyNode emptyNode = new EmptyNode(Bundle.DirectoryTreeTopComponent_emptyMimeNode_text()); diff --git a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/CountsRowDTO.java b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/CountsRowDTO.java index ee640d4af8..b2eb1f9fae 100644 --- a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/CountsRowDTO.java +++ b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/CountsRowDTO.java @@ -19,6 +19,7 @@ package org.sleuthkit.autopsy.mainui.datamodel; import com.google.common.collect.ImmutableList; +import java.text.MessageFormat; import java.util.Arrays; import java.util.List; import org.openide.util.NbBundle.Messages; @@ -36,19 +37,26 @@ import org.openide.util.NbBundle.Messages; "CountsRowResultDTO_columns_count_description=Name" }) public class CountsRowDTO implements RowDTO { - - private static final String DEFAULT_TYPE_ID = "COUNTS"; - - public static ColumnKey DISPLAY_NAME_COL = new ColumnKey( + private static final ColumnKey DISPLAY_NAME_COL = new ColumnKey( Bundle.CountsRowResultDTO_columns_displayName_name(), Bundle.CountsRowResultDTO_columns_displayName_displayName(), Bundle.CountsRowResultDTO_columns_displayName_description()); - public static ColumnKey COUNT_COL = new ColumnKey( + private static final ColumnKey COUNT_COL = new ColumnKey( Bundle.CountsRowResultDTO_columns_count_name(), Bundle.CountsRowResultDTO_columns_count_displayName(), Bundle.CountsRowResultDTO_columns_count_description()); + private static final List DEFAULT_KEYS = ImmutableList.of(DISPLAY_NAME_COL, COUNT_COL); + + /** + * @return The default column keys to be displayed for a counts row (display + * name and count). + */ + public static List getDefaultColumnKeys() { + return DEFAULT_KEYS; + } + private final long id; private final String displayName; private final long count; @@ -56,10 +64,17 @@ public class CountsRowDTO implements RowDTO { private final String typeId; private final T typeData; - public CountsRowDTO(T typeData, long id, String displayName, long count) { - this(DEFAULT_TYPE_ID, typeData, id, displayName, count); - } - + /** + * Main constructor. + * + * @param typeId The string id for the type of result. + * @param typeData Data for this particular row's type (i.e. + * BlackboardArtifact.Type for counts of a particular + * artifact type). + * @param id The numerical id of this row. + * @param displayName The display name of this row. + * @param count The count of results for this row. + */ public CountsRowDTO(String typeId, T typeData, long id, String displayName, long count) { this.typeId = typeId; this.id = id; @@ -74,10 +89,16 @@ public class CountsRowDTO implements RowDTO { return id; } + /** + * @return The display name of this row. + */ public String getDisplayName() { return displayName; } + /** + * @return The count of results for this row. + */ public long getCount() { return count; } @@ -92,9 +113,12 @@ public class CountsRowDTO implements RowDTO { return typeId; } + /** + * + * @return Data for this particular row's type (i.e. BlackboardArtifact.Type + * for counts of a particular artifact type). + */ public T getTypeData() { return typeData; } - - } diff --git a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/DataArtifactDAO.java b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/DataArtifactDAO.java index 73e57da6eb..e02e43bfe5 100644 --- a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/DataArtifactDAO.java +++ b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/DataArtifactDAO.java @@ -20,6 +20,7 @@ package org.sleuthkit.autopsy.mainui.datamodel; import com.google.common.cache.Cache; import com.google.common.cache.CacheBuilder; +import java.sql.SQLException; import java.text.MessageFormat; import java.util.ArrayList; import java.util.Collections; @@ -27,11 +28,12 @@ import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.Map.Entry; import java.util.concurrent.ExecutionException; +import java.util.logging.Level; import java.util.stream.Collectors; import java.util.stream.Stream; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; +import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.Blackboard; import org.sleuthkit.datamodel.BlackboardArtifact; @@ -46,6 +48,8 @@ import org.sleuthkit.datamodel.TskCoreException; */ public class DataArtifactDAO extends BlackboardArtifactDAO { + private static Logger logger = Logger.getLogger(DataArtifactDAO.class.getName()); + private static DataArtifactDAO instance = null; synchronized static DataArtifactDAO getInstance() { @@ -69,15 +73,15 @@ public class DataArtifactDAO extends BlackboardArtifactDAO { List arts = (dataSourceId != null) ? blackboard.getDataArtifacts(artType.getTypeID(), dataSourceId) : blackboard.getDataArtifacts(artType.getTypeID()); - + Stream pagedStream = arts.stream() .sorted(Comparator.comparing(art -> art.getId())) .skip(cacheKey.getStartItem()); - + if (cacheKey.getMaxResultsCount() != null) { pagedStream = pagedStream.limit(cacheKey.getMaxResultsCount()); } - + List pagedArtifacts = pagedStream.collect(Collectors.toList()); Map> artifactAttributes = new HashMap<>(); @@ -147,10 +151,10 @@ public class DataArtifactDAO extends BlackboardArtifactDAO { @Override RowDTO createRow(BlackboardArtifact artifact, Content srcContent, Content linkedFile, boolean isTimelineSupported, List cellValues, long id) throws IllegalArgumentException { - if (! (artifact instanceof DataArtifact)) { + if (!(artifact instanceof DataArtifact)) { throw new IllegalArgumentException("Can not make row for artifact with ID: " + artifact.getId() + " - artifact must be a data artifact"); } - return new DataArtifactRowDTO((DataArtifact)artifact, srcContent, linkedFile, isTimelineSupported, cellValues, id); + return new DataArtifactRowDTO((DataArtifact) artifact, srcContent, linkedFile, isTimelineSupported, cellValues, id); } public DataArtifactTableSearchResultsDTO getDataArtifactsForTable(DataArtifactSearchParam artifactKey) throws ExecutionException, IllegalArgumentException { @@ -169,31 +173,63 @@ public class DataArtifactDAO extends BlackboardArtifactDAO { public void dropDataArtifactCache() { dataArtifactCache.invalidateAll(); } - - - public SearchResultsDTO getDataArtifactsCounts() throws ExecutionException { - SleuthkitCase skCase = getCase(); - String query = "SELECT artifact_type_id, COUNT(*) AS count FROM blackboard_artifacts GROUP BY artifact_type_id"; - Map typeCounts = new HashMap<>(); - skCase.getCaseDbAccessManager().select(query, (resultSet) -> { - while (resultSet.next()) { - typeCounts. - typeIdCounts.put(resultSet.getInt("artifact_type_id"), resultSet.getLong("count")); - } - }); - - - Map typeCounts = new HashMap<>(); - for (Entry typeIdCount : typeIdCounts.entrySet()) { - typeCounts.put(skCase.getArtifactType(typeIdCount.getKey()), typeIdCount.getValue()); + + /** + * Returns a search results dto containing rows of counts data. + * + * @param dataSourceId The data source object id for which the results + * should be filtered or null if no data source + * filtering. + * + * @return The results where rows are CountsRowDTO of + * DataArtifactSearchParam. + * + * @throws ExecutionException + */ + public SearchResultsDTO getDataArtifactsCounts(Long dataSourceId) throws ExecutionException { + try { + // get artifact types and counts + SleuthkitCase skCase = getCase(); + String query = "SELECT artifact_type_id, COUNT(*) AS count " + + "FROM blackboard_artifacts " + + (dataSourceId == null ? "" : "data_source_obj_id = " + dataSourceId) + + "GROUP BY artifact_type_id"; + Map typeCounts = new HashMap<>(); + skCase.getCaseDbAccessManager().select(query, (resultSet) -> { + try { + while (resultSet.next()) { + int artifactTypeId = resultSet.getInt("artifact_type_id"); + BlackboardArtifact.Type type = skCase.getBlackboard().getArtifactType(artifactTypeId); + long count = resultSet.getLong("count"); + typeCounts.put(type, count); + } + } catch (TskCoreException | SQLException ex) { + logger.log(Level.WARNING, "An error occurred while fetching artifact type counts.", ex); + } + }); + + // get row dto's sorted by display name + List typeCountRows = typeCounts.entrySet().stream() + .map(entry -> { + return new CountsRowDTO<>( + BlackboardArtifact.Category.DATA_ARTIFACT.name(), + new DataArtifactSearchParam(entry.getKey(), dataSourceId), + entry.getKey().getTypeID(), + entry.getKey().getDisplayName(), + entry.getValue()); + }) + .sorted(Comparator.comparing(countRow -> countRow.getDisplayName())) + .collect(Collectors.toList()); + + // return results + return new BaseSearchResultsDTO( + BlackboardArtifact.Category.DATA_ARTIFACT.name(), + BlackboardArtifact.Category.DATA_ARTIFACT.getDisplayName(), + CountsRowDTO.getDefaultColumnKeys(), + typeCountRows); + + } catch (NoCurrentCaseException | TskCoreException ex) { + throw new ExecutionException("An error occurred while fetching data artifact counts.", ex); } - - typeCounts.entrySet().stream() - .map(entry -> new CountsRowDTO<>( - BlackboardArtifact.Type.Category.DATA_ARTIFACT.name(), - entry.getKey(), - entry.getKey().getTypeId(), - entry.getKey().getDisplayName(), - entry.getValue())); } } diff --git a/Core/src/org/sleuthkit/autopsy/mainui/nodes/DataArtifactTypeTreeNode.java b/Core/src/org/sleuthkit/autopsy/mainui/nodes/DataArtifactTypeTreeNode.java new file mode 100644 index 0000000000..38ed7798db --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/mainui/nodes/DataArtifactTypeTreeNode.java @@ -0,0 +1,37 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2021 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.mainui.nodes; + +import org.sleuthkit.autopsy.corecomponents.DataResultTopComponent; +import org.sleuthkit.autopsy.mainui.datamodel.CountsRowDTO; +import org.sleuthkit.autopsy.mainui.datamodel.DataArtifactSearchParam; + +/** + * Display name and count of a data artifact type in the tree. + */ +public class DataArtifactTypeTreeNode extends TreeCountNode { + public DataArtifactTypeTreeNode(CountsRowDTO rowData) { + super(rowData); + } + + @Override + public void respondSelection(DataResultTopComponent dataResultPanel) { + dataResultPanel.displayDataArtifact(this.getRowData().getTypeData()); + } +} diff --git a/Core/src/org/sleuthkit/autopsy/mainui/nodes/SelectionResponder.java b/Core/src/org/sleuthkit/autopsy/mainui/nodes/SelectionResponder.java new file mode 100755 index 0000000000..b7370485d6 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/mainui/nodes/SelectionResponder.java @@ -0,0 +1,33 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2021 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.mainui.nodes; + +import org.sleuthkit.autopsy.corecomponents.DataResultTopComponent; + +/** + * Interface for Nodes that can respond to a tree selection event. + */ +public interface SelectionResponder { + /** + * Method to be called on tree nodes that can handle selection. + * + * @param dataResultPanel + */ + void respondSelection(DataResultTopComponent dataResultPanel); +} diff --git a/Core/src/org/sleuthkit/autopsy/mainui/nodes/TreeChildFactory.java b/Core/src/org/sleuthkit/autopsy/mainui/nodes/TreeChildFactory.java new file mode 100644 index 0000000000..3a6413dec1 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/mainui/nodes/TreeChildFactory.java @@ -0,0 +1,57 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2021 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.mainui.nodes; + + +import java.util.List; +import org.openide.nodes.ChildFactory; +import org.openide.nodes.Node; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.mainui.datamodel.SearchResultsDTO; + +/** + * Factory for populating tree with results. + */ +public class TreeChildFactory extends ChildFactory { + private static final Logger logger = Logger.getLogger(TreeChildFactory.class.getName()); + private SearchResultsDTO results; + + public TreeChildFactory(SearchResultsDTO initialResults) { + this.results = initialResults; + } + + @Override + protected boolean createKeys(List toPopulate) { + + } + + @Override + protected Node createNodeForKey(T key) { + + } + + public void update(SearchResultsDTO newResults) { + this.results = newResults; + this.refresh(false); + } + + public long getResultCount() { + return results == null ? 0 : results.getTotalResultsCount(); + } +} diff --git a/Core/src/org/sleuthkit/autopsy/mainui/nodes/TreeCountNode.java b/Core/src/org/sleuthkit/autopsy/mainui/nodes/TreeCountNode.java new file mode 100644 index 0000000000..2e864b2c46 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/mainui/nodes/TreeCountNode.java @@ -0,0 +1,84 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2021 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.mainui.nodes; + +import org.openide.nodes.AbstractNode; +import org.openide.nodes.Children; +import org.openide.util.Lookup; +import org.openide.util.lookup.Lookups; +import org.python.icu.text.MessageFormat; +import org.sleuthkit.autopsy.mainui.datamodel.CountsRowDTO; + +/** + * A node to be displayed in the tree that shows the count. + */ +public abstract class TreeCountNode extends AbstractNode implements SelectionResponder { + + /** + * Returns the default lookup based on the row dto. + * @param rowData The row dto data. + * @return The lookup to use in the node. + */ + protected static Lookup getDefaultLookup(CountsRowDTO rowData) { + return Lookups.fixed(rowData, rowData.getTypeData()); + } + + private CountsRowDTO rowData; + + /** + * Main constructor assuming a leaf node with default lookup. + * @param rowData The data to back the node. + */ + public TreeCountNode(CountsRowDTO rowData) { + this(rowData, Children.LEAF, getDefaultLookup(rowData)); + } + + /** + * Constructor. + * @param rowData The data to back the node. Must be non-null. + * @param children The children of this node. + * @param lookup The lookup for this node. + */ + protected TreeCountNode(CountsRowDTO rowData, Children children, Lookup lookup) { + super(children, lookup); + updateData(rowData); + } + + protected CountsRowDTO getRowData() { + return rowData; + } + + /** + * Sets the display name of the node to include the display name and count of the row. + * @param rowData The row data. + */ + protected void setDisplayName(CountsRowDTO rowData) { + this.setDisplayName(MessageFormat.format("{0} ({1})", rowData.getDisplayName(), rowData.getCount())); + } + + /** + * Updates the backing data of this node. + * @param updatedData The updated data. Must be non-null. + * @throws IllegalArgumentException + */ + public void updateData(CountsRowDTO updatedData) { + this.rowData = updatedData; + this.setDisplayName(updatedData); + } +} From f7bc03212d5f7291f5787d46eb874fe14784a4e7 Mon Sep 17 00:00:00 2001 From: Greg DiCristofaro Date: Tue, 26 Oct 2021 10:50:57 -0400 Subject: [PATCH 04/38] updates for file --- .../sleuthkit/autopsy/mainui/nodes/TreeChildFactory.java | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/mainui/nodes/TreeChildFactory.java b/Core/src/org/sleuthkit/autopsy/mainui/nodes/TreeChildFactory.java index 3a6413dec1..92573bac23 100644 --- a/Core/src/org/sleuthkit/autopsy/mainui/nodes/TreeChildFactory.java +++ b/Core/src/org/sleuthkit/autopsy/mainui/nodes/TreeChildFactory.java @@ -23,12 +23,13 @@ import java.util.List; import org.openide.nodes.ChildFactory; import org.openide.nodes.Node; import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.mainui.datamodel.CountsRowDTO; import org.sleuthkit.autopsy.mainui.datamodel.SearchResultsDTO; /** * Factory for populating tree with results. */ -public class TreeChildFactory extends ChildFactory { +public class TreeChildFactory extends ChildFactory> { private static final Logger logger = Logger.getLogger(TreeChildFactory.class.getName()); private SearchResultsDTO results; @@ -38,14 +39,14 @@ public class TreeChildFactory extends ChildFactory { @Override protected boolean createKeys(List toPopulate) { - + } @Override protected Node createNodeForKey(T key) { - + return new TreeCountNode<>(); } - + public void update(SearchResultsDTO newResults) { this.results = newResults; this.refresh(false); From 9661973a7070637f8d7c72caa0ecadb43b39a60f Mon Sep 17 00:00:00 2001 From: Greg DiCristofaro Date: Tue, 26 Oct 2021 11:19:10 -0400 Subject: [PATCH 05/38] counts row dto inherits from base row --- .../mainui/datamodel/CountsRowDTO.java | 25 +--------- .../mainui/nodes/TreeChildFactory.java | 50 ++++++++++++++++++- 2 files changed, 51 insertions(+), 24 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/CountsRowDTO.java b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/CountsRowDTO.java index b2eb1f9fae..533509adf3 100644 --- a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/CountsRowDTO.java +++ b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/CountsRowDTO.java @@ -19,7 +19,6 @@ package org.sleuthkit.autopsy.mainui.datamodel; import com.google.common.collect.ImmutableList; -import java.text.MessageFormat; import java.util.Arrays; import java.util.List; import org.openide.util.NbBundle.Messages; @@ -36,7 +35,7 @@ import org.openide.util.NbBundle.Messages; "CountsRowResultDTO_columns_count_displayName=Name", "CountsRowResultDTO_columns_count_description=Name" }) -public class CountsRowDTO implements RowDTO { +public class CountsRowDTO extends BaseRowDTO { private static final ColumnKey DISPLAY_NAME_COL = new ColumnKey( Bundle.CountsRowResultDTO_columns_displayName_name(), Bundle.CountsRowResultDTO_columns_displayName_displayName(), @@ -57,11 +56,8 @@ public class CountsRowDTO implements RowDTO { return DEFAULT_KEYS; } - private final long id; private final String displayName; private final long count; - private final List cellValues; - private final String typeId; private final T typeData; /** @@ -76,19 +72,12 @@ public class CountsRowDTO implements RowDTO { * @param count The count of results for this row. */ public CountsRowDTO(String typeId, T typeData, long id, String displayName, long count) { - this.typeId = typeId; - this.id = id; + super(ImmutableList.of(Arrays.asList(displayName, count)), typeId, id); this.displayName = displayName; this.count = count; - this.cellValues = ImmutableList.of(Arrays.asList(displayName, count)); this.typeData = typeData; } - @Override - public long getId() { - return id; - } - /** * @return The display name of this row. */ @@ -103,16 +92,6 @@ public class CountsRowDTO implements RowDTO { return count; } - @Override - public List getCellValues() { - return cellValues; - } - - @Override - public String getTypeId() { - return typeId; - } - /** * * @return Data for this particular row's type (i.e. BlackboardArtifact.Type diff --git a/Core/src/org/sleuthkit/autopsy/mainui/nodes/TreeChildFactory.java b/Core/src/org/sleuthkit/autopsy/mainui/nodes/TreeChildFactory.java index 92573bac23..d9dba9eb17 100644 --- a/Core/src/org/sleuthkit/autopsy/mainui/nodes/TreeChildFactory.java +++ b/Core/src/org/sleuthkit/autopsy/mainui/nodes/TreeChildFactory.java @@ -19,12 +19,18 @@ package org.sleuthkit.autopsy.mainui.nodes; +import java.util.Comparator; +import java.util.HashMap; import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; import org.openide.nodes.ChildFactory; import org.openide.nodes.Node; import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.datamodel.Artifacts; import org.sleuthkit.autopsy.mainui.datamodel.CountsRowDTO; import org.sleuthkit.autopsy.mainui.datamodel.SearchResultsDTO; +import org.sleuthkit.datamodel.BlackboardArtifact; /** * Factory for populating tree with results. @@ -33,13 +39,55 @@ public class TreeChildFactory extends ChildFactory> { private static final Logger logger = Logger.getLogger(TreeChildFactory.class.getName()); private SearchResultsDTO results; + private final Map> typeNodeMap = new HashMap<>(); + public TreeChildFactory(SearchResultsDTO initialResults) { this.results = initialResults; } @Override - protected boolean createKeys(List toPopulate) { + protected boolean createKeys(List> toPopulate) { + + List> rowsToAdd = typeNodeMap.values().stream() + .sorted((a,b) -> a.getDisplayName().compareToIgnoreCase(b.getDisplayName())) + .collect(Collectors.toList()); + + toPopulate.addAll(rowsToAdd); + return true; + + List allKeysSorted = types.stream() + // filter types by category and ensure they are not in the list of ignored types + .filter(tp -> category.equals(tp.getCategory()) && !IGNORED_TYPES.contains(tp)) + .map(tp -> { + // if typeNodeMap already contains key, update the relevant node and return the node + if (typeNodeMap.containsKey(tp)) { + Artifacts.TypeNodeKey typeKey = typeNodeMap.get(tp); + typeKey.getNode().updateDisplayName(); + return typeKey; + } else { + // if key is not in map, create the type key and add to map + Artifacts.TypeNodeKey newTypeKey = getTypeKey(tp, skCase, filteringDSObjId); + for (BlackboardArtifact.Type recordType : newTypeKey.getApplicableTypes()) { + typeNodeMap.put(recordType, newTypeKey); + } + return newTypeKey; + } + }) + // ensure record is returned + .filter(record -> record != null) + // there are potentially multiple types that apply to the same node (i.e. Interesting Files / Artifacts) + // ensure the keys are distinct + .distinct() + // sort by display name + .sorted((a, b) -> { + String aSafe = (a.getNode() == null || a.getNode().getDisplayName() == null) ? "" : a.getNode().getDisplayName(); + String bSafe = (b.getNode() == null || b.getNode().getDisplayName() == null) ? "" : b.getNode().getDisplayName(); + return aSafe.compareToIgnoreCase(bSafe); + }) + .collect(Collectors.toList()); + + list.addAll(allKeysSorted); } @Override From de37a8ed3b20e99a34a2f381a48716bdac5a28e8 Mon Sep 17 00:00:00 2001 From: Greg DiCristofaro Date: Tue, 26 Oct 2021 12:55:02 -0400 Subject: [PATCH 06/38] tree factory updates --- .../mainui/nodes/TreeChildFactory.java | 74 +++++++++---------- 1 file changed, 33 insertions(+), 41 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/mainui/nodes/TreeChildFactory.java b/Core/src/org/sleuthkit/autopsy/mainui/nodes/TreeChildFactory.java index d9dba9eb17..8cc1f0bb64 100644 --- a/Core/src/org/sleuthkit/autopsy/mainui/nodes/TreeChildFactory.java +++ b/Core/src/org/sleuthkit/autopsy/mainui/nodes/TreeChildFactory.java @@ -19,75 +19,67 @@ package org.sleuthkit.autopsy.mainui.nodes; -import java.util.Comparator; +import java.util.ArrayList; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.stream.Collectors; import org.openide.nodes.ChildFactory; import org.openide.nodes.Node; import org.sleuthkit.autopsy.coreutils.Logger; -import org.sleuthkit.autopsy.datamodel.Artifacts; import org.sleuthkit.autopsy.mainui.datamodel.CountsRowDTO; import org.sleuthkit.autopsy.mainui.datamodel.SearchResultsDTO; -import org.sleuthkit.datamodel.BlackboardArtifact; /** * Factory for populating tree with results. */ -public class TreeChildFactory extends ChildFactory> { +public class TreeChildFactory extends ChildFactory { private static final Logger logger = Logger.getLogger(TreeChildFactory.class.getName()); private SearchResultsDTO results; - private final Map> typeNodeMap = new HashMap<>(); + private final Map> typeNodeMap = new HashMap<>(); public TreeChildFactory(SearchResultsDTO initialResults) { this.results = initialResults; } @Override - protected boolean createKeys(List> toPopulate) { + protected boolean createKeys(List toPopulate) { + SearchResultsDTO curResults = this.results; + Set resultsRowIds = curResults.getItems().stream() + .map(row -> row.getId()) + .collect(Collectors.toSet()); + // remove no longer present + Set toBeRemoved = new HashSet<>(typeNodeMap.keySet()); + toBeRemoved.removeAll(resultsRowIds); + for (Long presentId : toBeRemoved) { + typeNodeMap.remove(presentId); + } - List> rowsToAdd = typeNodeMap.values().stream() + List> rowsToReturn = new ArrayList<>(); + for (CountsRowDTO dto : curResults.getItems()) { + // update cached that remain + TreeCountNode currentlyCached = typeNodeMap.get(dto.getId()); + if (currentlyCached != null) { + currentlyCached.updateData(dto); + } else { + // add new items + typeNodeMap.put(dto.getId(), new TreeCountNode(dto)); + } + + idsToReturn.add(dto); + } + + List idsToReturn = rowsToReturn.stream() .sorted((a,b) -> a.getDisplayName().compareToIgnoreCase(b.getDisplayName())) + .map(row -> row.getId()) .collect(Collectors.toList()); - toPopulate.addAll(rowsToAdd); + toPopulate.addAll(idsToReturn); return true; - - List allKeysSorted = types.stream() - // filter types by category and ensure they are not in the list of ignored types - .filter(tp -> category.equals(tp.getCategory()) && !IGNORED_TYPES.contains(tp)) - .map(tp -> { - // if typeNodeMap already contains key, update the relevant node and return the node - if (typeNodeMap.containsKey(tp)) { - Artifacts.TypeNodeKey typeKey = typeNodeMap.get(tp); - typeKey.getNode().updateDisplayName(); - return typeKey; - } else { - // if key is not in map, create the type key and add to map - Artifacts.TypeNodeKey newTypeKey = getTypeKey(tp, skCase, filteringDSObjId); - for (BlackboardArtifact.Type recordType : newTypeKey.getApplicableTypes()) { - typeNodeMap.put(recordType, newTypeKey); - } - return newTypeKey; - } - }) - // ensure record is returned - .filter(record -> record != null) - // there are potentially multiple types that apply to the same node (i.e. Interesting Files / Artifacts) - // ensure the keys are distinct - .distinct() - // sort by display name - .sorted((a, b) -> { - String aSafe = (a.getNode() == null || a.getNode().getDisplayName() == null) ? "" : a.getNode().getDisplayName(); - String bSafe = (b.getNode() == null || b.getNode().getDisplayName() == null) ? "" : b.getNode().getDisplayName(); - return aSafe.compareToIgnoreCase(bSafe); - }) - .collect(Collectors.toList()); - - list.addAll(allKeysSorted); } @Override From f61f0f07ab4a0f3d433ccbc051ccc7e355b67819 Mon Sep 17 00:00:00 2001 From: Greg DiCristofaro Date: Tue, 26 Oct 2021 15:02:47 -0400 Subject: [PATCH 07/38] merge from new_table_load --- .../autopsy/mainui/nodes/UpdatableNode.java | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 Core/src/org/sleuthkit/autopsy/mainui/nodes/UpdatableNode.java diff --git a/Core/src/org/sleuthkit/autopsy/mainui/nodes/UpdatableNode.java b/Core/src/org/sleuthkit/autopsy/mainui/nodes/UpdatableNode.java new file mode 100644 index 0000000000..2e7cf3ace1 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/mainui/nodes/UpdatableNode.java @@ -0,0 +1,48 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2021 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.mainui.nodes; + +import org.openide.nodes.AbstractNode; +import org.openide.nodes.Children; +import org.openide.util.Lookup; +import org.sleuthkit.autopsy.mainui.datamodel.RowDTO; +import org.sleuthkit.autopsy.mainui.datamodel.SearchResultsDTO; + +/** + * Node that can update itself based on updated search results and row. + */ +public abstract class UpdatableNode extends AbstractNode { + + protected UpdatableNode(Children children) { + super(children); + } + + protected UpdatableNode(Children children, Lookup lookup) { + super(children, lookup); + } + + /** + * Triggers an update in rendering (i.e. display name) based on data for + * this row. + * + * @param results The results in which this row appears. + * @param row The backing row data. + */ + public abstract void update(SearchResultsDTO results, RowDTO row); +} From 3ed4ba6d956bf842663a0238ad131b430858096d Mon Sep 17 00:00:00 2001 From: Greg DiCristofaro Date: Tue, 26 Oct 2021 16:32:50 -0400 Subject: [PATCH 08/38] fixes and improvements --- .../autopsy/datamodel/DataArtifacts.java | 21 +++++- .../mainui/datamodel/DataArtifactDAO.java | 12 ++-- .../nodes/DataArtifactTypeTreeNode.java | 16 ++++- .../mainui/nodes/TreeChildFactory.java | 18 ++++- .../autopsy/mainui/nodes/TreeCountNode.java | 65 +++++++++++++++---- 5 files changed, 108 insertions(+), 24 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/DataArtifacts.java b/Core/src/org/sleuthkit/autopsy/datamodel/DataArtifacts.java index 54e9e5da0a..c21b6f3e30 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/DataArtifacts.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/DataArtifacts.java @@ -18,9 +18,13 @@ */ package org.sleuthkit.autopsy.datamodel; +import java.util.concurrent.ExecutionException; +import java.util.logging.Level; import org.openide.nodes.Children; import org.openide.util.NbBundle; -import org.sleuthkit.datamodel.BlackboardArtifact; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.mainui.datamodel.MainDAO; +import org.sleuthkit.autopsy.mainui.nodes.TreeChildFactory; /** * Analysis Results node support. @@ -29,6 +33,8 @@ import org.sleuthkit.datamodel.BlackboardArtifact; "DataArtifacts_name=Data Artifacts",}) public class DataArtifacts implements AutopsyVisitableItem { + private static final Logger logger = Logger.getLogger(DataArtifacts.class.getName()); + /** * Returns the name of this node that is the key in the children object. * @@ -43,6 +49,17 @@ public class DataArtifacts implements AutopsyVisitableItem { */ static class RootNode extends Artifacts.BaseArtifactNode { + private static Children getChildren(long filteringDSObjId) { + try { + return Children.create( + new TreeChildFactory(MainDAO.getInstance().getDataArtifactsDAO() + .getDataArtifactCounts(filteringDSObjId > 0 ? filteringDSObjId : null)), true); + } catch (ExecutionException ex) { + logger.log(Level.WARNING, "An error occurred while fetching keys for data artifacts tree.", ex); + return Children.LEAF; + } + } + /** * Main constructor. * @@ -52,7 +69,7 @@ public class DataArtifacts implements AutopsyVisitableItem { * equal to 0. */ RootNode(long filteringDSObjId) { - super(Children.create(new Artifacts.TypeFactory(BlackboardArtifact.Category.DATA_ARTIFACT, filteringDSObjId), true), + super(getChildren(filteringDSObjId), "org/sleuthkit/autopsy/images/extracted_content.png", DataArtifacts.getName(), DataArtifacts.getName()); diff --git a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/DataArtifactDAO.java b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/DataArtifactDAO.java index e02e43bfe5..0497ac305e 100644 --- a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/DataArtifactDAO.java +++ b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/DataArtifactDAO.java @@ -186,14 +186,14 @@ public class DataArtifactDAO extends BlackboardArtifactDAO { * * @throws ExecutionException */ - public SearchResultsDTO getDataArtifactsCounts(Long dataSourceId) throws ExecutionException { + public SearchResultsDTO getDataArtifactCounts(Long dataSourceId) throws ExecutionException { try { // get artifact types and counts SleuthkitCase skCase = getCase(); - String query = "SELECT artifact_type_id, COUNT(*) AS count " - + "FROM blackboard_artifacts " - + (dataSourceId == null ? "" : "data_source_obj_id = " + dataSourceId) - + "GROUP BY artifact_type_id"; + String query = "artifact_type_id, COUNT(*) AS count " + + " FROM blackboard_artifacts " + + (dataSourceId == null ? "" : (" WHERE data_source_obj_id = " + dataSourceId + " ")) + + " GROUP BY artifact_type_id"; Map typeCounts = new HashMap<>(); skCase.getCaseDbAccessManager().select(query, (resultSet) -> { try { @@ -212,7 +212,7 @@ public class DataArtifactDAO extends BlackboardArtifactDAO { List typeCountRows = typeCounts.entrySet().stream() .map(entry -> { return new CountsRowDTO<>( - BlackboardArtifact.Category.DATA_ARTIFACT.name(), + entry.getKey().getTypeName(), new DataArtifactSearchParam(entry.getKey(), dataSourceId), entry.getKey().getTypeID(), entry.getKey().getDisplayName(), diff --git a/Core/src/org/sleuthkit/autopsy/mainui/nodes/DataArtifactTypeTreeNode.java b/Core/src/org/sleuthkit/autopsy/mainui/nodes/DataArtifactTypeTreeNode.java index 38ed7798db..af8780750f 100644 --- a/Core/src/org/sleuthkit/autopsy/mainui/nodes/DataArtifactTypeTreeNode.java +++ b/Core/src/org/sleuthkit/autopsy/mainui/nodes/DataArtifactTypeTreeNode.java @@ -19,16 +19,28 @@ package org.sleuthkit.autopsy.mainui.nodes; import org.sleuthkit.autopsy.corecomponents.DataResultTopComponent; +import org.sleuthkit.autopsy.datamodel.utils.IconsUtil; import org.sleuthkit.autopsy.mainui.datamodel.CountsRowDTO; import org.sleuthkit.autopsy.mainui.datamodel.DataArtifactSearchParam; +import org.sleuthkit.datamodel.BlackboardArtifact; /** * Display name and count of a data artifact type in the tree. */ public class DataArtifactTypeTreeNode extends TreeCountNode { - public DataArtifactTypeTreeNode(CountsRowDTO rowData) { - super(rowData); + private static String getIconPath(BlackboardArtifact.Type artType) { + String iconPath = IconsUtil.getIconFilePath(artType.getTypeID()); + return iconPath != null && iconPath.charAt(0) == '/' ? iconPath.substring(1) : iconPath; } + + + public DataArtifactTypeTreeNode(CountsRowDTO rowData) { + super(rowData.getTypeData().getArtifactType().getTypeName(), + getIconPath(rowData.getTypeData().getArtifactType()), + rowData, + DataArtifactSearchParam.class); + } + @Override public void respondSelection(DataResultTopComponent dataResultPanel) { diff --git a/Core/src/org/sleuthkit/autopsy/mainui/nodes/TreeChildFactory.java b/Core/src/org/sleuthkit/autopsy/mainui/nodes/TreeChildFactory.java index c8d8e7e309..4d9b6c8b3e 100644 --- a/Core/src/org/sleuthkit/autopsy/mainui/nodes/TreeChildFactory.java +++ b/Core/src/org/sleuthkit/autopsy/mainui/nodes/TreeChildFactory.java @@ -25,15 +25,22 @@ import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.logging.Level; import org.openide.nodes.ChildFactory; import org.openide.nodes.Node; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.mainui.datamodel.CountsRowDTO; +import org.sleuthkit.autopsy.mainui.datamodel.DataArtifactSearchParam; import org.sleuthkit.autopsy.mainui.datamodel.RowDTO; import org.sleuthkit.autopsy.mainui.datamodel.SearchResultsDTO; +import org.sleuthkit.datamodel.BlackboardArtifact; /** * Factory for populating tree with results. */ public class TreeChildFactory extends ChildFactory { + private static final Logger logger = Logger.getLogger(TreeChildFactory.class.getName()); + private final Map typeNodeMap = new HashMap<>(); private SearchResultsDTO results; @@ -72,7 +79,16 @@ public class TreeChildFactory extends ChildFactory { } protected UpdatableNode createNewNode(SearchResultsDTO searchResults, RowDTO rowData) { - + try { + if (BlackboardArtifact.Category.DATA_ARTIFACT.name().equals(searchResults.getTypeId())) { + return new DataArtifactTypeTreeNode((CountsRowDTO) rowData); + } else { + return null; + } + } catch (ClassCastException ex) { + logger.log(Level.WARNING, "Unable to cast to proper type", ex); + return null; + } } @Override diff --git a/Core/src/org/sleuthkit/autopsy/mainui/nodes/TreeCountNode.java b/Core/src/org/sleuthkit/autopsy/mainui/nodes/TreeCountNode.java index 2e864b2c46..8929006b20 100644 --- a/Core/src/org/sleuthkit/autopsy/mainui/nodes/TreeCountNode.java +++ b/Core/src/org/sleuthkit/autopsy/mainui/nodes/TreeCountNode.java @@ -18,54 +18,82 @@ */ package org.sleuthkit.autopsy.mainui.nodes; -import org.openide.nodes.AbstractNode; +import java.text.MessageFormat; +import java.util.logging.Level; import org.openide.nodes.Children; import org.openide.util.Lookup; import org.openide.util.lookup.Lookups; -import org.python.icu.text.MessageFormat; +import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.mainui.datamodel.CountsRowDTO; +import org.sleuthkit.autopsy.mainui.datamodel.RowDTO; +import org.sleuthkit.autopsy.mainui.datamodel.SearchResultsDTO; /** * A node to be displayed in the tree that shows the count. */ -public abstract class TreeCountNode extends AbstractNode implements SelectionResponder { +public abstract class TreeCountNode extends UpdatableNode implements SelectionResponder { + + private static final Logger logger = Logger.getLogger(TreeCountNode.class.getName()); /** * Returns the default lookup based on the row dto. + * * @param rowData The row dto data. + * * @return The lookup to use in the node. */ protected static Lookup getDefaultLookup(CountsRowDTO rowData) { return Lookups.fixed(rowData, rowData.getTypeData()); } + private final Class dataObjType; private CountsRowDTO rowData; /** * Main constructor assuming a leaf node with default lookup. - * @param rowData The data to back the node. + * + * @param nodeName The name of the node. + * @param icon The path of the icon or null. + * @param rowData The data to back the node. + * @param dataObjType The type of the underlying data object within the + * counts row dto. */ - public TreeCountNode(CountsRowDTO rowData) { - this(rowData, Children.LEAF, getDefaultLookup(rowData)); + protected TreeCountNode(String nodeName, String icon, CountsRowDTO rowData, Class dataObjType) { + this(nodeName, icon, rowData, Children.LEAF, getDefaultLookup(rowData), dataObjType); } /** * Constructor. - * @param rowData The data to back the node. Must be non-null. - * @param children The children of this node. - * @param lookup The lookup for this node. + * + * @param nodeName The name of the node. + * @param icon The path of the icon or null. + * @param rowData The data to back the node. Must be non-null. + * @param children The children of this node. + * @param lookup The lookup for this node. + * @param dataObjType The type of the underlying data object within the + * counts row dto. */ - protected TreeCountNode(CountsRowDTO rowData, Children children, Lookup lookup) { + protected TreeCountNode(String nodeName, String icon, CountsRowDTO rowData, Children children, Lookup lookup, Class dataObjType) { super(children, lookup); + setName(nodeName); + if (icon != null) { + setIconBaseWithExtension(icon); + } updateData(rowData); + this.dataObjType = dataObjType; } + /** + * @return The current backing row data. + */ protected CountsRowDTO getRowData() { return rowData; } /** - * Sets the display name of the node to include the display name and count of the row. + * Sets the display name of the node to include the display name and count + * of the row. + * * @param rowData The row data. */ protected void setDisplayName(CountsRowDTO rowData) { @@ -74,11 +102,22 @@ public abstract class TreeCountNode extends AbstractNode implements Selection /** * Updates the backing data of this node. - * @param updatedData The updated data. Must be non-null. + * + * @param updatedData The updated data. Must be non-null. + * * @throws IllegalArgumentException */ - public void updateData(CountsRowDTO updatedData) { + private void updateData(CountsRowDTO updatedData) { this.rowData = updatedData; this.setDisplayName(updatedData); } + + @Override + public void update(SearchResultsDTO results, RowDTO row) { + if (row instanceof CountsRowDTO && ((CountsRowDTO) row).getTypeData().getClass().isAssignableFrom(this.dataObjType)) { + updateData((CountsRowDTO) row); + } else { + logger.log(Level.WARNING, MessageFormat.format("Unable to cast row dto {0} to generic of {1}", row, dataObjType)); + } + } } From 8593de7808edb8e3cb725abc2e582d065e6939da Mon Sep 17 00:00:00 2001 From: Eugene Livis Date: Tue, 26 Oct 2021 16:44:52 -0400 Subject: [PATCH 09/38] First cut --- .../mainui/datamodel/Bundle.properties-MERGED | 19 ++ .../datamodel/FileTypeSizeSearchParams.java | 1 + .../autopsy/mainui/datamodel/TagsDAO.java | 253 ++++++++++++++++++ .../mainui/datamodel/TagsSearchParams.java | 92 +++++++ 4 files changed, 365 insertions(+) create mode 100755 Core/src/org/sleuthkit/autopsy/mainui/datamodel/TagsDAO.java create mode 100755 Core/src/org/sleuthkit/autopsy/mainui/datamodel/TagsSearchParams.java diff --git a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/Bundle.properties-MERGED index bbc9d5517b..ba656b0a66 100644 --- a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/Bundle.properties-MERGED @@ -46,7 +46,26 @@ FileExtRootFilter_documents_displayName=Documents FileExtRootFilter_executable_displayName=Executable FileExtRootFilter_image_displayName=Images FileExtRootFilter_video_displayName=Video +FileTag.name.text=File Tag FileTypesByMimeType.name.text=By MIME Type +TagsDAO.fileColumns.accessTimeColLbl=Accessed Time +TagsDAO.fileColumns.changeTimeColLbl=Changed Time +TagsDAO.fileColumns.commentColLbl=Comment +TagsDAO.fileColumns.createdTimeColLbl=Created Time +TagsDAO.fileColumns.filePathColLbl=File Path +TagsDAO.fileColumns.md5HashColLbl=MD5 Hash +TagsDAO.fileColumns.modifiedTimeColLbl=Modified Time +TagsDAO.fileColumns.nameColLbl=Name +TagsDAO.fileColumns.noDescription=No Description +TagsDAO.fileColumns.originalName=Original Name +TagsDAO.fileColumns.sizeColLbl=Size +TagsDAO.fileColumns.userNameColLbl=User Name +TagsDAO.tagColumns.commentColLbl=Comment +TagsDAO.tagColumns.origNameColLbl=Original Name +TagsDAO.tagColumns.sourceNameColLbl=Source Name +TagsDAO.tagColumns.sourcePathColLbl=Source File Path +TagsDAO.tagColumns.typeColLbl=Result Type +TagsDAO.tagColumns.userNameColLbl=User Name ThreePanelViewsDAO.fileColumns.accessTimeColLbl=Access Time ThreePanelViewsDAO.fileColumns.attrAddrColLbl=Attr. Addr. ThreePanelViewsDAO.fileColumns.changeTimeColLbl=Change Time diff --git a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/FileTypeSizeSearchParams.java b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/FileTypeSizeSearchParams.java index 9cfa31744c..03b767373f 100755 --- a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/FileTypeSizeSearchParams.java +++ b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/FileTypeSizeSearchParams.java @@ -79,6 +79,7 @@ public class FileTypeSizeSearchParams extends BaseSearchParams { int hash = 7; hash = 23 * hash + Objects.hashCode(this.sizeFilter); hash = 23 * hash + Objects.hashCode(this.dataSourceId); + hash = 23 * hash + Objects.hashCode(super.hashCode()); return hash; } diff --git a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/TagsDAO.java b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/TagsDAO.java new file mode 100755 index 0000000000..3364a5ceb2 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/TagsDAO.java @@ -0,0 +1,253 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2021 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.mainui.datamodel; + +import com.google.common.cache.Cache; +import com.google.common.cache.CacheBuilder; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; +import org.apache.commons.lang3.StringUtils; +import org.openide.util.NbBundle; +import org.openide.util.NbBundle.Messages; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; +import static org.sleuthkit.autopsy.core.UserPreferences.hideKnownFilesInViewsTree; +import static org.sleuthkit.autopsy.core.UserPreferences.hideSlackFilesInViewsTree; +import org.sleuthkit.autopsy.coreutils.TimeZoneUtils; +import org.sleuthkit.autopsy.datamodel.FileTypeExtensions; +import org.sleuthkit.autopsy.mainui.datamodel.FileRowDTO.ExtensionMediaType; +import org.sleuthkit.datamodel.AbstractFile; +import org.sleuthkit.datamodel.BlackboardArtifact; +import org.sleuthkit.datamodel.SleuthkitCase; +import org.sleuthkit.datamodel.TskCoreException; +import org.sleuthkit.datamodel.TskData; + +/** + * Provides information to populate the results viewer for data in the views + * section. + */ +@Messages({"TagsDAO.fileColumns.nameColLbl=Name", + "TagsDAO.fileColumns.originalName=Original Name", + "TagsDAO.fileColumns.filePathColLbl=File Path", + "TagsDAO.fileColumns.commentColLbl=Comment", + "TagsDAO.fileColumns.modifiedTimeColLbl=Modified Time", + "TagsDAO.fileColumns.changeTimeColLbl=Changed Time", + "TagsDAO.fileColumns.accessTimeColLbl=Accessed Time", + "TagsDAO.fileColumns.createdTimeColLbl=Created Time", + "TagsDAO.fileColumns.sizeColLbl=Size", + "TagsDAO.fileColumns.md5HashColLbl=MD5 Hash", + "TagsDAO.fileColumns.userNameColLbl=User Name", + "TagsDAO.fileColumns.noDescription=No Description", + "TagsDAO.tagColumns.sourceNameColLbl=Source Name", + "TagsDAO.tagColumns.origNameColLbl=Original Name", + "TagsDAO.tagColumns.sourcePathColLbl=Source File Path", + "TagsDAO.tagColumns.typeColLbl=Result Type", + "TagsDAO.tagColumns.commentColLbl=Comment", + "TagsDAO.tagColumns.userNameColLbl=User Name"}) +public class TagsDAO { + + private static final int CACHE_SIZE = 15; // rule of thumb: 5 entries times number of cached SearchParams sub-types + private static final long CACHE_DURATION = 2; + private static final TimeUnit CACHE_DURATION_UNITS = TimeUnit.MINUTES; + private final Cache searchParamsCache = CacheBuilder.newBuilder().maximumSize(CACHE_SIZE).expireAfterAccess(CACHE_DURATION, CACHE_DURATION_UNITS).build(); + + private static final String FILE_TAG_TYPE_ID = "FILE_TAG"; + private static final String RESULT_TAG_TYPE_ID = "RESULT_TAG"; + + private static final List FILE_TAG_COLUMNS = Arrays.asList( + getFileColumnKey(Bundle.TagsDAO_fileColumns_nameColLbl()), + getFileColumnKey(Bundle.TagsDAO_fileColumns_originalName()), + getFileColumnKey(Bundle.TagsDAO_fileColumns_filePathColLbl()), + getFileColumnKey(Bundle.TagsDAO_fileColumns_commentColLbl()), + getFileColumnKey(Bundle.TagsDAO_fileColumns_modifiedTimeColLbl()), + getFileColumnKey(Bundle.TagsDAO_fileColumns_changeTimeColLbl()), + getFileColumnKey(Bundle.TagsDAO_fileColumns_accessTimeColLbl()), + getFileColumnKey(Bundle.TagsDAO_fileColumns_createdTimeColLbl()), + getFileColumnKey(Bundle.TagsDAO_fileColumns_sizeColLbl()), + getFileColumnKey(Bundle.TagsDAO_fileColumns_md5HashColLbl()), + getFileColumnKey(Bundle.TagsDAO_fileColumns_userNameColLbl())); + + private static final List RESULT_TAG_COLUMNS = Arrays.asList( + getFileColumnKey(Bundle.TagsDAO_tagColumns_sourceNameColLbl()), + getFileColumnKey(Bundle.TagsDAO_tagColumns_origNameColLbl()), + getFileColumnKey(Bundle.TagsDAO_tagColumns_sourcePathColLbl()), + getFileColumnKey(Bundle.TagsDAO_tagColumns_typeColLbl()), + getFileColumnKey(Bundle.TagsDAO_tagColumns_commentColLbl()), + getFileColumnKey(Bundle.TagsDAO_tagColumns_userNameColLbl())); + + private static TagsDAO instance = null; + + synchronized static TagsDAO getInstance() { + if (instance == null) { + instance = new TagsDAO(); + } + + return instance; + } + + private static ColumnKey getFileColumnKey(String name) { + return new ColumnKey(name, name, Bundle.TagsDAO_fileColumns_noDescription()); + } + + private SleuthkitCase getCase() throws NoCurrentCaseException { + return Case.getCurrentCaseThrows().getSleuthkitCase(); + } + + static ExtensionMediaType getExtensionMediaType(String ext) { + if (StringUtils.isBlank(ext)) { + return ExtensionMediaType.UNCATEGORIZED; + } else { + ext = "." + ext; + } + if (FileTypeExtensions.getImageExtensions().contains(ext)) { + return ExtensionMediaType.IMAGE; + } else if (FileTypeExtensions.getVideoExtensions().contains(ext)) { + return ExtensionMediaType.VIDEO; + } else if (FileTypeExtensions.getAudioExtensions().contains(ext)) { + return ExtensionMediaType.AUDIO; + } else if (FileTypeExtensions.getDocumentExtensions().contains(ext)) { + return ExtensionMediaType.DOC; + } else if (FileTypeExtensions.getExecutableExtensions().contains(ext)) { + return ExtensionMediaType.EXECUTABLE; + } else if (FileTypeExtensions.getTextExtensions().contains(ext)) { + return ExtensionMediaType.TEXT; + } else if (FileTypeExtensions.getWebExtensions().contains(ext)) { + return ExtensionMediaType.WEB; + } else if (FileTypeExtensions.getPDFExtensions().contains(ext)) { + return ExtensionMediaType.PDF; + } else if (FileTypeExtensions.getArchiveExtensions().contains(ext)) { + return ExtensionMediaType.ARCHIVE; + } else { + return ExtensionMediaType.UNCATEGORIZED; + } + } + + public SearchResultsDTO getFilesByMime(FileTypeMimeSearchParams key) throws ExecutionException, IllegalArgumentException { + if (key.getMimeType() == null) { + throw new IllegalArgumentException("Must have non-null filter"); + } else if (key.getDataSourceId() != null && key.getDataSourceId() <= 0) { + throw new IllegalArgumentException("Data source id must be greater than 0 or null"); + } + + return searchParamsCache.get(key, () -> fetchMimeSearchResultsDTOs(key.getMimeType(), key.getDataSourceId(), key.getStartItem(), key.getMaxResultsCount())); + } + + private String getFileMimeWhereStatement(String mimeType, Long dataSourceId) { + + String whereClause = "(dir_type = " + TskData.TSK_FS_NAME_TYPE_ENUM.REG.getValue() + ")" + + " AND (type IN (" + + TskData.TSK_DB_FILES_TYPE_ENUM.FS.ordinal() + "," + + TskData.TSK_DB_FILES_TYPE_ENUM.CARVED.ordinal() + "," + + TskData.TSK_DB_FILES_TYPE_ENUM.DERIVED.ordinal() + "," + + TskData.TSK_DB_FILES_TYPE_ENUM.LAYOUT_FILE.ordinal() + "," + + TskData.TSK_DB_FILES_TYPE_ENUM.LOCAL.ordinal() + + (hideSlackFilesInViewsTree() ? "" : ("," + TskData.TSK_DB_FILES_TYPE_ENUM.SLACK.ordinal())) + + "))" + + (dataSourceId != null && dataSourceId > 0 ? " AND data_source_obj_id = " + dataSourceId : " ") + + (hideKnownFilesInViewsTree() ? (" AND (known IS NULL OR known != " + TskData.FileKnown.KNOWN.getFileKnownValue() + ")") : "") + + " AND mime_type = '" + mimeType + "'"; + + return whereClause; + } + + @NbBundle.Messages({"FileTag.name.text=File Tag"}) + private SearchResultsDTO fetchMimeSearchResultsDTOs(String mimeType, Long dataSourceId, long startItem, Long maxResultCount) throws NoCurrentCaseException, TskCoreException { + String whereStatement = getFileMimeWhereStatement(mimeType, dataSourceId); + final String FILE_TAG_DISPLAY_NAME = Bundle.FileTag_name_text(); + return fetchFileViewFiles(whereStatement, FILE_TAG_DISPLAY_NAME, startItem, maxResultCount); + } + + private SearchResultsDTO fetchFileViewFiles(String originalWhereStatement, String displayName, long startItem, Long maxResultCount) throws NoCurrentCaseException, TskCoreException { + + // Add offset and/or paging, if specified + String modifiedWhereStatement = originalWhereStatement + + " ORDER BY obj_id ASC" + + (maxResultCount != null && maxResultCount > 0 ? " LIMIT " + maxResultCount : "") + + (startItem > 0 ? " OFFSET " + startItem : ""); + + List files = getCase().findAllFilesWhere(modifiedWhereStatement); + + long totalResultsCount; + // get total number of results + if ( (startItem == 0) // offset is zero AND + && ( (maxResultCount != null && files.size() < maxResultCount) // number of results is less than max + || (maxResultCount == null)) ) { // OR max number of results was not specified + totalResultsCount = files.size(); + } else { + // do a query to get total number of results + totalResultsCount = getCase().countFilesWhere(originalWhereStatement); + } + + List fileRows = new ArrayList<>(); + for (AbstractFile file : files) { + + List cellValues = Arrays.asList( + file.getName(), // GVDTODO handle . and .. from getContentDisplayName() + // GVDTODO translation column + null, + //GVDTDO replace nulls with SCO + null, + null, + null, + file.getUniquePath(), + TimeZoneUtils.getFormattedTime(file.getMtime()), + TimeZoneUtils.getFormattedTime(file.getCtime()), + TimeZoneUtils.getFormattedTime(file.getAtime()), + TimeZoneUtils.getFormattedTime(file.getCrtime()), + file.getSize(), + file.getDirFlagAsString(), + file.getMetaFlagsAsString(), + // mode, + // userid, + // groupid, + // metaAddr, + // attrAddr, + // typeDir, + // typeMeta, + + file.getKnown().getName(), + StringUtils.defaultString(file.getMd5Hash()), + StringUtils.defaultString(file.getSha256Hash()), + // objectId, + + StringUtils.defaultString(file.getMIMEType()), + file.getNameExtension() + ); + + fileRows.add(new FileRowDTO( + file, + file.getId(), + file.getName(), + file.getNameExtension(), + getExtensionMediaType(file.getNameExtension()), + file.isDirNameFlagSet(TskData.TSK_FS_NAME_FLAG_ENUM.ALLOC), + file.getType(), + cellValues)); + } + + return new BaseSearchResultsDTO(FILE_TAG_TYPE_ID, displayName, FILE_TAG_COLUMNS, fileRows, startItem, totalResultsCount); + } + +} diff --git a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/TagsSearchParams.java b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/TagsSearchParams.java new file mode 100755 index 0000000000..6baa78aa8c --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/TagsSearchParams.java @@ -0,0 +1,92 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2021 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.mainui.datamodel; + +import java.util.Objects; + +/** + * Key for accessing data about tags from the DAO. + */ +public class TagsSearchParams extends BaseSearchParams { + + public enum TagType { + FILE, + RESULT; + } + + private final TagType type; + private final String tagName; + private final Long dataSourceId; + + public TagsSearchParams(String tagName, TagType type, Long dataSourceId) { + this.tagName = tagName; + this.type = type; + this.dataSourceId = dataSourceId; + } + + public TagsSearchParams(String tagName, TagType type, Long dataSourceId, long startItem, Long maxResultsCount) { + super(startItem, maxResultsCount); + this.tagName = tagName; + this.type = type; + this.dataSourceId = dataSourceId; + } + + public String getMimeType() { + return tagName; + } + + public Long getDataSourceId() { + return dataSourceId; + } + + @Override + public int hashCode() { + int hash = 7; + hash = 23 * hash + Objects.hashCode(this.tagName); + hash = 23 * hash + Objects.hashCode(this.type); + hash = 23 * hash + Objects.hashCode(this.dataSourceId); + hash = 23 * hash + Objects.hashCode(super.hashCode()); + return hash; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final TagsSearchParams other = (TagsSearchParams) obj; + if (!(this.tagName.equals(other.tagName))) { + return false; + } + if (!Objects.equals(this.dataSourceId, other.dataSourceId)) { + return false; + } + if (!Objects.equals(this.type, other.type)) { + return false; + } + return super.equalFields(other); + } + +} From 849f21609e7ef1905825dee5537238d09c8900da Mon Sep 17 00:00:00 2001 From: Greg DiCristofaro Date: Tue, 26 Oct 2021 19:44:18 -0400 Subject: [PATCH 10/38] bug fixes and beginning to work on breaking nodes out of DirectoryTreeFilterChildren --- .../DirectoryTreeFilterChildren.java | 36 ++++++++++--------- .../DirectoryTreeTopComponent.java | 6 ++-- .../mainui/datamodel/DataArtifactDAO.java | 32 ++++++++++++++++- 3 files changed, 55 insertions(+), 19 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeFilterChildren.java b/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeFilterChildren.java index e84dd16da6..c41fd39b8f 100644 --- a/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeFilterChildren.java +++ b/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeFilterChildren.java @@ -38,6 +38,7 @@ import org.sleuthkit.autopsy.datamodel.LocalDirectoryNode; import org.sleuthkit.autopsy.datamodel.SlackFileNode; import org.sleuthkit.autopsy.datamodel.VirtualDirectoryNode; import org.sleuthkit.autopsy.datamodel.VolumeNode; +import org.sleuthkit.autopsy.mainui.nodes.DataArtifactTypeTreeNode; import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE; import org.sleuthkit.datamodel.Content; @@ -83,6 +84,10 @@ class DirectoryTreeFilterChildren extends FilterNode.Children { */ @Override protected Node[] createNodes(Node origNode) { +// if (origNode instanceof DataArtifactTypeTreeNode) { +// return new Node[]{origNode}; +// } else + if (origNode == null || !(origNode instanceof DisplayableItemNode)) { return new Node[]{}; } @@ -119,7 +124,7 @@ class DirectoryTreeFilterChildren extends FilterNode.Children { && !((Directory) c).getName().equals(".."))) { ret = false; break; - } else if(AbstractContentNode.contentHasVisibleContentChildren(c)){ + } else if (AbstractContentNode.contentHasVisibleContentChildren(c)) { //fie has children, such as derived files ret = false; break; @@ -202,7 +207,7 @@ class DirectoryTreeFilterChildren extends FilterNode.Children { if ((childContent instanceof AbstractFile) && ((AbstractFile) childContent).isDir()) { return false; } else { - if(AbstractContentNode.contentHasVisibleContentChildren(childContent)){ + if (AbstractContentNode.contentHasVisibleContentChildren(childContent)) { return false; } } @@ -224,7 +229,7 @@ class DirectoryTreeFilterChildren extends FilterNode.Children { public Boolean visit(LayoutFileNode fn) { return visitDeep(fn); } - + @Override public Boolean visit(SlackFileNode sfn) { return visitDeep(sfn); @@ -253,11 +258,11 @@ class DirectoryTreeFilterChildren extends FilterNode.Children { @Override public Boolean visit(BlackboardArtifactNode bbafn) { // Only show Message arttifacts with children - if ( (bbafn.getArtifact().getArtifactTypeID() == ARTIFACT_TYPE.TSK_EMAIL_MSG.getTypeID()) || - (bbafn.getArtifact().getArtifactTypeID() == ARTIFACT_TYPE.TSK_MESSAGE.getTypeID()) ) { - return bbafn.hasContentChildren(); + if ((bbafn.getArtifact().getArtifactTypeID() == ARTIFACT_TYPE.TSK_EMAIL_MSG.getTypeID()) + || (bbafn.getArtifact().getArtifactTypeID() == ARTIFACT_TYPE.TSK_MESSAGE.getTypeID())) { + return bbafn.hasContentChildren(); } - + return false; } } @@ -291,7 +296,7 @@ class DirectoryTreeFilterChildren extends FilterNode.Children { public Boolean visit(LayoutFileNode ln) { return ln.hasVisibleContentChildren(); } - + @Override public Boolean visit(SlackFileNode sfn) { return sfn.hasVisibleContentChildren(); @@ -303,7 +308,6 @@ class DirectoryTreeFilterChildren extends FilterNode.Children { //return vdn.hasContentChildren(); } - @Override public Boolean visit(LocalDirectoryNode ldn) { return true; @@ -311,18 +315,18 @@ class DirectoryTreeFilterChildren extends FilterNode.Children { @Override public Boolean visit(FileTypesNode fileTypes) { - return defaultVisit(fileTypes); + return defaultVisit(fileTypes); } - + @Override public Boolean visit(BlackboardArtifactNode bbafn) { - + // Only show Message arttifacts with children - if ( (bbafn.getArtifact().getArtifactTypeID() == ARTIFACT_TYPE.TSK_EMAIL_MSG.getTypeID()) || - (bbafn.getArtifact().getArtifactTypeID() == ARTIFACT_TYPE.TSK_MESSAGE.getTypeID()) ) { - return bbafn.hasContentChildren(); + if ((bbafn.getArtifact().getArtifactTypeID() == ARTIFACT_TYPE.TSK_EMAIL_MSG.getTypeID()) + || (bbafn.getArtifact().getArtifactTypeID() == ARTIFACT_TYPE.TSK_MESSAGE.getTypeID())) { + return bbafn.hasContentChildren(); } - + return false; } diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.java b/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.java index f3ed597bc1..32349f2b84 100644 --- a/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.java +++ b/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.java @@ -610,7 +610,7 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat } }; - root = new DirectoryTreeFilterNode(root, true); +// root = new DirectoryTreeFilterNode(root, true); em.setRootContext(root); em.getRootContext().setName(currentCase.getName()); @@ -866,7 +866,9 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat try { Node treeNode = DirectoryTreeTopComponent.this.getSelectedNode(); if (treeNode != null) { - Node originNode = ((DirectoryTreeFilterNode) treeNode).getOriginal(); + Node originNode = treeNode instanceof DirectoryTreeFilterNode + ? ((DirectoryTreeFilterNode) treeNode).getOriginal() : + treeNode; //set node, wrap in filter node first to filter out children Node drfn = new DataResultFilterNode(originNode, DirectoryTreeTopComponent.this.em); if(originNode instanceof SelectionResponder) { diff --git a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/DataArtifactDAO.java b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/DataArtifactDAO.java index 0497ac305e..31a132b758 100644 --- a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/DataArtifactDAO.java +++ b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/DataArtifactDAO.java @@ -28,15 +28,22 @@ import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.concurrent.ExecutionException; import java.util.logging.Level; import java.util.stream.Collectors; import java.util.stream.Stream; +import org.python.google.common.collect.Sets; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.Blackboard; import org.sleuthkit.datamodel.BlackboardArtifact; +import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_DOWNLOAD_SOURCE; +import static org.sleuthkit.datamodel.BlackboardArtifact.Type.TSK_ASSOCIATED_OBJECT; +import static org.sleuthkit.datamodel.BlackboardArtifact.Type.TSK_DATA_SOURCE_USAGE; +import static org.sleuthkit.datamodel.BlackboardArtifact.Type.TSK_GEN_INFO; +import static org.sleuthkit.datamodel.BlackboardArtifact.Type.TSK_TL_EVENT; import org.sleuthkit.datamodel.BlackboardAttribute; import org.sleuthkit.datamodel.Content; import org.sleuthkit.datamodel.DataArtifact; @@ -48,6 +55,26 @@ import org.sleuthkit.datamodel.TskCoreException; */ public class DataArtifactDAO extends BlackboardArtifactDAO { + /** + * Types that should not be shown in the tree. + */ + @SuppressWarnings("deprecation") + private static final Set IGNORED_TYPES = Sets.newHashSet( + // these are shown in other parts of the UI (and different node types) + TSK_DATA_SOURCE_USAGE, + TSK_GEN_INFO, + new BlackboardArtifact.Type(TSK_DOWNLOAD_SOURCE), + TSK_TL_EVENT, + //This is not meant to be shown in the UI at all. It is more of a meta artifact. + TSK_ASSOCIATED_OBJECT + ); + + private static final String IGNORED_TYPES_SQL_SET = IGNORED_TYPES.stream() + .map(tp -> Integer.toString(tp.getTypeID())) + .collect(Collectors.joining(", ")); + + +// private static final String IGNORED_TYPES_SQL_SET = "(1)"; private static Logger logger = Logger.getLogger(DataArtifactDAO.class.getName()); private static DataArtifactDAO instance = null; @@ -192,7 +219,10 @@ public class DataArtifactDAO extends BlackboardArtifactDAO { SleuthkitCase skCase = getCase(); String query = "artifact_type_id, COUNT(*) AS count " + " FROM blackboard_artifacts " - + (dataSourceId == null ? "" : (" WHERE data_source_obj_id = " + dataSourceId + " ")) + + " WHERE artifact_type_id NOT IN (" + IGNORED_TYPES_SQL_SET + ") " + + " AND artifact_type_id IN " + + " (SELECT artifact_type_id FROM blackboard_artifact_types WHERE category_type = " + BlackboardArtifact.Category.DATA_ARTIFACT.getID() + ")" + + (dataSourceId == null ? "" : (" AND data_source_obj_id = " + dataSourceId + " ")) + " GROUP BY artifact_type_id"; Map typeCounts = new HashMap<>(); skCase.getCaseDbAccessManager().select(query, (resultSet) -> { From 7bfd22a6aee426f8d1d32b6df016a2e70e33fe77 Mon Sep 17 00:00:00 2001 From: Greg DiCristofaro Date: Wed, 27 Oct 2021 09:14:00 -0400 Subject: [PATCH 11/38] updates --- .../DirectoryTreeFilterChildren.java | 19 +++++++++++++--- .../DirectoryTreeTopComponent.java | 22 +++++++++++++------ 2 files changed, 31 insertions(+), 10 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeFilterChildren.java b/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeFilterChildren.java index c41fd39b8f..2bd642b3a9 100644 --- a/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeFilterChildren.java +++ b/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeFilterChildren.java @@ -39,6 +39,7 @@ import org.sleuthkit.autopsy.datamodel.SlackFileNode; import org.sleuthkit.autopsy.datamodel.VirtualDirectoryNode; import org.sleuthkit.autopsy.datamodel.VolumeNode; import org.sleuthkit.autopsy.mainui.nodes.DataArtifactTypeTreeNode; +import org.sleuthkit.autopsy.mainui.nodes.UpdatableNode; import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE; import org.sleuthkit.datamodel.Content; @@ -74,6 +75,18 @@ class DirectoryTreeFilterChildren extends FilterNode.Children { return new DirectoryTreeFilterNode(arg0, createChildren); } + public static class FilterAcceptedNode extends FilterNode { + + public FilterAcceptedNode(Node original) { + super(original); + } + + @Override + public Node getOriginal() { + return super.getOriginal(); + } + } + /* * This method takes in a node as an argument and will create a new one if * it should be displayed in the tree. If it is to be displayed, it also @@ -84,10 +97,10 @@ class DirectoryTreeFilterChildren extends FilterNode.Children { */ @Override protected Node[] createNodes(Node origNode) { -// if (origNode instanceof DataArtifactTypeTreeNode) { -// return new Node[]{origNode}; +// if (origNode instanceof DataArtifacts.RootNode || origNode instanceof UpdatableNode) { +// return new Node[]{new FilterAcceptedNode(origNode)}; // } else - +// if (origNode == null || !(origNode instanceof DisplayableItemNode)) { return new Node[]{}; } diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.java b/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.java index 32349f2b84..5b5f4081b2 100644 --- a/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.java +++ b/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.java @@ -53,6 +53,7 @@ import org.openide.explorer.ExplorerUtils; import org.openide.explorer.view.BeanTreeView; import org.openide.nodes.AbstractNode; import org.openide.nodes.Children; +import org.openide.nodes.FilterNode; import org.openide.nodes.Node; import org.openide.nodes.NodeNotFoundException; import org.openide.nodes.NodeOp; @@ -866,12 +867,19 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat try { Node treeNode = DirectoryTreeTopComponent.this.getSelectedNode(); if (treeNode != null) { - Node originNode = treeNode instanceof DirectoryTreeFilterNode - ? ((DirectoryTreeFilterNode) treeNode).getOriginal() : - treeNode; + + Node originNode; + if (treeNode instanceof DirectoryTreeFilterNode) { + originNode = ((DirectoryTreeFilterNode) treeNode).getOriginal(); +// } else if (treeNode instanceof FilterAcceptedNode) { +// originNode = ((FilterAcceptedNode) treeNode).getOriginal(); + } else { + originNode = treeNode; + } + //set node, wrap in filter node first to filter out children Node drfn = new DataResultFilterNode(originNode, DirectoryTreeTopComponent.this.em); - if(originNode instanceof SelectionResponder) { + if (originNode instanceof SelectionResponder) { ((SelectionResponder) originNode).respondSelection(dataResult); } else if (FileTypesByMimeType.isEmptyMimeTypeNode(originNode)) { //Special case for when File Type Identification has not yet been run and @@ -1278,7 +1286,7 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat return; } } - + final Set finalHosts = hosts; Optional osAccountListNodeOpt = Stream.of(em.getRootContext().getChildren().getNodes(true)) @@ -1498,12 +1506,12 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat private Node getInterestingItemNode(Children typesChildren, BlackboardArtifact.Type artifactType, BlackboardArtifact art) { Node interestingItemsRootNode = typesChildren.findChild(artifactType.getDisplayName()); Children setNodeChildren = (interestingItemsRootNode == null) ? null : interestingItemsRootNode.getChildren(); - + // set node children for type could not be found, so return null. if (setNodeChildren == null) { return null; } - + String setName = null; try { setName = art.getAttributes().stream() From bfe85312c1ef20b7238f4a324b19962039eabdc8 Mon Sep 17 00:00:00 2001 From: Greg DiCristofaro Date: Wed, 27 Oct 2021 09:28:25 -0400 Subject: [PATCH 12/38] some cleanup --- .../autopsy/directorytree/DirectoryTreeFilterChildren.java | 2 -- .../org/sleuthkit/autopsy/mainui/datamodel/DataArtifactDAO.java | 2 -- 2 files changed, 4 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeFilterChildren.java b/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeFilterChildren.java index 2bd642b3a9..b8f6567822 100644 --- a/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeFilterChildren.java +++ b/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeFilterChildren.java @@ -38,8 +38,6 @@ import org.sleuthkit.autopsy.datamodel.LocalDirectoryNode; import org.sleuthkit.autopsy.datamodel.SlackFileNode; import org.sleuthkit.autopsy.datamodel.VirtualDirectoryNode; import org.sleuthkit.autopsy.datamodel.VolumeNode; -import org.sleuthkit.autopsy.mainui.nodes.DataArtifactTypeTreeNode; -import org.sleuthkit.autopsy.mainui.nodes.UpdatableNode; import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE; import org.sleuthkit.datamodel.Content; diff --git a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/DataArtifactDAO.java b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/DataArtifactDAO.java index 31a132b758..f284d2efcf 100644 --- a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/DataArtifactDAO.java +++ b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/DataArtifactDAO.java @@ -73,8 +73,6 @@ public class DataArtifactDAO extends BlackboardArtifactDAO { .map(tp -> Integer.toString(tp.getTypeID())) .collect(Collectors.joining(", ")); - -// private static final String IGNORED_TYPES_SQL_SET = "(1)"; private static Logger logger = Logger.getLogger(DataArtifactDAO.class.getName()); private static DataArtifactDAO instance = null; From e8f9ed9fbc2bf035d5127476c996ba8269722fb7 Mon Sep 17 00:00:00 2001 From: Eugene Livis Date: Wed, 27 Oct 2021 13:20:01 -0400 Subject: [PATCH 13/38] Resolved built issues --- .../mainui/datamodel/FileTypeSizeSearchParams.java | 1 - .../sleuthkit/autopsy/mainui/datamodel/TagsDAO.java | 11 ++++++++--- .../autopsy/mainui/datamodel/TagsSearchParams.java | 6 ++---- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/FileTypeSizeSearchParams.java b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/FileTypeSizeSearchParams.java index 91dcb1e1a7..bba60bcb7c 100755 --- a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/FileTypeSizeSearchParams.java +++ b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/FileTypeSizeSearchParams.java @@ -73,7 +73,6 @@ public class FileTypeSizeSearchParams { int hash = 7; hash = 23 * hash + Objects.hashCode(this.sizeFilter); hash = 23 * hash + Objects.hashCode(this.dataSourceId); - hash = 23 * hash + Objects.hashCode(super.hashCode()); return hash; } diff --git a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/TagsDAO.java b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/TagsDAO.java index 3364a5ceb2..4a422d3e2a 100755 --- a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/TagsDAO.java +++ b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/TagsDAO.java @@ -71,7 +71,7 @@ public class TagsDAO { private static final int CACHE_SIZE = 15; // rule of thumb: 5 entries times number of cached SearchParams sub-types private static final long CACHE_DURATION = 2; private static final TimeUnit CACHE_DURATION_UNITS = TimeUnit.MINUTES; - private final Cache searchParamsCache = CacheBuilder.newBuilder().maximumSize(CACHE_SIZE).expireAfterAccess(CACHE_DURATION, CACHE_DURATION_UNITS).build(); + private final Cache, SearchResultsDTO> searchParamsCache = CacheBuilder.newBuilder().maximumSize(CACHE_SIZE).expireAfterAccess(CACHE_DURATION, CACHE_DURATION_UNITS).build(); private static final String FILE_TAG_TYPE_ID = "FILE_TAG"; private static final String RESULT_TAG_TYPE_ID = "RESULT_TAG"; @@ -144,14 +144,19 @@ public class TagsDAO { } } - public SearchResultsDTO getFilesByMime(FileTypeMimeSearchParams key) throws ExecutionException, IllegalArgumentException { + public SearchResultsDTO getFilesByMime(FileTypeMimeSearchParams key, long startItem, Long maxCount, boolean hardRefresh) throws ExecutionException, IllegalArgumentException { if (key.getMimeType() == null) { throw new IllegalArgumentException("Must have non-null filter"); } else if (key.getDataSourceId() != null && key.getDataSourceId() <= 0) { throw new IllegalArgumentException("Data source id must be greater than 0 or null"); } + + SearchParams searchParams = new SearchParams<>(key, startItem, maxCount); + if (hardRefresh) { + this.searchParamsCache.invalidate(searchParams); + } - return searchParamsCache.get(key, () -> fetchMimeSearchResultsDTOs(key.getMimeType(), key.getDataSourceId(), key.getStartItem(), key.getMaxResultsCount())); + return searchParamsCache.get(searchParams, () -> fetchMimeSearchResultsDTOs(key.getMimeType(), key.getDataSourceId(), startItem, maxCount)); } private String getFileMimeWhereStatement(String mimeType, Long dataSourceId) { diff --git a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/TagsSearchParams.java b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/TagsSearchParams.java index 6baa78aa8c..80a97392a6 100755 --- a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/TagsSearchParams.java +++ b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/TagsSearchParams.java @@ -23,7 +23,7 @@ import java.util.Objects; /** * Key for accessing data about tags from the DAO. */ -public class TagsSearchParams extends BaseSearchParams { +public class TagsSearchParams { public enum TagType { FILE, @@ -41,7 +41,6 @@ public class TagsSearchParams extends BaseSearchParams { } public TagsSearchParams(String tagName, TagType type, Long dataSourceId, long startItem, Long maxResultsCount) { - super(startItem, maxResultsCount); this.tagName = tagName; this.type = type; this.dataSourceId = dataSourceId; @@ -61,7 +60,6 @@ public class TagsSearchParams extends BaseSearchParams { hash = 23 * hash + Objects.hashCode(this.tagName); hash = 23 * hash + Objects.hashCode(this.type); hash = 23 * hash + Objects.hashCode(this.dataSourceId); - hash = 23 * hash + Objects.hashCode(super.hashCode()); return hash; } @@ -86,7 +84,7 @@ public class TagsSearchParams extends BaseSearchParams { if (!Objects.equals(this.type, other.type)) { return false; } - return super.equalFields(other); + return true; } } From de3afe9ff99fbdffeff4b8bf0771a98320344940 Mon Sep 17 00:00:00 2001 From: Greg DiCristofaro Date: Wed, 27 Oct 2021 15:16:05 -0400 Subject: [PATCH 14/38] updates for factory and nodes --- .../autopsy/datamodel/DataArtifacts.java | 13 +- .../mainui/datamodel/CountsRowDTO.java | 103 ------------- .../mainui/datamodel/DataArtifactDAO.java | 15 +- .../autopsy/mainui/datamodel/TreeDTO.java | 145 ++++++++++++++++++ .../mainui/nodes/DataArtifactTypeFactory.java | 71 +++++++++ .../nodes/DataArtifactTypeTreeNode.java | 49 ------ .../mainui/nodes/TreeChildFactory.java | 94 +++++++----- .../autopsy/mainui/nodes/TreeCountNode.java | 123 --------------- .../autopsy/mainui/nodes/TreeNode.java | 127 +++++++++++++++ 9 files changed, 404 insertions(+), 336 deletions(-) delete mode 100644 Core/src/org/sleuthkit/autopsy/mainui/datamodel/CountsRowDTO.java create mode 100644 Core/src/org/sleuthkit/autopsy/mainui/datamodel/TreeDTO.java create mode 100644 Core/src/org/sleuthkit/autopsy/mainui/nodes/DataArtifactTypeFactory.java delete mode 100644 Core/src/org/sleuthkit/autopsy/mainui/nodes/DataArtifactTypeTreeNode.java delete mode 100644 Core/src/org/sleuthkit/autopsy/mainui/nodes/TreeCountNode.java create mode 100644 Core/src/org/sleuthkit/autopsy/mainui/nodes/TreeNode.java diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/DataArtifacts.java b/Core/src/org/sleuthkit/autopsy/datamodel/DataArtifacts.java index c21b6f3e30..10f36e9077 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/DataArtifacts.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/DataArtifacts.java @@ -23,8 +23,7 @@ import java.util.logging.Level; import org.openide.nodes.Children; import org.openide.util.NbBundle; import org.sleuthkit.autopsy.coreutils.Logger; -import org.sleuthkit.autopsy.mainui.datamodel.MainDAO; -import org.sleuthkit.autopsy.mainui.nodes.TreeChildFactory; +import org.sleuthkit.autopsy.mainui.nodes.DataArtifactTypeFactory; /** * Analysis Results node support. @@ -50,14 +49,8 @@ public class DataArtifacts implements AutopsyVisitableItem { static class RootNode extends Artifacts.BaseArtifactNode { private static Children getChildren(long filteringDSObjId) { - try { - return Children.create( - new TreeChildFactory(MainDAO.getInstance().getDataArtifactsDAO() - .getDataArtifactCounts(filteringDSObjId > 0 ? filteringDSObjId : null)), true); - } catch (ExecutionException ex) { - logger.log(Level.WARNING, "An error occurred while fetching keys for data artifacts tree.", ex); - return Children.LEAF; - } + return Children.create( + new DataArtifactTypeFactory(filteringDSObjId > 0 ? filteringDSObjId : null), true); } /** diff --git a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/CountsRowDTO.java b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/CountsRowDTO.java deleted file mode 100644 index 533509adf3..0000000000 --- a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/CountsRowDTO.java +++ /dev/null @@ -1,103 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2021 Basis Technology Corp. - * Contact: carrier sleuthkit org - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.sleuthkit.autopsy.mainui.datamodel; - -import com.google.common.collect.ImmutableList; -import java.util.Arrays; -import java.util.List; -import org.openide.util.NbBundle.Messages; - -/** - * - * A row result providing a category and a count for that category. - */ -@Messages({ - "CountsRowResultDTO_columns_displayName_name=displayName", - "CountsRowResultDTO_columns_displayName_displayName=Name", - "CountsRowResultDTO_columns_displayName_description=Name", - "CountsRowResultDTO_columns_count_name=displayName", - "CountsRowResultDTO_columns_count_displayName=Name", - "CountsRowResultDTO_columns_count_description=Name" -}) -public class CountsRowDTO extends BaseRowDTO { - private static final ColumnKey DISPLAY_NAME_COL = new ColumnKey( - Bundle.CountsRowResultDTO_columns_displayName_name(), - Bundle.CountsRowResultDTO_columns_displayName_displayName(), - Bundle.CountsRowResultDTO_columns_displayName_description()); - - private static final ColumnKey COUNT_COL = new ColumnKey( - Bundle.CountsRowResultDTO_columns_count_name(), - Bundle.CountsRowResultDTO_columns_count_displayName(), - Bundle.CountsRowResultDTO_columns_count_description()); - - private static final List DEFAULT_KEYS = ImmutableList.of(DISPLAY_NAME_COL, COUNT_COL); - - /** - * @return The default column keys to be displayed for a counts row (display - * name and count). - */ - public static List getDefaultColumnKeys() { - return DEFAULT_KEYS; - } - - private final String displayName; - private final long count; - private final T typeData; - - /** - * Main constructor. - * - * @param typeId The string id for the type of result. - * @param typeData Data for this particular row's type (i.e. - * BlackboardArtifact.Type for counts of a particular - * artifact type). - * @param id The numerical id of this row. - * @param displayName The display name of this row. - * @param count The count of results for this row. - */ - public CountsRowDTO(String typeId, T typeData, long id, String displayName, long count) { - super(ImmutableList.of(Arrays.asList(displayName, count)), typeId, id); - this.displayName = displayName; - this.count = count; - this.typeData = typeData; - } - - /** - * @return The display name of this row. - */ - public String getDisplayName() { - return displayName; - } - - /** - * @return The count of results for this row. - */ - public long getCount() { - return count; - } - - /** - * - * @return Data for this particular row's type (i.e. BlackboardArtifact.Type - * for counts of a particular artifact type). - */ - public T getTypeData() { - return typeData; - } -} diff --git a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/DataArtifactDAO.java b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/DataArtifactDAO.java index f284d2efcf..2ace0ca2be 100644 --- a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/DataArtifactDAO.java +++ b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/DataArtifactDAO.java @@ -36,6 +36,7 @@ import java.util.stream.Stream; import org.python.google.common.collect.Sets; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.mainui.datamodel.TreeDTO.TreeItemDTO; import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.Blackboard; import org.sleuthkit.datamodel.BlackboardArtifact; @@ -211,7 +212,7 @@ public class DataArtifactDAO extends BlackboardArtifactDAO { * * @throws ExecutionException */ - public SearchResultsDTO getDataArtifactCounts(Long dataSourceId) throws ExecutionException { + public TreeDTO getDataArtifactCounts(Long dataSourceId) throws ExecutionException { try { // get artifact types and counts SleuthkitCase skCase = getCase(); @@ -237,10 +238,10 @@ public class DataArtifactDAO extends BlackboardArtifactDAO { }); // get row dto's sorted by display name - List typeCountRows = typeCounts.entrySet().stream() + List> treeItemRows = typeCounts.entrySet().stream() .map(entry -> { - return new CountsRowDTO<>( - entry.getKey().getTypeName(), + return new TreeItemDTO<>( + BlackboardArtifact.Category.DATA_ARTIFACT.name(), new DataArtifactSearchParam(entry.getKey(), dataSourceId), entry.getKey().getTypeID(), entry.getKey().getDisplayName(), @@ -250,11 +251,7 @@ public class DataArtifactDAO extends BlackboardArtifactDAO { .collect(Collectors.toList()); // return results - return new BaseSearchResultsDTO( - BlackboardArtifact.Category.DATA_ARTIFACT.name(), - BlackboardArtifact.Category.DATA_ARTIFACT.getDisplayName(), - CountsRowDTO.getDefaultColumnKeys(), - typeCountRows); + return new TreeDTO<>(treeItemRows); } catch (NoCurrentCaseException | TskCoreException ex) { throw new ExecutionException("An error occurred while fetching data artifact counts.", ex); diff --git a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/TreeDTO.java b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/TreeDTO.java new file mode 100644 index 0000000000..9bce388b95 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/TreeDTO.java @@ -0,0 +1,145 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2021 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.mainui.datamodel; + +import java.util.List; +import java.util.Objects; + +/** + * A list of items to display in the tree. + */ +public class TreeDTO { + + private final List> items; + + /** + * Main constructor. + * + * @param items The items to display. + */ + public TreeDTO(List> items) { + this.items = items; + } + + /** + * @return The items to display. + */ + public List> getItems() { + return items; + } + + /** + * A result providing a category and a count for that category. Equals and + * hashCode are based on id, type id, and type data. + */ + public static class TreeItemDTO { + + private final String displayName; + private final String typeId; + private final Long count; + private final T typeData; + private final long id; + + /** + * Main constructor. + * + * @param typeId The id of this item type. + * @param typeData Data for this particular row's type (i.e. + * BlackboardArtifact.Type for counts of a particular + * artifact type). + * @param id The numerical id of this row. + * @param displayName The display name of this row. + * @param count The count of results for this row or null if not + * applicable. + */ + public TreeItemDTO(String typeId, T typeData, long id, String displayName, Long count) { + this.typeId = typeId; + this.id = id; + this.displayName = displayName; + this.count = count; + this.typeData = typeData; + } + + /** + * @return The display name of this row. + */ + public String getDisplayName() { + return displayName; + } + + /** + * @return The count of results for this row or null if not applicable. + */ + public Long getCount() { + return count; + } + + /** + * + * @return Data for this particular row's type (i.e. + * BlackboardArtifact.Type for counts of a particular artifact + * type). + */ + public T getTypeData() { + return typeData; + } + + /** + * @return The numerical id for this item. + */ + public long getId() { + return id; + } + + @Override + public int hashCode() { + int hash = 7; + hash = 29 * hash + Objects.hashCode(this.typeId); + hash = 29 * hash + Objects.hashCode(this.typeData); + hash = 29 * hash + (int) (this.id ^ (this.id >>> 32)); + return hash; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final TreeItemDTO other = (TreeItemDTO) obj; + if (this.id != other.id) { + return false; + } + if (!Objects.equals(this.typeId, other.typeId)) { + return false; + } + if (!Objects.equals(this.typeData, other.typeData)) { + return false; + } + return true; + } + + + } +} diff --git a/Core/src/org/sleuthkit/autopsy/mainui/nodes/DataArtifactTypeFactory.java b/Core/src/org/sleuthkit/autopsy/mainui/nodes/DataArtifactTypeFactory.java new file mode 100644 index 0000000000..fccba030af --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/mainui/nodes/DataArtifactTypeFactory.java @@ -0,0 +1,71 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2021 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.mainui.nodes; + +import java.util.concurrent.ExecutionException; +import org.sleuthkit.autopsy.corecomponents.DataResultTopComponent; +import org.sleuthkit.autopsy.datamodel.utils.IconsUtil; +import org.sleuthkit.autopsy.mainui.datamodel.DataArtifactSearchParam; +import org.sleuthkit.autopsy.mainui.datamodel.MainDAO; +import org.sleuthkit.autopsy.mainui.datamodel.TreeDTO; +import org.sleuthkit.datamodel.BlackboardArtifact; + +/** + * Factory for displaying data artifact types in the tree. + */ +public class DataArtifactTypeFactory extends TreeChildFactory { + + private final Long dataSourceId; + + public DataArtifactTypeFactory(Long dataSourceId) { + this.dataSourceId = dataSourceId; + } + + @Override + protected TreeDTO getChildResults() throws IllegalArgumentException, ExecutionException { + return MainDAO.getInstance().getDataArtifactsDAO().getDataArtifactCounts(dataSourceId); + } + + @Override + protected TreeNode createNewNode(TreeDTO.TreeItemDTO rowData) { + return new DataArtifactTypeTreeNode(rowData); + } + + /** + * Display name and count of a data artifact type in the tree. + */ + public static class DataArtifactTypeTreeNode extends TreeNode { + + private static String getIconPath(BlackboardArtifact.Type artType) { + String iconPath = IconsUtil.getIconFilePath(artType.getTypeID()); + return iconPath != null && iconPath.charAt(0) == '/' ? iconPath.substring(1) : iconPath; + } + + public DataArtifactTypeTreeNode(TreeDTO.TreeItemDTO itemData) { + super(itemData.getTypeData().getArtifactType().getTypeName(), + getIconPath(itemData.getTypeData().getArtifactType()), + itemData); + } + + @Override + public void respondSelection(DataResultTopComponent dataResultPanel) { + dataResultPanel.displayDataArtifact(this.getItemData().getTypeData()); + } + } +} diff --git a/Core/src/org/sleuthkit/autopsy/mainui/nodes/DataArtifactTypeTreeNode.java b/Core/src/org/sleuthkit/autopsy/mainui/nodes/DataArtifactTypeTreeNode.java deleted file mode 100644 index af8780750f..0000000000 --- a/Core/src/org/sleuthkit/autopsy/mainui/nodes/DataArtifactTypeTreeNode.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2021 Basis Technology Corp. - * Contact: carrier sleuthkit org - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.sleuthkit.autopsy.mainui.nodes; - -import org.sleuthkit.autopsy.corecomponents.DataResultTopComponent; -import org.sleuthkit.autopsy.datamodel.utils.IconsUtil; -import org.sleuthkit.autopsy.mainui.datamodel.CountsRowDTO; -import org.sleuthkit.autopsy.mainui.datamodel.DataArtifactSearchParam; -import org.sleuthkit.datamodel.BlackboardArtifact; - -/** - * Display name and count of a data artifact type in the tree. - */ -public class DataArtifactTypeTreeNode extends TreeCountNode { - private static String getIconPath(BlackboardArtifact.Type artType) { - String iconPath = IconsUtil.getIconFilePath(artType.getTypeID()); - return iconPath != null && iconPath.charAt(0) == '/' ? iconPath.substring(1) : iconPath; - } - - - public DataArtifactTypeTreeNode(CountsRowDTO rowData) { - super(rowData.getTypeData().getArtifactType().getTypeName(), - getIconPath(rowData.getTypeData().getArtifactType()), - rowData, - DataArtifactSearchParam.class); - } - - - @Override - public void respondSelection(DataResultTopComponent dataResultPanel) { - dataResultPanel.displayDataArtifact(this.getRowData().getTypeData()); - } -} diff --git a/Core/src/org/sleuthkit/autopsy/mainui/nodes/TreeChildFactory.java b/Core/src/org/sleuthkit/autopsy/mainui/nodes/TreeChildFactory.java index 4d9b6c8b3e..72b11d25c9 100644 --- a/Core/src/org/sleuthkit/autopsy/mainui/nodes/TreeChildFactory.java +++ b/Core/src/org/sleuthkit/autopsy/mainui/nodes/TreeChildFactory.java @@ -18,86 +18,96 @@ */ package org.sleuthkit.autopsy.mainui.nodes; - import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.concurrent.ExecutionException; import java.util.logging.Level; import org.openide.nodes.ChildFactory; import org.openide.nodes.Node; import org.sleuthkit.autopsy.coreutils.Logger; -import org.sleuthkit.autopsy.mainui.datamodel.CountsRowDTO; -import org.sleuthkit.autopsy.mainui.datamodel.DataArtifactSearchParam; -import org.sleuthkit.autopsy.mainui.datamodel.RowDTO; -import org.sleuthkit.autopsy.mainui.datamodel.SearchResultsDTO; -import org.sleuthkit.datamodel.BlackboardArtifact; +import org.sleuthkit.autopsy.mainui.datamodel.TreeDTO; +import org.sleuthkit.autopsy.mainui.datamodel.TreeDTO.TreeItemDTO; /** * Factory for populating tree with results. */ -public class TreeChildFactory extends ChildFactory { +public abstract class TreeChildFactory extends ChildFactory.Detachable> { + private static final Logger logger = Logger.getLogger(TreeChildFactory.class.getName()); - - private final Map typeNodeMap = new HashMap<>(); - private SearchResultsDTO results; - - public TreeChildFactory(SearchResultsDTO initialResults) { - this.results = initialResults; - } + + private final Map, TreeNode> typeNodeMap = new HashMap<>(); + private TreeDTO curResults = null; @Override - protected boolean createKeys(List toPopulate) { - SearchResultsDTO curResults = this.results; - Set resultRows = new HashSet<>(curResults.getItems()); - + protected boolean createKeys(List> toPopulate) { + if (curResults == null) { + try { + curResults = getChildResults(); + } catch (IllegalArgumentException | ExecutionException ex) { + logger.log(Level.WARNING, "An error occurred while fetching keys", ex); + return false; + } + } + + Set> resultRows = new HashSet<>(curResults.getItems()); + // remove no longer present - Set toBeRemoved = new HashSet<>(typeNodeMap.keySet()); + Set> toBeRemoved = new HashSet<>(typeNodeMap.keySet()); toBeRemoved.removeAll(resultRows); - for (RowDTO presentId : toBeRemoved) { + for (TreeItemDTO presentId : toBeRemoved) { typeNodeMap.remove(presentId); } - - List rowsToReturn = new ArrayList<>(); - for (RowDTO dto : curResults.getItems()) { + + List> rowsToReturn = new ArrayList<>(); + for (TreeItemDTO dto : curResults.getItems()) { // update cached that remain - UpdatableNode currentlyCached = typeNodeMap.get(dto.getId()); + TreeNode currentlyCached = typeNodeMap.get(dto.getId()); if (currentlyCached != null) { - currentlyCached.update(curResults, dto); + currentlyCached.update(dto); } else { // add new items - typeNodeMap.put(dto, createNewNode(curResults, dto)); + typeNodeMap.put(dto, createNewNode(dto)); } - + rowsToReturn.add(dto); } toPopulate.addAll(rowsToReturn); return true; } - - protected UpdatableNode createNewNode(SearchResultsDTO searchResults, RowDTO rowData) { - try { - if (BlackboardArtifact.Category.DATA_ARTIFACT.name().equals(searchResults.getTypeId())) { - return new DataArtifactTypeTreeNode((CountsRowDTO) rowData); - } else { - return null; - } - } catch (ClassCastException ex) { - logger.log(Level.WARNING, "Unable to cast to proper type", ex); - return null; - } + + @Override + protected void removeNotify() { + curResults = null; + typeNodeMap.clear(); + super.removeNotify(); } @Override - protected Node createNodeForKey(RowDTO key) { + protected void addNotify() { + super.addNotify(); + } + + @Override + protected Node createNodeForKey(TreeItemDTO key) { return typeNodeMap.get(key); } - public void update(SearchResultsDTO newResults) { - this.results = newResults; + public void update() { + try { + this.curResults = getChildResults(); + } catch (IllegalArgumentException | ExecutionException ex) { + logger.log(Level.WARNING, "An error occurred while fetching keys", ex); + return; + } this.refresh(false); } + + protected abstract TreeNode createNewNode(TreeItemDTO rowData); + + protected abstract TreeDTO getChildResults() throws IllegalArgumentException, ExecutionException; } diff --git a/Core/src/org/sleuthkit/autopsy/mainui/nodes/TreeCountNode.java b/Core/src/org/sleuthkit/autopsy/mainui/nodes/TreeCountNode.java deleted file mode 100644 index 8929006b20..0000000000 --- a/Core/src/org/sleuthkit/autopsy/mainui/nodes/TreeCountNode.java +++ /dev/null @@ -1,123 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2021 Basis Technology Corp. - * Contact: carrier sleuthkit org - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.sleuthkit.autopsy.mainui.nodes; - -import java.text.MessageFormat; -import java.util.logging.Level; -import org.openide.nodes.Children; -import org.openide.util.Lookup; -import org.openide.util.lookup.Lookups; -import org.sleuthkit.autopsy.coreutils.Logger; -import org.sleuthkit.autopsy.mainui.datamodel.CountsRowDTO; -import org.sleuthkit.autopsy.mainui.datamodel.RowDTO; -import org.sleuthkit.autopsy.mainui.datamodel.SearchResultsDTO; - -/** - * A node to be displayed in the tree that shows the count. - */ -public abstract class TreeCountNode extends UpdatableNode implements SelectionResponder { - - private static final Logger logger = Logger.getLogger(TreeCountNode.class.getName()); - - /** - * Returns the default lookup based on the row dto. - * - * @param rowData The row dto data. - * - * @return The lookup to use in the node. - */ - protected static Lookup getDefaultLookup(CountsRowDTO rowData) { - return Lookups.fixed(rowData, rowData.getTypeData()); - } - - private final Class dataObjType; - private CountsRowDTO rowData; - - /** - * Main constructor assuming a leaf node with default lookup. - * - * @param nodeName The name of the node. - * @param icon The path of the icon or null. - * @param rowData The data to back the node. - * @param dataObjType The type of the underlying data object within the - * counts row dto. - */ - protected TreeCountNode(String nodeName, String icon, CountsRowDTO rowData, Class dataObjType) { - this(nodeName, icon, rowData, Children.LEAF, getDefaultLookup(rowData), dataObjType); - } - - /** - * Constructor. - * - * @param nodeName The name of the node. - * @param icon The path of the icon or null. - * @param rowData The data to back the node. Must be non-null. - * @param children The children of this node. - * @param lookup The lookup for this node. - * @param dataObjType The type of the underlying data object within the - * counts row dto. - */ - protected TreeCountNode(String nodeName, String icon, CountsRowDTO rowData, Children children, Lookup lookup, Class dataObjType) { - super(children, lookup); - setName(nodeName); - if (icon != null) { - setIconBaseWithExtension(icon); - } - updateData(rowData); - this.dataObjType = dataObjType; - } - - /** - * @return The current backing row data. - */ - protected CountsRowDTO getRowData() { - return rowData; - } - - /** - * Sets the display name of the node to include the display name and count - * of the row. - * - * @param rowData The row data. - */ - protected void setDisplayName(CountsRowDTO rowData) { - this.setDisplayName(MessageFormat.format("{0} ({1})", rowData.getDisplayName(), rowData.getCount())); - } - - /** - * Updates the backing data of this node. - * - * @param updatedData The updated data. Must be non-null. - * - * @throws IllegalArgumentException - */ - private void updateData(CountsRowDTO updatedData) { - this.rowData = updatedData; - this.setDisplayName(updatedData); - } - - @Override - public void update(SearchResultsDTO results, RowDTO row) { - if (row instanceof CountsRowDTO && ((CountsRowDTO) row).getTypeData().getClass().isAssignableFrom(this.dataObjType)) { - updateData((CountsRowDTO) row); - } else { - logger.log(Level.WARNING, MessageFormat.format("Unable to cast row dto {0} to generic of {1}", row, dataObjType)); - } - } -} diff --git a/Core/src/org/sleuthkit/autopsy/mainui/nodes/TreeNode.java b/Core/src/org/sleuthkit/autopsy/mainui/nodes/TreeNode.java new file mode 100644 index 0000000000..0922c33e40 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/mainui/nodes/TreeNode.java @@ -0,0 +1,127 @@ +/* + * Autopsy Forensic Bitemser + * + * Copyright 2021 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.mainui.nodes; + +import java.text.MessageFormat; +import java.util.logging.Level; +import org.openide.nodes.AbstractNode; +import org.openide.nodes.Children; +import org.openide.util.Lookup; +import org.openide.util.lookup.Lookups; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.mainui.datamodel.TreeDTO.TreeItemDTO; + +/** + * A node to be displayed in the tree that shows the count. + */ +public abstract class TreeNode extends AbstractNode implements SelectionResponder { + + private static final Logger logger = Logger.getLogger(TreeNode.class.getName()); + + /** + * Returns the default lookup based on the item dto. + * + * @param itemData The item dto data. + * + * @return The lookup to use in the node. + */ + protected static Lookup getDefaultLookup(TreeItemDTO itemData) { + return Lookups.fixed(itemData, itemData.getTypeData()); + } + + private TreeItemDTO itemData; + + /** + * Main constructor assuming a leaf node with default lookup. + * + * @param nodeName The name of the node. + * @param icon The path of the icon or null. + * @param itemData The data to back the node. + * @param dataObjType The type of the underlying data object within the + * counts item dto. + */ + protected TreeNode(String nodeName, String icon, TreeItemDTO itemData) { + this(nodeName, icon, itemData, Children.LEAF, getDefaultLookup(itemData)); + } + + /** + * Constructor. + * + * @param nodeName The name of the node. + * @param icon The path of the icon or null. + * @param itemData The data to back the node. Must be non-null. + * @param children The children of this node. + * @param lookup The lookup for this node. + * @param dataObjType The type of the underlying data object within the + * counts item dto. + */ + protected TreeNode(String nodeName, String icon, TreeItemDTO itemData, Children children, Lookup lookup) { + super(children, lookup); + setName(nodeName); + if (icon != null) { + setIconBaseWithExtension(icon); + } + update(itemData); + } + + /** + * @return The current backing item data. + */ + protected TreeItemDTO getItemData() { + return itemData; + } + + /** + * Sets the display name of the node to include the display name and count + * of the item. + * + * @param itemData The item data. + */ + protected void setDisplayName(TreeItemDTO itemData) { + String displayName = itemData.getCount() == null + ? itemData.getDisplayName() + : MessageFormat.format("{0} ({1})", itemData.getDisplayName(), itemData.getCount()); + + this.setDisplayName(displayName); + } + + /** + * Updates the backing data of this node. + * + * @param updatedData The updated data. Must be non-null. + * + * @thitems IllegalArgumentException + */ + public void update(TreeItemDTO updatedData) { + if (this.itemData != null && + (updatedData == null || + this.itemData.getId() != updatedData.getId() || + !this.itemData.getTypeData().equals(updatedData.getTypeData()))) { + logger.log(Level.WARNING, MessageFormat.format( + "Expected update data to have same id and type data but received [id: {0}, type: {1}] replacing [id: {2}, type: {3}]", + (updatedData == null ? "" : updatedData.getId()), + (updatedData == null ? "" : updatedData.getTypeData()), + this.itemData.getId(), + this.itemData.getTypeData())); + return; + } + this.itemData = updatedData; + this.setDisplayName(updatedData); + } +} From 94e8f893a9e9c59bca5ccaa0f14fd4378ea4387e Mon Sep 17 00:00:00 2001 From: Eugene Livis Date: Wed, 27 Oct 2021 17:00:45 -0400 Subject: [PATCH 15/38] More work --- .../autopsy/mainui/datamodel/TagsDAO.java | 221 ++++++++++-------- .../mainui/datamodel/TagsSearchParams.java | 19 +- 2 files changed, 136 insertions(+), 104 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/TagsDAO.java b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/TagsDAO.java index 4a422d3e2a..d71f199191 100755 --- a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/TagsDAO.java +++ b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/TagsDAO.java @@ -22,27 +22,25 @@ import com.google.common.cache.Cache; import com.google.common.cache.CacheBuilder; import java.util.ArrayList; import java.util.Arrays; -import java.util.HashMap; import java.util.List; -import java.util.Map; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; -import java.util.stream.Collectors; import org.apache.commons.lang3.StringUtils; import org.openide.util.NbBundle; import org.openide.util.NbBundle.Messages; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; -import static org.sleuthkit.autopsy.core.UserPreferences.hideKnownFilesInViewsTree; -import static org.sleuthkit.autopsy.core.UserPreferences.hideSlackFilesInViewsTree; +import org.sleuthkit.autopsy.core.UserPreferences; import org.sleuthkit.autopsy.coreutils.TimeZoneUtils; import org.sleuthkit.autopsy.datamodel.FileTypeExtensions; import org.sleuthkit.autopsy.mainui.datamodel.FileRowDTO.ExtensionMediaType; import org.sleuthkit.datamodel.AbstractFile; -import org.sleuthkit.datamodel.BlackboardArtifact; +import org.sleuthkit.datamodel.BlackboardArtifactTag; +import org.sleuthkit.datamodel.Content; +import org.sleuthkit.datamodel.ContentTag; import org.sleuthkit.datamodel.SleuthkitCase; +import org.sleuthkit.datamodel.TagName; import org.sleuthkit.datamodel.TskCoreException; -import org.sleuthkit.datamodel.TskData; /** * Provides information to populate the results viewer for data in the views @@ -73,12 +71,16 @@ public class TagsDAO { private static final TimeUnit CACHE_DURATION_UNITS = TimeUnit.MINUTES; private final Cache, SearchResultsDTO> searchParamsCache = CacheBuilder.newBuilder().maximumSize(CACHE_SIZE).expireAfterAccess(CACHE_DURATION, CACHE_DURATION_UNITS).build(); + private static final String USER_NAME_PROPERTY = "user.name"; //NON-NLS + private static final String FILE_TAG_TYPE_ID = "FILE_TAG"; private static final String RESULT_TAG_TYPE_ID = "RESULT_TAG"; - + private static final String FILE_TAG_DISPLAY_NAME = "File Tag"; + private static final String RESULT_TAG_DISPLAY_NAME = "Result Tag"; + private static final List FILE_TAG_COLUMNS = Arrays.asList( getFileColumnKey(Bundle.TagsDAO_fileColumns_nameColLbl()), - getFileColumnKey(Bundle.TagsDAO_fileColumns_originalName()), + getFileColumnKey(Bundle.TagsDAO_fileColumns_originalName()), // ELODO handle translation getFileColumnKey(Bundle.TagsDAO_fileColumns_filePathColLbl()), getFileColumnKey(Bundle.TagsDAO_fileColumns_commentColLbl()), getFileColumnKey(Bundle.TagsDAO_fileColumns_modifiedTimeColLbl()), @@ -144,115 +146,146 @@ public class TagsDAO { } } - public SearchResultsDTO getFilesByMime(FileTypeMimeSearchParams key, long startItem, Long maxCount, boolean hardRefresh) throws ExecutionException, IllegalArgumentException { - if (key.getMimeType() == null) { + public SearchResultsDTO getTags(TagsSearchParams key, long startItem, Long maxCount, boolean hardRefresh) throws ExecutionException, IllegalArgumentException { + if (key.getTagName() == null) { throw new IllegalArgumentException("Must have non-null filter"); } else if (key.getDataSourceId() != null && key.getDataSourceId() <= 0) { throw new IllegalArgumentException("Data source id must be greater than 0 or null"); } - SearchParams searchParams = new SearchParams<>(key, startItem, maxCount); + SearchParams searchParams = new SearchParams<>(key, startItem, maxCount); if (hardRefresh) { this.searchParamsCache.invalidate(searchParams); } - return searchParamsCache.get(searchParams, () -> fetchMimeSearchResultsDTOs(key.getMimeType(), key.getDataSourceId(), startItem, maxCount)); - } - - private String getFileMimeWhereStatement(String mimeType, Long dataSourceId) { - - String whereClause = "(dir_type = " + TskData.TSK_FS_NAME_TYPE_ENUM.REG.getValue() + ")" - + " AND (type IN (" - + TskData.TSK_DB_FILES_TYPE_ENUM.FS.ordinal() + "," - + TskData.TSK_DB_FILES_TYPE_ENUM.CARVED.ordinal() + "," - + TskData.TSK_DB_FILES_TYPE_ENUM.DERIVED.ordinal() + "," - + TskData.TSK_DB_FILES_TYPE_ENUM.LAYOUT_FILE.ordinal() + "," - + TskData.TSK_DB_FILES_TYPE_ENUM.LOCAL.ordinal() - + (hideSlackFilesInViewsTree() ? "" : ("," + TskData.TSK_DB_FILES_TYPE_ENUM.SLACK.ordinal())) - + "))" - + (dataSourceId != null && dataSourceId > 0 ? " AND data_source_obj_id = " + dataSourceId : " ") - + (hideKnownFilesInViewsTree() ? (" AND (known IS NULL OR known != " + TskData.FileKnown.KNOWN.getFileKnownValue() + ")") : "") - + " AND mime_type = '" + mimeType + "'"; - - return whereClause; + return searchParamsCache.get(searchParams, () -> fetchTagsDTOs(key.getTagName(), key.getTagType(), key.getDataSourceId(), startItem, maxCount)); } @NbBundle.Messages({"FileTag.name.text=File Tag"}) - private SearchResultsDTO fetchMimeSearchResultsDTOs(String mimeType, Long dataSourceId, long startItem, Long maxResultCount) throws NoCurrentCaseException, TskCoreException { - String whereStatement = getFileMimeWhereStatement(mimeType, dataSourceId); - final String FILE_TAG_DISPLAY_NAME = Bundle.FileTag_name_text(); - return fetchFileViewFiles(whereStatement, FILE_TAG_DISPLAY_NAME, startItem, maxResultCount); + private SearchResultsDTO fetchTagsDTOs(TagName tagName, TagsSearchParams.TagType type, Long dataSourceId, long startItem, Long maxResultCount) throws NoCurrentCaseException, TskCoreException { + if (null == type) { + // ELTODO throw? + return null; + } + + switch (type) { + case FILE: + return fetchFileTags(tagName, dataSourceId, startItem, maxResultCount); + case RESULT: + return fetchResultTags(tagName, dataSourceId, startItem, maxResultCount); + default: + // ELTODO throw? + return null; + } } + + /* GET RESULT TAGS + * BlackboardArtifactTagNodeFactory.createKeys(List tags) + * BlackboardArtifactTagNode.createSheet() + */ + + /* GET FILE TAGS + * ContentTagNodeFactory.createKeys(List tags) + * ContentTagNode.createSheet() + */ + + private SearchResultsDTO fetchResultTags(TagName tagName, Long dataSourceId, long startItem, Long maxResultCount) throws NoCurrentCaseException, TskCoreException { - private SearchResultsDTO fetchFileViewFiles(String originalWhereStatement, String displayName, long startItem, Long maxResultCount) throws NoCurrentCaseException, TskCoreException { + // ELTODO startItem, maxResultCount - // Add offset and/or paging, if specified - String modifiedWhereStatement = originalWhereStatement - + " ORDER BY obj_id ASC" - + (maxResultCount != null && maxResultCount > 0 ? " LIMIT " + maxResultCount : "") - + (startItem > 0 ? " OFFSET " + startItem : ""); - - List files = getCase().findAllFilesWhere(modifiedWhereStatement); - - long totalResultsCount; - // get total number of results - if ( (startItem == 0) // offset is zero AND - && ( (maxResultCount != null && files.size() < maxResultCount) // number of results is less than max - || (maxResultCount == null)) ) { // OR max number of results was not specified - totalResultsCount = files.size(); + List tags = new ArrayList<>(); + // Use the blackboard artifact tags bearing the specified tag name as the tags. + List artifactTags = (dataSourceId != null && dataSourceId > 0) + ? Case.getCurrentCaseThrows().getServices().getTagsManager().getBlackboardArtifactTagsByTagName(tagName, dataSourceId) + : Case.getCurrentCaseThrows().getServices().getTagsManager().getBlackboardArtifactTagsByTagName(tagName); + if (UserPreferences.showOnlyCurrentUserTags()) { + String userName = System.getProperty(USER_NAME_PROPERTY); + for (BlackboardArtifactTag tag : artifactTags) { + if (userName.equals(tag.getUserName())) { + tags.add(tag); + } + } } else { - // do a query to get total number of results - totalResultsCount = getCase().countFilesWhere(originalWhereStatement); + tags.addAll(artifactTags); } List fileRows = new ArrayList<>(); - for (AbstractFile file : files) { + for (BlackboardArtifactTag tag : tags) { + String name = tag.getContent().getName(); // As a backup. + try { + name = tag.getArtifact().getShortDescription(); + } catch (TskCoreException ignore) { + // it's a WARNING, skip + } + String contentPath; + try { + contentPath = tag.getContent().getUniquePath(); + } catch (TskCoreException ex) { + contentPath = NbBundle.getMessage(this.getClass(), "BlackboardArtifactTagNode.createSheet.unavail.text"); + } + List cellValues = Arrays.asList( - file.getName(), // GVDTODO handle . and .. from getContentDisplayName() - // GVDTODO translation column - null, - //GVDTDO replace nulls with SCO - null, - null, - null, - file.getUniquePath(), - TimeZoneUtils.getFormattedTime(file.getMtime()), - TimeZoneUtils.getFormattedTime(file.getCtime()), - TimeZoneUtils.getFormattedTime(file.getAtime()), - TimeZoneUtils.getFormattedTime(file.getCrtime()), - file.getSize(), - file.getDirFlagAsString(), - file.getMetaFlagsAsString(), - // mode, - // userid, - // groupid, - // metaAddr, - // attrAddr, - // typeDir, - // typeMeta, + name, + null, // ELTODO translation column + contentPath, + tag.getArtifact().getDisplayName(), + tag.getComment(), + tag.getUserName()); - file.getKnown().getName(), - StringUtils.defaultString(file.getMd5Hash()), - StringUtils.defaultString(file.getSha256Hash()), - // objectId, - - StringUtils.defaultString(file.getMIMEType()), - file.getNameExtension() - ); - - fileRows.add(new FileRowDTO( - file, - file.getId(), - file.getName(), - file.getNameExtension(), - getExtensionMediaType(file.getNameExtension()), - file.isDirNameFlagSet(TskData.TSK_FS_NAME_FLAG_ENUM.ALLOC), - file.getType(), - cellValues)); + fileRows.add(new BaseRowDTO( + cellValues, + RESULT_TAG_TYPE_ID, + tag.getId())); } - return new BaseSearchResultsDTO(FILE_TAG_TYPE_ID, displayName, FILE_TAG_COLUMNS, fileRows, startItem, totalResultsCount); + return new BaseSearchResultsDTO(RESULT_TAG_TYPE_ID, RESULT_TAG_DISPLAY_NAME, RESULT_TAG_COLUMNS, fileRows, startItem, fileRows.size()); } + + private SearchResultsDTO fetchFileTags(TagName tagName, Long dataSourceId, long startItem, Long maxResultCount) throws NoCurrentCaseException, TskCoreException { + // ELTODO startItem, maxResultCount + + List tags = new ArrayList<>(); + List contentTags = (dataSourceId != null && dataSourceId > 0) + ? Case.getCurrentCaseThrows().getServices().getTagsManager().getContentTagsByTagName(tagName, dataSourceId) + : Case.getCurrentCaseThrows().getServices().getTagsManager().getContentTagsByTagName(tagName); + if (UserPreferences.showOnlyCurrentUserTags()) { + String userName = System.getProperty(USER_NAME_PROPERTY); + for (ContentTag tag : contentTags) { + if (userName.equals(tag.getUserName())) { + tags.add(tag); + } + } + } else { + tags.addAll(contentTags); + } + + List fileRows = new ArrayList<>(); + for (ContentTag tag : tags) { + Content content = tag.getContent(); + String contentPath = content.getUniquePath(); + AbstractFile file = content instanceof AbstractFile ? (AbstractFile) content : null; + + List cellValues = Arrays.asList( + content.getName(), + null, // ELTODO translation column + contentPath, + tag.getComment(), + file != null ? TimeZoneUtils.getFormattedTime(file.getMtime()) : "", + file != null ? TimeZoneUtils.getFormattedTime(file.getCtime()) : "", + file != null ? TimeZoneUtils.getFormattedTime(file.getAtime()) : "", + file != null ? TimeZoneUtils.getFormattedTime(file.getCrtime()) : "", + content.getSize(), + file != null ? StringUtils.defaultString(file.getMd5Hash()) : "", + tag.getUserName()); + + fileRows.add(new BaseRowDTO( + cellValues, + FILE_TAG_TYPE_ID, + file.getId())); + } + + return new BaseSearchResultsDTO(FILE_TAG_TYPE_ID, FILE_TAG_DISPLAY_NAME, FILE_TAG_COLUMNS, fileRows, startItem, fileRows.size()); + } } diff --git a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/TagsSearchParams.java b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/TagsSearchParams.java index 80a97392a6..2f239047cd 100755 --- a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/TagsSearchParams.java +++ b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/TagsSearchParams.java @@ -19,6 +19,7 @@ package org.sleuthkit.autopsy.mainui.datamodel; import java.util.Objects; +import org.sleuthkit.datamodel.TagName; /** * Key for accessing data about tags from the DAO. @@ -31,25 +32,23 @@ public class TagsSearchParams { } private final TagType type; - private final String tagName; + private final TagName tagName; private final Long dataSourceId; - public TagsSearchParams(String tagName, TagType type, Long dataSourceId) { + public TagsSearchParams(TagName tagName, TagType type, Long dataSourceId) { this.tagName = tagName; this.type = type; this.dataSourceId = dataSourceId; } - public TagsSearchParams(String tagName, TagType type, Long dataSourceId, long startItem, Long maxResultsCount) { - this.tagName = tagName; - this.type = type; - this.dataSourceId = dataSourceId; - } - - public String getMimeType() { + public TagName getTagName() { return tagName; } + public TagType getTagType() { + return type; + } + public Long getDataSourceId() { return dataSourceId; } @@ -75,7 +74,7 @@ public class TagsSearchParams { return false; } final TagsSearchParams other = (TagsSearchParams) obj; - if (!(this.tagName.equals(other.tagName))) { + if (!Objects.equals(this.tagName, other.tagName)) { return false; } if (!Objects.equals(this.dataSourceId, other.dataSourceId)) { From ac236e6134d70ce863c1cf6578ed1d38e3017624 Mon Sep 17 00:00:00 2001 From: Greg DiCristofaro Date: Wed, 27 Oct 2021 18:54:54 -0400 Subject: [PATCH 16/38] updates to factory, node, and filter node handling --- .../autopsy/datamodel/DataArtifacts.java | 9 ++++++- .../DirectoryTreeFilterChildren.java | 24 ++++++------------- .../DirectoryTreeTopComponent.java | 5 ++-- .../mainui/datamodel/Bundle.properties-MERGED | 6 ----- .../mainui/nodes/DataArtifactTypeFactory.java | 4 ++++ 5 files changed, 21 insertions(+), 27 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/DataArtifacts.java b/Core/src/org/sleuthkit/autopsy/datamodel/DataArtifacts.java index 10f36e9077..b52d0dcd8f 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/DataArtifacts.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/DataArtifacts.java @@ -21,6 +21,7 @@ package org.sleuthkit.autopsy.datamodel; import java.util.concurrent.ExecutionException; import java.util.logging.Level; import org.openide.nodes.Children; +import org.openide.nodes.Node; import org.openide.util.NbBundle; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.mainui.nodes.DataArtifactTypeFactory; @@ -46,12 +47,13 @@ public class DataArtifacts implements AutopsyVisitableItem { /** * Parent node of all data artifacts. */ - static class RootNode extends Artifacts.BaseArtifactNode { + public static class RootNode extends Artifacts.BaseArtifactNode { private static Children getChildren(long filteringDSObjId) { return Children.create( new DataArtifactTypeFactory(filteringDSObjId > 0 ? filteringDSObjId : null), true); } + private final long filteringDSObjId; /** * Main constructor. @@ -66,6 +68,11 @@ public class DataArtifacts implements AutopsyVisitableItem { "org/sleuthkit/autopsy/images/extracted_content.png", DataArtifacts.getName(), DataArtifacts.getName()); + this.filteringDSObjId = filteringDSObjId; + } + + public Node clone() { + return new RootNode(this.filteringDSObjId); } } diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeFilterChildren.java b/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeFilterChildren.java index b8f6567822..3577002a1a 100644 --- a/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeFilterChildren.java +++ b/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeFilterChildren.java @@ -25,9 +25,11 @@ import org.openide.nodes.Children; import org.sleuthkit.autopsy.datamodel.DirectoryNode; import org.openide.nodes.FilterNode; import org.openide.nodes.Node; +import org.openide.util.Lookup; import org.sleuthkit.autopsy.datamodel.AbstractAbstractFileNode; import org.sleuthkit.autopsy.datamodel.AbstractContentNode; import org.sleuthkit.autopsy.datamodel.BlackboardArtifactNode; +import org.sleuthkit.autopsy.datamodel.DataArtifacts; import org.sleuthkit.autopsy.datamodel.DisplayableItemNode; import org.sleuthkit.autopsy.datamodel.DisplayableItemNodeVisitor; import org.sleuthkit.autopsy.datamodel.FileNode; @@ -38,6 +40,7 @@ import org.sleuthkit.autopsy.datamodel.LocalDirectoryNode; import org.sleuthkit.autopsy.datamodel.SlackFileNode; import org.sleuthkit.autopsy.datamodel.VirtualDirectoryNode; import org.sleuthkit.autopsy.datamodel.VolumeNode; +import org.sleuthkit.autopsy.mainui.nodes.TreeNode; import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE; import org.sleuthkit.datamodel.Content; @@ -73,18 +76,6 @@ class DirectoryTreeFilterChildren extends FilterNode.Children { return new DirectoryTreeFilterNode(arg0, createChildren); } - public static class FilterAcceptedNode extends FilterNode { - - public FilterAcceptedNode(Node original) { - super(original); - } - - @Override - public Node getOriginal() { - return super.getOriginal(); - } - } - /* * This method takes in a node as an argument and will create a new one if * it should be displayed in the tree. If it is to be displayed, it also @@ -95,11 +86,10 @@ class DirectoryTreeFilterChildren extends FilterNode.Children { */ @Override protected Node[] createNodes(Node origNode) { -// if (origNode instanceof DataArtifacts.RootNode || origNode instanceof UpdatableNode) { -// return new Node[]{new FilterAcceptedNode(origNode)}; -// } else -// - if (origNode == null || !(origNode instanceof DisplayableItemNode)) { + if (origNode instanceof DataArtifacts.RootNode) { + Node cloned = ((DataArtifacts.RootNode) origNode).clone(); + return new Node[]{cloned}; + } else if (origNode == null || !(origNode instanceof DisplayableItemNode)) { return new Node[]{}; } diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.java b/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.java index 5b5f4081b2..142d7384d5 100644 --- a/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.java +++ b/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.java @@ -90,6 +90,7 @@ import org.sleuthkit.autopsy.datamodel.Tags; import org.sleuthkit.autopsy.datamodel.ViewsNode; import org.sleuthkit.autopsy.datamodel.accounts.Accounts; import org.sleuthkit.autopsy.datamodel.accounts.BINRange; +import org.sleuthkit.autopsy.directorytree.DirectoryTreeFilterChildren.FilterAcceptedNode; import org.sleuthkit.autopsy.mainui.nodes.SelectionResponder; import org.sleuthkit.datamodel.Account; import org.sleuthkit.datamodel.BlackboardArtifact; @@ -611,7 +612,7 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat } }; -// root = new DirectoryTreeFilterNode(root, true); + root = new DirectoryTreeFilterNode(root, true); em.setRootContext(root); em.getRootContext().setName(currentCase.getName()); @@ -871,8 +872,6 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat Node originNode; if (treeNode instanceof DirectoryTreeFilterNode) { originNode = ((DirectoryTreeFilterNode) treeNode).getOriginal(); -// } else if (treeNode instanceof FilterAcceptedNode) { -// originNode = ((FilterAcceptedNode) treeNode).getOriginal(); } else { originNode = treeNode; } diff --git a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/Bundle.properties-MERGED index bbc9d5517b..8a6939075b 100644 --- a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/Bundle.properties-MERGED @@ -28,12 +28,6 @@ BlackboardArtifactDAO.columnKeys.score.name=Score BlackboardArtifactDAO.columnKeys.srcFile.description=Source Name BlackboardArtifactDAO.columnKeys.srcFile.displayName=Source Name BlackboardArtifactDAO.columnKeys.srcFile.name=Source Name -CountsRowResultDTO_columns_count_description=Name -CountsRowResultDTO_columns_count_displayName=Name -CountsRowResultDTO_columns_count_name=displayName -CountsRowResultDTO_columns_displayName_description=Name -CountsRowResultDTO_columns_displayName_displayName=Name -CountsRowResultDTO_columns_displayName_name=displayName FileExtDocumentFilter_html_displayName=HTML FileExtDocumentFilter_office_displayName=Office FileExtDocumentFilter_pdf_displayName=PDF diff --git a/Core/src/org/sleuthkit/autopsy/mainui/nodes/DataArtifactTypeFactory.java b/Core/src/org/sleuthkit/autopsy/mainui/nodes/DataArtifactTypeFactory.java index fccba030af..d83a6ea9a5 100644 --- a/Core/src/org/sleuthkit/autopsy/mainui/nodes/DataArtifactTypeFactory.java +++ b/Core/src/org/sleuthkit/autopsy/mainui/nodes/DataArtifactTypeFactory.java @@ -33,6 +33,10 @@ public class DataArtifactTypeFactory extends TreeChildFactory Date: Wed, 27 Oct 2021 19:09:15 -0400 Subject: [PATCH 17/38] cleanup --- .../mainui/nodes/TreeChildFactory.java | 14 ++++++ .../autopsy/mainui/nodes/UpdatableNode.java | 48 ------------------- 2 files changed, 14 insertions(+), 48 deletions(-) delete mode 100644 Core/src/org/sleuthkit/autopsy/mainui/nodes/UpdatableNode.java diff --git a/Core/src/org/sleuthkit/autopsy/mainui/nodes/TreeChildFactory.java b/Core/src/org/sleuthkit/autopsy/mainui/nodes/TreeChildFactory.java index 72b11d25c9..1aed15abb5 100644 --- a/Core/src/org/sleuthkit/autopsy/mainui/nodes/TreeChildFactory.java +++ b/Core/src/org/sleuthkit/autopsy/mainui/nodes/TreeChildFactory.java @@ -97,6 +97,9 @@ public abstract class TreeChildFactory extends ChildFactory.Detachable extends ChildFactory.Detachable rowData); + /** + * Fetches data from the database to populate this part of the tree. + * @return The data. + * @throws IllegalArgumentException + * @throws ExecutionException + */ protected abstract TreeDTO getChildResults() throws IllegalArgumentException, ExecutionException; } diff --git a/Core/src/org/sleuthkit/autopsy/mainui/nodes/UpdatableNode.java b/Core/src/org/sleuthkit/autopsy/mainui/nodes/UpdatableNode.java deleted file mode 100644 index 2e7cf3ace1..0000000000 --- a/Core/src/org/sleuthkit/autopsy/mainui/nodes/UpdatableNode.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2021 Basis Technology Corp. - * Contact: carrier sleuthkit org - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.sleuthkit.autopsy.mainui.nodes; - -import org.openide.nodes.AbstractNode; -import org.openide.nodes.Children; -import org.openide.util.Lookup; -import org.sleuthkit.autopsy.mainui.datamodel.RowDTO; -import org.sleuthkit.autopsy.mainui.datamodel.SearchResultsDTO; - -/** - * Node that can update itself based on updated search results and row. - */ -public abstract class UpdatableNode extends AbstractNode { - - protected UpdatableNode(Children children) { - super(children); - } - - protected UpdatableNode(Children children, Lookup lookup) { - super(children, lookup); - } - - /** - * Triggers an update in rendering (i.e. display name) based on data for - * this row. - * - * @param results The results in which this row appears. - * @param row The backing row data. - */ - public abstract void update(SearchResultsDTO results, RowDTO row); -} From 2680d652dbb15423f34d3f1c792a16b7d4f4d689 Mon Sep 17 00:00:00 2001 From: Greg DiCristofaro Date: Thu, 28 Oct 2021 10:11:14 -0400 Subject: [PATCH 18/38] rename of tree dto to tree results dto --- .../autopsy/mainui/datamodel/DataArtifactDAO.java | 6 +++--- .../datamodel/{TreeDTO.java => TreeResultsDTO.java} | 4 ++-- .../autopsy/mainui/nodes/DataArtifactTypeFactory.java | 8 ++++---- .../sleuthkit/autopsy/mainui/nodes/TreeChildFactory.java | 8 ++++---- Core/src/org/sleuthkit/autopsy/mainui/nodes/TreeNode.java | 2 +- 5 files changed, 14 insertions(+), 14 deletions(-) rename Core/src/org/sleuthkit/autopsy/mainui/datamodel/{TreeDTO.java => TreeResultsDTO.java} (97%) diff --git a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/DataArtifactDAO.java b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/DataArtifactDAO.java index 009448de4f..8de5066496 100644 --- a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/DataArtifactDAO.java +++ b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/DataArtifactDAO.java @@ -33,7 +33,7 @@ import java.util.logging.Level; import java.util.stream.Collectors; import org.python.google.common.collect.Sets; import org.sleuthkit.autopsy.coreutils.Logger; -import org.sleuthkit.autopsy.mainui.datamodel.TreeDTO.TreeItemDTO; +import org.sleuthkit.autopsy.mainui.datamodel.TreeResultsDTO.TreeItemDTO; import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_DOWNLOAD_SOURCE; import static org.sleuthkit.datamodel.BlackboardArtifact.Type.TSK_ASSOCIATED_OBJECT; import static org.sleuthkit.datamodel.BlackboardArtifact.Type.TSK_DATA_SOURCE_USAGE; @@ -152,7 +152,7 @@ public class DataArtifactDAO extends BlackboardArtifactDAO { * * @throws ExecutionException */ - public TreeDTO getDataArtifactCounts(Long dataSourceId) throws ExecutionException { + public TreeResultsDTO getDataArtifactCounts(Long dataSourceId) throws ExecutionException { try { // get artifact types and counts SleuthkitCase skCase = getCase(); @@ -191,7 +191,7 @@ public class DataArtifactDAO extends BlackboardArtifactDAO { .collect(Collectors.toList()); // return results - return new TreeDTO<>(treeItemRows); + return new TreeResultsDTO<>(treeItemRows); } catch (NoCurrentCaseException | TskCoreException ex) { throw new ExecutionException("An error occurred while fetching data artifact counts.", ex); diff --git a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/TreeDTO.java b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/TreeResultsDTO.java similarity index 97% rename from Core/src/org/sleuthkit/autopsy/mainui/datamodel/TreeDTO.java rename to Core/src/org/sleuthkit/autopsy/mainui/datamodel/TreeResultsDTO.java index 9bce388b95..61d23c5193 100644 --- a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/TreeDTO.java +++ b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/TreeResultsDTO.java @@ -24,7 +24,7 @@ import java.util.Objects; /** * A list of items to display in the tree. */ -public class TreeDTO { +public class TreeResultsDTO { private final List> items; @@ -33,7 +33,7 @@ public class TreeDTO { * * @param items The items to display. */ - public TreeDTO(List> items) { + public TreeResultsDTO(List> items) { this.items = items; } diff --git a/Core/src/org/sleuthkit/autopsy/mainui/nodes/DataArtifactTypeFactory.java b/Core/src/org/sleuthkit/autopsy/mainui/nodes/DataArtifactTypeFactory.java index d83a6ea9a5..16fcd35f99 100644 --- a/Core/src/org/sleuthkit/autopsy/mainui/nodes/DataArtifactTypeFactory.java +++ b/Core/src/org/sleuthkit/autopsy/mainui/nodes/DataArtifactTypeFactory.java @@ -23,7 +23,7 @@ import org.sleuthkit.autopsy.corecomponents.DataResultTopComponent; import org.sleuthkit.autopsy.datamodel.utils.IconsUtil; import org.sleuthkit.autopsy.mainui.datamodel.DataArtifactSearchParam; import org.sleuthkit.autopsy.mainui.datamodel.MainDAO; -import org.sleuthkit.autopsy.mainui.datamodel.TreeDTO; +import org.sleuthkit.autopsy.mainui.datamodel.TreeResultsDTO; import org.sleuthkit.datamodel.BlackboardArtifact; /** @@ -42,12 +42,12 @@ public class DataArtifactTypeFactory extends TreeChildFactory getChildResults() throws IllegalArgumentException, ExecutionException { + protected TreeResultsDTO getChildResults() throws IllegalArgumentException, ExecutionException { return MainDAO.getInstance().getDataArtifactsDAO().getDataArtifactCounts(dataSourceId); } @Override - protected TreeNode createNewNode(TreeDTO.TreeItemDTO rowData) { + protected TreeNode createNewNode(TreeResultsDTO.TreeItemDTO rowData) { return new DataArtifactTypeTreeNode(rowData); } @@ -61,7 +61,7 @@ public class DataArtifactTypeFactory extends TreeChildFactory itemData) { + public DataArtifactTypeTreeNode(TreeResultsDTO.TreeItemDTO itemData) { super(itemData.getTypeData().getArtifactType().getTypeName(), getIconPath(itemData.getTypeData().getArtifactType()), itemData); diff --git a/Core/src/org/sleuthkit/autopsy/mainui/nodes/TreeChildFactory.java b/Core/src/org/sleuthkit/autopsy/mainui/nodes/TreeChildFactory.java index 1aed15abb5..10e8fa39f0 100644 --- a/Core/src/org/sleuthkit/autopsy/mainui/nodes/TreeChildFactory.java +++ b/Core/src/org/sleuthkit/autopsy/mainui/nodes/TreeChildFactory.java @@ -29,8 +29,8 @@ import java.util.logging.Level; import org.openide.nodes.ChildFactory; import org.openide.nodes.Node; import org.sleuthkit.autopsy.coreutils.Logger; -import org.sleuthkit.autopsy.mainui.datamodel.TreeDTO; -import org.sleuthkit.autopsy.mainui.datamodel.TreeDTO.TreeItemDTO; +import org.sleuthkit.autopsy.mainui.datamodel.TreeResultsDTO; +import org.sleuthkit.autopsy.mainui.datamodel.TreeResultsDTO.TreeItemDTO; /** * Factory for populating tree with results. @@ -40,7 +40,7 @@ public abstract class TreeChildFactory extends ChildFactory.Detachable, TreeNode> typeNodeMap = new HashMap<>(); - private TreeDTO curResults = null; + private TreeResultsDTO curResults = null; @Override protected boolean createKeys(List> toPopulate) { @@ -123,5 +123,5 @@ public abstract class TreeChildFactory extends ChildFactory.Detachable getChildResults() throws IllegalArgumentException, ExecutionException; + protected abstract TreeResultsDTO getChildResults() throws IllegalArgumentException, ExecutionException; } diff --git a/Core/src/org/sleuthkit/autopsy/mainui/nodes/TreeNode.java b/Core/src/org/sleuthkit/autopsy/mainui/nodes/TreeNode.java index 0922c33e40..76ce51c7d3 100644 --- a/Core/src/org/sleuthkit/autopsy/mainui/nodes/TreeNode.java +++ b/Core/src/org/sleuthkit/autopsy/mainui/nodes/TreeNode.java @@ -25,7 +25,7 @@ import org.openide.nodes.Children; import org.openide.util.Lookup; import org.openide.util.lookup.Lookups; import org.sleuthkit.autopsy.coreutils.Logger; -import org.sleuthkit.autopsy.mainui.datamodel.TreeDTO.TreeItemDTO; +import org.sleuthkit.autopsy.mainui.datamodel.TreeResultsDTO.TreeItemDTO; /** * A node to be displayed in the tree that shows the count. From e9d6d6e9e5956fd7b67ca90fd20ab2434a5fff42 Mon Sep 17 00:00:00 2001 From: Greg DiCristofaro Date: Thu, 28 Oct 2021 10:59:39 -0400 Subject: [PATCH 19/38] moving code to BlackboardArtifactDAO --- .../datamodel/BlackboardArtifactDAO.java | 55 ++++++++++++++++++ .../mainui/datamodel/DataArtifactDAO.java | 58 +------------------ 2 files changed, 57 insertions(+), 56 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/BlackboardArtifactDAO.java b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/BlackboardArtifactDAO.java index af0bbc6945..c8067d401c 100644 --- a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/BlackboardArtifactDAO.java +++ b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/BlackboardArtifactDAO.java @@ -1,6 +1,7 @@ package org.sleuthkit.autopsy.mainui.datamodel; import com.google.common.collect.ImmutableSet; +import java.sql.SQLException; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -10,13 +11,21 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.concurrent.ExecutionException; +import java.util.logging.Level; import java.util.stream.Collectors; import java.util.stream.Stream; import org.openide.util.NbBundle; +import org.python.google.common.collect.Sets; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.BlackboardArtifact; +import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_DOWNLOAD_SOURCE; +import static org.sleuthkit.datamodel.BlackboardArtifact.Type.TSK_ASSOCIATED_OBJECT; +import static org.sleuthkit.datamodel.BlackboardArtifact.Type.TSK_DATA_SOURCE_USAGE; +import static org.sleuthkit.datamodel.BlackboardArtifact.Type.TSK_GEN_INFO; +import static org.sleuthkit.datamodel.BlackboardArtifact.Type.TSK_TL_EVENT; import org.sleuthkit.datamodel.BlackboardAttribute; import org.sleuthkit.datamodel.Content; import org.sleuthkit.datamodel.SleuthkitCase; @@ -112,6 +121,24 @@ abstract class BlackboardArtifactDAO { Bundle.BlackboardArtifactDAO_columnKeys_dataSource_description() ); + /** + * Types that should not be shown in the tree. + */ + @SuppressWarnings("deprecation") + private static final Set IGNORED_TYPES = Sets.newHashSet( + // these are shown in other parts of the UI (and different node types) + TSK_DATA_SOURCE_USAGE, + TSK_GEN_INFO, + new BlackboardArtifact.Type(TSK_DOWNLOAD_SOURCE), + TSK_TL_EVENT, + //This is not meant to be shown in the UI at all. It is more of a meta artifact. + TSK_ASSOCIATED_OBJECT + ); + + private static final String IGNORED_TYPES_SQL_SET = IGNORED_TYPES.stream() + .map(tp -> Integer.toString(tp.getTypeID())) + .collect(Collectors.joining(", ")); + TableData createTableData(BlackboardArtifact.Type artType, List arts) throws TskCoreException, NoCurrentCaseException { Map> artifactAttributes = new HashMap<>(); for (BlackboardArtifact art : arts) { @@ -328,4 +355,32 @@ abstract class BlackboardArtifactDAO { this.rows = rows; } } + + Map getCounts(BlackboardArtifact.Category category, Long dataSourceId) { + + // get artifact types and counts + SleuthkitCase skCase = getCase(); + String query = "artifact_type_id, COUNT(*) AS count " + + " FROM blackboard_artifacts " + + " WHERE artifact_type_id NOT IN (" + IGNORED_TYPES_SQL_SET + ") " + + " AND artifact_type_id IN " + + " (SELECT artifact_type_id FROM blackboard_artifact_types WHERE category_type = " + BlackboardArtifact.Category.DATA_ARTIFACT.getID() + ")" + + (dataSourceId == null ? "" : (" AND data_source_obj_id = " + dataSourceId + " ")) + + " GROUP BY artifact_type_id"; + Map typeCounts = new HashMap<>(); + skCase.getCaseDbAccessManager().select(query, (resultSet) -> { + try { + while (resultSet.next()) { + int artifactTypeId = resultSet.getInt("artifact_type_id"); + BlackboardArtifact.Type type = skCase.getBlackboard().getArtifactType(artifactTypeId); + long count = resultSet.getLong("count"); + typeCounts.put(type, count); + } + } catch (TskCoreException | SQLException ex) { + logger.log(Level.WARNING, "An error occurred while fetching artifact type counts.", ex); + } + }); + + return typeCounts; + } } diff --git a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/DataArtifactDAO.java b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/DataArtifactDAO.java index 8de5066496..245f595731 100644 --- a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/DataArtifactDAO.java +++ b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/DataArtifactDAO.java @@ -20,25 +20,13 @@ package org.sleuthkit.autopsy.mainui.datamodel; import com.google.common.cache.Cache; import com.google.common.cache.CacheBuilder; -import java.sql.SQLException; import java.beans.PropertyChangeEvent; import java.text.MessageFormat; import java.util.ArrayList; import java.util.Comparator; -import java.util.HashMap; import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.logging.Level; import java.util.stream.Collectors; -import org.python.google.common.collect.Sets; import org.sleuthkit.autopsy.coreutils.Logger; -import org.sleuthkit.autopsy.mainui.datamodel.TreeResultsDTO.TreeItemDTO; -import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_DOWNLOAD_SOURCE; -import static org.sleuthkit.datamodel.BlackboardArtifact.Type.TSK_ASSOCIATED_OBJECT; -import static org.sleuthkit.datamodel.BlackboardArtifact.Type.TSK_DATA_SOURCE_USAGE; -import static org.sleuthkit.datamodel.BlackboardArtifact.Type.TSK_GEN_INFO; -import static org.sleuthkit.datamodel.BlackboardArtifact.Type.TSK_TL_EVENT; import java.util.concurrent.ExecutionException; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; import org.sleuthkit.autopsy.ingest.ModuleDataEvent; @@ -47,7 +35,6 @@ import org.sleuthkit.datamodel.Blackboard; import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.Content; import org.sleuthkit.datamodel.DataArtifact; -import org.sleuthkit.datamodel.SleuthkitCase; import org.sleuthkit.datamodel.TskCoreException; /** @@ -55,24 +42,6 @@ import org.sleuthkit.datamodel.TskCoreException; */ public class DataArtifactDAO extends BlackboardArtifactDAO { - /** - * Types that should not be shown in the tree. - */ - @SuppressWarnings("deprecation") - private static final Set IGNORED_TYPES = Sets.newHashSet( - // these are shown in other parts of the UI (and different node types) - TSK_DATA_SOURCE_USAGE, - TSK_GEN_INFO, - new BlackboardArtifact.Type(TSK_DOWNLOAD_SOURCE), - TSK_TL_EVENT, - //This is not meant to be shown in the UI at all. It is more of a meta artifact. - TSK_ASSOCIATED_OBJECT - ); - - private static final String IGNORED_TYPES_SQL_SET = IGNORED_TYPES.stream() - .map(tp -> Integer.toString(tp.getTypeID())) - .collect(Collectors.joining(", ")); - private static Logger logger = Logger.getLogger(DataArtifactDAO.class.getName()); private static DataArtifactDAO instance = null; @@ -154,33 +123,10 @@ public class DataArtifactDAO extends BlackboardArtifactDAO { */ public TreeResultsDTO getDataArtifactCounts(Long dataSourceId) throws ExecutionException { try { - // get artifact types and counts - SleuthkitCase skCase = getCase(); - String query = "artifact_type_id, COUNT(*) AS count " - + " FROM blackboard_artifacts " - + " WHERE artifact_type_id NOT IN (" + IGNORED_TYPES_SQL_SET + ") " - + " AND artifact_type_id IN " - + " (SELECT artifact_type_id FROM blackboard_artifact_types WHERE category_type = " + BlackboardArtifact.Category.DATA_ARTIFACT.getID() + ")" - + (dataSourceId == null ? "" : (" AND data_source_obj_id = " + dataSourceId + " ")) - + " GROUP BY artifact_type_id"; - Map typeCounts = new HashMap<>(); - skCase.getCaseDbAccessManager().select(query, (resultSet) -> { - try { - while (resultSet.next()) { - int artifactTypeId = resultSet.getInt("artifact_type_id"); - BlackboardArtifact.Type type = skCase.getBlackboard().getArtifactType(artifactTypeId); - long count = resultSet.getLong("count"); - typeCounts.put(type, count); - } - } catch (TskCoreException | SQLException ex) { - logger.log(Level.WARNING, "An error occurred while fetching artifact type counts.", ex); - } - }); - // get row dto's sorted by display name - List> treeItemRows = typeCounts.entrySet().stream() + List> treeItemRows = typeCounts.entrySet().stream() .map(entry -> { - return new TreeItemDTO<>( + return new TreeResultsDTO.TreeItemDTO<>( BlackboardArtifact.Category.DATA_ARTIFACT.name(), new DataArtifactSearchParam(entry.getKey(), dataSourceId), entry.getKey().getTypeID(), From 1b3fdbf433ed09acd463071164d8a141941b0ca4 Mon Sep 17 00:00:00 2001 From: Greg DiCristofaro Date: Thu, 28 Oct 2021 12:29:50 -0400 Subject: [PATCH 20/38] listener updates and use id as key --- .../mainui/datamodel/DataArtifactDAO.java | 13 +- .../mainui/datamodel/TreeResultsDTO.java | 36 ---- .../mainui/nodes/DataArtifactTypeFactory.java | 40 ++++- .../mainui/nodes/TreeChildFactory.java | 164 ++++++++++++++---- .../autopsy/mainui/nodes/TreeNode.java | 37 ++-- 5 files changed, 199 insertions(+), 91 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/DataArtifactDAO.java b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/DataArtifactDAO.java index 8de5066496..4a7facd99f 100644 --- a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/DataArtifactDAO.java +++ b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/DataArtifactDAO.java @@ -20,6 +20,7 @@ package org.sleuthkit.autopsy.mainui.datamodel; import com.google.common.cache.Cache; import com.google.common.cache.CacheBuilder; +import com.google.common.collect.ImmutableSet; import java.sql.SQLException; import java.beans.PropertyChangeEvent; import java.text.MessageFormat; @@ -31,7 +32,6 @@ import java.util.Map; import java.util.Set; import java.util.logging.Level; import java.util.stream.Collectors; -import org.python.google.common.collect.Sets; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.mainui.datamodel.TreeResultsDTO.TreeItemDTO; import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_DOWNLOAD_SOURCE; @@ -59,7 +59,7 @@ public class DataArtifactDAO extends BlackboardArtifactDAO { * Types that should not be shown in the tree. */ @SuppressWarnings("deprecation") - private static final Set IGNORED_TYPES = Sets.newHashSet( + private static final Set IGNORED_TYPES = ImmutableSet.of( // these are shown in other parts of the UI (and different node types) TSK_DATA_SOURCE_USAGE, TSK_GEN_INFO, @@ -85,6 +85,15 @@ public class DataArtifactDAO extends BlackboardArtifactDAO { return instance; } + /** + * @return The set of types that are not shown in the tree. + */ + public static Set getIgnoredTreeTypes() { + return IGNORED_TYPES; + } + + + private final Cache, DataArtifactTableSearchResultsDTO> dataArtifactCache = CacheBuilder.newBuilder().maximumSize(1000).build(); private DataArtifactTableSearchResultsDTO fetchDataArtifactsForTable(SearchParams cacheKey) throws NoCurrentCaseException, TskCoreException { diff --git a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/TreeResultsDTO.java b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/TreeResultsDTO.java index 61d23c5193..d2ec1f3c81 100644 --- a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/TreeResultsDTO.java +++ b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/TreeResultsDTO.java @@ -19,7 +19,6 @@ package org.sleuthkit.autopsy.mainui.datamodel; import java.util.List; -import java.util.Objects; /** * A list of items to display in the tree. @@ -106,40 +105,5 @@ public class TreeResultsDTO { public long getId() { return id; } - - @Override - public int hashCode() { - int hash = 7; - hash = 29 * hash + Objects.hashCode(this.typeId); - hash = 29 * hash + Objects.hashCode(this.typeData); - hash = 29 * hash + (int) (this.id ^ (this.id >>> 32)); - return hash; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null) { - return false; - } - if (getClass() != obj.getClass()) { - return false; - } - final TreeItemDTO other = (TreeItemDTO) obj; - if (this.id != other.id) { - return false; - } - if (!Objects.equals(this.typeId, other.typeId)) { - return false; - } - if (!Objects.equals(this.typeData, other.typeData)) { - return false; - } - return true; - } - - } } diff --git a/Core/src/org/sleuthkit/autopsy/mainui/nodes/DataArtifactTypeFactory.java b/Core/src/org/sleuthkit/autopsy/mainui/nodes/DataArtifactTypeFactory.java index 16fcd35f99..50ef7cb987 100644 --- a/Core/src/org/sleuthkit/autopsy/mainui/nodes/DataArtifactTypeFactory.java +++ b/Core/src/org/sleuthkit/autopsy/mainui/nodes/DataArtifactTypeFactory.java @@ -18,13 +18,20 @@ */ package org.sleuthkit.autopsy.mainui.nodes; +import java.beans.PropertyChangeEvent; import java.util.concurrent.ExecutionException; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; import org.sleuthkit.autopsy.corecomponents.DataResultTopComponent; import org.sleuthkit.autopsy.datamodel.utils.IconsUtil; +import org.sleuthkit.autopsy.ingest.IngestManager; +import org.sleuthkit.autopsy.ingest.ModuleDataEvent; +import org.sleuthkit.autopsy.mainui.datamodel.DataArtifactDAO; import org.sleuthkit.autopsy.mainui.datamodel.DataArtifactSearchParam; import org.sleuthkit.autopsy.mainui.datamodel.MainDAO; import org.sleuthkit.autopsy.mainui.datamodel.TreeResultsDTO; import org.sleuthkit.datamodel.BlackboardArtifact; +import org.sleuthkit.datamodel.BlackboardArtifact.Category; /** * Factory for displaying data artifact types in the tree. @@ -35,6 +42,7 @@ public class DataArtifactTypeFactory extends TreeChildFactory createNewNode(TreeResultsDTO.TreeItemDTO rowData) { + protected TreeNode createNewNode(TreeResultsDTO.TreeItemDTO rowData) { return new DataArtifactTypeTreeNode(rowData); } + @Override + public boolean isRefreshRequired(PropertyChangeEvent evt) { + String eventType = evt.getPropertyName(); + if (eventType.equals(IngestManager.IngestModuleEvent.DATA_ADDED.toString())) { + /** + * This is a stop gap measure until a different way of handling the + * closing of cases is worked out. Currently, remote events may be + * received for a case that is already closed. + */ + try { + Case.getCurrentCaseThrows(); + /** + * Due to some unresolved issues with how cases are closed, it + * is possible for the event to have a null oldValue if the + * event is a remote event. + */ + final ModuleDataEvent event = (ModuleDataEvent) evt.getOldValue(); + if (null != event && Category.DATA_ARTIFACT.equals(event.getBlackboardArtifactType().getCategory()) + && !(DataArtifactDAO.getIgnoredTreeTypes().contains(event.getBlackboardArtifactType()))) { + return true; + } + } catch (NoCurrentCaseException notUsed) { + /** + * Case is closed, do nothing. + */ + } + } + return false; + } + /** * Display name and count of a data artifact type in the tree. */ diff --git a/Core/src/org/sleuthkit/autopsy/mainui/nodes/TreeChildFactory.java b/Core/src/org/sleuthkit/autopsy/mainui/nodes/TreeChildFactory.java index 10e8fa39f0..1f490b233a 100644 --- a/Core/src/org/sleuthkit/autopsy/mainui/nodes/TreeChildFactory.java +++ b/Core/src/org/sleuthkit/autopsy/mainui/nodes/TreeChildFactory.java @@ -18,83 +18,124 @@ */ package org.sleuthkit.autopsy.mainui.nodes; +import com.google.common.collect.MapMaker; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; import java.util.ArrayList; +import java.util.EnumSet; import java.util.HashMap; -import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ExecutionException; import java.util.logging.Level; +import java.util.stream.Collectors; import org.openide.nodes.ChildFactory; import org.openide.nodes.Node; +import org.openide.util.WeakListeners; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.guiutils.RefreshThrottler; +import org.sleuthkit.autopsy.guiutils.RefreshThrottler.Refresher; +import org.sleuthkit.autopsy.ingest.IngestManager; import org.sleuthkit.autopsy.mainui.datamodel.TreeResultsDTO; import org.sleuthkit.autopsy.mainui.datamodel.TreeResultsDTO.TreeItemDTO; /** * Factory for populating tree with results. */ -public abstract class TreeChildFactory extends ChildFactory.Detachable> { +public abstract class TreeChildFactory extends ChildFactory.Detachable implements Refresher { private static final Logger logger = Logger.getLogger(TreeChildFactory.class.getName()); - private final Map, TreeNode> typeNodeMap = new HashMap<>(); + private static final Set INGEST_JOB_EVENTS_OF_INTEREST + = EnumSet.of(IngestManager.IngestJobEvent.COMPLETED, IngestManager.IngestJobEvent.CANCELLED); + + private final RefreshThrottler refreshThrottler = new RefreshThrottler(this); + + private final PropertyChangeListener pcl = (PropertyChangeEvent evt) -> { + String eventType = evt.getPropertyName(); + if (eventType.equals(Case.Events.CURRENT_CASE.toString())) { + // case was closed. Remove listeners so that we don't get called with a stale case handle + if (evt.getNewValue() == null) { + removeNotify(); + } + } else if (eventType.equals(IngestManager.IngestJobEvent.COMPLETED.toString()) + || eventType.equals(IngestManager.IngestJobEvent.CANCELLED.toString())) { + /** + * This is a stop gap measure until a different way of handling the + * closing of cases is worked out. Currently, remote events may be + * received for a case that is already closed. + */ + try { + Case.getCurrentCaseThrows(); + refresh(false); + } catch (NoCurrentCaseException notUsed) { + /** + * Case is closed, do nothing. + */ + } + } + }; + + private final PropertyChangeListener weakPcl = WeakListeners.propertyChange(pcl, null); + + private final Map> typeNodeMap = new MapMaker().weakValues().makeMap(); private TreeResultsDTO curResults = null; + private Map> idMapping = new HashMap<>(); @Override - protected boolean createKeys(List> toPopulate) { + protected boolean createKeys(List toPopulate) { if (curResults == null) { try { - curResults = getChildResults(); + updateData(); } catch (IllegalArgumentException | ExecutionException ex) { logger.log(Level.WARNING, "An error occurred while fetching keys", ex); return false; } } - Set> resultRows = new HashSet<>(curResults.getItems()); - - // remove no longer present - Set> toBeRemoved = new HashSet<>(typeNodeMap.keySet()); - toBeRemoved.removeAll(resultRows); - for (TreeItemDTO presentId : toBeRemoved) { - typeNodeMap.remove(presentId); - } - - List> rowsToReturn = new ArrayList<>(); + // update existing cached nodes + List curResultIds = new ArrayList<>(); for (TreeItemDTO dto : curResults.getItems()) { - // update cached that remain TreeNode currentlyCached = typeNodeMap.get(dto.getId()); if (currentlyCached != null) { currentlyCached.update(dto); - } else { - // add new items - typeNodeMap.put(dto, createNewNode(dto)); } - - rowsToReturn.add(dto); } - toPopulate.addAll(rowsToReturn); + toPopulate.addAll(curResultIds); return true; } @Override - protected void removeNotify() { - curResults = null; - typeNodeMap.clear(); - super.removeNotify(); + protected Node createNodeForKey(Long treeItemId) { + return typeNodeMap.computeIfAbsent(treeItemId, (id) -> { + TreeItemDTO itemData = idMapping.get(id); + // create new node if data for node exists. otherwise, return null. + return itemData == null + ? null + : createNewNode(itemData); + }); + } + + /** + * Updates local data by fetching data from the DAO's. + * + * @throws IllegalArgumentException + * @throws ExecutionException + */ + protected void updateData() throws IllegalArgumentException, ExecutionException { + this.curResults = getChildResults(); + this.idMapping = curResults.getItems().stream() + .collect(Collectors.toMap(item -> item.getId(), item -> item, (item1, item2) -> item1)); + } @Override - protected void addNotify() { - super.addNotify(); - } - - @Override - protected Node createNodeForKey(TreeItemDTO key) { - return typeNodeMap.get(key); + public void refresh() { + update(); } /** @@ -102,7 +143,7 @@ public abstract class TreeChildFactory extends ChildFactory.Detachable extends ChildFactory.Detachable rowData); + protected abstract TreeNode createNewNode(TreeItemDTO rowData); /** * Fetches data from the database to populate this part of the tree. + * * @return The data. + * * @throws IllegalArgumentException - * @throws ExecutionException + * @throws ExecutionException */ protected abstract TreeResultsDTO getChildResults() throws IllegalArgumentException, ExecutionException; } diff --git a/Core/src/org/sleuthkit/autopsy/mainui/nodes/TreeNode.java b/Core/src/org/sleuthkit/autopsy/mainui/nodes/TreeNode.java index 76ce51c7d3..848501d929 100644 --- a/Core/src/org/sleuthkit/autopsy/mainui/nodes/TreeNode.java +++ b/Core/src/org/sleuthkit/autopsy/mainui/nodes/TreeNode.java @@ -91,14 +91,20 @@ public abstract class TreeNode extends AbstractNode implements SelectionRespo * Sets the display name of the node to include the display name and count * of the item. * - * @param itemData The item data. + * @param prevData The previous item data (may be null). + * @param curData The item data (must be non-null). */ - protected void setDisplayName(TreeItemDTO itemData) { - String displayName = itemData.getCount() == null - ? itemData.getDisplayName() - : MessageFormat.format("{0} ({1})", itemData.getDisplayName(), itemData.getCount()); + protected void updateDisplayName(TreeItemDTO prevData, TreeItemDTO curData) { + // update display name only if there is a change. + if (prevData == null + || !prevData.getDisplayName().equals(curData.getDisplayName()) + || prevData.getCount() != curData.getCount()) { + String displayName = curData.getCount() == null + ? curData.getDisplayName() + : MessageFormat.format("{0} ({1})", curData.getDisplayName(), curData.getCount()); - this.setDisplayName(displayName); + this.setDisplayName(displayName); + } } /** @@ -109,19 +115,18 @@ public abstract class TreeNode extends AbstractNode implements SelectionRespo * @thitems IllegalArgumentException */ public void update(TreeItemDTO updatedData) { - if (this.itemData != null && - (updatedData == null || - this.itemData.getId() != updatedData.getId() || - !this.itemData.getTypeData().equals(updatedData.getTypeData()))) { + if (updatedData == null) { + logger.log(Level.WARNING, "Expected non-null updatedData"); + } else if (this.itemData != null && this.itemData.getId() != updatedData.getId()) { logger.log(Level.WARNING, MessageFormat.format( - "Expected update data to have same id and type data but received [id: {0}, type: {1}] replacing [id: {2}, type: {3}]", - (updatedData == null ? "" : updatedData.getId()), - (updatedData == null ? "" : updatedData.getTypeData()), - this.itemData.getId(), - this.itemData.getTypeData())); + "Expected update data to have same id but received [id: {0}] replacing [id: {1}]", + updatedData.getId(), + this.itemData.getId())); return; } + + TreeItemDTO prevData = this.itemData; this.itemData = updatedData; - this.setDisplayName(updatedData); + updateDisplayName(prevData, updatedData); } } From 61719cdebe8fb258fc2fe7f40091bead01d54e62 Mon Sep 17 00:00:00 2001 From: Greg DiCristofaro Date: Thu, 28 Oct 2021 12:41:25 -0400 Subject: [PATCH 21/38] bug fix --- .../src/org/sleuthkit/autopsy/mainui/nodes/TreeChildFactory.java | 1 + 1 file changed, 1 insertion(+) diff --git a/Core/src/org/sleuthkit/autopsy/mainui/nodes/TreeChildFactory.java b/Core/src/org/sleuthkit/autopsy/mainui/nodes/TreeChildFactory.java index 1f490b233a..c2cc9ea36a 100644 --- a/Core/src/org/sleuthkit/autopsy/mainui/nodes/TreeChildFactory.java +++ b/Core/src/org/sleuthkit/autopsy/mainui/nodes/TreeChildFactory.java @@ -103,6 +103,7 @@ public abstract class TreeChildFactory extends ChildFactory.Detachable if (currentlyCached != null) { currentlyCached.update(dto); } + curResultIds.add(dto.getId()); } toPopulate.addAll(curResultIds); From 24cf8315b89435084a92dd79f97cc8debf0ec907 Mon Sep 17 00:00:00 2001 From: apriestman Date: Thu, 28 Oct 2021 15:08:41 -0400 Subject: [PATCH 22/38] Optimize artifact methods --- .../mainui/datamodel/AnalysisResultDAO.java | 31 +++---- .../datamodel/AnalysisResultSearchParam.java | 51 +---------- .../datamodel/BlackboardArtifactDAO.java | 35 ++++++++ .../BlackboardArtifactSearchParam.java | 72 ++++++++++++++++ .../mainui/datamodel/DataArtifactDAO.java | 32 +++---- .../datamodel/DataArtifactSearchParam.java | 50 +---------- .../mainui/datamodel/TableSearchTest.java | 86 +++++++++++++++++++ 7 files changed, 230 insertions(+), 127 deletions(-) create mode 100644 Core/src/org/sleuthkit/autopsy/mainui/datamodel/BlackboardArtifactSearchParam.java diff --git a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/AnalysisResultDAO.java b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/AnalysisResultDAO.java index c499abbfd3..c044fa8587 100644 --- a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/AnalysisResultDAO.java +++ b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/AnalysisResultDAO.java @@ -107,29 +107,26 @@ public class AnalysisResultDAO extends BlackboardArtifactDAO { } // TODO We can probably combine all the caches at some point - private final Cache, AnalysisResultTableSearchResultsDTO> analysisResultCache = CacheBuilder.newBuilder().maximumSize(1000).build(); + private final Cache, AnalysisResultTableSearchResultsDTO> analysisResultCache = CacheBuilder.newBuilder().maximumSize(1000).build(); private final Cache, AnalysisResultTableSearchResultsDTO> hashHitCache = CacheBuilder.newBuilder().maximumSize(1000).build(); private final Cache, AnalysisResultTableSearchResultsDTO> keywordHitCache = CacheBuilder.newBuilder().maximumSize(1000).build(); - private AnalysisResultTableSearchResultsDTO fetchAnalysisResultsForTable(SearchParams cacheKey) throws NoCurrentCaseException, TskCoreException { + private AnalysisResultTableSearchResultsDTO fetchAnalysisResultsForTable(SearchParams cacheKey) throws NoCurrentCaseException, TskCoreException { SleuthkitCase skCase = getCase(); Blackboard blackboard = skCase.getBlackboard(); - - Long dataSourceId = cacheKey.getParamData().getDataSourceId(); BlackboardArtifact.Type artType = cacheKey.getParamData().getArtifactType(); - - // get analysis results + List arts = new ArrayList<>(); - if (dataSourceId != null) { - arts.addAll(blackboard.getAnalysisResultsByType(artType.getTypeID(), dataSourceId)); - } else { - arts.addAll(blackboard.getAnalysisResultsByType(artType.getTypeID())); - } - - List pagedArtifacts = getPaged(arts, cacheKey); - TableData tableData = createTableData(artType, pagedArtifacts); - return new AnalysisResultTableSearchResultsDTO(artType, tableData.columnKeys, tableData.rows, cacheKey.getStartItem(), arts.size()); + String pagedWhereClause = getWhereClause(cacheKey); + arts.addAll(blackboard.getAnalysisResultsWhere(pagedWhereClause)); + blackboard.loadBlackboardAttributes(arts); + + // Get total number of results + long totalResultsCount = getTotalResultsCount(cacheKey, arts.size()); + + TableData tableData = createTableData(artType, arts); + return new AnalysisResultTableSearchResultsDTO(artType, tableData.columnKeys, tableData.rows, cacheKey.getStartItem(), totalResultsCount); } private AnalysisResultTableSearchResultsDTO fetchSetNameHitsForTable(SearchParams cacheKey) throws NoCurrentCaseException, TskCoreException { @@ -139,7 +136,7 @@ public class AnalysisResultDAO extends BlackboardArtifactDAO { Long dataSourceId = cacheKey.getParamData().getDataSourceId(); BlackboardArtifact.Type artType = cacheKey.getParamData().getArtifactType(); - + // Get all hash set hits List allHashHits; if (dataSourceId != null) { @@ -237,7 +234,7 @@ public class AnalysisResultDAO extends BlackboardArtifactDAO { + "Received artifact type: {0}; data source id: {1}", artType, artifactKey.getDataSourceId() == null ? "" : artifactKey.getDataSourceId())); } - SearchParams searchParams = new SearchParams<>(artifactKey, startItem, maxCount); + SearchParams searchParams = new SearchParams<>(artifactKey, startItem, maxCount); if (hardRefresh) { analysisResultCache.invalidate(searchParams); } diff --git a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/AnalysisResultSearchParam.java b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/AnalysisResultSearchParam.java index 677a61e225..4f2805a650 100644 --- a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/AnalysisResultSearchParam.java +++ b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/AnalysisResultSearchParam.java @@ -18,57 +18,14 @@ */ package org.sleuthkit.autopsy.mainui.datamodel; -import java.util.Objects; import org.sleuthkit.datamodel.BlackboardArtifact; /** * Key for analysis result in order to retrieve data from DAO. */ -public class AnalysisResultSearchParam { - private final BlackboardArtifact.Type artifactType; - private final Long dataSourceId; - +public class AnalysisResultSearchParam extends BlackboardArtifactSearchParam { + public AnalysisResultSearchParam(BlackboardArtifact.Type artifactType, Long dataSourceId) { - this.artifactType = artifactType; - this.dataSourceId = dataSourceId; - } - - public BlackboardArtifact.Type getArtifactType() { - return artifactType; - } - - public Long getDataSourceId() { - return dataSourceId; - } - - @Override - public int hashCode() { - int hash = 7; - hash = 79 * hash + Objects.hashCode(this.artifactType); - hash = 79 * hash + Objects.hashCode(this.dataSourceId); - return hash; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null) { - return false; - } - if (getClass() != obj.getClass()) { - return false; - } - final AnalysisResultSearchParam other = (AnalysisResultSearchParam) obj; - if (!Objects.equals(this.artifactType, other.artifactType)) { - return false; - } - if (!Objects.equals(this.dataSourceId, other.dataSourceId)) { - return false; - } - return true; - } - - + super(artifactType, dataSourceId); + } } diff --git a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/BlackboardArtifactDAO.java b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/BlackboardArtifactDAO.java index af0bbc6945..7118ac366e 100644 --- a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/BlackboardArtifactDAO.java +++ b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/BlackboardArtifactDAO.java @@ -16,6 +16,7 @@ import org.openide.util.NbBundle; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; import org.sleuthkit.datamodel.AbstractFile; +import org.sleuthkit.datamodel.Blackboard; import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.BlackboardAttribute; import org.sleuthkit.datamodel.Content; @@ -218,6 +219,40 @@ abstract class BlackboardArtifactDAO { return attrTypes.stream() .anyMatch(tp -> BlackboardAttribute.TSK_BLACKBOARD_ATTRIBUTE_VALUE_TYPE.DATETIME.equals(tp.getValueType())); } + + String getWhereClause(SearchParams cacheKey) { + Long dataSourceId = cacheKey.getParamData().getDataSourceId(); + BlackboardArtifact.Type artType = cacheKey.getParamData().getArtifactType(); + + String originalWhereClause = " artifacts.artifact_type_id = " + artType.getTypeID() + " "; + if (dataSourceId != null) { + originalWhereClause += " AND artifacts.data_source_obj_id = " + dataSourceId + " "; + } + + String pagedWhereClause = originalWhereClause + + " ORDER BY artifacts.obj_id ASC" + + (cacheKey.getMaxResultsCount() != null && cacheKey.getMaxResultsCount() > 0 ? " LIMIT " + cacheKey.getMaxResultsCount() : "") + + (cacheKey.getStartItem() > 0 ? " OFFSET " + cacheKey.getStartItem() : ""); + return pagedWhereClause; + } + + long getTotalResultsCount(SearchParams cacheKey, long currentPageSize) throws TskCoreException, NoCurrentCaseException { + Blackboard blackboard = getCase().getBlackboard(); + Long dataSourceId = cacheKey.getParamData().getDataSourceId(); + BlackboardArtifact.Type artType = cacheKey.getParamData().getArtifactType(); + + if ( (cacheKey.getStartItem() == 0) // offset is zero AND + && ( (cacheKey.getMaxResultsCount() != null && currentPageSize < cacheKey.getMaxResultsCount()) // number of results is less than max + || (cacheKey.getMaxResultsCount() == null)) ) { // OR max number of results was not specified + return currentPageSize; + } else { + if (dataSourceId != null) { + return blackboard.getArtifactsCount(artType.getTypeID(), dataSourceId); + } else { + return blackboard.getArtifactsCount(artType.getTypeID()); + } + } + } String getDataSourceName(Content srcContent) throws TskCoreException { Content dataSource = srcContent.getDataSource(); diff --git a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/BlackboardArtifactSearchParam.java b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/BlackboardArtifactSearchParam.java new file mode 100644 index 0000000000..2bfd9a3929 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/BlackboardArtifactSearchParam.java @@ -0,0 +1,72 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2021 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.mainui.datamodel; + +import java.util.Objects; +import org.sleuthkit.datamodel.BlackboardArtifact; + +/** + * Key for data artifact in order to retrieve data from DAO. + */ +public class BlackboardArtifactSearchParam { + private final BlackboardArtifact.Type artifactType; + private final Long dataSourceId; + + public BlackboardArtifactSearchParam(BlackboardArtifact.Type artifactType, Long dataSourceId) { + this.artifactType = artifactType; + this.dataSourceId = dataSourceId; + } + + public BlackboardArtifact.Type getArtifactType() { + return artifactType; + } + + public Long getDataSourceId() { + return dataSourceId; + } + + @Override + public int hashCode() { + int hash = 7; + hash = 67 * hash + Objects.hashCode(this.artifactType); + hash = 67 * hash + Objects.hashCode(this.dataSourceId); + return hash; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final BlackboardArtifactSearchParam other = (BlackboardArtifactSearchParam) obj; + if (!Objects.equals(this.artifactType, other.artifactType)) { + return false; + } + if (!Objects.equals(this.dataSourceId, other.dataSourceId)) { + return false; + } + return true; + } +} diff --git a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/DataArtifactDAO.java b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/DataArtifactDAO.java index 7ff31b270d..f5b140e79d 100644 --- a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/DataArtifactDAO.java +++ b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/DataArtifactDAO.java @@ -32,6 +32,7 @@ import org.sleuthkit.datamodel.Blackboard; import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.Content; import org.sleuthkit.datamodel.DataArtifact; +import org.sleuthkit.datamodel.SleuthkitCase; import org.sleuthkit.datamodel.TskCoreException; /** @@ -49,25 +50,24 @@ public class DataArtifactDAO extends BlackboardArtifactDAO { return instance; } - private final Cache, DataArtifactTableSearchResultsDTO> dataArtifactCache = CacheBuilder.newBuilder().maximumSize(1000).build(); + private final Cache, DataArtifactTableSearchResultsDTO> dataArtifactCache = CacheBuilder.newBuilder().maximumSize(1000).build(); - private DataArtifactTableSearchResultsDTO fetchDataArtifactsForTable(SearchParams cacheKey) throws NoCurrentCaseException, TskCoreException { - Blackboard blackboard = getCase().getBlackboard(); - - Long dataSourceId = cacheKey.getParamData().getDataSourceId(); + private DataArtifactTableSearchResultsDTO fetchDataArtifactsForTable(SearchParams cacheKey) throws NoCurrentCaseException, TskCoreException { + + SleuthkitCase skCase = getCase(); + Blackboard blackboard = skCase.getBlackboard(); BlackboardArtifact.Type artType = cacheKey.getParamData().getArtifactType(); - // get analysis results + String pagedWhereClause = getWhereClause(cacheKey); + List arts = new ArrayList<>(); - if (dataSourceId != null) { - arts.addAll(blackboard.getDataArtifacts(artType.getTypeID(), dataSourceId)); - } else { - arts.addAll(blackboard.getDataArtifacts(artType.getTypeID())); - } - - List pagedArtifacts = getPaged(arts, cacheKey); - TableData tableData = createTableData(artType, pagedArtifacts); - return new DataArtifactTableSearchResultsDTO(artType, tableData.columnKeys, tableData.rows, cacheKey.getStartItem(), arts.size()); + arts.addAll(blackboard.getDataArtifactsWhere(pagedWhereClause)); + blackboard.loadBlackboardAttributes(arts); + + long totalResultsCount = getTotalResultsCount(cacheKey, arts.size()); + + TableData tableData = createTableData(artType, arts); + return new DataArtifactTableSearchResultsDTO(artType, tableData.columnKeys, tableData.rows, cacheKey.getStartItem(), totalResultsCount); } @Override @@ -88,7 +88,7 @@ public class DataArtifactDAO extends BlackboardArtifactDAO { + "Received artifact type: {0}; data source id: {1}", artType, artifactKey.getDataSourceId() == null ? "" : artifactKey.getDataSourceId())); } - SearchParams searchParams = new SearchParams<>(artifactKey, startItem, maxCount); + SearchParams searchParams = new SearchParams<>(artifactKey, startItem, maxCount); if (hardRefresh) { this.dataArtifactCache.invalidate(searchParams); } diff --git a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/DataArtifactSearchParam.java b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/DataArtifactSearchParam.java index 89200ea2da..0956cf4b5c 100644 --- a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/DataArtifactSearchParam.java +++ b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/DataArtifactSearchParam.java @@ -18,58 +18,14 @@ */ package org.sleuthkit.autopsy.mainui.datamodel; -import java.util.Objects; import org.sleuthkit.datamodel.BlackboardArtifact; /** * Key for data artifact in order to retrieve data from DAO. */ -public class DataArtifactSearchParam { - private final BlackboardArtifact.Type artifactType; - private final Long dataSourceId; +public class DataArtifactSearchParam extends BlackboardArtifactSearchParam { public DataArtifactSearchParam(BlackboardArtifact.Type artifactType, Long dataSourceId) { - this.artifactType = artifactType; - this.dataSourceId = dataSourceId; - } - - public BlackboardArtifact.Type getArtifactType() { - return artifactType; - } - - public Long getDataSourceId() { - return dataSourceId; - } - - @Override - public int hashCode() { - int hash = 7; - hash = 67 * hash + Objects.hashCode(this.artifactType); - hash = 67 * hash + Objects.hashCode(this.dataSourceId); - return hash; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null) { - return false; - } - if (getClass() != obj.getClass()) { - return false; - } - final DataArtifactSearchParam other = (DataArtifactSearchParam) obj; - if (!Objects.equals(this.artifactType, other.artifactType)) { - return false; - } - if (!Objects.equals(this.dataSourceId, other.dataSourceId)) { - return false; - } - return true; - } - - - + super (artifactType, dataSourceId); + } } diff --git a/Core/test/qa-functional/src/org/sleuthkit/autopsy/mainui/datamodel/TableSearchTest.java b/Core/test/qa-functional/src/org/sleuthkit/autopsy/mainui/datamodel/TableSearchTest.java index 64097081a6..86ae8909f2 100644 --- a/Core/test/qa-functional/src/org/sleuthkit/autopsy/mainui/datamodel/TableSearchTest.java +++ b/Core/test/qa-functional/src/org/sleuthkit/autopsy/mainui/datamodel/TableSearchTest.java @@ -72,6 +72,8 @@ public class TableSearchTest extends NbTestCase { private static final String ARTIFACT_CONFIGURATION = "Test configuration"; private static final String ARTIFACT_JUSTIFICATION = "Test justification"; private static final Score ARTIFACT_SCORE = Score.SCORE_LIKELY_NOTABLE; + private static final long ARTIFACT_COUNT_WEB_BOOKMARK = 125; + private static final long ARTIFACT_COUNT_YARA = 150; // Values for the hash set hit tests private static final String HASH_SET_1 = "Hash Set 1"; @@ -226,6 +228,13 @@ public class TableSearchTest extends NbTestCase { customDataArtifactSourceFile = fileA3; customDataArtifactLinkedFile = fileA2; + // Add a lot of web bookmark data artifacts + for (int i = 0;i < ARTIFACT_COUNT_WEB_BOOKMARK;i++) { + attrs.clear(); + attrs.add(new BlackboardAttribute(BlackboardAttribute.Type.TSK_COMMENT, MODULE_NAME, Integer.toString(i))); + fileA1.newDataArtifact(BlackboardArtifact.Type.TSK_WEB_BOOKMARK, attrs); + } + // Add analysis results // Data source 1: Encryption detected (2), custom type // Data source 2: Encryption detected @@ -250,6 +259,13 @@ public class TableSearchTest extends NbTestCase { customAnalysisResult = customDataArtifact.newAnalysisResult(customAnalysisResultType, ARTIFACT_SCORE, ARTIFACT_CONCLUSION, ARTIFACT_CONFIGURATION, ARTIFACT_JUSTIFICATION, attrs).getAnalysisResult(); customAnalysisResultSource = customDataArtifact; + // Add a lot of YARA hit analysis results + for (int i = 0;i < ARTIFACT_COUNT_YARA;i++) { + attrs.clear(); + attrs.add(new BlackboardAttribute(BlackboardAttribute.Type.TSK_COMMENT, MODULE_NAME, Integer.toString(i))); + fileA1.newAnalysisResult(BlackboardArtifact.Type.TSK_YARA_HIT, Score.SCORE_NOTABLE, "conclusion", "configuration", "justification", attrs); + } + // Add hash hits attrs.clear(); attrs.add(new BlackboardAttribute(BlackboardAttribute.Type.TSK_SET_NAME, MODULE_NAME, HASH_SET_1)); @@ -373,6 +389,41 @@ public class TableSearchTest extends NbTestCase { assertTrue(dataArtifactRowDTO.getCellValues().contains(ARTIFACT_INT)); assertTrue(dataArtifactRowDTO.getCellValues().contains(ARTIFACT_DOUBLE)); + // Test paging + Long pageSize = new Long(100); + assertTrue(ARTIFACT_COUNT_WEB_BOOKMARK > pageSize); + + // Get the first page + param = new DataArtifactSearchParam(BlackboardArtifact.Type.TSK_WEB_BOOKMARK, null); + results = dataArtifactDAO.getDataArtifactsForTable(param, 0, pageSize, false); + assertEquals(ARTIFACT_COUNT_WEB_BOOKMARK, results.getTotalResultsCount()); + assertEquals(pageSize.longValue(), results.getItems().size()); + + // Save all artifact IDs from the first page + Set firstPageObjIds = new HashSet<>(); + for (RowDTO row : results.getItems()) { + assertTrue(row instanceof DataArtifactRowDTO); + DataArtifactRowDTO dataRow = (DataArtifactRowDTO) row; + assertTrue(dataRow.getDataArtifact() != null); + firstPageObjIds.add(dataRow.getDataArtifact().getId()); + } + assertEquals(pageSize.longValue(), firstPageObjIds.size()); + + // Get the second page + param = new DataArtifactSearchParam(BlackboardArtifact.Type.TSK_WEB_BOOKMARK, null); + results = dataArtifactDAO.getDataArtifactsForTable(param, pageSize, pageSize, false); + assertEquals(ARTIFACT_COUNT_WEB_BOOKMARK, results.getTotalResultsCount()); + assertEquals(ARTIFACT_COUNT_WEB_BOOKMARK - pageSize, results.getItems().size()); + + // Make sure no artifacts from the second page appeared on the first + for (RowDTO row : results.getItems()) { + assertTrue(row instanceof DataArtifactRowDTO); + DataArtifactRowDTO dataRow = (DataArtifactRowDTO) row; + assertTrue(dataRow.getDataArtifact() != null); + assertFalse("Data artifact ID: " + dataRow.getDataArtifact().getId() + " appeared on both page 1 and page 2", + firstPageObjIds.contains(dataRow.getDataArtifact().getId())); + } + } catch (ExecutionException ex) { Exceptions.printStackTrace(ex); Assert.fail(ex.getMessage()); @@ -528,6 +579,41 @@ public class TableSearchTest extends NbTestCase { assertTrue(analysisResultRowDTO.getCellValues().contains(ARTIFACT_CONFIGURATION)); assertTrue(analysisResultRowDTO.getCellValues().contains(ARTIFACT_CONCLUSION)); + // Test paging + Long pageSize = new Long(100); + assertTrue(ARTIFACT_COUNT_YARA > pageSize); + + // Get the first page + param = new AnalysisResultSearchParam(BlackboardArtifact.Type.TSK_YARA_HIT, null); + results = analysisResultDAO.getAnalysisResultsForTable(param, 0, pageSize, false); + assertEquals(ARTIFACT_COUNT_YARA, results.getTotalResultsCount()); + assertEquals(pageSize.longValue(), results.getItems().size()); + + // Save all artifact IDs from the first page + Set firstPageObjIds = new HashSet<>(); + for (RowDTO row : results.getItems()) { + assertTrue(row instanceof AnalysisResultRowDTO); + AnalysisResultRowDTO analysisRow = (AnalysisResultRowDTO) row; + assertTrue(analysisRow.getAnalysisResult() != null); + firstPageObjIds.add(analysisRow.getAnalysisResult().getId()); + } + assertEquals(pageSize.longValue(), firstPageObjIds.size()); + + // Get the second page + param = new AnalysisResultSearchParam(BlackboardArtifact.Type.TSK_YARA_HIT, null); + results = analysisResultDAO.getAnalysisResultsForTable(param, pageSize, pageSize, false); + assertEquals(ARTIFACT_COUNT_YARA, results.getTotalResultsCount()); + assertEquals(ARTIFACT_COUNT_YARA - pageSize, results.getItems().size()); + + // Make sure no artifacts from the second page appeared on the first + for (RowDTO row : results.getItems()) { + assertTrue(row instanceof AnalysisResultRowDTO); + AnalysisResultRowDTO analysisRow = (AnalysisResultRowDTO) row; + assertTrue(analysisRow.getAnalysisResult() != null); + assertFalse("Analysis result ID: " + analysisRow.getAnalysisResult().getId() + " appeared on both page 1 and page 2", + firstPageObjIds.contains(analysisRow.getAnalysisResult().getId())); + } + } catch (ExecutionException ex) { Exceptions.printStackTrace(ex); Assert.fail(ex.getMessage()); From 5a44fe0aaa95cebbe6b8ebdcec1c06cb5ae0d74b Mon Sep 17 00:00:00 2001 From: Eugene Livis Date: Thu, 28 Oct 2021 15:09:17 -0400 Subject: [PATCH 23/38] First cut at functional tests --- .../autopsy/mainui/datamodel/MainDAO.java | 5 ++ .../mainui/datamodel/TableSearchTest.java | 80 ++++++++++++++++++- 2 files changed, 83 insertions(+), 2 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/MainDAO.java b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/MainDAO.java index 5849ca88d5..f17c5fe52d 100644 --- a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/MainDAO.java +++ b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/MainDAO.java @@ -37,6 +37,7 @@ public class MainDAO { private final DataArtifactDAO dataArtifactDAO = DataArtifactDAO.getInstance(); private final AnalysisResultDAO analysisResultDAO = AnalysisResultDAO.getInstance(); private final ViewsDAO viewsDAO = ViewsDAO.getInstance(); + private final TagsDAO tagsDAO = TagsDAO.getInstance(); public DataArtifactDAO getDataArtifactsDAO() { return dataArtifactDAO; @@ -49,4 +50,8 @@ public class MainDAO { public ViewsDAO getViewsDAO() { return viewsDAO; } + + public TagsDAO getTagsDAO() { + return tagsDAO; + } } diff --git a/Core/test/qa-functional/src/org/sleuthkit/autopsy/mainui/datamodel/TableSearchTest.java b/Core/test/qa-functional/src/org/sleuthkit/autopsy/mainui/datamodel/TableSearchTest.java index 64097081a6..297b194b18 100644 --- a/Core/test/qa-functional/src/org/sleuthkit/autopsy/mainui/datamodel/TableSearchTest.java +++ b/Core/test/qa-functional/src/org/sleuthkit/autopsy/mainui/datamodel/TableSearchTest.java @@ -32,6 +32,7 @@ import org.netbeans.junit.NbModuleSuite; import org.netbeans.junit.NbTestCase; import org.openide.util.Exceptions; import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.casemodule.services.TagsManager; import org.sleuthkit.autopsy.testutils.CaseUtils; import org.sleuthkit.autopsy.testutils.TestUtilsException; import org.sleuthkit.datamodel.AbstractFile; @@ -45,6 +46,7 @@ import org.sleuthkit.datamodel.DataArtifact; import org.sleuthkit.datamodel.DataSource; import org.sleuthkit.datamodel.Score; import org.sleuthkit.datamodel.SleuthkitCase; +import org.sleuthkit.datamodel.TagName; import org.sleuthkit.datamodel.TskData; import org.sleuthkit.datamodel.TskCoreException; @@ -85,6 +87,7 @@ public class TableSearchTest extends NbTestCase { private static final String KEYWORD_PREVIEW = "There is a bomb."; // Extension and MIME type test + private static AbstractFile customFile; private static final String CUSTOM_MIME_TYPE = "fake/type"; private static final String CUSTOM_MIME_TYPE_FILE_NAME = "test.fake"; private static final String CUSTOM_EXTENSION = "fake"; @@ -98,6 +101,7 @@ public class TableSearchTest extends NbTestCase { Case openCase = null; // The case for testing SleuthkitCase db = null; // The case database Blackboard blackboard = null; // The blackboard + TagsManager tagsManager = null;// Tags manager DataSource dataSource1 = null; // A local files data source DataSource dataSource2 = null; // A local files data source @@ -123,6 +127,10 @@ public class TableSearchTest extends NbTestCase { // Keyword hits test AnalysisResult keywordHitAnalysisResult = null; // A keyword hit Content keywordHitSource = null; // The source of the keyword hit above + + // Tags test + TagName knownTag1 = null; + TagName tag2 = null; public static Test suite() { NbModuleSuite.Configuration conf = NbModuleSuite.createConfiguration(TableSearchTest.class). @@ -148,6 +156,7 @@ public class TableSearchTest extends NbTestCase { mimeSearchTest(); extensionSearchTest(); sizeSearchTest(); + tagsTest(); } /** @@ -159,6 +168,7 @@ public class TableSearchTest extends NbTestCase { openCase = CaseUtils.createAsCurrentCase("testTableSearchCase"); db = openCase.getSleuthkitCase(); blackboard = db.getBlackboard(); + tagsManager = openCase.getServices().getTagsManager(); // Add two logical files data sources SleuthkitCase.CaseDbTransaction trans = db.beginTransaction(); @@ -189,7 +199,7 @@ public class TableSearchTest extends NbTestCase { fileB1.setMIMEType("text/plain"); fileB1.save(); - AbstractFile customFile = db.addLocalFile(CUSTOM_MIME_TYPE_FILE_NAME, "", 67000000, 0, 0, 0, 0, true, TskData.EncodingType.NONE, folderB1); + customFile = db.addLocalFile(CUSTOM_MIME_TYPE_FILE_NAME, "", 67000000, 0, 0, 0, 0, true, TskData.EncodingType.NONE, folderB1); customFile.setMIMEType(CUSTOM_MIME_TYPE); customFile.save(); @@ -306,7 +316,25 @@ public class TableSearchTest extends NbTestCase { null, KEYWORD_SET_1, null, attrs).getAnalysisResult(); keywordHitSource = hashHitAnalysisResult; - } catch (TestUtilsException | TskCoreException | BlackboardException ex) { + // Add tags ---- + knownTag1 = tagsManager.addTagName("Tag 1", "Descrition", TagName.HTML_COLOR.RED, TskData.FileKnown.KNOWN); + tag2 = tagsManager.addTagName("Tag 2", "Descrition"); + + // Tag the custom artifacts in data source 1 + openCase.getServices().getTagsManager().addBlackboardArtifactTag(customDataArtifact, knownTag1, "Comment"); + openCase.getServices().getTagsManager().addBlackboardArtifactTag(customAnalysisResult, tag2, "Comment 2"); + + // Tag file in data source 1 + openCase.getServices().getTagsManager().addContentTag(fileA2, tag2); + openCase.getServices().getTagsManager().addContentTag(fileA3, tag2); + + // Tag file in data source 2 + openCase.getServices().getTagsManager().addContentTag(fileB1, tag2); + + // Tag the custom file in data source 2 + openCase.getServices().getTagsManager().addContentTag(customFile, knownTag1); + + } catch (TestUtilsException | TskCoreException | BlackboardException | TagsManager.TagNameAlreadyExistsException ex) { Exceptions.printStackTrace(ex); Assert.fail(ex.getMessage()); } @@ -476,6 +504,52 @@ public class TableSearchTest extends NbTestCase { } } + public void tagsTest() { + // Quick test that everything is initialized + assertTrue(db != null); + + try { + TagsDAO tagsDAO = MainDAO.getInstance().getTagsDAO(); + + // Get "Tag1" file tags from data source 1 + TagsSearchParams param = new TagsSearchParams(knownTag1, TagsSearchParams.TagType.FILE, dataSource1.getId()); + SearchResultsDTO results = tagsDAO.getTags(param, 0, null, false); + assertEquals(0, results.getTotalResultsCount()); + assertEquals(0, results.getItems().size()); + + // Get "Tag2" file tags from data source 1 + param = new TagsSearchParams(tag2, TagsSearchParams.TagType.FILE, dataSource1.getId()); + results = tagsDAO.getTags(param, 0, null, false); + assertEquals(2, results.getTotalResultsCount()); + assertEquals(2, results.getItems().size()); + + // Get "Tag2" file tags from all data sources + param = new TagsSearchParams(tag2, TagsSearchParams.TagType.FILE, null); + results = tagsDAO.getTags(param, 0, null, false); + assertEquals(3, results.getTotalResultsCount()); + assertEquals(3, results.getItems().size()); + + // Get "Tag1" result tags from data source 1 + param = new TagsSearchParams(knownTag1, TagsSearchParams.TagType.RESULT, dataSource1.getId()); + results = tagsDAO.getTags(param, 0, null, false); + assertEquals(1, results.getTotalResultsCount()); + assertEquals(1, results.getItems().size()); + + // Get "Tag1" result tags from data source 2 + param = new TagsSearchParams(knownTag1, TagsSearchParams.TagType.RESULT, dataSource2.getId()); + results = tagsDAO.getTags(param, 0, null, false); + assertEquals(0, results.getTotalResultsCount()); + assertEquals(0, results.getItems().size()); + + // Test custom tags + + + } catch (ExecutionException ex) { + Exceptions.printStackTrace(ex); + Assert.fail(ex.getMessage()); + } + } + public void analysisResultSearchTest() { // Quick test that everything is initialized assertTrue(db != null); @@ -721,5 +795,7 @@ public class TableSearchTest extends NbTestCase { } openCase = null; db = null; + blackboard = null; + tagsManager = null; } } From 39332f78db5a0212107324c679c2629fa7aab2e2 Mon Sep 17 00:00:00 2001 From: Eugene Livis Date: Thu, 28 Oct 2021 16:13:43 -0400 Subject: [PATCH 24/38] Functional tests --- .../mainui/datamodel/TableSearchTest.java | 48 +++++++++++++++---- 1 file changed, 39 insertions(+), 9 deletions(-) diff --git a/Core/test/qa-functional/src/org/sleuthkit/autopsy/mainui/datamodel/TableSearchTest.java b/Core/test/qa-functional/src/org/sleuthkit/autopsy/mainui/datamodel/TableSearchTest.java index 297b194b18..8fd5209e3a 100644 --- a/Core/test/qa-functional/src/org/sleuthkit/autopsy/mainui/datamodel/TableSearchTest.java +++ b/Core/test/qa-functional/src/org/sleuthkit/autopsy/mainui/datamodel/TableSearchTest.java @@ -94,6 +94,15 @@ public class TableSearchTest extends NbTestCase { private static final Set CUSTOM_EXTENSIONS = Collections.unmodifiableSet(new HashSet<>(Arrays.asList("." + CUSTOM_EXTENSION))); //NON-NLS private static final Set EMPTY_RESULT_SET_EXTENSIONS = Collections.unmodifiableSet(new HashSet<>(Arrays.asList(".blah", ".blah2", ".crazy"))); //NON-NLS + // Tag test + private static final String TAG_COMMENT = "Tag comment"; + private static final String TAG_DESCRIPTION = "Tag description"; + private static final String MD5_COLUMN = "MD5 Hash"; + private static final String FILE_PATH_COLUMN = "File Path"; + private static final String MODIFIED_TIME_COLUMN = "Modified Time"; + private static final String SOURCE_NAME_COLUMN = "Source Name"; + private static final String SOURCE_FILE_PATH_COLUMN = "Source File Path"; + ///////////////////////////////////////////////// // Data to be used across the test methods. // These are initialized in setUpCaseDatabase(). @@ -317,11 +326,11 @@ public class TableSearchTest extends NbTestCase { keywordHitSource = hashHitAnalysisResult; // Add tags ---- - knownTag1 = tagsManager.addTagName("Tag 1", "Descrition", TagName.HTML_COLOR.RED, TskData.FileKnown.KNOWN); + knownTag1 = tagsManager.addTagName("Tag 1", TAG_DESCRIPTION, TagName.HTML_COLOR.RED, TskData.FileKnown.KNOWN); tag2 = tagsManager.addTagName("Tag 2", "Descrition"); // Tag the custom artifacts in data source 1 - openCase.getServices().getTagsManager().addBlackboardArtifactTag(customDataArtifact, knownTag1, "Comment"); + openCase.getServices().getTagsManager().addBlackboardArtifactTag(customDataArtifact, knownTag1, TAG_COMMENT); openCase.getServices().getTagsManager().addBlackboardArtifactTag(customAnalysisResult, tag2, "Comment 2"); // Tag file in data source 1 @@ -528,6 +537,28 @@ public class TableSearchTest extends NbTestCase { results = tagsDAO.getTags(param, 0, null, false); assertEquals(3, results.getTotalResultsCount()); assertEquals(3, results.getItems().size()); + + // Check that a few of the expected file tag column names are present + List columnDisplayNames = results.getColumns().stream().map(p -> p.getDisplayName()).collect(Collectors.toList()); + assertTrue(columnDisplayNames.contains(MD5_COLUMN)); + assertTrue(columnDisplayNames.contains(FILE_PATH_COLUMN)); + assertTrue(columnDisplayNames.contains(MODIFIED_TIME_COLUMN)); + + // Check that the result tag columns are not present + assertFalse(columnDisplayNames.contains(SOURCE_NAME_COLUMN)); + assertFalse(columnDisplayNames.contains(SOURCE_FILE_PATH_COLUMN)); + + // Get "Tag1" result tags from data source 2 + param = new TagsSearchParams(knownTag1, TagsSearchParams.TagType.RESULT, dataSource2.getId()); + results = tagsDAO.getTags(param, 0, null, false); + assertEquals(0, results.getTotalResultsCount()); + assertEquals(0, results.getItems().size()); + + // Get "Tag2" result tags from data source 1 + param = new TagsSearchParams(tag2, TagsSearchParams.TagType.RESULT, dataSource1.getId()); + results = tagsDAO.getTags(param, 0, null, false); + assertEquals(1, results.getTotalResultsCount()); + assertEquals(1, results.getItems().size()); // Get "Tag1" result tags from data source 1 param = new TagsSearchParams(knownTag1, TagsSearchParams.TagType.RESULT, dataSource1.getId()); @@ -535,14 +566,13 @@ public class TableSearchTest extends NbTestCase { assertEquals(1, results.getTotalResultsCount()); assertEquals(1, results.getItems().size()); - // Get "Tag1" result tags from data source 2 - param = new TagsSearchParams(knownTag1, TagsSearchParams.TagType.RESULT, dataSource2.getId()); - results = tagsDAO.getTags(param, 0, null, false); - assertEquals(0, results.getTotalResultsCount()); - assertEquals(0, results.getItems().size()); - - // Test custom tags + // Get the row + RowDTO rowDTO = results.getItems().get(0); + assertTrue(rowDTO instanceof BaseRowDTO); + BaseRowDTO tagResultRowDTO = (BaseRowDTO) rowDTO; + // Check that some of the expected result tag column values are present + assertTrue(tagResultRowDTO.getCellValues().contains(TAG_COMMENT)); } catch (ExecutionException ex) { Exceptions.printStackTrace(ex); From 05fdf573e92b59f13d0499af58c12366d5b54406 Mon Sep 17 00:00:00 2001 From: Eugene Livis Date: Thu, 28 Oct 2021 16:24:05 -0400 Subject: [PATCH 25/38] More functional tests --- .../mainui/datamodel/TableSearchTest.java | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/Core/test/qa-functional/src/org/sleuthkit/autopsy/mainui/datamodel/TableSearchTest.java b/Core/test/qa-functional/src/org/sleuthkit/autopsy/mainui/datamodel/TableSearchTest.java index 8fd5209e3a..d4cf05dbae 100644 --- a/Core/test/qa-functional/src/org/sleuthkit/autopsy/mainui/datamodel/TableSearchTest.java +++ b/Core/test/qa-functional/src/org/sleuthkit/autopsy/mainui/datamodel/TableSearchTest.java @@ -538,6 +538,20 @@ public class TableSearchTest extends NbTestCase { assertEquals(3, results.getTotalResultsCount()); assertEquals(3, results.getItems().size()); + // Get "Tag1" file tags from data source 2 + param = new TagsSearchParams(knownTag1, TagsSearchParams.TagType.FILE, dataSource2.getId()); + results = tagsDAO.getTags(param, 0, null, false); + assertEquals(1, results.getTotalResultsCount()); + assertEquals(1, results.getItems().size()); + + // Get the row + RowDTO rowDTO = results.getItems().get(0); + assertTrue(rowDTO instanceof BaseRowDTO); + BaseRowDTO tagResultRowDTO = (BaseRowDTO) rowDTO; + + // Check that the file tag is for the custom file + assertTrue(tagResultRowDTO.getCellValues().contains(customFile.getName())); + // Check that a few of the expected file tag column names are present List columnDisplayNames = results.getColumns().stream().map(p -> p.getDisplayName()).collect(Collectors.toList()); assertTrue(columnDisplayNames.contains(MD5_COLUMN)); @@ -567,9 +581,9 @@ public class TableSearchTest extends NbTestCase { assertEquals(1, results.getItems().size()); // Get the row - RowDTO rowDTO = results.getItems().get(0); + rowDTO = results.getItems().get(0); assertTrue(rowDTO instanceof BaseRowDTO); - BaseRowDTO tagResultRowDTO = (BaseRowDTO) rowDTO; + tagResultRowDTO = (BaseRowDTO) rowDTO; // Check that some of the expected result tag column values are present assertTrue(tagResultRowDTO.getCellValues().contains(TAG_COMMENT)); From 0aa29f3d42d622c801b3a6f19af50b340be872be Mon Sep 17 00:00:00 2001 From: Greg DiCristofaro Date: Fri, 29 Oct 2021 07:59:31 -0400 Subject: [PATCH 26/38] commenting --- .../mainui/datamodel/AnalysisResultDAO.java | 30 ++++++++++- .../mainui/datamodel/TreeResultsDTO.java | 9 ++++ .../nodes/AnalysisResultTypeFactory.java | 52 ++++++++++++++++--- 3 files changed, 83 insertions(+), 8 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/AnalysisResultDAO.java b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/AnalysisResultDAO.java index 7df96e63aa..47a8c7d2fc 100644 --- a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/AnalysisResultDAO.java +++ b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/AnalysisResultDAO.java @@ -344,6 +344,19 @@ public class AnalysisResultDAO extends BlackboardArtifactDAO { } } + /** + * + * @param type The artifact type to filter on. + * @param setNameAttr The blackboard attribute denoting the set name. + * @param dataSourceId The data source object id for which the results + * should be filtered or null if no data source + * filtering. + * + * @return + * + * @throws IllegalArgumentException + * @throws ExecutionException + */ Map getSetCounts(BlackboardArtifact.Type type, BlackboardAttribute.Type setNameAttr, Long dataSourceId) throws IllegalArgumentException, ExecutionException { if (dataSourceId != null && dataSourceId <= 0) { throw new IllegalArgumentException("Expected data source id to be > 0"); @@ -370,7 +383,9 @@ public class AnalysisResultDAO extends BlackboardArtifactDAO { while (resultSet.next()) { String setName = resultSet.getString("set_name"); long count = resultSet.getLong("count"); - setCounts.put(setName, count); + if (setName != null) { + setCounts.put(setName, count); + } } } catch (SQLException ex) { logger.log(Level.WARNING, "An error occurred while fetching set name counts.", ex); @@ -383,6 +398,19 @@ public class AnalysisResultDAO extends BlackboardArtifactDAO { } } + /** + * Get counts for individual sets of TSK_HASHSET_HIT's to be used in the + * tree view. + * + * @param dataSourceId The data source object id for which the results + * should be filtered or null if no data source + * filtering. + * + * @return The sets along with counts to display. + * + * @throws IllegalArgumentException + * @throws ExecutionException + */ public TreeResultsDTO getHashSetCounts(Long dataSourceId) throws IllegalArgumentException, ExecutionException { List> allSets = getSetCounts(BlackboardArtifact.Type.TSK_HASHSET_HIT, BlackboardAttribute.Type.TSK_SET_NAME, dataSourceId).entrySet().stream() diff --git a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/TreeResultsDTO.java b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/TreeResultsDTO.java index c1e27d61fd..2d42b4464e 100644 --- a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/TreeResultsDTO.java +++ b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/TreeResultsDTO.java @@ -107,5 +107,14 @@ public class TreeResultsDTO { public Object getId() { return id; } + + /** + * @return The id of this item type. + */ + public String getTypeId() { + return typeId; + } + + } } diff --git a/Core/src/org/sleuthkit/autopsy/mainui/nodes/AnalysisResultTypeFactory.java b/Core/src/org/sleuthkit/autopsy/mainui/nodes/AnalysisResultTypeFactory.java index 933a22c70e..e77b473fba 100644 --- a/Core/src/org/sleuthkit/autopsy/mainui/nodes/AnalysisResultTypeFactory.java +++ b/Core/src/org/sleuthkit/autopsy/mainui/nodes/AnalysisResultTypeFactory.java @@ -36,10 +36,22 @@ import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.BlackboardArtifact.Category; /** - * Factory for displaying data artifact types in the tree. + * Factory for displaying analysis result types in the tree. */ public class AnalysisResultTypeFactory extends TreeChildFactory { + /** + * Returns the path to the icon to use for this artifact type. + * + * @param artType The artifact type. + * + * @return The path to the icon to use for this artifact type. + */ + private static String getIconPath(BlackboardArtifact.Type artType) { + String iconPath = IconsUtil.getIconFilePath(artType.getTypeID()); + return iconPath != null && iconPath.charAt(0) == '/' ? iconPath.substring(1) : iconPath; + } + private final Long dataSourceId; /** @@ -95,16 +107,16 @@ public class AnalysisResultTypeFactory extends TreeChildFactory { + /** + * Main constructor. + * + * @param itemData The data to display. + */ public AnalysisResultTypeTreeNode(TreeResultsDTO.TreeItemDTO itemData) { super(itemData.getTypeData().getArtifactType().getTypeName(), getIconPath(itemData.getTypeData().getArtifactType()), @@ -117,8 +129,16 @@ public class AnalysisResultTypeFactory extends TreeChildFactory { + /** + * Main constructor. + * + * @param itemData The data to display. + */ public HashHitTypeNode(TreeResultsDTO.TreeItemDTO itemData) { super(itemData.getTypeData().getArtifactType().getTypeName(), getIconPath(itemData.getTypeData().getArtifactType()), @@ -133,10 +153,20 @@ public class AnalysisResultTypeFactory extends TreeChildFactory { private final Long dataSourceId; + /** + * Main constructor. + * + * @param dataSourceId The data source object id for which the results + * should be filtered or null if no data source + * filtering. + */ public HashHitSetFactory(Long dataSourceId) { this.dataSourceId = dataSourceId; } @@ -158,8 +188,16 @@ public class AnalysisResultTypeFactory extends TreeChildFactory { + /** + * Main constructor. + * + * @param itemData Set data to display. + */ public HashHitSetNode(TreeResultsDTO.TreeItemDTO itemData) { super(itemData.getTypeData().getSetName(), getIconPath(BlackboardArtifact.Type.TSK_HASHSET_HIT), itemData); } From 85686d974e0fc074f786406bd29b719d355e9ae8 Mon Sep 17 00:00:00 2001 From: Greg DiCristofaro Date: Fri, 29 Oct 2021 08:07:45 -0400 Subject: [PATCH 27/38] add hashset hit factory refresh --- .../nodes/AnalysisResultTypeFactory.java | 27 ++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/Core/src/org/sleuthkit/autopsy/mainui/nodes/AnalysisResultTypeFactory.java b/Core/src/org/sleuthkit/autopsy/mainui/nodes/AnalysisResultTypeFactory.java index e77b473fba..5dc9063738 100644 --- a/Core/src/org/sleuthkit/autopsy/mainui/nodes/AnalysisResultTypeFactory.java +++ b/Core/src/org/sleuthkit/autopsy/mainui/nodes/AnalysisResultTypeFactory.java @@ -183,7 +183,32 @@ public class AnalysisResultTypeFactory extends TreeChildFactory Date: Fri, 29 Oct 2021 08:12:53 -0400 Subject: [PATCH 28/38] comment update --- .../sleuthkit/autopsy/mainui/datamodel/AnalysisResultDAO.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/AnalysisResultDAO.java b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/AnalysisResultDAO.java index 47a8c7d2fc..8c40dcabcf 100644 --- a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/AnalysisResultDAO.java +++ b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/AnalysisResultDAO.java @@ -352,7 +352,7 @@ public class AnalysisResultDAO extends BlackboardArtifactDAO { * should be filtered or null if no data source * filtering. * - * @return + * @return A mapping of set names to their counts. * * @throws IllegalArgumentException * @throws ExecutionException From 4f018b328130555015cccb703d1175582585e537 Mon Sep 17 00:00:00 2001 From: Eugene Livis Date: Fri, 29 Oct 2021 10:39:34 -0400 Subject: [PATCH 29/38] Cleaning things up --- .../autopsy/mainui/datamodel/TagsDAO.java | 77 ++++--------------- 1 file changed, 13 insertions(+), 64 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/TagsDAO.java b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/TagsDAO.java index d71f199191..5c6059fa5d 100755 --- a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/TagsDAO.java +++ b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/TagsDAO.java @@ -32,18 +32,15 @@ import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; import org.sleuthkit.autopsy.core.UserPreferences; import org.sleuthkit.autopsy.coreutils.TimeZoneUtils; -import org.sleuthkit.autopsy.datamodel.FileTypeExtensions; -import org.sleuthkit.autopsy.mainui.datamodel.FileRowDTO.ExtensionMediaType; import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.BlackboardArtifactTag; import org.sleuthkit.datamodel.Content; import org.sleuthkit.datamodel.ContentTag; -import org.sleuthkit.datamodel.SleuthkitCase; import org.sleuthkit.datamodel.TagName; import org.sleuthkit.datamodel.TskCoreException; /** - * Provides information to populate the results viewer for data in the views + * Provides information to populate the results viewer for data in the tags * section. */ @Messages({"TagsDAO.fileColumns.nameColLbl=Name", @@ -66,7 +63,7 @@ import org.sleuthkit.datamodel.TskCoreException; "TagsDAO.tagColumns.userNameColLbl=User Name"}) public class TagsDAO { - private static final int CACHE_SIZE = 15; // rule of thumb: 5 entries times number of cached SearchParams sub-types + private static final int CACHE_SIZE = 5; // rule of thumb: 5 entries times number of cached SearchParams sub-types private static final long CACHE_DURATION = 2; private static final TimeUnit CACHE_DURATION_UNITS = TimeUnit.MINUTES; private final Cache, SearchResultsDTO> searchParamsCache = CacheBuilder.newBuilder().maximumSize(CACHE_SIZE).expireAfterAccess(CACHE_DURATION, CACHE_DURATION_UNITS).build(); @@ -75,12 +72,10 @@ public class TagsDAO { private static final String FILE_TAG_TYPE_ID = "FILE_TAG"; private static final String RESULT_TAG_TYPE_ID = "RESULT_TAG"; - private static final String FILE_TAG_DISPLAY_NAME = "File Tag"; - private static final String RESULT_TAG_DISPLAY_NAME = "Result Tag"; private static final List FILE_TAG_COLUMNS = Arrays.asList( getFileColumnKey(Bundle.TagsDAO_fileColumns_nameColLbl()), - getFileColumnKey(Bundle.TagsDAO_fileColumns_originalName()), // ELODO handle translation + getFileColumnKey(Bundle.TagsDAO_fileColumns_originalName()), // GVDTODO handle translation getFileColumnKey(Bundle.TagsDAO_fileColumns_filePathColLbl()), getFileColumnKey(Bundle.TagsDAO_fileColumns_commentColLbl()), getFileColumnKey(Bundle.TagsDAO_fileColumns_modifiedTimeColLbl()), @@ -112,45 +107,14 @@ public class TagsDAO { private static ColumnKey getFileColumnKey(String name) { return new ColumnKey(name, name, Bundle.TagsDAO_fileColumns_noDescription()); } - - private SleuthkitCase getCase() throws NoCurrentCaseException { - return Case.getCurrentCaseThrows().getSleuthkitCase(); - } - - static ExtensionMediaType getExtensionMediaType(String ext) { - if (StringUtils.isBlank(ext)) { - return ExtensionMediaType.UNCATEGORIZED; - } else { - ext = "." + ext; - } - if (FileTypeExtensions.getImageExtensions().contains(ext)) { - return ExtensionMediaType.IMAGE; - } else if (FileTypeExtensions.getVideoExtensions().contains(ext)) { - return ExtensionMediaType.VIDEO; - } else if (FileTypeExtensions.getAudioExtensions().contains(ext)) { - return ExtensionMediaType.AUDIO; - } else if (FileTypeExtensions.getDocumentExtensions().contains(ext)) { - return ExtensionMediaType.DOC; - } else if (FileTypeExtensions.getExecutableExtensions().contains(ext)) { - return ExtensionMediaType.EXECUTABLE; - } else if (FileTypeExtensions.getTextExtensions().contains(ext)) { - return ExtensionMediaType.TEXT; - } else if (FileTypeExtensions.getWebExtensions().contains(ext)) { - return ExtensionMediaType.WEB; - } else if (FileTypeExtensions.getPDFExtensions().contains(ext)) { - return ExtensionMediaType.PDF; - } else if (FileTypeExtensions.getArchiveExtensions().contains(ext)) { - return ExtensionMediaType.ARCHIVE; - } else { - return ExtensionMediaType.UNCATEGORIZED; - } - } public SearchResultsDTO getTags(TagsSearchParams key, long startItem, Long maxCount, boolean hardRefresh) throws ExecutionException, IllegalArgumentException { if (key.getTagName() == null) { - throw new IllegalArgumentException("Must have non-null filter"); + throw new IllegalArgumentException("Must have non-null tag name"); } else if (key.getDataSourceId() != null && key.getDataSourceId() <= 0) { throw new IllegalArgumentException("Data source id must be greater than 0 or null"); + } else if (key.getTagType() == null) { + throw new IllegalArgumentException("Must have non-null tag type"); } SearchParams searchParams = new SearchParams<>(key, startItem, maxCount); @@ -161,34 +125,19 @@ public class TagsDAO { return searchParamsCache.get(searchParams, () -> fetchTagsDTOs(key.getTagName(), key.getTagType(), key.getDataSourceId(), startItem, maxCount)); } - @NbBundle.Messages({"FileTag.name.text=File Tag"}) + @NbBundle.Messages({"FileTag.name.text=File Tag", + "ResultTag.name.text=Result Tag"}) private SearchResultsDTO fetchTagsDTOs(TagName tagName, TagsSearchParams.TagType type, Long dataSourceId, long startItem, Long maxResultCount) throws NoCurrentCaseException, TskCoreException { - if (null == type) { - // ELTODO throw? - return null; - } - switch (type) { case FILE: return fetchFileTags(tagName, dataSourceId, startItem, maxResultCount); case RESULT: return fetchResultTags(tagName, dataSourceId, startItem, maxResultCount); default: - // ELTODO throw? - return null; + throw new IllegalArgumentException("Unsupported tag type"); } } - /* GET RESULT TAGS - * BlackboardArtifactTagNodeFactory.createKeys(List tags) - * BlackboardArtifactTagNode.createSheet() - */ - - /* GET FILE TAGS - * ContentTagNodeFactory.createKeys(List tags) - * ContentTagNode.createSheet() - */ - private SearchResultsDTO fetchResultTags(TagName tagName, Long dataSourceId, long startItem, Long maxResultCount) throws NoCurrentCaseException, TskCoreException { // ELTODO startItem, maxResultCount @@ -227,7 +176,7 @@ public class TagsDAO { List cellValues = Arrays.asList( name, - null, // ELTODO translation column + null, // GVDTODO translation column contentPath, tag.getArtifact().getDisplayName(), tag.getComment(), @@ -239,7 +188,7 @@ public class TagsDAO { tag.getId())); } - return new BaseSearchResultsDTO(RESULT_TAG_TYPE_ID, RESULT_TAG_DISPLAY_NAME, RESULT_TAG_COLUMNS, fileRows, startItem, fileRows.size()); + return new BaseSearchResultsDTO(RESULT_TAG_TYPE_ID, Bundle.ResultTag_name_text(), RESULT_TAG_COLUMNS, fileRows, 0, fileRows.size()); } private SearchResultsDTO fetchFileTags(TagName tagName, Long dataSourceId, long startItem, Long maxResultCount) throws NoCurrentCaseException, TskCoreException { @@ -269,7 +218,7 @@ public class TagsDAO { List cellValues = Arrays.asList( content.getName(), - null, // ELTODO translation column + null, // GVDTODO translation column contentPath, tag.getComment(), file != null ? TimeZoneUtils.getFormattedTime(file.getMtime()) : "", @@ -286,6 +235,6 @@ public class TagsDAO { file.getId())); } - return new BaseSearchResultsDTO(FILE_TAG_TYPE_ID, FILE_TAG_DISPLAY_NAME, FILE_TAG_COLUMNS, fileRows, startItem, fileRows.size()); + return new BaseSearchResultsDTO(FILE_TAG_TYPE_ID, Bundle.FileTag_name_text(), FILE_TAG_COLUMNS, fileRows, 0, fileRows.size()); } } From ee81e21ac9420a457d964e8325c3926f76ef1752 Mon Sep 17 00:00:00 2001 From: apriestman Date: Fri, 29 Oct 2021 11:07:20 -0400 Subject: [PATCH 30/38] Slightly optimize hash hits --- .../mainui/datamodel/AnalysisResultDAO.java | 30 +++++++++++-------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/AnalysisResultDAO.java b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/AnalysisResultDAO.java index c044fa8587..16e1976870 100644 --- a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/AnalysisResultDAO.java +++ b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/AnalysisResultDAO.java @@ -136,27 +136,29 @@ public class AnalysisResultDAO extends BlackboardArtifactDAO { Long dataSourceId = cacheKey.getParamData().getDataSourceId(); BlackboardArtifact.Type artType = cacheKey.getParamData().getArtifactType(); - - // Get all hash set hits - List allHashHits; - if (dataSourceId != null) { - allHashHits = blackboard.getAnalysisResultsByType(artType.getTypeID(), dataSourceId); - } else { - allHashHits = blackboard.getAnalysisResultsByType(artType.getTypeID()); - } + // We currently can't make a query on the set name field because need to use a prepared statement + String originalWhereClause = " artifacts.artifact_type_id = " + artType.getTypeID() + " "; + if (dataSourceId != null) { + originalWhereClause += " AND artifacts.data_source_obj_id = " + dataSourceId + " "; + } + + List allHashHits = new ArrayList<>(); + allHashHits.addAll(blackboard.getAnalysisResultsWhere(originalWhereClause)); + blackboard.loadBlackboardAttributes(allHashHits); + // Filter for the selected set - List arts = new ArrayList<>(); - for (AnalysisResult art : allHashHits) { + List hashHits = new ArrayList<>(); + for (BlackboardArtifact art : allHashHits) { BlackboardAttribute setNameAttr = art.getAttribute(BlackboardAttribute.Type.TSK_SET_NAME); if ((setNameAttr != null) && cacheKey.getParamData().getSetName().equals(setNameAttr.getValueString())) { - arts.add(art); + hashHits.add(art); } } - List pagedArtifacts = getPaged(arts, cacheKey); + List pagedArtifacts = getPaged(hashHits, cacheKey); TableData tableData = createTableData(artType, pagedArtifacts); - return new AnalysisResultTableSearchResultsDTO(artType, tableData.columnKeys, tableData.rows, cacheKey.getStartItem(), arts.size()); + return new AnalysisResultTableSearchResultsDTO(artType, tableData.columnKeys, tableData.rows, cacheKey.getStartItem(), hashHits.size()); } @Override @@ -261,6 +263,8 @@ public class AnalysisResultDAO extends BlackboardArtifactDAO { return hashHitCache.get(searchParams, () -> fetchSetNameHitsForTable(searchParams)); } + // TODO - JIRA-8117 + // This needs to use more than just the set name public AnalysisResultTableSearchResultsDTO getKeywordHitsForTable(KeywordHitSearchParam artifactKey, long startItem, Long maxCount, boolean hardRefresh) throws ExecutionException, IllegalArgumentException { if (artifactKey.getDataSourceId() != null && artifactKey.getDataSourceId() < 0) { throw new IllegalArgumentException(MessageFormat.format("Illegal data. " From 80fabc3c18a7d23ebfaaeb57ff93dc13cd766416 Mon Sep 17 00:00:00 2001 From: Greg DiCristofaro Date: Fri, 29 Oct 2021 15:30:35 -0400 Subject: [PATCH 31/38] work on keyword set counts --- .../mainui/datamodel/AnalysisResultDAO.java | 119 +++++++++++++++--- .../AnalysisResultSetSearchParam.java | 2 +- 2 files changed, 106 insertions(+), 15 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/AnalysisResultDAO.java b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/AnalysisResultDAO.java index 8c40dcabcf..275eb343cb 100644 --- a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/AnalysisResultDAO.java +++ b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/AnalysisResultDAO.java @@ -32,7 +32,9 @@ import java.util.Set; import java.util.concurrent.ExecutionException; import java.util.logging.Level; import java.util.stream.Collectors; +import org.apache.commons.lang3.StringUtils; import org.openide.util.NbBundle; +import org.openide.util.NbBundle.Messages; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.ingest.ModuleDataEvent; @@ -365,17 +367,17 @@ public class AnalysisResultDAO extends BlackboardArtifactDAO { try { // get artifact types and counts SleuthkitCase skCase = getCase(); - String query = "value_text AS set_name, COUNT(*) AS count FROM \n" - + "(SELECT attr.value_text FROM\n" - + "blackboard_attributes attr\n" - + (dataSourceId == null ? "" : "LEFT JOIN blackboard_artifacts art ON attr.artifact_id = art.artifact_id\n") - + "WHERE attr.artifact_type_id = " + type.getTypeID() + "\n" - + (dataSourceId == null ? "" : "AND art.data_source_obj_id = " + dataSourceId + "\n") - + "AND attr.attribute_type_id = " + setNameAttr.getTypeID() + "\n" - // inner query and group by artifact id ensures that count of artifacts is - // determined even if data breaks invariants and has more than one TSK_SET_NAME attr per artifact - + "GROUP BY attr.value_text, attr.artifact_id)\n" - + "GROUP BY value_text"; + String query = " set_name, COUNT(*) AS count \n" + + "FROM ( \n" + + " SELECT art.artifact_id, \n" + + " (SELECT value_text \n" + + " FROM blackboard_attributes attr \n" + + " WHERE attr.artifact_id = art.artifact_id AND attr.attribute_type_id = " + setNameAttr.getTypeID() + " LIMIT 1) AS set_name \n" + + " FROM blackboard_artifacts art \n" + + " WHERE art.artifact_type_id = " + type.getTypeID() + " \n" + + ((dataSourceId == null) ? "" : " AND art.data_source_obj_id = " + dataSourceId + " \n") + + ") \n" + + "GROUP BY set_name"; Map setCounts = new HashMap<>(); skCase.getCaseDbAccessManager().select(query, (resultSet) -> { @@ -383,9 +385,7 @@ public class AnalysisResultDAO extends BlackboardArtifactDAO { while (resultSet.next()) { String setName = resultSet.getString("set_name"); long count = resultSet.getLong("count"); - if (setName != null) { - setCounts.put(setName, count); - } + setCounts.put(setName, count); } } catch (SQLException ex) { logger.log(Level.WARNING, "An error occurred while fetching set name counts.", ex); @@ -414,6 +414,7 @@ public class AnalysisResultDAO extends BlackboardArtifactDAO { public TreeResultsDTO getHashSetCounts(Long dataSourceId) throws IllegalArgumentException, ExecutionException { List> allSets = getSetCounts(BlackboardArtifact.Type.TSK_HASHSET_HIT, BlackboardAttribute.Type.TSK_SET_NAME, dataSourceId).entrySet().stream() + .filter(entry -> entry.getKey() == null) .map(entry -> new TreeItemDTO<>( BlackboardArtifact.Type.TSK_HASHSET_HIT.getTypeName(), new HashHitSearchParam(dataSourceId, entry.getKey()), @@ -427,6 +428,96 @@ public class AnalysisResultDAO extends BlackboardArtifactDAO { return new TreeResultsDTO<>(allSets); } + @Messages({ + "AnalysisResultDAO_getKeywordHitCounts_adhocName=Adhoc Results" + }) + public TreeResultsDTO getKeywordHitCounts(Long dataSourceId) throws IllegalArgumentException, ExecutionException { + List> allSets + = getSetCounts(BlackboardArtifact.Type.TSK_KEYWORD_HIT, BlackboardAttribute.Type.TSK_SET_NAME, dataSourceId).entrySet().stream() + .map(entry -> { + return new TreeItemDTO<>( + BlackboardArtifact.Type.TSK_KEYWORD_HIT.getTypeName(), + new AnalysisResultSetSearchParam(BlackboardArtifact.Type.TSK_KEYWORD_HIT, dataSourceId, entry.getKey()), + entry.getKey(), + entry.getKey() == null ? Bundle.AnalysisResultDAO_getKeywordHitCounts_adhocName() : entry.getKey(), + entry.getValue()); + }) + .sorted((a, b) -> StringUtils.compareIgnoreCase(a.getTypeData().getSetName(), b.getTypeData().getSetName(), true)) + .collect(Collectors.toList()); + + return new TreeResultsDTO<>(allSets); + } + + // GVDTODO +// public TreeResultsDTO getKeywordSetCounts(String setName /*or null for ad hoc */, Long dataSourceId) throws IllegalArgumentException, ExecutionException { +// if (dataSourceId != null && dataSourceId <= 0) { +// throw new IllegalArgumentException("Expected data source id to be > 0"); +// } +// +// try { +// // get artifact types and counts +// SleuthkitCase skCase = getCase(); +// String query = "set_name, search_type, search_term, COUNT(*) AS count \n" +// + "FROM ( \n" +// + " SELECT artifact_id, set_name, search_type, \n" +// // if search type is 1 or no regexp, the search_term is TSK_KEYWORD, otherwise TSK_KEYWORD_REGEXP +// + " CASE \n" +// + " WHEN search_type = 1 OR regexp_str IS NULL THEN \n" +// + " (SELECT value_text \n" +// + " FROM blackboard_attributes attr \n" +// + " WHERE attr.artifact_id = res.artifact_id \n" +// + " AND attr.attribute_type_id = " + BlackboardAttribute.Type.TSK_KEYWORD.getTypeID() + " \n" +// + " LIMIT 1) \n" +// + " ELSE \n" +// + " regexp_str \n" +// + " END AS search_term \n" +// + " FROM ( \n" +// // get set name, keyword_search_type, regex, and set name for each keyword +// + " SELECT \n" +// + " art.artifact_id AS artifact_id, \n" +// + " (SELECT value_text \n" +// + " FROM blackboard_attributes attr \n" +// + " WHERE attr.artifact_id = art.artifact_id \n" +// + " AND attr.attribute_type_id = " + BlackboardAttribute.Type.TSK_SET_NAME.getTypeID() + " \n" +// + " LIMIT 1) AS set_name, \n" +// + " (SELECT value_int32 \n" +// + " FROM blackboard_attributes attr \n" +// + " WHERE attr.artifact_id = art.artifact_id \n" +// + " AND attr.attribute_type_id = " + BlackboardAttribute.Type.TSK_KEYWORD_SEARCH_TYPE.getTypeID() + " \n" +// + " LIMIT 1) AS search_type, \n" +// + " (SELECT value_text \n" +// + " FROM blackboard_attributes attr \n" +// + " WHERE attr.artifact_id = art.artifact_id \n" +// + " AND attr.attribute_type_id = " + BlackboardAttribute.Type.TSK_KEYWORD_REGEXP.getTypeID() + " \n" +// + " LIMIT 1) AS regexp_str, \n" +// + " FROM blackboard_artifacts art \n" +// + " WHERE art.artifact_type_id = " + BlackboardArtifact.Type.TSK_KEYWORD_HIT.getTypeID() + " \n" +// + ((dataSourceId == null) ? "" : " AND art.data_source_obj_id = ?\n") +// + " ) res \n" +// + ((setName == null) ? " WHERE set_name IS NULL\n" : " WHERE set_name = ?\n") +// + ") \n" +// + "GROUP BY set_name, search_type, search_term"; +// +// // GVDTODO fix right here +// Map setCounts = new HashMap<>(); +// skCase.getCaseDbAccessManager().select(query, (resultSet) -> { +// try { +// while (resultSet.next()) { +// String setName = resultSet.getString("set_name"); +// long count = resultSet.getLong("count"); +// setCounts.put(setName, count); +// } +// } catch (SQLException ex) { +// logger.log(Level.WARNING, "An error occurred while fetching set name counts.", ex); +// } +// }); +// +// return setCounts; +// } catch (NoCurrentCaseException | TskCoreException ex) { +// throw new ExecutionException("An error occurred while fetching set counts", ex); +// } +// } + /** * Handles basic functionality of fetching and paging of analysis results. */ diff --git a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/AnalysisResultSetSearchParam.java b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/AnalysisResultSetSearchParam.java index 4a0bd28b4f..767491c3fe 100644 --- a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/AnalysisResultSetSearchParam.java +++ b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/AnalysisResultSetSearchParam.java @@ -24,7 +24,7 @@ import org.sleuthkit.datamodel.BlackboardArtifact; /** * Base class for search params for analysis results that filter by set name. */ -abstract class AnalysisResultSetSearchParam extends AnalysisResultSearchParam { +public class AnalysisResultSetSearchParam extends AnalysisResultSearchParam { private final String setName; From fd99b07e307548af73a576fb606fbee3fe53b096 Mon Sep 17 00:00:00 2001 From: Eugene Livis Date: Fri, 29 Oct 2021 17:33:50 -0400 Subject: [PATCH 32/38] Added paging --- .../autopsy/mainui/datamodel/TagsDAO.java | 150 +++++++++++++----- 1 file changed, 114 insertions(+), 36 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/TagsDAO.java b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/TagsDAO.java index 5c6059fa5d..5dcf62a35a 100755 --- a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/TagsDAO.java +++ b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/TagsDAO.java @@ -20,11 +20,17 @@ package org.sleuthkit.autopsy.mainui.datamodel; import com.google.common.cache.Cache; import com.google.common.cache.CacheBuilder; +import java.beans.PropertyChangeEvent; import java.util.ArrayList; import java.util.Arrays; +import java.util.Comparator; +import java.util.EnumSet; import java.util.List; +import java.util.Set; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; +import java.util.stream.Stream; import org.apache.commons.lang3.StringUtils; import org.openide.util.NbBundle; import org.openide.util.NbBundle.Messages; @@ -32,16 +38,19 @@ import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; import org.sleuthkit.autopsy.core.UserPreferences; import org.sleuthkit.autopsy.coreutils.TimeZoneUtils; +import org.sleuthkit.autopsy.mainui.nodes.DAOFetcher; import org.sleuthkit.datamodel.AbstractFile; +import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.BlackboardArtifactTag; import org.sleuthkit.datamodel.Content; import org.sleuthkit.datamodel.ContentTag; +import org.sleuthkit.datamodel.Tag; import org.sleuthkit.datamodel.TagName; import org.sleuthkit.datamodel.TskCoreException; /** - * Provides information to populate the results viewer for data in the tags - * section. + * Provides information to populate the results viewer for data in the allTags + section. */ @Messages({"TagsDAO.fileColumns.nameColLbl=Name", "TagsDAO.fileColumns.originalName=Original Name", @@ -122,28 +131,49 @@ public class TagsDAO { this.searchParamsCache.invalidate(searchParams); } - return searchParamsCache.get(searchParams, () -> fetchTagsDTOs(key.getTagName(), key.getTagType(), key.getDataSourceId(), startItem, maxCount)); - } + return searchParamsCache.get(searchParams, () -> fetchTagsDTOs(searchParams)); + } @NbBundle.Messages({"FileTag.name.text=File Tag", "ResultTag.name.text=Result Tag"}) - private SearchResultsDTO fetchTagsDTOs(TagName tagName, TagsSearchParams.TagType type, Long dataSourceId, long startItem, Long maxResultCount) throws NoCurrentCaseException, TskCoreException { - switch (type) { + private SearchResultsDTO fetchTagsDTOs(SearchParams cacheKey) throws NoCurrentCaseException, TskCoreException { + switch (cacheKey.getParamData().getTagType()) { case FILE: - return fetchFileTags(tagName, dataSourceId, startItem, maxResultCount); + return fetchFileTags(cacheKey); case RESULT: - return fetchResultTags(tagName, dataSourceId, startItem, maxResultCount); + return fetchResultTags(cacheKey); default: throw new IllegalArgumentException("Unsupported tag type"); } } - private SearchResultsDTO fetchResultTags(TagName tagName, Long dataSourceId, long startItem, Long maxResultCount) throws NoCurrentCaseException, TskCoreException { + /** + * Returns a list of paged tag results. + * + * @param tags The tag results. + * @param searchParams The search parameters including the paging. + * + * @return The list of paged tag results. + */ + List getPaged(List tags, SearchParams searchParams) { + Stream pagedTagsStream = tags.stream() + .sorted(Comparator.comparing((tag) -> tag.getId())) + .skip(searchParams.getStartItem()); - // ELTODO startItem, maxResultCount + if (searchParams.getMaxResultsCount() != null) { + pagedTagsStream = pagedTagsStream.limit(searchParams.getMaxResultsCount()); + } + + return pagedTagsStream.collect(Collectors.toList()); + } + + private SearchResultsDTO fetchResultTags(SearchParams cacheKey) throws NoCurrentCaseException, TskCoreException { + + Long dataSourceId = cacheKey.getParamData().getDataSourceId(); + TagName tagName = cacheKey.getParamData().getTagName(); - List tags = new ArrayList<>(); - // Use the blackboard artifact tags bearing the specified tag name as the tags. + // get all tag results + List allTags = new ArrayList<>(); List artifactTags = (dataSourceId != null && dataSourceId > 0) ? Case.getCurrentCaseThrows().getServices().getTagsManager().getBlackboardArtifactTagsByTagName(tagName, dataSourceId) : Case.getCurrentCaseThrows().getServices().getTagsManager().getBlackboardArtifactTagsByTagName(tagName); @@ -151,51 +181,57 @@ public class TagsDAO { String userName = System.getProperty(USER_NAME_PROPERTY); for (BlackboardArtifactTag tag : artifactTags) { if (userName.equals(tag.getUserName())) { - tags.add(tag); + allTags.add(tag); } } } else { - tags.addAll(artifactTags); + allTags.addAll(artifactTags); } + + // get current page of tag results + List pagedTags = getPaged(allTags, cacheKey); List fileRows = new ArrayList<>(); - for (BlackboardArtifactTag tag : tags) { - String name = tag.getContent().getName(); // As a backup. + for (Tag tag : pagedTags) { + BlackboardArtifactTag blackboardTag = (BlackboardArtifactTag) tag; + + String name = blackboardTag.getContent().getName(); // As a backup. try { - name = tag.getArtifact().getShortDescription(); + name = blackboardTag.getArtifact().getShortDescription(); } catch (TskCoreException ignore) { // it's a WARNING, skip } String contentPath; try { - contentPath = tag.getContent().getUniquePath(); + contentPath = blackboardTag.getContent().getUniquePath(); } catch (TskCoreException ex) { contentPath = NbBundle.getMessage(this.getClass(), "BlackboardArtifactTagNode.createSheet.unavail.text"); } - List cellValues = Arrays.asList( - name, + List cellValues = Arrays.asList(name, null, // GVDTODO translation column contentPath, - tag.getArtifact().getDisplayName(), - tag.getComment(), - tag.getUserName()); + blackboardTag.getArtifact().getDisplayName(), + blackboardTag.getComment(), + blackboardTag.getUserName()); fileRows.add(new BaseRowDTO( cellValues, RESULT_TAG_TYPE_ID, - tag.getId())); + blackboardTag.getId())); } - return new BaseSearchResultsDTO(RESULT_TAG_TYPE_ID, Bundle.ResultTag_name_text(), RESULT_TAG_COLUMNS, fileRows, 0, fileRows.size()); + return new BaseSearchResultsDTO(RESULT_TAG_TYPE_ID, Bundle.ResultTag_name_text(), RESULT_TAG_COLUMNS, fileRows, 0, allTags.size()); } - private SearchResultsDTO fetchFileTags(TagName tagName, Long dataSourceId, long startItem, Long maxResultCount) throws NoCurrentCaseException, TskCoreException { + private SearchResultsDTO fetchFileTags(SearchParams cacheKey) throws NoCurrentCaseException, TskCoreException { - // ELTODO startItem, maxResultCount + Long dataSourceId = cacheKey.getParamData().getDataSourceId(); + TagName tagName = cacheKey.getParamData().getTagName(); - List tags = new ArrayList<>(); + // get all tag results + List allTags = new ArrayList<>(); List contentTags = (dataSourceId != null && dataSourceId > 0) ? Case.getCurrentCaseThrows().getServices().getTagsManager().getContentTagsByTagName(tagName, dataSourceId) : Case.getCurrentCaseThrows().getServices().getTagsManager().getContentTagsByTagName(tagName); @@ -203,16 +239,20 @@ public class TagsDAO { String userName = System.getProperty(USER_NAME_PROPERTY); for (ContentTag tag : contentTags) { if (userName.equals(tag.getUserName())) { - tags.add(tag); + allTags.add(tag); } } } else { - tags.addAll(contentTags); + allTags.addAll(contentTags); } - + + // get current page of tag results + List pagedTags = getPaged(allTags, cacheKey); + List fileRows = new ArrayList<>(); - for (ContentTag tag : tags) { - Content content = tag.getContent(); + for (Tag tag : pagedTags) { + ContentTag contentTag = (ContentTag) tag; + Content content = contentTag.getContent(); String contentPath = content.getUniquePath(); AbstractFile file = content instanceof AbstractFile ? (AbstractFile) content : null; @@ -220,14 +260,14 @@ public class TagsDAO { content.getName(), null, // GVDTODO translation column contentPath, - tag.getComment(), + contentTag.getComment(), file != null ? TimeZoneUtils.getFormattedTime(file.getMtime()) : "", file != null ? TimeZoneUtils.getFormattedTime(file.getCtime()) : "", file != null ? TimeZoneUtils.getFormattedTime(file.getAtime()) : "", file != null ? TimeZoneUtils.getFormattedTime(file.getCrtime()) : "", content.getSize(), file != null ? StringUtils.defaultString(file.getMd5Hash()) : "", - tag.getUserName()); + contentTag.getUserName()); fileRows.add(new BaseRowDTO( cellValues, @@ -235,6 +275,44 @@ public class TagsDAO { file.getId())); } - return new BaseSearchResultsDTO(FILE_TAG_TYPE_ID, Bundle.FileTag_name_text(), FILE_TAG_COLUMNS, fileRows, 0, fileRows.size()); + return new BaseSearchResultsDTO(FILE_TAG_TYPE_ID, Bundle.FileTag_name_text(), FILE_TAG_COLUMNS, fileRows, 0, allTags.size()); } + + /** + * Handles fetching and paging of data for allTags. + */ + public static class TagFetcher extends DAOFetcher { + + /** + * Main constructor. + * + * @param params Parameters to handle fetching of data. + */ + public TagFetcher(TagsSearchParams params) { + super(params); + } + + @Override + public SearchResultsDTO getSearchResults(int pageSize, int pageIdx, boolean hardRefresh) throws ExecutionException { + return MainDAO.getInstance().getTagsDAO().getTags(this.getParameters(), pageIdx * pageSize, (long) pageSize, hardRefresh); + } + + @Override + public boolean isRefreshRequired(PropertyChangeEvent evt) { + TagsSearchParams params = this.getParameters(); + String eventType = evt.getPropertyName(); + if (eventType.equals(Case.Events.BLACKBOARD_ARTIFACT_TAG_ADDED.toString()) + || eventType.equals(Case.Events.BLACKBOARD_ARTIFACT_TAG_DELETED.toString())) { + + return params.getTagType() == TagsSearchParams.TagType.RESULT; + } + + if (eventType.equals(Case.Events.CONTENT_TAG_ADDED.toString()) + || eventType.equals(Case.Events.CONTENT_TAG_DELETED.toString())) { + + return params.getTagType() == TagsSearchParams.TagType.FILE; + } + return false; + } + } } From dd614db675e86225b36f27d25af178ae85909ed9 Mon Sep 17 00:00:00 2001 From: Greg DiCristofaro Date: Mon, 1 Nov 2021 10:50:24 -0400 Subject: [PATCH 33/38] query updates --- .../mainui/datamodel/AnalysisResultDAO.java | 219 ++++++++++-------- .../nodes/AnalysisResultTypeFactory.java | 126 +++++++--- 2 files changed, 213 insertions(+), 132 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/AnalysisResultDAO.java b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/AnalysisResultDAO.java index 275eb343cb..20c2d403c2 100644 --- a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/AnalysisResultDAO.java +++ b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/AnalysisResultDAO.java @@ -30,6 +30,7 @@ import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ExecutionException; +import java.util.function.BiFunction; import java.util.logging.Level; import java.util.stream.Collectors; import org.apache.commons.lang3.StringUtils; @@ -359,7 +360,7 @@ public class AnalysisResultDAO extends BlackboardArtifactDAO { * @throws IllegalArgumentException * @throws ExecutionException */ - Map getSetCounts(BlackboardArtifact.Type type, BlackboardAttribute.Type setNameAttr, Long dataSourceId) throws IllegalArgumentException, ExecutionException { + Map getSetCountsMap(BlackboardArtifact.Type type, BlackboardAttribute.Type setNameAttr, Long dataSourceId) throws IllegalArgumentException, ExecutionException { if (dataSourceId != null && dataSourceId <= 0) { throw new IllegalArgumentException("Expected data source id to be > 0"); } @@ -399,125 +400,143 @@ public class AnalysisResultDAO extends BlackboardArtifactDAO { } /** - * Get counts for individual sets of TSK_HASHSET_HIT's to be used in the + * Get counts for individual sets of the provided type to be used in the * tree view. * + * @param type The blackboard artifact type. * @param dataSourceId The data source object id for which the results * should be filtered or null if no data source * filtering. + * @param nullSetName For artifacts with no set, this is the name to + * provide. If null, artifacts without a set name will + * be ignored. + * @param converter Means of converting from data source id and set name + * to an AnalysisResultSetSearchParam * * @return The sets along with counts to display. * * @throws IllegalArgumentException * @throws ExecutionException */ - public TreeResultsDTO getHashSetCounts(Long dataSourceId) throws IllegalArgumentException, ExecutionException { - List> allSets - = getSetCounts(BlackboardArtifact.Type.TSK_HASHSET_HIT, BlackboardAttribute.Type.TSK_SET_NAME, dataSourceId).entrySet().stream() - .filter(entry -> entry.getKey() == null) - .map(entry -> new TreeItemDTO<>( - BlackboardArtifact.Type.TSK_HASHSET_HIT.getTypeName(), - new HashHitSearchParam(dataSourceId, entry.getKey()), - entry.getKey(), - entry.getKey(), - entry.getValue()) - ) + private TreeResultsDTO getSetCounts( + BlackboardArtifact.Type type, + Long dataSourceId, + String nullSetName, + BiFunction converter) throws IllegalArgumentException, ExecutionException { + + List> allSets + = getSetCountsMap(type, BlackboardAttribute.Type.TSK_SET_NAME, dataSourceId).entrySet().stream() + .filter(entry -> nullSetName != null || entry.getKey() != null) + .map(entry -> { + return new TreeItemDTO<>( + type.getTypeName(), + converter.apply(dataSourceId, entry.getKey()), + entry.getKey(), + entry.getKey() == null ? nullSetName : entry.getKey(), + entry.getValue()); + }) .sorted((a, b) -> a.getDisplayName().compareToIgnoreCase(b.getDisplayName())) .collect(Collectors.toList()); return new TreeResultsDTO<>(allSets); } - @Messages({ - "AnalysisResultDAO_getKeywordHitCounts_adhocName=Adhoc Results" - }) - public TreeResultsDTO getKeywordHitCounts(Long dataSourceId) throws IllegalArgumentException, ExecutionException { - List> allSets - = getSetCounts(BlackboardArtifact.Type.TSK_KEYWORD_HIT, BlackboardAttribute.Type.TSK_SET_NAME, dataSourceId).entrySet().stream() - .map(entry -> { - return new TreeItemDTO<>( - BlackboardArtifact.Type.TSK_KEYWORD_HIT.getTypeName(), - new AnalysisResultSetSearchParam(BlackboardArtifact.Type.TSK_KEYWORD_HIT, dataSourceId, entry.getKey()), - entry.getKey(), - entry.getKey() == null ? Bundle.AnalysisResultDAO_getKeywordHitCounts_adhocName() : entry.getKey(), - entry.getValue()); - }) - .sorted((a, b) -> StringUtils.compareIgnoreCase(a.getTypeData().getSetName(), b.getTypeData().getSetName(), true)) - .collect(Collectors.toList()); - - return new TreeResultsDTO<>(allSets); + public TreeResultsDTO getHashHitSetCounts(Long dataSourceId) throws IllegalArgumentException, ExecutionException { + return getSetCounts(BlackboardArtifact.Type.TSK_HASHSET_HIT, dataSourceId, null, (dsId, setName) -> new HashHitSearchParam(dsId, setName)); } - // GVDTODO -// public TreeResultsDTO getKeywordSetCounts(String setName /*or null for ad hoc */, Long dataSourceId) throws IllegalArgumentException, ExecutionException { -// if (dataSourceId != null && dataSourceId <= 0) { -// throw new IllegalArgumentException("Expected data source id to be > 0"); -// } -// -// try { -// // get artifact types and counts -// SleuthkitCase skCase = getCase(); -// String query = "set_name, search_type, search_term, COUNT(*) AS count \n" -// + "FROM ( \n" -// + " SELECT artifact_id, set_name, search_type, \n" -// // if search type is 1 or no regexp, the search_term is TSK_KEYWORD, otherwise TSK_KEYWORD_REGEXP -// + " CASE \n" -// + " WHEN search_type = 1 OR regexp_str IS NULL THEN \n" -// + " (SELECT value_text \n" -// + " FROM blackboard_attributes attr \n" -// + " WHERE attr.artifact_id = res.artifact_id \n" -// + " AND attr.attribute_type_id = " + BlackboardAttribute.Type.TSK_KEYWORD.getTypeID() + " \n" -// + " LIMIT 1) \n" -// + " ELSE \n" -// + " regexp_str \n" -// + " END AS search_term \n" -// + " FROM ( \n" -// // get set name, keyword_search_type, regex, and set name for each keyword -// + " SELECT \n" -// + " art.artifact_id AS artifact_id, \n" -// + " (SELECT value_text \n" -// + " FROM blackboard_attributes attr \n" -// + " WHERE attr.artifact_id = art.artifact_id \n" -// + " AND attr.attribute_type_id = " + BlackboardAttribute.Type.TSK_SET_NAME.getTypeID() + " \n" -// + " LIMIT 1) AS set_name, \n" -// + " (SELECT value_int32 \n" -// + " FROM blackboard_attributes attr \n" -// + " WHERE attr.artifact_id = art.artifact_id \n" -// + " AND attr.attribute_type_id = " + BlackboardAttribute.Type.TSK_KEYWORD_SEARCH_TYPE.getTypeID() + " \n" -// + " LIMIT 1) AS search_type, \n" -// + " (SELECT value_text \n" -// + " FROM blackboard_attributes attr \n" -// + " WHERE attr.artifact_id = art.artifact_id \n" -// + " AND attr.attribute_type_id = " + BlackboardAttribute.Type.TSK_KEYWORD_REGEXP.getTypeID() + " \n" -// + " LIMIT 1) AS regexp_str, \n" -// + " FROM blackboard_artifacts art \n" -// + " WHERE art.artifact_type_id = " + BlackboardArtifact.Type.TSK_KEYWORD_HIT.getTypeID() + " \n" -// + ((dataSourceId == null) ? "" : " AND art.data_source_obj_id = ?\n") -// + " ) res \n" -// + ((setName == null) ? " WHERE set_name IS NULL\n" : " WHERE set_name = ?\n") -// + ") \n" -// + "GROUP BY set_name, search_type, search_term"; -// -// // GVDTODO fix right here -// Map setCounts = new HashMap<>(); -// skCase.getCaseDbAccessManager().select(query, (resultSet) -> { -// try { -// while (resultSet.next()) { -// String setName = resultSet.getString("set_name"); -// long count = resultSet.getLong("count"); -// setCounts.put(setName, count); -// } -// } catch (SQLException ex) { -// logger.log(Level.WARNING, "An error occurred while fetching set name counts.", ex); -// } -// }); -// -// return setCounts; -// } catch (NoCurrentCaseException | TskCoreException ex) { -// throw new ExecutionException("An error occurred while fetching set counts", ex); -// } -// } + public TreeResultsDTO getSetCounts(BlackboardArtifact.Type type, Long dataSourceId, String nullSetName) throws IllegalArgumentException, ExecutionException { + return getSetCounts(type, dataSourceId, nullSetName, (dsId, setName) -> new AnalysisResultSetSearchParam(type, dsId, setName)); + } +// @Messages({ + // "AnalysisResultDAO_getKeywordHitCounts_adhocName=Adhoc Results" + // }) + // public TreeResultsDTO getKeywordHitCounts(Long dataSourceId) throws IllegalArgumentException, ExecutionException { + // List> allSets + // = getSetCountsMap(BlackboardArtifact.Type.TSK_KEYWORD_HIT, BlackboardAttribute.Type.TSK_SET_NAME, dataSourceId).entrySet().stream() + // .map(entry -> { + // return new TreeItemDTO<>( + // BlackboardArtifact.Type.TSK_KEYWORD_HIT.getTypeName(), + // new AnalysisResultSetSearchParam(BlackboardArtifact.Type.TSK_KEYWORD_HIT, dataSourceId, entry.getKey()), + // entry.getKey(), + // entry.getKey() == null ? Bundle.AnalysisResultDAO_getKeywordHitCounts_adhocName() : entry.getKey(), + // entry.getValue()); + // }) + // .sorted((a, b) -> StringUtils.compareIgnoreCase(a.getTypeData().getSetName(), b.getTypeData().getSetName(), true)) + // .collect(Collectors.toList()); + // + // return new TreeResultsDTO<>(allSets); + // } + // GVDTODO + // public TreeResultsDTO getKeywordSetCounts(String setName /*or null for ad hoc */, Long dataSourceId) throws IllegalArgumentException, ExecutionException { + // if (dataSourceId != null && dataSourceId <= 0) { + // throw new IllegalArgumentException("Expected data source id to be > 0"); + // } + // + // try { + // // get artifact types and counts + // SleuthkitCase skCase = getCase(); + // String query = "set_name, search_type, search_term, COUNT(*) AS count \n" + // + "FROM ( \n" + // + " SELECT artifact_id, set_name, search_type, \n" + // // if search type is 1 or no regexp, the search_term is TSK_KEYWORD, otherwise TSK_KEYWORD_REGEXP + // + " CASE \n" + // + " WHEN search_type = 1 OR regexp_str IS NULL THEN \n" + // + " (SELECT value_text \n" + // + " FROM blackboard_attributes attr \n" + // + " WHERE attr.artifact_id = res.artifact_id \n" + // + " AND attr.attribute_type_id = " + BlackboardAttribute.Type.TSK_KEYWORD.getTypeID() + " \n" + // + " LIMIT 1) \n" + // + " ELSE \n" + // + " regexp_str \n" + // + " END AS search_term \n" + // + " FROM ( \n" + // // get set name, keyword_search_type, regex, and set name for each keyword + // + " SELECT \n" + // + " art.artifact_id AS artifact_id, \n" + // + " (SELECT value_text \n" + // + " FROM blackboard_attributes attr \n" + // + " WHERE attr.artifact_id = art.artifact_id \n" + // + " AND attr.attribute_type_id = " + BlackboardAttribute.Type.TSK_SET_NAME.getTypeID() + " \n" + // + " LIMIT 1) AS set_name, \n" + // + " (SELECT value_int32 \n" + // + " FROM blackboard_attributes attr \n" + // + " WHERE attr.artifact_id = art.artifact_id \n" + // + " AND attr.attribute_type_id = " + BlackboardAttribute.Type.TSK_KEYWORD_SEARCH_TYPE.getTypeID() + " \n" + // + " LIMIT 1) AS search_type, \n" + // + " (SELECT value_text \n" + // + " FROM blackboard_attributes attr \n" + // + " WHERE attr.artifact_id = art.artifact_id \n" + // + " AND attr.attribute_type_id = " + BlackboardAttribute.Type.TSK_KEYWORD_REGEXP.getTypeID() + " \n" + // + " LIMIT 1) AS regexp_str, \n" + // + " FROM blackboard_artifacts art \n" + // + " WHERE art.artifact_type_id = " + BlackboardArtifact.Type.TSK_KEYWORD_HIT.getTypeID() + " \n" + // + ((dataSourceId == null) ? "" : " AND art.data_source_obj_id = ?\n") + // + " ) res \n" + // + ((setName == null) ? " WHERE set_name IS NULL\n" : " WHERE set_name = ?\n") + // + ") \n" + // + "GROUP BY set_name, search_type, search_term"; + // + // // GVDTODO fix right here + // Map setCounts = new HashMap<>(); + // skCase.getCaseDbAccessManager().select(query, (resultSet) -> { + // try { + // while (resultSet.next()) { + // String setName = resultSet.getString("set_name"); + // long count = resultSet.getLong("count"); + // setCounts.put(setName, count); + // } + // } catch (SQLException ex) { + // logger.log(Level.WARNING, "An error occurred while fetching set name counts.", ex); + // } + // }); + // + // return setCounts; + // } catch (NoCurrentCaseException | TskCoreException ex) { + // throw new ExecutionException("An error occurred while fetching set counts", ex); + // } + // } /** * Handles basic functionality of fetching and paging of analysis results. */ diff --git a/Core/src/org/sleuthkit/autopsy/mainui/nodes/AnalysisResultTypeFactory.java b/Core/src/org/sleuthkit/autopsy/mainui/nodes/AnalysisResultTypeFactory.java index 5dc9063738..22f1ce6d2b 100644 --- a/Core/src/org/sleuthkit/autopsy/mainui/nodes/AnalysisResultTypeFactory.java +++ b/Core/src/org/sleuthkit/autopsy/mainui/nodes/AnalysisResultTypeFactory.java @@ -20,6 +20,7 @@ package org.sleuthkit.autopsy.mainui.nodes; import java.beans.PropertyChangeEvent; import java.util.concurrent.ExecutionException; +import org.openide.nodes.ChildFactory; import org.openide.nodes.Children; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; @@ -107,6 +108,44 @@ public class AnalysisResultTypeFactory extends TreeChildFactory { + static class TreeTypeNode extends TreeNode { /** * Main constructor. * * @param itemData The data to display. */ - public HashHitTypeNode(TreeResultsDTO.TreeItemDTO itemData) { + public TreeTypeNode(TreeResultsDTO.TreeItemDTO itemData, ChildFactory childFactory) { super(itemData.getTypeData().getArtifactType().getTypeName(), getIconPath(itemData.getTypeData().getArtifactType()), itemData, - Children.create(new HashHitSetFactory(itemData.getTypeData().getDataSourceId()), true), + Children.create(childFactory, true), getDefaultLookup(itemData)); } @@ -152,6 +191,15 @@ public class AnalysisResultTypeFactory extends TreeChildFactory { + + } /** * Factory displaying all hashset sets with count in the tree. @@ -178,39 +226,13 @@ public class AnalysisResultTypeFactory extends TreeChildFactory getChildResults() throws IllegalArgumentException, ExecutionException { - return MainDAO.getInstance().getAnalysisResultDAO().getHashSetCounts(dataSourceId); + return MainDAO.getInstance().getAnalysisResultDAO().getHashHitSetCounts(dataSourceId); } @Override public boolean isRefreshRequired(PropertyChangeEvent evt) { - String eventType = evt.getPropertyName(); - if (eventType.equals(IngestManager.IngestModuleEvent.DATA_ADDED.toString())) { - /** - * This is a stop gap measure until a different way of handling - * the closing of cases is worked out. Currently, remote events - * may be received for a case that is already closed. - */ - try { - Case.getCurrentCaseThrows(); - /** - * Due to some unresolved issues with how cases are closed, - * it is possible for the event to have a null oldValue if - * the event is a remote event. - */ - final ModuleDataEvent event = (ModuleDataEvent) evt.getOldValue(); - // GVDTODO it may be necessary to have more fine-grained check for refresh here. - if (null != event && BlackboardArtifact.Type.TSK_HASHSET_HIT.equals(event.getBlackboardArtifactType())) { - return true; - } - } catch (NoCurrentCaseException notUsed) { - /** - * Case is closed, do nothing. - */ - } - } - return false; + return AnalysisResultTypeFactory.isRefreshRequired(BlackboardArtifact.Type.TSK_HASHSET_HIT, evt); } - } /** @@ -233,4 +255,44 @@ public class AnalysisResultTypeFactory extends TreeChildFactory Date: Mon, 1 Nov 2021 12:47:39 -0400 Subject: [PATCH 34/38] commenting out work towards specialized analysis result items --- .../mainui/datamodel/AnalysisResultDAO.java | 371 +++++++----------- .../nodes/AnalysisResultTypeFactory.java | 339 +++++++++------- 2 files changed, 346 insertions(+), 364 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/AnalysisResultDAO.java b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/AnalysisResultDAO.java index 20c2d403c2..38481604ae 100644 --- a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/AnalysisResultDAO.java +++ b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/AnalysisResultDAO.java @@ -347,196 +347,110 @@ public class AnalysisResultDAO extends BlackboardArtifactDAO { } } - /** - * - * @param type The artifact type to filter on. - * @param setNameAttr The blackboard attribute denoting the set name. - * @param dataSourceId The data source object id for which the results - * should be filtered or null if no data source - * filtering. - * - * @return A mapping of set names to their counts. - * - * @throws IllegalArgumentException - * @throws ExecutionException - */ - Map getSetCountsMap(BlackboardArtifact.Type type, BlackboardAttribute.Type setNameAttr, Long dataSourceId) throws IllegalArgumentException, ExecutionException { - if (dataSourceId != null && dataSourceId <= 0) { - throw new IllegalArgumentException("Expected data source id to be > 0"); - } +// GVDTODO code to use in a future PR +// /** +// * +// * @param type The artifact type to filter on. +// * @param setNameAttr The blackboard attribute denoting the set name. +// * @param dataSourceId The data source object id for which the results +// * should be filtered or null if no data source +// * filtering. +// * +// * @return A mapping of set names to their counts. +// * +// * @throws IllegalArgumentException +// * @throws ExecutionException +// */ +// Map getSetCountsMap(BlackboardArtifact.Type type, BlackboardAttribute.Type setNameAttr, Long dataSourceId) throws IllegalArgumentException, ExecutionException { +// if (dataSourceId != null && dataSourceId <= 0) { +// throw new IllegalArgumentException("Expected data source id to be > 0"); +// } +// +// try { +// // get artifact types and counts +// SleuthkitCase skCase = getCase(); +// String query = " set_name, COUNT(*) AS count \n" +// + "FROM ( \n" +// + " SELECT art.artifact_id, \n" +// + " (SELECT value_text \n" +// + " FROM blackboard_attributes attr \n" +// + " WHERE attr.artifact_id = art.artifact_id AND attr.attribute_type_id = " + setNameAttr.getTypeID() + " LIMIT 1) AS set_name \n" +// + " FROM blackboard_artifacts art \n" +// + " WHERE art.artifact_type_id = " + type.getTypeID() + " \n" +// + ((dataSourceId == null) ? "" : " AND art.data_source_obj_id = " + dataSourceId + " \n") +// + ") \n" +// + "GROUP BY set_name"; +// +// Map setCounts = new HashMap<>(); +// skCase.getCaseDbAccessManager().select(query, (resultSet) -> { +// try { +// while (resultSet.next()) { +// String setName = resultSet.getString("set_name"); +// long count = resultSet.getLong("count"); +// setCounts.put(setName, count); +// } +// } catch (SQLException ex) { +// logger.log(Level.WARNING, "An error occurred while fetching set name counts.", ex); +// } +// }); +// +// return setCounts; +// } catch (NoCurrentCaseException | TskCoreException ex) { +// throw new ExecutionException("An error occurred while fetching set counts", ex); +// } +// } +// +// /** +// * Get counts for individual sets of the provided type to be used in the +// * tree view. +// * +// * @param type The blackboard artifact type. +// * @param dataSourceId The data source object id for which the results +// * should be filtered or null if no data source +// * filtering. +// * @param nullSetName For artifacts with no set, this is the name to +// * provide. If null, artifacts without a set name will +// * be ignored. +// * @param converter Means of converting from data source id and set name +// * to an AnalysisResultSetSearchParam +// * +// * @return The sets along with counts to display. +// * +// * @throws IllegalArgumentException +// * @throws ExecutionException +// */ +// private TreeResultsDTO getSetCounts( +// BlackboardArtifact.Type type, +// Long dataSourceId, +// String nullSetName, +// BiFunction converter) throws IllegalArgumentException, ExecutionException { +// +// List> allSets +// = getSetCountsMap(type, BlackboardAttribute.Type.TSK_SET_NAME, dataSourceId).entrySet().stream() +// .filter(entry -> nullSetName != null || entry.getKey() != null) +// .map(entry -> { +// return new TreeItemDTO<>( +// type.getTypeName(), +// converter.apply(dataSourceId, entry.getKey()), +// entry.getKey(), +// entry.getKey() == null ? nullSetName : entry.getKey(), +// entry.getValue()); +// }) +// .sorted((a, b) -> a.getDisplayName().compareToIgnoreCase(b.getDisplayName())) +// .collect(Collectors.toList()); +// +// return new TreeResultsDTO<>(allSets); +// } +// +// public TreeResultsDTO getHashHitSetCounts(Long dataSourceId) throws IllegalArgumentException, ExecutionException { +// return getSetCounts(BlackboardArtifact.Type.TSK_HASHSET_HIT, dataSourceId, null, (dsId, setName) -> new HashHitSearchParam(dsId, setName)); +// } +// +// public TreeResultsDTO getSetCounts(BlackboardArtifact.Type type, Long dataSourceId, String nullSetName) throws IllegalArgumentException, ExecutionException { +// return getSetCounts(type, dataSourceId, nullSetName, (dsId, setName) -> new AnalysisResultSetSearchParam(type, dsId, setName)); +// } - try { - // get artifact types and counts - SleuthkitCase skCase = getCase(); - String query = " set_name, COUNT(*) AS count \n" - + "FROM ( \n" - + " SELECT art.artifact_id, \n" - + " (SELECT value_text \n" - + " FROM blackboard_attributes attr \n" - + " WHERE attr.artifact_id = art.artifact_id AND attr.attribute_type_id = " + setNameAttr.getTypeID() + " LIMIT 1) AS set_name \n" - + " FROM blackboard_artifacts art \n" - + " WHERE art.artifact_type_id = " + type.getTypeID() + " \n" - + ((dataSourceId == null) ? "" : " AND art.data_source_obj_id = " + dataSourceId + " \n") - + ") \n" - + "GROUP BY set_name"; - - Map setCounts = new HashMap<>(); - skCase.getCaseDbAccessManager().select(query, (resultSet) -> { - try { - while (resultSet.next()) { - String setName = resultSet.getString("set_name"); - long count = resultSet.getLong("count"); - setCounts.put(setName, count); - } - } catch (SQLException ex) { - logger.log(Level.WARNING, "An error occurred while fetching set name counts.", ex); - } - }); - - return setCounts; - } catch (NoCurrentCaseException | TskCoreException ex) { - throw new ExecutionException("An error occurred while fetching set counts", ex); - } - } - - /** - * Get counts for individual sets of the provided type to be used in the - * tree view. - * - * @param type The blackboard artifact type. - * @param dataSourceId The data source object id for which the results - * should be filtered or null if no data source - * filtering. - * @param nullSetName For artifacts with no set, this is the name to - * provide. If null, artifacts without a set name will - * be ignored. - * @param converter Means of converting from data source id and set name - * to an AnalysisResultSetSearchParam - * - * @return The sets along with counts to display. - * - * @throws IllegalArgumentException - * @throws ExecutionException - */ - private TreeResultsDTO getSetCounts( - BlackboardArtifact.Type type, - Long dataSourceId, - String nullSetName, - BiFunction converter) throws IllegalArgumentException, ExecutionException { - - List> allSets - = getSetCountsMap(type, BlackboardAttribute.Type.TSK_SET_NAME, dataSourceId).entrySet().stream() - .filter(entry -> nullSetName != null || entry.getKey() != null) - .map(entry -> { - return new TreeItemDTO<>( - type.getTypeName(), - converter.apply(dataSourceId, entry.getKey()), - entry.getKey(), - entry.getKey() == null ? nullSetName : entry.getKey(), - entry.getValue()); - }) - .sorted((a, b) -> a.getDisplayName().compareToIgnoreCase(b.getDisplayName())) - .collect(Collectors.toList()); - - return new TreeResultsDTO<>(allSets); - } - - public TreeResultsDTO getHashHitSetCounts(Long dataSourceId) throws IllegalArgumentException, ExecutionException { - return getSetCounts(BlackboardArtifact.Type.TSK_HASHSET_HIT, dataSourceId, null, (dsId, setName) -> new HashHitSearchParam(dsId, setName)); - } - - public TreeResultsDTO getSetCounts(BlackboardArtifact.Type type, Long dataSourceId, String nullSetName) throws IllegalArgumentException, ExecutionException { - return getSetCounts(type, dataSourceId, nullSetName, (dsId, setName) -> new AnalysisResultSetSearchParam(type, dsId, setName)); - } - -// @Messages({ - // "AnalysisResultDAO_getKeywordHitCounts_adhocName=Adhoc Results" - // }) - // public TreeResultsDTO getKeywordHitCounts(Long dataSourceId) throws IllegalArgumentException, ExecutionException { - // List> allSets - // = getSetCountsMap(BlackboardArtifact.Type.TSK_KEYWORD_HIT, BlackboardAttribute.Type.TSK_SET_NAME, dataSourceId).entrySet().stream() - // .map(entry -> { - // return new TreeItemDTO<>( - // BlackboardArtifact.Type.TSK_KEYWORD_HIT.getTypeName(), - // new AnalysisResultSetSearchParam(BlackboardArtifact.Type.TSK_KEYWORD_HIT, dataSourceId, entry.getKey()), - // entry.getKey(), - // entry.getKey() == null ? Bundle.AnalysisResultDAO_getKeywordHitCounts_adhocName() : entry.getKey(), - // entry.getValue()); - // }) - // .sorted((a, b) -> StringUtils.compareIgnoreCase(a.getTypeData().getSetName(), b.getTypeData().getSetName(), true)) - // .collect(Collectors.toList()); - // - // return new TreeResultsDTO<>(allSets); - // } - // GVDTODO - // public TreeResultsDTO getKeywordSetCounts(String setName /*or null for ad hoc */, Long dataSourceId) throws IllegalArgumentException, ExecutionException { - // if (dataSourceId != null && dataSourceId <= 0) { - // throw new IllegalArgumentException("Expected data source id to be > 0"); - // } - // - // try { - // // get artifact types and counts - // SleuthkitCase skCase = getCase(); - // String query = "set_name, search_type, search_term, COUNT(*) AS count \n" - // + "FROM ( \n" - // + " SELECT artifact_id, set_name, search_type, \n" - // // if search type is 1 or no regexp, the search_term is TSK_KEYWORD, otherwise TSK_KEYWORD_REGEXP - // + " CASE \n" - // + " WHEN search_type = 1 OR regexp_str IS NULL THEN \n" - // + " (SELECT value_text \n" - // + " FROM blackboard_attributes attr \n" - // + " WHERE attr.artifact_id = res.artifact_id \n" - // + " AND attr.attribute_type_id = " + BlackboardAttribute.Type.TSK_KEYWORD.getTypeID() + " \n" - // + " LIMIT 1) \n" - // + " ELSE \n" - // + " regexp_str \n" - // + " END AS search_term \n" - // + " FROM ( \n" - // // get set name, keyword_search_type, regex, and set name for each keyword - // + " SELECT \n" - // + " art.artifact_id AS artifact_id, \n" - // + " (SELECT value_text \n" - // + " FROM blackboard_attributes attr \n" - // + " WHERE attr.artifact_id = art.artifact_id \n" - // + " AND attr.attribute_type_id = " + BlackboardAttribute.Type.TSK_SET_NAME.getTypeID() + " \n" - // + " LIMIT 1) AS set_name, \n" - // + " (SELECT value_int32 \n" - // + " FROM blackboard_attributes attr \n" - // + " WHERE attr.artifact_id = art.artifact_id \n" - // + " AND attr.attribute_type_id = " + BlackboardAttribute.Type.TSK_KEYWORD_SEARCH_TYPE.getTypeID() + " \n" - // + " LIMIT 1) AS search_type, \n" - // + " (SELECT value_text \n" - // + " FROM blackboard_attributes attr \n" - // + " WHERE attr.artifact_id = art.artifact_id \n" - // + " AND attr.attribute_type_id = " + BlackboardAttribute.Type.TSK_KEYWORD_REGEXP.getTypeID() + " \n" - // + " LIMIT 1) AS regexp_str, \n" - // + " FROM blackboard_artifacts art \n" - // + " WHERE art.artifact_type_id = " + BlackboardArtifact.Type.TSK_KEYWORD_HIT.getTypeID() + " \n" - // + ((dataSourceId == null) ? "" : " AND art.data_source_obj_id = ?\n") - // + " ) res \n" - // + ((setName == null) ? " WHERE set_name IS NULL\n" : " WHERE set_name = ?\n") - // + ") \n" - // + "GROUP BY set_name, search_type, search_term"; - // - // // GVDTODO fix right here - // Map setCounts = new HashMap<>(); - // skCase.getCaseDbAccessManager().select(query, (resultSet) -> { - // try { - // while (resultSet.next()) { - // String setName = resultSet.getString("set_name"); - // long count = resultSet.getLong("count"); - // setCounts.put(setName, count); - // } - // } catch (SQLException ex) { - // logger.log(Level.WARNING, "An error occurred while fetching set name counts.", ex); - // } - // }); - // - // return setCounts; - // } catch (NoCurrentCaseException | TskCoreException ex) { - // throw new ExecutionException("An error occurred while fetching set counts", ex); - // } - // } + /** * Handles basic functionality of fetching and paging of analysis results. */ @@ -582,43 +496,44 @@ public class AnalysisResultDAO extends BlackboardArtifactDAO { } } - /** - * Handles fetching and paging of hashset hits. - */ - public static class HashsetResultFetcher extends AbstractAnalysisResultFetcher { - - /** - * Main constructor. - * - * @param params Parameters to handle fetching of data. - */ - public HashsetResultFetcher(HashHitSearchParam params) { - super(params); - } - - @Override - public SearchResultsDTO getSearchResults(int pageSize, int pageIdx, boolean hardRefresh) throws ExecutionException { - return MainDAO.getInstance().getAnalysisResultDAO().getHashHitsForTable(this.getParameters(), pageIdx * pageSize, (long) pageSize, hardRefresh); - } - } - - /** - * Handles fetching and paging of keyword hits. - */ - public static class KeywordHitResultFetcher extends AbstractAnalysisResultFetcher { - - /** - * Main constructor. - * - * @param params Parameters to handle fetching of data. - */ - public KeywordHitResultFetcher(KeywordHitSearchParam params) { - super(params); - } - - @Override - public SearchResultsDTO getSearchResults(int pageSize, int pageIdx, boolean hardRefresh) throws ExecutionException { - return MainDAO.getInstance().getAnalysisResultDAO().getKeywordHitsForTable(this.getParameters(), pageIdx * pageSize, (long) pageSize, hardRefresh); - } - } +// GVDTODO code to use in a future PR +// /** +// * Handles fetching and paging of hashset hits. +// */ +// public static class HashsetResultFetcher extends AbstractAnalysisResultFetcher { +// +// /** +// * Main constructor. +// * +// * @param params Parameters to handle fetching of data. +// */ +// public HashsetResultFetcher(HashHitSearchParam params) { +// super(params); +// } +// +// @Override +// public SearchResultsDTO getSearchResults(int pageSize, int pageIdx, boolean hardRefresh) throws ExecutionException { +// return MainDAO.getInstance().getAnalysisResultDAO().getHashHitsForTable(this.getParameters(), pageIdx * pageSize, (long) pageSize, hardRefresh); +// } +// } +// +// /** +// * Handles fetching and paging of keyword hits. +// */ +// public static class KeywordHitResultFetcher extends AbstractAnalysisResultFetcher { +// +// /** +// * Main constructor. +// * +// * @param params Parameters to handle fetching of data. +// */ +// public KeywordHitResultFetcher(KeywordHitSearchParam params) { +// super(params); +// } +// +// @Override +// public SearchResultsDTO getSearchResults(int pageSize, int pageIdx, boolean hardRefresh) throws ExecutionException { +// return MainDAO.getInstance().getAnalysisResultDAO().getKeywordHitsForTable(this.getParameters(), pageIdx * pageSize, (long) pageSize, hardRefresh); +// } +// } } diff --git a/Core/src/org/sleuthkit/autopsy/mainui/nodes/AnalysisResultTypeFactory.java b/Core/src/org/sleuthkit/autopsy/mainui/nodes/AnalysisResultTypeFactory.java index 22f1ce6d2b..4c43fcb5a7 100644 --- a/Core/src/org/sleuthkit/autopsy/mainui/nodes/AnalysisResultTypeFactory.java +++ b/Core/src/org/sleuthkit/autopsy/mainui/nodes/AnalysisResultTypeFactory.java @@ -18,10 +18,10 @@ */ package org.sleuthkit.autopsy.mainui.nodes; +import com.google.common.collect.ImmutableSet; import java.beans.PropertyChangeEvent; +import java.util.Set; import java.util.concurrent.ExecutionException; -import org.openide.nodes.ChildFactory; -import org.openide.nodes.Children; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; import org.sleuthkit.autopsy.corecomponents.DataResultTopComponent; @@ -30,7 +30,6 @@ import org.sleuthkit.autopsy.ingest.IngestManager; import org.sleuthkit.autopsy.ingest.ModuleDataEvent; import org.sleuthkit.autopsy.mainui.datamodel.AnalysisResultDAO; import org.sleuthkit.autopsy.mainui.datamodel.AnalysisResultSearchParam; -import org.sleuthkit.autopsy.mainui.datamodel.HashHitSearchParam; import org.sleuthkit.autopsy.mainui.datamodel.MainDAO; import org.sleuthkit.autopsy.mainui.datamodel.TreeResultsDTO; import org.sleuthkit.datamodel.BlackboardArtifact; @@ -40,6 +39,12 @@ import org.sleuthkit.datamodel.BlackboardArtifact.Category; * Factory for displaying analysis result types in the tree. */ public class AnalysisResultTypeFactory extends TreeChildFactory { + + private static Set SET_TREE_ARTIFACTS = ImmutableSet.of( + BlackboardArtifact.Type.TSK_HASHSET_HIT.getTypeID(), + BlackboardArtifact.Type.TSK_INTERESTING_ARTIFACT_HIT.getTypeID(), + BlackboardArtifact.Type.TSK_INTERESTING_FILE_HIT.getTypeID() + ); /** * Returns the path to the icon to use for this artifact type. @@ -71,11 +76,13 @@ public class AnalysisResultTypeFactory extends TreeChildFactory createNewNode(TreeResultsDTO.TreeItemDTO rowData) { - if (BlackboardArtifact.Type.TSK_HASHSET_HIT.equals(rowData.getTypeData().getArtifactType())) { - return new HashHitTypeNode(rowData); - } else { - return new AnalysisResultTypeTreeNode(rowData); - } +// if (SET_TREE_ARTIFACTS.contains(rowData.getTypeData().getArtifactType().getTypeID())) { +// return new TreeTypeNode(rowData, new TreeSetFactory(rowData.getTypeData().getArtifactType(), dataSourceId, null)); +// } else if (BlackboardArtifact.Type.TSK_KEYWORD_HIT.equals(rowData.getTypeData().getArtifactType())) { +// return new TreeTypeNode(rowData, new TreeSetFactory(rowData.getTypeData().getArtifactType(), dataSourceId, null)); +// } else { + return new AnalysisResultTypeTreeNode(rowData); +// } } @Override @@ -149,7 +156,7 @@ public class AnalysisResultTypeFactory extends TreeChildFactory { + static class AnalysisResultTypeTreeNode extends TreeNode { /** * Main constructor. @@ -168,131 +175,191 @@ public class AnalysisResultTypeFactory extends TreeChildFactory { - - /** - * Main constructor. - * - * @param itemData The data to display. - */ - public TreeTypeNode(TreeResultsDTO.TreeItemDTO itemData, ChildFactory childFactory) { - super(itemData.getTypeData().getArtifactType().getTypeName(), - getIconPath(itemData.getTypeData().getArtifactType()), - itemData, - Children.create(childFactory, true), - getDefaultLookup(itemData)); - } - - @Override - public void respondSelection(DataResultTopComponent dataResultPanel) { - // GVDTODO...NO OP??? - } - } - - - - /** - * The root hashset hit type in the tree. - */ - public static class HashHitTypeNode extends TreeNode { - - } - - /** - * Factory displaying all hashset sets with count in the tree. - */ - public static class HashHitSetFactory extends TreeChildFactory { - - private final Long dataSourceId; - - /** - * Main constructor. - * - * @param dataSourceId The data source object id for which the results - * should be filtered or null if no data source - * filtering. - */ - public HashHitSetFactory(Long dataSourceId) { - this.dataSourceId = dataSourceId; - } - - @Override - protected TreeNode createNewNode(TreeResultsDTO.TreeItemDTO rowData) { - return new HashHitSetNode(rowData); - } - - @Override - protected TreeResultsDTO getChildResults() throws IllegalArgumentException, ExecutionException { - return MainDAO.getInstance().getAnalysisResultDAO().getHashHitSetCounts(dataSourceId); - } - - @Override - public boolean isRefreshRequired(PropertyChangeEvent evt) { - return AnalysisResultTypeFactory.isRefreshRequired(BlackboardArtifact.Type.TSK_HASHSET_HIT, evt); - } - } - - /** - * A node displaying the set name and count for a hashset hit set. - */ - public static class HashHitSetNode extends TreeNode { - - /** - * Main constructor. - * - * @param itemData Set data to display. - */ - public HashHitSetNode(TreeResultsDTO.TreeItemDTO itemData) { - super(itemData.getTypeData().getSetName(), getIconPath(BlackboardArtifact.Type.TSK_HASHSET_HIT), itemData); - } - - @Override - public void respondSelection(DataResultTopComponent dataResultPanel) { - dataResultPanel.displayHashHits(this.getItemData().getTypeData()); - } - - } - - public static class InterestingItemTypeNode - - ; - - public static class InterestingItemTypeFactory - - ; - - public static class InterestingItemSetNode - - ; - - public static class KeywordTypeNode - - ; - - public static class KeywordTypeFactory - - ; - - public static class KeywordSetNode - - ; - - public static class KeywordSetFactory - - ; - - public static class KeywordSearchTermNode - - ; - - public static class KeywordSearchTermFactory - - ; - - public static class KeywordFoundMatchNode - - ; +// +// /** +// * An analysis result type node that has nested children. +// */ +// static class TreeTypeNode extends TreeNode { +// +// /** +// * Main constructor. +// * +// * @param itemData The data to display. +// */ +// public TreeTypeNode(TreeResultsDTO.TreeItemDTO itemData, ChildFactory childFactory) { +// super(itemData.getTypeData().getArtifactType().getTypeName(), +// getIconPath(itemData.getTypeData().getArtifactType()), +// itemData, +// Children.create(childFactory, true), +// getDefaultLookup(itemData)); +// } +// +// @Override +// public void respondSelection(DataResultTopComponent dataResultPanel) { +// // GVDTODO...NO OP??? +// } +// } +// +// /** +// * Factory displaying all hashset sets with count in the tree. +// */ +// static class TreeSetFactory extends TreeChildFactory { +// +// private final BlackboardArtifact.Type artifactType; +// private final Long dataSourceId; +// private final String nullSetName; +// +// /** +// * Main constructor. +// * +// * @param artifactType The type of artifact. +// * @param dataSourceId The data source object id for which the results +// * should be filtered or null if no data source +// * filtering. +// * @param nullSetName The name of the set for artifacts with no +// * TSK_SET_NAME value. If null, items are omitted. +// */ +// public TreeSetFactory(BlackboardArtifact.Type artifactType, Long dataSourceId, String nullSetName) { +// this.artifactType = artifactType; +// this.dataSourceId = dataSourceId; +// this.nullSetName = nullSetName; +// } +// +// @Override +// protected TreeResultsDTO getChildResults() throws IllegalArgumentException, ExecutionException { +// return MainDAO.getInstance().getAnalysisResultDAO().getSetCounts(this.artifactType, this.dataSourceId, this.nullSetName); +// } +// +// @Override +// public boolean isRefreshRequired(PropertyChangeEvent evt) { +// return AnalysisResultTypeFactory.isRefreshRequired(artifactType, evt); +// } +// +// @Override +// protected TreeNode createNewNode(TreeResultsDTO.TreeItemDTO rowData) { +// return new TreeSetTypeNode(rowData, Children.LEAF); +// } +// } +// +// /** +// * A node for a set within an artifact type. +// */ +// static class TreeSetTypeNode extends TreeNode { +// +// /** +// * Main constructor. +// * +// * @param artifactType The type of artifact. +// * @param itemData The data to display. +// */ +// public TreeSetTypeNode(TreeResultsDTO.TreeItemDTO itemData, Children children) { +// super(itemData.getTypeData().getArtifactType().getTypeName(), +// getIconPath(itemData.getTypeData().getArtifactType()), +// itemData, +// children, +// getDefaultLookup(itemData)); +// } +// +// @Override +// public void respondSelection(DataResultTopComponent dataResultPanel) { +// dataResultPanel.displayAnalysisResultSet(this.getItemData().getTypeData()); +// } +// } +// +// +// @Messages({ +// "AnalysisResultTypeFactory_adHocName=Adhoc Results" +// }) +// static class KeywordSetFactory extends TreeSetFactory { +// +// public KeywordSetFactory(Long dataSourceId) { +// super(BlackboardArtifact.Type.TSK_KEYWORD_HIT, dataSourceId, Bundle.AnalysisResultTypeFactory_adHocName()); +// } +// +// @Override +// protected TreeNode createNewNode(TreeResultsDTO.TreeItemDTO rowData) { +// return new TreeSetTypeNode(rowData, Children.LEAF); +// } +// +// +// +// } +// +// public static class KeywordSearchTermParams { +// private final String setName; +// private final String searchTerm; +// private final boolean hasChildren; +// private final Long dataSourceId; +// +// public KeywordSearchTermParams(String setName, String searchTerm, boolean hasChildren, Long dataSourceId) { +// this.setName = setName; +// this.searchTerm = searchTerm; +// this.hasChildren = hasChildren; +// this.dataSourceId = dataSourceId; +// } +// +// public String getSetName() { +// return setName; +// } +// +// public String getSearchTerm() { +// return searchTerm; +// } +// +// public boolean hasChildren() { +// return hasChildren; +// } +// +// public Long getDataSourceId() { +// return dataSourceId; +// } +// } +// +// static class KeywordSearchTermFactory extends TreeChildFactory { +// private final AnalysisResultSetSearchParam setParams; +// +// public KeywordSearchTermFactory(AnalysisResultSetSearchParam setParams) { +// this.setParams = setParams; +// } +// +// +// @Override +// protected TreeNode createNewNode(TreeResultsDTO.TreeItemDTO rowData) { +// return new KeywordSearchTermNode(rowData); +// } +// +// @Override +// protected TreeResultsDTO getChildResults() throws IllegalArgumentException, ExecutionException { +// return MainDAO.getInstance().getAnalysisResultDAO().getKeywordSetCounts(this.setParams); +// } +// +// @Override +// public boolean isRefreshRequired(PropertyChangeEvent evt) { +// return AnalysisResultTypeFactory.isRefreshRequired(BlackboardArtifact.Type.TSK_KEYWORD_HIT, evt); +// } +// +// } +// +// static class KeywordSearchTermNode extends TreeNode { +// +// public KeywordSearchTermNode(TreeResultsDTO.TreeItemDTO itemData) { +// super(itemData.getTypeData().getSearchTerm(), +// getIconPath(BlackboardArtifact.Type.TSK_KEYWORD_HIT), +// itemData, +// itemData.getTypeData().hasChildren() ? Children.create(new KeywordFoundMatchFactory(itemData), true) : Children.LEAF, +// getDefaultLookup(itemData)); +// } +// +// @Override +// public void respondSelection(DataResultTopComponent dataResultPanel) { +// KeywordSearchTermParams searchParams = this.getItemData().getTypeData(); +// +// if (!searchParams.hasChildren()) { +// dataResultPanel.displayKeywordHits(new KeywordHitSearchParam(searchParams.getDataSourceId(), searchParams.getSetName(), null, searchParams.getSearchTerm())); +// } +// } +// +// } +// public static class KeywordFoundMatchFactory +// public static class KeywordFoundMatchNode } From aba0273248e5f58f979342cae7e01fa38c62273d Mon Sep 17 00:00:00 2001 From: Greg DiCristofaro Date: Mon, 1 Nov 2021 12:54:09 -0400 Subject: [PATCH 35/38] icon utils fix --- .../org/sleuthkit/autopsy/datamodel/utils/IconsUtil.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/utils/IconsUtil.java b/Core/src/org/sleuthkit/autopsy/datamodel/utils/IconsUtil.java index 1e1ad86b9f..ce3a6763d9 100755 --- a/Core/src/org/sleuthkit/autopsy/datamodel/utils/IconsUtil.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/utils/IconsUtil.java @@ -25,6 +25,7 @@ import org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE; * Utility methods for handling icons */ public final class IconsUtil { + private IconsUtil() { } @@ -136,6 +137,11 @@ public final class IconsUtil { imageFile = "red-circle-exclamation.png"; //NON-NLS } else if (typeID == BlackboardArtifact.Type.TSK_HASHSET_HIT.getTypeID()) { imageFile = "hashset_hits.png"; + } else if (typeID == BlackboardArtifact.Type.TSK_KEYWORD_HIT.getTypeID()) { + imageFile = "keyword_hits.png"; + } else if (typeID == BlackboardArtifact.Type.TSK_INTERESTING_ARTIFACT_HIT.getTypeID() + || typeID == BlackboardArtifact.Type.TSK_INTERESTING_FILE_HIT.getTypeID()) { + imageFile = "interesting_item.png"; } else { imageFile = "artifact-icon.png"; //NON-NLS } From d7658d4aff0aa09814f5d61a46f11d504fe6a69a Mon Sep 17 00:00:00 2001 From: Eugene Livis Date: Mon, 1 Nov 2021 12:56:00 -0400 Subject: [PATCH 36/38] Added cache invalidation logic --- .../autopsy/mainui/datamodel/TagsDAO.java | 62 ++++++++++++++++++- 1 file changed, 59 insertions(+), 3 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/TagsDAO.java b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/TagsDAO.java index 5dcf62a35a..8050bd670e 100755 --- a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/TagsDAO.java +++ b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/TagsDAO.java @@ -36,8 +36,13 @@ import org.openide.util.NbBundle; import org.openide.util.NbBundle.Messages; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; +import org.sleuthkit.autopsy.casemodule.events.BlackBoardArtifactTagAddedEvent; +import org.sleuthkit.autopsy.casemodule.events.BlackBoardArtifactTagDeletedEvent; +import org.sleuthkit.autopsy.casemodule.events.ContentTagAddedEvent; +import org.sleuthkit.autopsy.casemodule.events.ContentTagDeletedEvent; import org.sleuthkit.autopsy.core.UserPreferences; import org.sleuthkit.autopsy.coreutils.TimeZoneUtils; +import org.sleuthkit.autopsy.events.AutopsyEvent; import org.sleuthkit.autopsy.mainui.nodes.DAOFetcher; import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.BlackboardArtifact; @@ -301,18 +306,69 @@ public class TagsDAO { public boolean isRefreshRequired(PropertyChangeEvent evt) { TagsSearchParams params = this.getParameters(); String eventType = evt.getPropertyName(); + + // handle artifact/result tag changes if (eventType.equals(Case.Events.BLACKBOARD_ARTIFACT_TAG_ADDED.toString()) || eventType.equals(Case.Events.BLACKBOARD_ARTIFACT_TAG_DELETED.toString())) { - return params.getTagType() == TagsSearchParams.TagType.RESULT; + // ignore non-artifact/result tag changes + if (params.getTagType() != TagsSearchParams.TagType.RESULT) { + return false; + } + + if (evt instanceof AutopsyEvent) { + if (evt instanceof BlackBoardArtifactTagAddedEvent) { + // An artifact associated with the current case has been tagged. + BlackBoardArtifactTagAddedEvent event = (BlackBoardArtifactTagAddedEvent) evt; + // ensure tag added event has a valid content id + if (event.getAddedTag() == null || event.getAddedTag().getContent() == null || event.getAddedTag().getArtifact() == null) { + return false; + } + return params.getTagName().getId() == event.getAddedTag().getId(); + } else if (evt instanceof BlackBoardArtifactTagDeletedEvent) { + // A tag has been removed from an artifact associated with the current case. + BlackBoardArtifactTagDeletedEvent event = (BlackBoardArtifactTagDeletedEvent) evt; + // ensure tag deleted event has a valid content id + BlackBoardArtifactTagDeletedEvent.DeletedBlackboardArtifactTagInfo deletedTagInfo = event.getDeletedTagInfo(); + if (deletedTagInfo == null) { + return false; + } + return params.getTagName().getId() == deletedTagInfo.getTagID(); + } + } } + // handle file/content tag changes if (eventType.equals(Case.Events.CONTENT_TAG_ADDED.toString()) || eventType.equals(Case.Events.CONTENT_TAG_DELETED.toString())) { - return params.getTagType() == TagsSearchParams.TagType.FILE; + // ignore non-file/content tag changes + if (params.getTagType() != TagsSearchParams.TagType.FILE) { + return false; + } + + if (evt instanceof AutopsyEvent) { + if (evt instanceof ContentTagAddedEvent) { + // Content associated with the current case has been tagged. + ContentTagAddedEvent event = (ContentTagAddedEvent) evt; + // ensure tag added event has a valid content id + if (event.getAddedTag() == null || event.getAddedTag().getContent() == null) { + return false; + } + return params.getTagName().getId() == event.getAddedTag().getId(); + } else if (evt instanceof ContentTagDeletedEvent) { + // A tag has been removed from content associated with the current case. + ContentTagDeletedEvent event = (ContentTagDeletedEvent) evt; + // ensure tag deleted event has a valid content id + ContentTagDeletedEvent.DeletedContentTagInfo deletedTagInfo = event.getDeletedTagInfo(); + if (deletedTagInfo == null) { + return false; + } + return params.getTagName().getId() == deletedTagInfo.getTagID(); + } + } } return false; } - } + } } From 4b8d50f7a3f1750c87506bc6c1580c32a5bf76e6 Mon Sep 17 00:00:00 2001 From: Greg DiCristofaro Date: Mon, 1 Nov 2021 13:00:22 -0400 Subject: [PATCH 37/38] fix --- .../mainui/datamodel/AnalysisResultDAO.java | 79 +++++++++---------- 1 file changed, 39 insertions(+), 40 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/AnalysisResultDAO.java b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/AnalysisResultDAO.java index 38481604ae..ff80dcf571 100644 --- a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/AnalysisResultDAO.java +++ b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/AnalysisResultDAO.java @@ -496,44 +496,43 @@ public class AnalysisResultDAO extends BlackboardArtifactDAO { } } -// GVDTODO code to use in a future PR -// /** -// * Handles fetching and paging of hashset hits. -// */ -// public static class HashsetResultFetcher extends AbstractAnalysisResultFetcher { -// -// /** -// * Main constructor. -// * -// * @param params Parameters to handle fetching of data. -// */ -// public HashsetResultFetcher(HashHitSearchParam params) { -// super(params); -// } -// -// @Override -// public SearchResultsDTO getSearchResults(int pageSize, int pageIdx, boolean hardRefresh) throws ExecutionException { -// return MainDAO.getInstance().getAnalysisResultDAO().getHashHitsForTable(this.getParameters(), pageIdx * pageSize, (long) pageSize, hardRefresh); -// } -// } -// -// /** -// * Handles fetching and paging of keyword hits. -// */ -// public static class KeywordHitResultFetcher extends AbstractAnalysisResultFetcher { -// -// /** -// * Main constructor. -// * -// * @param params Parameters to handle fetching of data. -// */ -// public KeywordHitResultFetcher(KeywordHitSearchParam params) { -// super(params); -// } -// -// @Override -// public SearchResultsDTO getSearchResults(int pageSize, int pageIdx, boolean hardRefresh) throws ExecutionException { -// return MainDAO.getInstance().getAnalysisResultDAO().getKeywordHitsForTable(this.getParameters(), pageIdx * pageSize, (long) pageSize, hardRefresh); -// } -// } + /** + * Handles fetching and paging of hashset hits. + */ + public static class HashsetResultFetcher extends AbstractAnalysisResultFetcher { + + /** + * Main constructor. + * + * @param params Parameters to handle fetching of data. + */ + public HashsetResultFetcher(HashHitSearchParam params) { + super(params); + } + + @Override + public SearchResultsDTO getSearchResults(int pageSize, int pageIdx, boolean hardRefresh) throws ExecutionException { + return MainDAO.getInstance().getAnalysisResultDAO().getHashHitsForTable(this.getParameters(), pageIdx * pageSize, (long) pageSize, hardRefresh); + } + } + + /** + * Handles fetching and paging of keyword hits. + */ + public static class KeywordHitResultFetcher extends AbstractAnalysisResultFetcher { + + /** + * Main constructor. + * + * @param params Parameters to handle fetching of data. + */ + public KeywordHitResultFetcher(KeywordHitSearchParam params) { + super(params); + } + + @Override + public SearchResultsDTO getSearchResults(int pageSize, int pageIdx, boolean hardRefresh) throws ExecutionException { + return MainDAO.getInstance().getAnalysisResultDAO().getKeywordHitsForTable(this.getParameters(), pageIdx * pageSize, (long) pageSize, hardRefresh); + } + } } From 3b1172cd1c9e94a41a00c5d1a8b9794870ccd006 Mon Sep 17 00:00:00 2001 From: Eugene Livis Date: Mon, 1 Nov 2021 14:35:08 -0400 Subject: [PATCH 38/38] Minor --- .../autopsy/mainui/datamodel/FileTypeSizeSearchParams.java | 4 ++-- Core/src/org/sleuthkit/autopsy/mainui/datamodel/TagsDAO.java | 3 --- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/FileTypeSizeSearchParams.java b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/FileTypeSizeSearchParams.java index bba60bcb7c..9801467ec6 100755 --- a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/FileTypeSizeSearchParams.java +++ b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/FileTypeSizeSearchParams.java @@ -71,8 +71,8 @@ public class FileTypeSizeSearchParams { @Override public int hashCode() { int hash = 7; - hash = 23 * hash + Objects.hashCode(this.sizeFilter); - hash = 23 * hash + Objects.hashCode(this.dataSourceId); + hash = 53 * hash + Objects.hashCode(this.sizeFilter); + hash = 53 * hash + Objects.hashCode(this.dataSourceId); return hash; } diff --git a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/TagsDAO.java b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/TagsDAO.java index 8050bd670e..46a706ebde 100755 --- a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/TagsDAO.java +++ b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/TagsDAO.java @@ -24,9 +24,7 @@ import java.beans.PropertyChangeEvent; import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator; -import java.util.EnumSet; import java.util.List; -import java.util.Set; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; @@ -45,7 +43,6 @@ import org.sleuthkit.autopsy.coreutils.TimeZoneUtils; import org.sleuthkit.autopsy.events.AutopsyEvent; import org.sleuthkit.autopsy.mainui.nodes.DAOFetcher; import org.sleuthkit.datamodel.AbstractFile; -import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.BlackboardArtifactTag; import org.sleuthkit.datamodel.Content; import org.sleuthkit.datamodel.ContentTag;