/*
* Autopsy Forensic Browser
*
* Copyright 2011-2016 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> 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.datamodel;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.Collections;
import java.util.List;
import java.util.Observable;
import java.util.Observer;
import java.util.logging.Level;
import org.openide.nodes.ChildFactory;
import org.openide.nodes.Children;
import org.openide.nodes.Node;
import org.openide.nodes.Sheet;
import org.openide.util.NbBundle;
import org.openide.util.lookup.Lookups;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.casemodule.services.TagsManager;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.ingest.IngestManager;
import org.sleuthkit.datamodel.BlackboardArtifactTag;
import org.sleuthkit.datamodel.ContentTag;
import org.sleuthkit.datamodel.TagName;
import org.sleuthkit.datamodel.TskCoreException;
/**
* Instances of this class act as keys for use by instances of the
* RootContentChildren class. RootContentChildren is a NetBeans child node
* factory built on top of the NetBeans Children.Keys class.
*/
public class Tags implements AutopsyVisitableItem {
// Creation of a RootNode object corresponding to a Tags object is done
// by a CreateAutopsyNodeVisitor dispatched from the AbstractContentChildren
// override of Children.Keys<T>.createNodes().
private final TagResults tagResults = new TagResults();
private final String DISPLAY_NAME = NbBundle.getMessage(RootNode.class, "TagsNode.displayName.text");
private final String ICON_PATH = "org/sleuthkit/autopsy/images/tag-folder-blue-icon-16.png"; //NON-NLS
@Override
public <T> T accept(AutopsyItemVisitor<T> v) {
return v.visit(this);
}
/**
* This class largely does nothing except act as a top-level object that the
* other nodes can listen to. This mimics what other nodes have (keword
* search, etc.), but theirs stores data.
*/
private class TagResults extends Observable {
public void update() {
setChanged();
notifyObservers();
}
}
/**
* Instances of this class are the root nodes of tree that is a sub-tree of
* the Autopsy presentation of the SleuthKit data model. The sub-tree
* consists of content and blackboard artifact tags, grouped first by tag
* type, then by tag name.
*/
public class RootNode extends DisplayableItemNode {
public RootNode() {
super(Children.create(new TagNameNodeFactory(), true), Lookups.singleton(DISPLAY_NAME));
super.setName(DISPLAY_NAME);
super.setDisplayName(DISPLAY_NAME);
this.setIconBaseWithExtension(ICON_PATH);
}
@Override
public boolean isLeafTypeNode() {
return false;
}
@Override
public <T> T accept(DisplayableItemNodeVisitor<T> v) {
return v.visit(this);
}
@Override
protected Sheet createSheet() {
Sheet propertySheet = super.createSheet();
Sheet.Set properties = propertySheet.get(Sheet.PROPERTIES);
if (properties == null) {
properties = Sheet.createPropertiesSet();
propertySheet.put(properties);
}
properties.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "TagsNode.createSheet.name.name"),
NbBundle.getMessage(this.getClass(), "TagsNode.createSheet.name.displayName"), "", getName()));
return propertySheet;
}
@Override
public String getItemType() {
return getClass().getName();
}
}
private class TagNameNodeFactory extends ChildFactory.Detachable<TagName> implements Observer {
private final PropertyChangeListener pcl = new PropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent evt) {
String eventType = evt.getPropertyName();
if (eventType.equals(Case.Events.BLACKBOARD_ARTIFACT_TAG_ADDED.toString())
|| eventType.equals(Case.Events.BLACKBOARD_ARTIFACT_TAG_DELETED.toString())
|| eventType.equals(Case.Events.CONTENT_TAG_ADDED.toString())
|| eventType.equals(Case.Events.CONTENT_TAG_DELETED.toString())) {
/**
* Checking for a current case 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.getCurrentCase();
refresh(true);
tagResults.update();
} catch (IllegalStateException notUsed) {
/**
* Case is closed, do nothing.
*/
}
} else if (eventType.equals(IngestManager.IngestJobEvent.COMPLETED.toString())
|| eventType.equals(IngestManager.IngestJobEvent.CANCELLED.toString())) {
/**
* Checking for a current case 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.getCurrentCase();
refresh(true);
tagResults.update();
} catch (IllegalStateException notUsed) {
/**
* Case is closed, do nothing.
*/
}
} else if (eventType.equals(Case.Events.CURRENT_CASE.toString())) {
// case was closed. Remove listeners so that this can be garbage collected
if (evt.getNewValue() == null) {
removeNotify();
}
}
}
};
@Override
protected void addNotify() {
IngestManager.getInstance().addIngestJobEventListener(pcl);
IngestManager.getInstance().addIngestModuleEventListener(pcl);
Case.addPropertyChangeListener(pcl);
tagResults.update();
tagResults.addObserver(this);
}
@Override
protected void removeNotify() {
IngestManager.getInstance().removeIngestJobEventListener(pcl);
IngestManager.getInstance().removeIngestModuleEventListener(pcl);
Case.removePropertyChangeListener(pcl);
tagResults.deleteObserver(this);
}
@Override
protected boolean createKeys(List<TagName> keys) {
try {
List<TagName> tagNamesInUse = Case.getCurrentCase().getServices().getTagsManager().getTagNamesInUse();
Collections.sort(tagNamesInUse);
keys.addAll(tagNamesInUse);
} catch (TskCoreException ex) {
Logger.getLogger(TagNameNodeFactory.class.getName()).log(Level.SEVERE, "Failed to get tag names", ex); //NON-NLS
}
return true;
}
@Override
protected Node createNodeForKey(TagName key) {
return new TagNameNode(key);
}
@Override
public void update(Observable o, Object arg) {
refresh(true);
}
}
/**
* Instances of this class are elements of Node hierarchies consisting of
* content and blackboard artifact tags, grouped first by tag type, then by
* tag name.
*/
public class TagNameNode extends DisplayableItemNode implements Observer {
private final String ICON_PATH = "org/sleuthkit/autopsy/images/tag-folder-blue-icon-16.png"; //NON-NLS
private final String BOOKMARK_TAG_ICON_PATH = "org/sleuthkit/autopsy/images/star-bookmark-icon-16.png"; //NON-NLS
private final TagName tagName;
public TagNameNode(TagName tagName) {
super(Children.create(new TagTypeNodeFactory(tagName), true), Lookups.singleton(NbBundle.getMessage(TagNameNode.class, "TagNameNode.namePlusTags.text", tagName.getDisplayName())));
this.tagName = tagName;
setName(tagName.getDisplayName());
updateDisplayName();
if (tagName.getDisplayName().equals(NbBundle.getMessage(this.getClass(), "TagNameNode.bookmark.text"))) {
setIconBaseWithExtension(BOOKMARK_TAG_ICON_PATH);
} else {
setIconBaseWithExtension(ICON_PATH);
}
tagResults.addObserver(this);
}
private void updateDisplayName() {
long tagsCount = 0;
try {
TagsManager tm = Case.getCurrentCase().getServices().getTagsManager();
tagsCount = tm.getContentTagsCountByTagName(tagName);
tagsCount += tm.getBlackboardArtifactTagsCountByTagName(tagName);
} catch (TskCoreException ex) {
Logger.getLogger(TagNameNode.class.getName()).log(Level.SEVERE, "Failed to get tags count for " + tagName.getDisplayName() + " tag name", ex); //NON-NLS
}
setDisplayName(tagName.getDisplayName() + " \u200E(\u200E" + tagsCount + ")\u200E");
}
@Override
protected Sheet createSheet() {
Sheet propertySheet = super.createSheet();
Sheet.Set properties = propertySheet.get(Sheet.PROPERTIES);
if (properties == null) {
properties = Sheet.createPropertiesSet();
propertySheet.put(properties);
}
properties.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "TagNameNode.createSheet.name.name"),
NbBundle.getMessage(this.getClass(), "TagNameNode.createSheet.name.displayName"), tagName.getDescription(), getName()));
return propertySheet;
}
@Override
public <T> T accept(DisplayableItemNodeVisitor<T> v) {
// See classes derived from DisplayableItemNodeVisitor<AbstractNode>
// for behavior added using the Visitor pattern.
return v.visit(this);
}
@Override
public boolean isLeafTypeNode() {
return false;
}
@Override
public void update(Observable o, Object arg) {
updateDisplayName();
}
@Override
public String getItemType() {
return getClass().getName();
}
}
/**
* Creates nodes for the two types of tags: file and artifact. Does not need
* observer / messages since it always has the same children
*/
private class TagTypeNodeFactory extends ChildFactory<String> {
private final TagName tagName;
private final String CONTENT_TAG_TYPE_NODE_KEY = NbBundle.getMessage(TagNameNode.class, "TagNameNode.contentTagTypeNodeKey.text");
private final String BLACKBOARD_ARTIFACT_TAG_TYPE_NODE_KEY = NbBundle.getMessage(TagNameNode.class, "TagNameNode.bbArtTagTypeNodeKey.text");
TagTypeNodeFactory(TagName tagName) {
super();
this.tagName = tagName;
}
@Override
protected boolean createKeys(List<String> keys) {
keys.add(CONTENT_TAG_TYPE_NODE_KEY);
keys.add(BLACKBOARD_ARTIFACT_TAG_TYPE_NODE_KEY);
return true;
}
@Override
protected Node createNodeForKey(String key) {
if (CONTENT_TAG_TYPE_NODE_KEY.equals(key)) {
return new ContentTagTypeNode(tagName);
} else if (BLACKBOARD_ARTIFACT_TAG_TYPE_NODE_KEY.equals(key)) {
return new BlackboardArtifactTagTypeNode(tagName);
} else {
Logger.getLogger(TagNameNode.class.getName()).log(Level.SEVERE, "{0} not a recognized key", key); //NON-NLS
return null;
}
}
}
private final String CONTENT_DISPLAY_NAME = NbBundle.getMessage(ContentTagTypeNode.class, "ContentTagTypeNode.displayName.text");
/**
* Node for the content tags. Children are specific tags. Instances of this
* class are are elements of a directory tree sub-tree consisting of content
* and blackboard artifact tags, grouped first by tag type, then by tag
* name.
*/
public class ContentTagTypeNode extends DisplayableItemNode implements Observer {
private final String ICON_PATH = "org/sleuthkit/autopsy/images/tag-folder-blue-icon-16.png"; //NON-NLS
private final TagName tagName;
public ContentTagTypeNode(TagName tagName) {
super(Children.create(new ContentTagNodeFactory(tagName), true), Lookups.singleton(tagName.getDisplayName() + " " + CONTENT_DISPLAY_NAME));
this.tagName = tagName;
super.setName(CONTENT_DISPLAY_NAME);
updateDisplayName();
this.setIconBaseWithExtension(ICON_PATH);
tagResults.addObserver(this);
}
private void updateDisplayName() {
long tagsCount = 0;
try {
tagsCount = Case.getCurrentCase().getServices().getTagsManager().getContentTagsCountByTagName(tagName);
} catch (TskCoreException ex) {
Logger.getLogger(ContentTagTypeNode.class.getName()).log(Level.SEVERE, "Failed to get content tags count for " + tagName.getDisplayName() + " tag name", ex); //NON-NLS
}
super.setDisplayName(CONTENT_DISPLAY_NAME + " (" + tagsCount + ")");
}
@Override
protected Sheet createSheet() {
Sheet propertySheet = super.createSheet();
Sheet.Set properties = propertySheet.get(Sheet.PROPERTIES);
if (properties == null) {
properties = Sheet.createPropertiesSet();
propertySheet.put(properties);
}
properties.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "ContentTagTypeNode.createSheet.name.name"),
NbBundle.getMessage(this.getClass(), "ContentTagTypeNode.createSheet.name.displayName"), "", getName()));
return propertySheet;
}
@Override
public <T> T accept(DisplayableItemNodeVisitor<T> v) {
return v.visit(this);
}
@Override
public boolean isLeafTypeNode() {
return true;
}
@Override
public void update(Observable o, Object arg) {
updateDisplayName();
}
@Override
public String getItemType() {
return getClass().getName();
}
}
private class ContentTagNodeFactory extends ChildFactory<ContentTag> implements Observer {
private final TagName tagName;
ContentTagNodeFactory(TagName tagName) {
super();
this.tagName = tagName;
tagResults.addObserver(this);
}
@Override
protected boolean createKeys(List<ContentTag> keys) {
// Use the content tags bearing the specified tag name as the keys.
try {
keys.addAll(Case.getCurrentCase().getServices().getTagsManager().getContentTagsByTagName(tagName));
} catch (TskCoreException ex) {
Logger.getLogger(ContentTagNodeFactory.class.getName()).log(Level.SEVERE, "Failed to get tag names", ex); //NON-NLS
}
return true;
}
@Override
protected Node createNodeForKey(ContentTag key) {
// The content tags to be wrapped are used as the keys.
return new ContentTagNode(key);
}
@Override
public void update(Observable o, Object arg) {
refresh(true);
}
}
private final String ARTIFACT_DISPLAY_NAME = NbBundle.getMessage(BlackboardArtifactTagTypeNode.class, "BlackboardArtifactTagTypeNode.displayName.text");
/**
* Instances of this class are elements in a sub-tree of the Autopsy
* presentation of the SleuthKit data model. The sub-tree consists of
* content and blackboard artifact tags, grouped first by tag type, then by
* tag name.
*/
public class BlackboardArtifactTagTypeNode extends DisplayableItemNode implements Observer {
private final TagName tagName;
private final String ICON_PATH = "org/sleuthkit/autopsy/images/tag-folder-blue-icon-16.png"; //NON-NLS
public BlackboardArtifactTagTypeNode(TagName tagName) {
super(Children.create(new BlackboardArtifactTagNodeFactory(tagName), true), Lookups.singleton(tagName.getDisplayName() + " " + ARTIFACT_DISPLAY_NAME));
this.tagName = tagName;
super.setName(ARTIFACT_DISPLAY_NAME);
this.setIconBaseWithExtension(ICON_PATH);
updateDisplayName();
tagResults.addObserver(this);
}
private void updateDisplayName() {
long tagsCount = 0;
try {
tagsCount = Case.getCurrentCase().getServices().getTagsManager().getBlackboardArtifactTagsCountByTagName(tagName);
} catch (TskCoreException ex) {
Logger.getLogger(BlackboardArtifactTagTypeNode.class.getName()).log(Level.SEVERE, "Failed to get blackboard artifact tags count for " + tagName.getDisplayName() + " tag name", ex); //NON-NLS
}
super.setDisplayName(ARTIFACT_DISPLAY_NAME + " (" + tagsCount + ")");
}
@Override
protected Sheet createSheet() {
Sheet propertySheet = super.createSheet();
Sheet.Set properties = propertySheet.get(Sheet.PROPERTIES);
if (properties == null) {
properties = Sheet.createPropertiesSet();
propertySheet.put(properties);
}
properties.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "BlackboardArtifactTagTypeNode.createSheet.name.name"),
NbBundle.getMessage(this.getClass(), "BlackboardArtifactTagTypeNode.createSheet.name.displayName"), "", getName()));
return propertySheet;
}
@Override
public <T> T accept(DisplayableItemNodeVisitor<T> v) {
return v.visit(this);
}
@Override
public boolean isLeafTypeNode() {
return true;
}
@Override
public void update(Observable o, Object arg) {
updateDisplayName();
}
@Override
public String getItemType() {
return getClass().getName();
}
}
private class BlackboardArtifactTagNodeFactory extends ChildFactory<BlackboardArtifactTag> implements Observer {
private final TagName tagName;
BlackboardArtifactTagNodeFactory(TagName tagName) {
super();
this.tagName = tagName;
tagResults.addObserver(this);
}
@Override
protected boolean createKeys(List<BlackboardArtifactTag> keys) {
try {
// Use the blackboard artifact tags bearing the specified tag name as the keys.
keys.addAll(Case.getCurrentCase().getServices().getTagsManager().getBlackboardArtifactTagsByTagName(tagName));
} catch (TskCoreException ex) {
Logger.getLogger(BlackboardArtifactTagNodeFactory.class.getName()).log(Level.SEVERE, "Failed to get tag names", ex); //NON-NLS
}
return true;
}
@Override
protected Node createNodeForKey(BlackboardArtifactTag key) {
// The blackboard artifact tags to be wrapped are used as the keys.
return new BlackboardArtifactTagNode(key);
}
@Override
public void update(Observable o, Object arg) {
refresh(true);
}
}
}