diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/ViewContextAction.java b/Core/src/org/sleuthkit/autopsy/directorytree/ViewContextAction.java index c8f5848243..323b340c7f 100644 --- a/Core/src/org/sleuthkit/autopsy/directorytree/ViewContextAction.java +++ b/Core/src/org/sleuthkit/autopsy/directorytree/ViewContextAction.java @@ -31,6 +31,7 @@ import java.util.stream.Collectors; import java.util.stream.Stream; import org.sleuthkit.autopsy.coreutils.Logger; import javax.swing.AbstractAction; +import org.apache.commons.lang3.StringUtils; import org.openide.nodes.AbstractNode; import org.openide.explorer.ExplorerManager; import org.openide.explorer.view.TreeView; @@ -84,7 +85,7 @@ public class ViewContextAction extends AbstractAction { * parent of the content, selecting the parent in the tree view, then * selecting the content in the results view. * - * @param displayName The display name for the action. + * @param displayName The display name for the action. * @param artifactNode The artifact node for the artifact. */ public ViewContextAction(String displayName, BlackboardArtifactNode artifactNode) { @@ -106,9 +107,9 @@ public class ViewContextAction extends AbstractAction { * parent of the content, selecting the parent in the tree view, then * selecting the content in the results view. * - * @param displayName The display name for the action. + * @param displayName The display name for the action. * @param fileSystemContentNode The file system content node for the - * content. + * content. */ public ViewContextAction(String displayName, AbstractFsContentNode fileSystemContentNode) { super(displayName); @@ -121,9 +122,9 @@ public class ViewContextAction extends AbstractAction { * content, selecting the parent in the tree view, then selecting the * content in the results view. * - * @param displayName The display name for the action. + * @param displayName The display name for the action. * @param abstractAbstractFileNode The AbstractAbstractFileNode node for the - * content. + * content. */ public ViewContextAction(String displayName, AbstractAbstractFileNode abstractAbstractFileNode) { super(displayName); @@ -137,7 +138,7 @@ public class ViewContextAction extends AbstractAction { * content in the results view. * * @param displayName The display name for the action. - * @param content The content. + * @param content The content. */ public ViewContextAction(String displayName, Content content) { super(displayName); @@ -149,7 +150,7 @@ public class ViewContextAction extends AbstractAction { * branch of the tree view to the level of the parent of the content, * selecting the parent in the tree view, then selecting the content in the * results view. - * + * * NOTE: This code will likely need updating in the event that the structure * of the nodes is changed (i.e. adding parent levels). Places to look when * changing node structure include: @@ -168,176 +169,224 @@ public class ViewContextAction extends AbstractAction { public void actionPerformed(ActionEvent event) { EventQueue.invokeLater(() -> { - /* - * Get the parent content for the content to be selected in the - * results view. If the parent content is null, then the specified - * content is a data source, and the parent tree view node is the - * "Data Sources" node. Otherwise, the tree view needs to be - * searched to find the parent treeview node. - */ - Content parentContent = null; - try { - parentContent = content.getParent(); - } catch (TskCoreException ex) { - MessageNotifyUtil.Message.error(Bundle.ViewContextAction_errorMessage_cannotFindDirectory()); - logger.log(Level.SEVERE, String.format("Could not get parent of Content object: %s", content), ex); //NON-NLS - return; - } - - if ((parentContent != null) - && (parentContent instanceof UnsupportedContent)) { + Content parentContent = getParentContent(this.content); + + if ((parentContent != null) && (parentContent instanceof UnsupportedContent)) { MessageNotifyUtil.Message.error(Bundle.ViewContextAction_errorMessage_unsupportedParent()); logger.log(Level.WARNING, String.format("Could not navigate to unsupported content with id: %d", parentContent.getId())); //NON-NLS return; } - /* - * Get the "Data Sources" node from the tree view. - */ + // Get the "Data Sources" node from the tree view. DirectoryTreeTopComponent treeViewTopComponent = DirectoryTreeTopComponent.findInstance(); ExplorerManager treeViewExplorerMgr = treeViewTopComponent.getExplorerManager(); + Node parentTreeViewNode = null; - if (Objects.equals(CasePreferences.getGroupItemsInTreeByDataSource(), true)) { // 'Group by Data Source' view - - SleuthkitCase skCase; - String dsname; - try { - // get the objid/name of the datasource of the selected content. - skCase = Case.getCurrentCaseThrows().getSleuthkitCase(); - long contentDSObjid = content.getDataSource().getId(); - DataSource datasource = skCase.getDataSource(contentDSObjid); - dsname = datasource.getName(); - Children rootChildren = treeViewExplorerMgr.getRootContext().getChildren(); - - if (null != parentContent) { - // the tree view needs to be searched to find the parent treeview node. - /* NOTE: we can't do a lookup by data source name here, becase if there - are multiple data sources with the same name, then "getChildren().findChild(dsname)" - simply returns the first one that it finds. Instead we have to loop over all - data sources with that name, and make sure we find the correct one. - */ - List dataSourceLevelNodes = Stream.of(rootChildren.getNodes()) - .flatMap(rootNode -> getDataSourceLevelNodes(rootNode).stream()) - .collect(Collectors.toList()); - - for (Node treeNode : dataSourceLevelNodes) { - // in the root, look for a data source node with the name of interest - if (!(treeNode.getName().equals(dsname))) { - continue; - } - - // for this data source, get the "Data Sources" child node - Node datasourceGroupingNode = treeNode.getChildren().findChild(DataSourceFilesNode.getNameIdentifier()); - - // check whether this is the data source we are looking for - parentTreeViewNode = findParentNodeInTree(parentContent, datasourceGroupingNode); - if (parentTreeViewNode != null) { - // found the data source node - break; - } - } - } else { - /* If the parent content is null, then the specified - * content is a data source, and the parent tree view node is the - * "Data Sources" node. */ - Node datasourceGroupingNode = rootChildren.findChild(dsname); - if (!Objects.isNull(datasourceGroupingNode)) { - Children dsChildren = datasourceGroupingNode.getChildren(); - parentTreeViewNode = dsChildren.findChild(DataSourceFilesNode.getNameIdentifier()); - } - } - - if (parentTreeViewNode == null) { - MessageNotifyUtil.Message.error(Bundle.ViewContextAction_errorMessage_cannotFindNode()); - logger.log(Level.SEVERE, "Failed to locate data source node in tree."); //NON-NLS - return; - } - } catch (NoCurrentCaseException | TskDataException | TskCoreException ex) { - MessageNotifyUtil.Message.error(Bundle.ViewContextAction_errorMessage_cannotFindNode()); - logger.log(Level.SEVERE, "Failed to locate data source node in tree.", ex); //NON-NLS - return; - } - } else { // Classic view - // Start the search at the DataSourcesNode - Children rootChildren = treeViewExplorerMgr.getRootContext().getChildren(); - Node rootDsNode = rootChildren == null ? null : rootChildren.findChild(DataSourcesNode.getNameIdentifier()); - if (rootDsNode != null) { - for (Node dataSourceLevelNode : getDataSourceLevelNodes(rootDsNode)) { - DataSource dataSource = dataSourceLevelNode.getLookup().lookup(DataSource.class); - if (dataSource != null) { - // the tree view needs to be searched to find the parent treeview node. - Node potentialParentTreeViewNode = findParentNodeInTree(parentContent, dataSourceLevelNode); - if (potentialParentTreeViewNode != null) { - parentTreeViewNode = potentialParentTreeViewNode; - break; - } - } - } + if (parentContent != null) { + if (Objects.equals(CasePreferences.getGroupItemsInTreeByDataSource(), true)) { + parentTreeViewNode = getParentNodeGroupedByPersonHost(treeViewExplorerMgr, parentContent); + } else { + parentTreeViewNode = getParentNodeGroupedByDataSource(treeViewExplorerMgr, parentContent); } } - /* - * Set the child selection info of the parent tree node, then select - * the parent node in the tree view. The results view will retrieve - * this selection info and use it to complete this action when the - * tree view top component responds to the selection of the parent - * node by pushing it into the results view top component. - */ - DisplayableItemNode undecoratedParentNode = (DisplayableItemNode) ((DirectoryTreeFilterNode) parentTreeViewNode).getOriginal(); - undecoratedParentNode.setChildNodeSelectionInfo(new ContentNodeSelectionInfo(content)); - if (content instanceof BlackboardArtifact) { - BlackboardArtifact artifact = ((BlackboardArtifact) content); - long associatedId = artifact.getObjectID(); - try { - Content associatedFileContent = artifact.getSleuthkitCase().getContentById(associatedId); - undecoratedParentNode.setChildNodeSelectionInfo(new ContentNodeSelectionInfo(associatedFileContent)); - } catch (TskCoreException ex) { - logger.log(Level.SEVERE, "Could not find associated content from artifact with id %d", artifact.getId()); - } + // if no node is found, report error and do nothing + if (parentTreeViewNode == null) { + MessageNotifyUtil.Message.error(Bundle.ViewContextAction_errorMessage_cannotFindNode()); + logger.log(Level.SEVERE, "Failed to locate data source node in tree."); //NON-NLS + return; } - TreeView treeView = treeViewTopComponent.getTree(); - treeView.expandNode(parentTreeViewNode); - if (treeViewTopComponent.getSelectedNode().equals(parentTreeViewNode)) { - //In the case where our tree view already has the destination directory selected - //due to an optimization in the ExplorerManager.setExploredContextAndSelection method - //the property change we listen for to call DirectoryTreeTopComponent.respondSelection - //will not be sent so we call it manually ourselves after making - //the directory listing the active tab. - treeViewTopComponent.setDirectoryListingActive(); - treeViewTopComponent.respondSelection(treeViewExplorerMgr.getSelectedNodes(), new Node[]{parentTreeViewNode}); - } else { - try { - treeViewExplorerMgr.setExploredContextAndSelection(parentTreeViewNode, new Node[]{parentTreeViewNode}); - } catch (PropertyVetoException ex) { - MessageNotifyUtil.Message.error(Bundle.ViewContextAction_errorMessage_cannotSelectDirectory()); - logger.log(Level.SEVERE, "Failed to select the parent node in the tree view", ex); //NON-NLS - } - } + setNodeSelection(this.content, parentTreeViewNode, treeViewTopComponent, treeViewExplorerMgr); }); } + /** + * Get the parent content for the content to be selected in the results + * view. If the parent content is null, then the specified content is a data + * source, and the parent tree view node is the "Data Sources" node. + * Otherwise, the tree view needs to be searched to find the parent treeview + * node. + * + * @param content The content whose parent will be returned. If this item is + * a datasource, it will be returned. + * + * @return The content if content is a data source or the parent of this + * content. + */ + private Content getParentContent(Content content) { + + try { + return (content instanceof DataSource) + ? content + : content.getParent(); + } catch (TskCoreException ex) { + MessageNotifyUtil.Message.error(Bundle.ViewContextAction_errorMessage_cannotFindDirectory()); + logger.log(Level.SEVERE, String.format("Could not get parent of Content object: %s", content), ex); //NON-NLS + return null; + } + } + + /** + * Returns the node in the tree related to the parentContent or null if + * can't be found. This method should be used when view is grouped by data + * source. + * + * @param treeViewExplorerMgr The explorer manager. + * @param parentContent The content whose equivalent node will be + * returned if found. + * + * @return The node if found or null. + */ + private Node getParentNodeGroupedByDataSource(ExplorerManager treeViewExplorerMgr, Content parentContent) { + // Classic view + // Start the search at the DataSourcesNode + Children rootChildren = treeViewExplorerMgr.getRootContext().getChildren(); + Node rootDsNode = rootChildren == null ? null : rootChildren.findChild(DataSourcesNode.getNameIdentifier()); + if (rootDsNode != null) { + for (Node dataSourceLevelNode : getDataSourceLevelNodes(rootDsNode)) { + DataSource dataSource = dataSourceLevelNode.getLookup().lookup(DataSource.class); + if (dataSource != null) { + // the tree view needs to be searched to find the parent treeview node. + Node potentialParentTreeViewNode = findParentNodeInTree(parentContent, dataSourceLevelNode); + if (potentialParentTreeViewNode != null) { + return potentialParentTreeViewNode; + } + } + } + } + + return null; + } + + /** + * Returns the node in the tree related to the parentContent or null if + * can't be found. This method should be used when view is grouped by + * hosts/persons. + * + * @param treeViewExplorerMgr The explorer manager. + * @param parentContent The content whose equivalent node will be + * returned if found. + * + * @return The node if found or null. + */ + private Node getParentNodeGroupedByPersonHost(ExplorerManager treeViewExplorerMgr, Content parentContent) { + // 'Group by Data Source' view + + SleuthkitCase skCase; + String dsname; + try { + // get the objid/name of the datasource of the selected content. + skCase = Case.getCurrentCaseThrows().getSleuthkitCase(); + long contentDSObjid = parentContent.getDataSource().getId(); + DataSource datasource = skCase.getDataSource(contentDSObjid); + dsname = datasource.getName(); + Children rootChildren = treeViewExplorerMgr.getRootContext().getChildren(); + + // the tree view needs to be searched to find the parent treeview node. + /* NOTE: we can't do a lookup by data source name here, becase if there + are multiple data sources with the same name, then "getChildren().findChild(dsname)" + simply returns the first one that it finds. Instead we have to loop over all + data sources with that name, and make sure we find the correct one. + */ + List dataSourceLevelNodes = Stream.of(rootChildren.getNodes(true)) + .flatMap(rootNode -> getDataSourceLevelNodes(rootNode).stream()) + .collect(Collectors.toList()); + + for (Node treeNode : dataSourceLevelNodes) { + // in the root, look for a data source node with the name of interest + if (!(treeNode.getName().equals(dsname))) { + continue; + } + + // for this data source, get the "Data Sources" child node + Node datasourceGroupingNode = treeNode.getChildren().findChild(DataSourceFilesNode.getNameIdentifier()); + + // check whether this is the data source we are looking for + Node parentTreeViewNode = findParentNodeInTree(parentContent, datasourceGroupingNode); + if (parentTreeViewNode != null) { + // found the data source node + return parentTreeViewNode; + } + } + } catch (NoCurrentCaseException | TskDataException | TskCoreException ex) { + MessageNotifyUtil.Message.error(Bundle.ViewContextAction_errorMessage_cannotFindNode()); + logger.log(Level.SEVERE, "Failed to locate data source node in tree.", ex); //NON-NLS + } + + return null; + } + + /** + * Set the node selection in the tree. + * @param content The content to select. + * @param parentTreeViewNode The node that is the parent of the content. + * @param treeViewTopComponent The DirectoryTreeTopComponent. + * @param treeViewExplorerMgr The ExplorerManager. + */ + private void setNodeSelection(Content content, Node parentTreeViewNode, DirectoryTreeTopComponent treeViewTopComponent, ExplorerManager treeViewExplorerMgr) { + /* + * Set the child selection info of the parent tree node, then select + * the parent node in the tree view. The results view will retrieve + * this selection info and use it to complete this action when the + * tree view top component responds to the selection of the parent + * node by pushing it into the results view top component. + */ + DisplayableItemNode undecoratedParentNode = (DisplayableItemNode) ((DirectoryTreeFilterNode) parentTreeViewNode).getOriginal(); + undecoratedParentNode.setChildNodeSelectionInfo(new ContentNodeSelectionInfo(content)); + if (content instanceof BlackboardArtifact) { + BlackboardArtifact artifact = ((BlackboardArtifact) content); + long associatedId = artifact.getObjectID(); + try { + Content associatedFileContent = artifact.getSleuthkitCase().getContentById(associatedId); + undecoratedParentNode.setChildNodeSelectionInfo(new ContentNodeSelectionInfo(associatedFileContent)); + } catch (TskCoreException ex) { + logger.log(Level.SEVERE, "Could not find associated content from artifact with id %d", artifact.getId()); + } + } + + TreeView treeView = treeViewTopComponent.getTree(); + treeView.expandNode(parentTreeViewNode); + if (treeViewTopComponent.getSelectedNode().equals(parentTreeViewNode)) { + //In the case where our tree view already has the destination directory selected + //due to an optimization in the ExplorerManager.setExploredContextAndSelection method + //the property change we listen for to call DirectoryTreeTopComponent.respondSelection + //will not be sent so we call it manually ourselves after making + //the directory listing the active tab. + treeViewTopComponent.setDirectoryListingActive(); + treeViewTopComponent.respondSelection(treeViewExplorerMgr.getSelectedNodes(), new Node[]{parentTreeViewNode}); + } else { + try { + treeViewExplorerMgr.setExploredContextAndSelection(parentTreeViewNode, new Node[]{parentTreeViewNode}); + } catch (PropertyVetoException ex) { + MessageNotifyUtil.Message.error(Bundle.ViewContextAction_errorMessage_cannotSelectDirectory()); + logger.log(Level.SEVERE, "Failed to select the parent node in the tree view", ex); //NON-NLS + } + } + } + /** * If the node has lookup of host or person, returns children. If not, just * returns itself. * * @param node The node. + * * @return The child nodes that are at the data source level. */ private List getDataSourceLevelNodes(Node node) { if (node == null) { return Collections.emptyList(); - } else if (node.getLookup().lookup(Host.class) != null || - node.getLookup().lookup(Person.class) != null || - DataSourcesNode.getNameIdentifier().equals(node.getLookup().lookup(String.class)) || - PersonNode.getUnknownPersonId().equals(node.getLookup().lookup(String.class))) { + } else if (node.getLookup().lookup(Host.class) != null + || node.getLookup().lookup(Person.class) != null + || DataSourcesNode.getNameIdentifier().equals(node.getLookup().lookup(String.class)) + || PersonNode.getUnknownPersonId().equals(node.getLookup().lookup(String.class))) { Children children = node.getChildren(); - Node[] childNodes = children == null ? null : children.getNodes(); + Node[] childNodes = children == null ? null : children.getNodes(true); if (childNodes == null) { return Collections.emptyList(); } - return Stream.of(node.getChildren().getNodes()) + return Stream.of(node.getChildren().getNodes(true)) .flatMap(parent -> getDataSourceLevelNodes(parent).stream()) .collect(Collectors.toList()); } else { @@ -350,7 +399,8 @@ public class ViewContextAction extends AbstractAction { * of the specified content. * * @param parentContent parent content for the content to be searched for - * @param node Node tree to search + * @param node Node tree to search + * * @return Node object of the matching parent, NULL if not found */ private Node findParentNodeInTree(Content parentContent, Node node) { @@ -377,6 +427,11 @@ public class ViewContextAction extends AbstractAction { Node dummyRootNode = new DirectoryTreeFilterNode(new AbstractNode(new RootContentChildren(contentBranch)), true); Children ancestorChildren = dummyRootNode.getChildren(); + // if content is the data source provided, return that. + if (ancestorChildren.getNodesCount() == 1 && StringUtils.equals(ancestorChildren.getNodeAt(0).getName(), node.getName())) { + return node; + } + /* * Search the tree for the parent node. Note that this algorithm * simply discards "extra" ancestor nodes not shown in the tree, @@ -387,8 +442,9 @@ public class ViewContextAction extends AbstractAction { Node parentTreeViewNode = null; for (int i = 0; i < ancestorChildren.getNodesCount(); i++) { Node ancestorNode = ancestorChildren.getNodeAt(i); - for (int j = 0; j < treeNodeChildren.getNodesCount(); j++) { - Node treeNode = treeNodeChildren.getNodeAt(j); + Node[] treeNodeChilds = treeNodeChildren.getNodes(true); + for (int j = 0; j < treeNodeChilds.length; j++) { + Node treeNode = treeNodeChilds[j]; if (ancestorNode.getName().equals(treeNode.getName())) { parentTreeViewNode = treeNode; treeNodeChildren = treeNode.getChildren();