Merge pull request #7042 from gdicristofaro/7696-viewContextFix

7696 view context fix
This commit is contained in:
Richard Cordovano 2021-06-17 11:16:59 -04:00 committed by GitHub
commit e222445609
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

View File

@ -31,6 +31,7 @@ import java.util.stream.Collectors;
import java.util.stream.Stream; import java.util.stream.Stream;
import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.Logger;
import javax.swing.AbstractAction; import javax.swing.AbstractAction;
import org.apache.commons.lang3.StringUtils;
import org.openide.nodes.AbstractNode; import org.openide.nodes.AbstractNode;
import org.openide.explorer.ExplorerManager; import org.openide.explorer.ExplorerManager;
import org.openide.explorer.view.TreeView; 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 * parent of the content, selecting the parent in the tree view, then
* selecting the content in the results view. * 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. * @param artifactNode The artifact node for the artifact.
*/ */
public ViewContextAction(String displayName, BlackboardArtifactNode artifactNode) { 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 * parent of the content, selecting the parent in the tree view, then
* selecting the content in the results view. * 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 * @param fileSystemContentNode The file system content node for the
* content. * content.
*/ */
public ViewContextAction(String displayName, AbstractFsContentNode<? extends AbstractFile> fileSystemContentNode) { public ViewContextAction(String displayName, AbstractFsContentNode<? extends AbstractFile> fileSystemContentNode) {
super(displayName); super(displayName);
@ -121,9 +122,9 @@ public class ViewContextAction extends AbstractAction {
* content, selecting the parent in the tree view, then selecting the * content, selecting the parent in the tree view, then selecting the
* content in the results view. * 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 * @param abstractAbstractFileNode The AbstractAbstractFileNode node for the
* content. * content.
*/ */
public ViewContextAction(String displayName, AbstractAbstractFileNode<? extends AbstractFile> abstractAbstractFileNode) { public ViewContextAction(String displayName, AbstractAbstractFileNode<? extends AbstractFile> abstractAbstractFileNode) {
super(displayName); super(displayName);
@ -137,7 +138,7 @@ public class ViewContextAction extends AbstractAction {
* content in the results view. * content in the results view.
* *
* @param displayName The display name for the action. * @param displayName The display name for the action.
* @param content The content. * @param content The content.
*/ */
public ViewContextAction(String displayName, Content content) { public ViewContextAction(String displayName, Content content) {
super(displayName); 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, * 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 * selecting the parent in the tree view, then selecting the content in the
* results view. * results view.
* *
* NOTE: This code will likely need updating in the event that the structure * 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 * of the nodes is changed (i.e. adding parent levels). Places to look when
* changing node structure include: * changing node structure include:
@ -168,176 +169,224 @@ public class ViewContextAction extends AbstractAction {
public void actionPerformed(ActionEvent event) { public void actionPerformed(ActionEvent event) {
EventQueue.invokeLater(() -> { EventQueue.invokeLater(() -> {
/* Content parentContent = getParentContent(this.content);
* Get the parent content for the content to be selected in the
* results view. If the parent content is null, then the specified if ((parentContent != null) && (parentContent instanceof UnsupportedContent)) {
* 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)) {
MessageNotifyUtil.Message.error(Bundle.ViewContextAction_errorMessage_unsupportedParent()); 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 logger.log(Level.WARNING, String.format("Could not navigate to unsupported content with id: %d", parentContent.getId())); //NON-NLS
return; return;
} }
/* // Get the "Data Sources" node from the tree view.
* Get the "Data Sources" node from the tree view.
*/
DirectoryTreeTopComponent treeViewTopComponent = DirectoryTreeTopComponent.findInstance(); DirectoryTreeTopComponent treeViewTopComponent = DirectoryTreeTopComponent.findInstance();
ExplorerManager treeViewExplorerMgr = treeViewTopComponent.getExplorerManager(); ExplorerManager treeViewExplorerMgr = treeViewTopComponent.getExplorerManager();
Node parentTreeViewNode = null; Node parentTreeViewNode = null;
if (Objects.equals(CasePreferences.getGroupItemsInTreeByDataSource(), true)) { // 'Group by Data Source' view if (parentContent != null) {
if (Objects.equals(CasePreferences.getGroupItemsInTreeByDataSource(), true)) {
SleuthkitCase skCase; parentTreeViewNode = getParentNodeGroupedByPersonHost(treeViewExplorerMgr, parentContent);
String dsname; } else {
try { parentTreeViewNode = getParentNodeGroupedByDataSource(treeViewExplorerMgr, parentContent);
// 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<Node> 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 no node is found, report error and do nothing
* Set the child selection info of the parent tree node, then select if (parentTreeViewNode == null) {
* the parent node in the tree view. The results view will retrieve MessageNotifyUtil.Message.error(Bundle.ViewContextAction_errorMessage_cannotFindNode());
* this selection info and use it to complete this action when the logger.log(Level.SEVERE, "Failed to locate data source node in tree."); //NON-NLS
* tree view top component responds to the selection of the parent return;
* 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(); setNodeSelection(this.content, parentTreeViewNode, treeViewTopComponent, treeViewExplorerMgr);
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
}
}
}); });
} }
/**
* 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<Node> 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 * If the node has lookup of host or person, returns children. If not, just
* returns itself. * returns itself.
* *
* @param node The node. * @param node The node.
*
* @return The child nodes that are at the data source level. * @return The child nodes that are at the data source level.
*/ */
private List<Node> getDataSourceLevelNodes(Node node) { private List<Node> getDataSourceLevelNodes(Node node) {
if (node == null) { if (node == null) {
return Collections.emptyList(); return Collections.emptyList();
} else if (node.getLookup().lookup(Host.class) != null || } else if (node.getLookup().lookup(Host.class) != null
node.getLookup().lookup(Person.class) != null || || node.getLookup().lookup(Person.class) != null
DataSourcesNode.getNameIdentifier().equals(node.getLookup().lookup(String.class)) || || DataSourcesNode.getNameIdentifier().equals(node.getLookup().lookup(String.class))
PersonNode.getUnknownPersonId().equals(node.getLookup().lookup(String.class))) { || PersonNode.getUnknownPersonId().equals(node.getLookup().lookup(String.class))) {
Children children = node.getChildren(); Children children = node.getChildren();
Node[] childNodes = children == null ? null : children.getNodes(); Node[] childNodes = children == null ? null : children.getNodes(true);
if (childNodes == null) { if (childNodes == null) {
return Collections.emptyList(); return Collections.emptyList();
} }
return Stream.of(node.getChildren().getNodes()) return Stream.of(node.getChildren().getNodes(true))
.flatMap(parent -> getDataSourceLevelNodes(parent).stream()) .flatMap(parent -> getDataSourceLevelNodes(parent).stream())
.collect(Collectors.toList()); .collect(Collectors.toList());
} else { } else {
@ -350,7 +399,8 @@ public class ViewContextAction extends AbstractAction {
* of the specified content. * of the specified content.
* *
* @param parentContent parent content for the content to be searched for * @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 * @return Node object of the matching parent, NULL if not found
*/ */
private Node findParentNodeInTree(Content parentContent, Node node) { 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); Node dummyRootNode = new DirectoryTreeFilterNode(new AbstractNode(new RootContentChildren(contentBranch)), true);
Children ancestorChildren = dummyRootNode.getChildren(); 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 * Search the tree for the parent node. Note that this algorithm
* simply discards "extra" ancestor nodes not shown in the tree, * simply discards "extra" ancestor nodes not shown in the tree,
@ -387,8 +442,9 @@ public class ViewContextAction extends AbstractAction {
Node parentTreeViewNode = null; Node parentTreeViewNode = null;
for (int i = 0; i < ancestorChildren.getNodesCount(); i++) { for (int i = 0; i < ancestorChildren.getNodesCount(); i++) {
Node ancestorNode = ancestorChildren.getNodeAt(i); Node ancestorNode = ancestorChildren.getNodeAt(i);
for (int j = 0; j < treeNodeChildren.getNodesCount(); j++) { Node[] treeNodeChilds = treeNodeChildren.getNodes(true);
Node treeNode = treeNodeChildren.getNodeAt(j); for (int j = 0; j < treeNodeChilds.length; j++) {
Node treeNode = treeNodeChilds[j];
if (ancestorNode.getName().equals(treeNode.getName())) { if (ancestorNode.getName().equals(treeNode.getName())) {
parentTreeViewNode = treeNode; parentTreeViewNode = treeNode;
treeNodeChildren = treeNode.getChildren(); treeNodeChildren = treeNode.getChildren();