diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java index 9c048e3176..5abb9a0056 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java @@ -671,6 +671,38 @@ public class Case { public void publishAnalysisResultDeleted(TskEvent.AnalysisResultsDeletedTskEvent event) { eventPublisher.publish(new AnalysisResultDeletedEvent(event.getAnalysisResultObjectIds())); } + + @Subscribe + public void publishBlackboardArtifactTagDeleted(TskEvent.BlackboardArtifactTagsDeletedTskEvent event) { + List tags = event.getTags(); + for(BlackboardArtifactTag tag: tags) { + eventPublisher.publish(new BlackBoardArtifactTagDeletedEvent(tag)); + } + } + + @Subscribe + public void publishBlackboardTagAdded(TskEvent.BlackboardArtifactTagsAddedTskEvent event) { + List tags = event.getTags(); + for(BlackboardArtifactTag tag: tags) { + eventPublisher.publish(new BlackBoardArtifactTagAddedEvent(tag)); + } + } + + @Subscribe + public void publishContentTagAdded(TskEvent.ContentTagsAddedTskEvent event) { + List tags = event.getTags(); + for(ContentTag tag: tags) { + eventPublisher.publish(new ContentTagAddedEvent(tag)); + } + } + + @Subscribe + public void publishContentTagDeleted(TskEvent.ContentTagsDeletedTskEvent event) { + List tags = event.getTags(); + for(ContentTag tag: tags) { + eventPublisher.publish(new ContentTagDeletedEvent(tag)); + } + } } /** @@ -1820,41 +1852,6 @@ public class Case { eventPublisher.publish(new DataSourceNameChangedEvent(dataSource, newName)); } - /** - * Notifies case event subscribers that a content tag has been added. - * - * This should not be called from the event dispatch thread (EDT) - * - * @param newTag new ContentTag added - */ - public void notifyContentTagAdded(ContentTag newTag) { - notifyContentTagAdded(newTag, null); - } - - /** - * Notifies case event subscribers that a content tag has been added. - * - * This should not be called from the event dispatch thread (EDT) - * - * @param newTag The added ContentTag. - * @param deletedTagList List of ContentTags that were removed as a result - * of the addition of newTag. - */ - public void notifyContentTagAdded(ContentTag newTag, List deletedTagList) { - eventPublisher.publish(new ContentTagAddedEvent(newTag, deletedTagList)); - } - - /** - * Notifies case event subscribers that a content tag has been deleted. - * - * This should not be called from the event dispatch thread (EDT) - * - * @param deletedTag ContentTag deleted - */ - public void notifyContentTagDeleted(ContentTag deletedTag) { - eventPublisher.publish(new ContentTagDeletedEvent(deletedTag)); - } - /** * Notifies case event subscribers that a tag definition has changed. * @@ -1885,41 +1882,6 @@ public class Case { } } - /** - * Notifies case event subscribers that an artifact tag has been added. - * - * This should not be called from the event dispatch thread (EDT) - * - * @param newTag new BlackboardArtifactTag added - */ - public void notifyBlackBoardArtifactTagAdded(BlackboardArtifactTag newTag) { - notifyBlackBoardArtifactTagAdded(newTag, null); - } - - /** - * Notifies case event subscribers that an artifact tag has been added. - * - * This should not be called from the event dispatch thread (EDT) - * - * @param newTag The added ContentTag. - * @param removedTagList List of ContentTags that were removed as a result - * of the addition of newTag. - */ - public void notifyBlackBoardArtifactTagAdded(BlackboardArtifactTag newTag, List removedTagList) { - eventPublisher.publish(new BlackBoardArtifactTagAddedEvent(newTag, removedTagList)); - } - - /** - * Notifies case event subscribers that an artifact tag has been deleted. - * - * This should not be called from the event dispatch thread (EDT) - * - * @param deletedTag BlackboardArtifactTag deleted - */ - public void notifyBlackBoardArtifactTagDeleted(BlackboardArtifactTag deletedTag) { - eventPublisher.publish(new BlackBoardArtifactTagDeletedEvent(deletedTag)); - } - /** * Adds a report to the case. * diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/services/TagsManager.java b/Core/src/org/sleuthkit/autopsy/casemodule/services/TagsManager.java index a4bd51bcce..53c695cf9d 100755 --- a/Core/src/org/sleuthkit/autopsy/casemodule/services/TagsManager.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/services/TagsManager.java @@ -66,9 +66,9 @@ public class TagsManager implements Closeable { private static String PROJECT_VIC_TAG_SET_NAME = "Project VIC"; private static final Object lock = new Object(); - + private final Map allTagNameMap = Collections.synchronizedMap(new HashMap<>()); - + private final PropertyChangeListener listener = new PropertyChangeListener() { @Override public void propertyChange(PropertyChangeEvent evt) { @@ -95,7 +95,7 @@ public class TagsManager implements Closeable { } } }; - + private final PropertyChangeListener weakListener = WeakListeners.propertyChange(listener, null); static { @@ -300,12 +300,12 @@ public class TagsManager implements Closeable { try { List tagSetsInCase = taggingMgr.getTagSets(); if (tagSetsInCase.isEmpty()) { - + // add the standard tag names for (TagNameDefinition def : TagNameDefinition.getStandardTagNameDefinitions()) { taggingMgr.addOrUpdateTagName(def.getDisplayName(), def.getDescription(), def.getColor(), def.getKnownStatus()); } - + //Assume new case and add all tag sets for (TagSetDefinition setDef : TagSetDefinition.readTagSetDefinitions()) { List tagNamesInSet = new ArrayList<>(); @@ -317,12 +317,12 @@ public class TagsManager implements Closeable { taggingMgr.addTagSet(setDef.getName(), tagNamesInSet); } } - } + } - for(TagName tagName: caseDb.getAllTagNames()) { + for (TagName tagName : caseDb.getAllTagNames()) { allTagNameMap.put(tagName.getDisplayName(), tagName); } - + } catch (TskCoreException ex) { LOGGER.log(Level.SEVERE, "Error updating standard tag name and tag set definitions", ex); } catch (IOException ex) { @@ -332,7 +332,7 @@ public class TagsManager implements Closeable { for (TagNameDefinition tagName : TagNameDefinition.getTagNameDefinitions()) { tagName.saveToCase(caseDb); } - + Case.addEventTypeSubscriber(Collections.singleton(Case.Events.TAG_NAMES_UPDATED), weakListener); Case.addEventTypeSubscriber(Collections.singleton(Case.Events.TAG_NAMES_ADDED), weakListener); Case.addEventTypeSubscriber(Collections.singleton(Case.Events.TAG_NAMES_DELETED), weakListener); @@ -359,7 +359,7 @@ public class TagsManager implements Closeable { * @throws TskCoreException If there is an error querying the case database. */ public TagSet getTagSet(TagName tagName) throws TskCoreException { - return caseDb.getTaggingManager().getTagSet(tagName); + return caseDb.getTaggingManager().getTagSet(tagName); } /** @@ -383,7 +383,7 @@ public class TagsManager implements Closeable { * @return A list, possibly empty, of TagName objects. */ public synchronized List getAllTagNames() { - + List tagNames = new ArrayList<>(); tagNames.addAll(allTagNameMap.values()); return tagNames; @@ -636,14 +636,6 @@ public class TagsManager implements Closeable { */ public ContentTag addContentTag(Content content, TagName tagName, String comment, long beginByteOffset, long endByteOffset) throws TskCoreException { TaggingManager.ContentTagChange tagChange = caseDb.getTaggingManager().addContentTag(content, tagName, comment, beginByteOffset, endByteOffset); - try { - Case currentCase = Case.getCurrentCaseThrows(); - - currentCase.notifyContentTagAdded(tagChange.getAddedTag(), tagChange.getRemovedTags().isEmpty() ? null : tagChange.getRemovedTags()); - - } catch (NoCurrentCaseException ex) { - throw new TskCoreException("Added a tag to a closed case", ex); - } return tagChange.getAddedTag(); } @@ -657,11 +649,6 @@ public class TagsManager implements Closeable { */ public void deleteContentTag(ContentTag tag) throws TskCoreException { caseDb.deleteContentTag(tag); - try { - Case.getCurrentCaseThrows().notifyContentTagDeleted(tag); - } catch (NoCurrentCaseException ex) { - throw new TskCoreException("Deleted a tag from a closed case", ex); - } } /** @@ -857,12 +844,6 @@ public class TagsManager implements Closeable { */ public BlackboardArtifactTag addBlackboardArtifactTag(BlackboardArtifact artifact, TagName tagName, String comment) throws TskCoreException { TaggingManager.BlackboardArtifactTagChange tagChange = caseDb.getTaggingManager().addArtifactTag(artifact, tagName, comment); - try { - Case currentCase = Case.getCurrentCaseThrows(); - currentCase.notifyBlackBoardArtifactTagAdded(tagChange.getAddedTag(), tagChange.getRemovedTags().isEmpty() ? null : tagChange.getRemovedTags()); - } catch (NoCurrentCaseException ex) { - throw new TskCoreException("Added a tag to a closed case", ex); - } return tagChange.getAddedTag(); } @@ -876,11 +857,6 @@ public class TagsManager implements Closeable { */ public void deleteBlackboardArtifactTag(BlackboardArtifactTag tag) throws TskCoreException { caseDb.deleteBlackboardArtifactTag(tag); - try { - Case.getCurrentCaseThrows().notifyBlackBoardArtifactTagDeleted(tag); - } catch (NoCurrentCaseException ex) { - throw new TskCoreException("Deleted a tag from a closed case", ex); - } } /** diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/DisplayableItemNodeVisitor.java b/Core/src/org/sleuthkit/autopsy/datamodel/DisplayableItemNodeVisitor.java index 76b7b7f352..766f2b713d 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/DisplayableItemNodeVisitor.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/DisplayableItemNodeVisitor.java @@ -91,12 +91,6 @@ public interface DisplayableItemNodeVisitor { */ T visit(Tags.RootNode node); - T visit(Tags.TagNameNode node); - - T visit(Tags.ContentTagTypeNode node); - - T visit(Tags.BlackboardArtifactTagTypeNode node); - /* * Reports @@ -270,21 +264,6 @@ public interface DisplayableItemNodeVisitor { return defaultVisit(node); } - @Override - public T visit(Tags.TagNameNode node) { - return defaultVisit(node); - } - - @Override - public T visit(Tags.ContentTagTypeNode node) { - return defaultVisit(node); - } - - @Override - public T visit(Tags.BlackboardArtifactTagTypeNode node) { - return defaultVisit(node); - } - @Override public T visit(Reports.ReportsListNode node) { return defaultVisit(node); diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/RootContentChildren.java b/Core/src/org/sleuthkit/autopsy/datamodel/RootContentChildren.java index 695c3f6930..4ac4df209a 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/RootContentChildren.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/RootContentChildren.java @@ -97,7 +97,7 @@ public class RootContentChildren extends Children.Keys { public static Node createNode(Object key) { if (key instanceof Tags) { Tags tagsNodeKey = (Tags) key; - return tagsNodeKey.new RootNode(tagsNodeKey.filteringDataSourceObjId()); + return new Tags.RootNode(tagsNodeKey.filteringDataSourceObjId()); } else if (key instanceof DataSources) { DataSources dataSourcesKey = (DataSources) key; return new DataSourceFilesNode(dataSourcesKey.filteringDataSourceObjId()); diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/Tags.java b/Core/src/org/sleuthkit/autopsy/datamodel/Tags.java index 34c8be3ec3..c2239340ca 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/Tags.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/Tags.java @@ -18,39 +18,12 @@ */ package org.sleuthkit.autopsy.datamodel; -import java.beans.PropertyChangeEvent; -import java.beans.PropertyChangeListener; -import java.util.Collections; -import java.util.Comparator; -import java.util.EnumSet; -import java.util.List; -import java.util.Observable; -import java.util.Observer; -import java.util.Set; -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.WeakListeners; import org.openide.util.lookup.Lookups; -import org.sleuthkit.autopsy.casemodule.Case; -import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; -import org.sleuthkit.autopsy.casemodule.services.TagsManager; -import org.sleuthkit.autopsy.core.UserPreferences; -import org.sleuthkit.autopsy.corecomponents.DataResultTopComponent; -import org.sleuthkit.autopsy.coreutils.Logger; -import org.sleuthkit.autopsy.ingest.IngestManager; -import org.sleuthkit.autopsy.mainui.datamodel.TagsSearchParams; -import static org.sleuthkit.autopsy.mainui.datamodel.TagsSearchParams.TagType.FILE; -import static org.sleuthkit.autopsy.mainui.datamodel.TagsSearchParams.TagType.RESULT; -import org.sleuthkit.autopsy.corecomponents.SelectionResponder; -import org.sleuthkit.autopsy.tags.TagUtils; -import org.sleuthkit.datamodel.BlackboardArtifactTag; -import org.sleuthkit.datamodel.ContentTag; -import org.sleuthkit.datamodel.TagName; -import org.sleuthkit.datamodel.TskCoreException; +import org.sleuthkit.autopsy.mainui.nodes.TagNameFactory; /** * Instances of this class act as keys for use by instances of the @@ -58,16 +31,7 @@ import org.sleuthkit.datamodel.TskCoreException; * factory built on top of the NetBeans Children.Keys class. */ public class Tags { - // Creation of a RootNode object corresponding to a Tags object is done - // by a CreateAutopsyNodeVisitor dispatched from the AbstractContentChildren - // override of Children.Keys.createNodes(). - private final static String DISPLAY_NAME = NbBundle.getMessage(RootNode.class, "TagsNode.displayName.text"); - private static final Set INGEST_JOB_EVENTS_OF_INTEREST = EnumSet.of(IngestManager.IngestJobEvent.COMPLETED, IngestManager.IngestJobEvent.CANCELLED); - private static final String USER_NAME_PROPERTY = "user.name"; //NON-NLS - private final TagResults tagResults = new TagResults(); - private final String ICON_PATH = "org/sleuthkit/autopsy/images/tag-folder-blue-icon-16.png"; //NON-NLS - private final long filteringDSObjId; // 0 if not filtering/grouping by data source Tags() { @@ -90,35 +54,24 @@ public class Tags { long filteringDataSourceObjId() { return this.filteringDSObjId; } - - - - /** - * 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 static class RootNode extends DisplayableItemNode { + + private final static String ICON_PATH = "org/sleuthkit/autopsy/images/tag-folder-blue-icon-16.png"; //NON-NLS + private final Long dataSourceObjId; - public RootNode(long objId) { - super(Children.create(new TagNameNodeFactory(objId), true), Lookups.singleton(DISPLAY_NAME)); + public RootNode(Long dsId) { + super(Children.create(new TagNameFactory(dsId != null && dsId> 0 ? dsId : null), true), Lookups.singleton(DISPLAY_NAME)); super.setName(DISPLAY_NAME); super.setDisplayName(DISPLAY_NAME); this.setIconBaseWithExtension(ICON_PATH); + this.dataSourceObjId = dsId != null && dsId> 0 ? dsId : null; } @Override @@ -148,423 +101,16 @@ public class Tags { public String getItemType() { return getClass().getName(); } - + + public Node clone() { + return new RootNode(dataSourceObjId); + } + /** * Cause the contents of the RootNode and its children to be updated. */ public void refresh() { - tagResults.update(); - } - - } - - private class TagNameNodeFactory extends ChildFactory.Detachable implements Observer { - - private final long filteringDSObjId; // 0 if not filtering/grouping by data source - - private final Set CASE_EVENTS_OF_INTEREST = EnumSet.of(Case.Events.BLACKBOARD_ARTIFACT_TAG_ADDED, - Case.Events.BLACKBOARD_ARTIFACT_TAG_DELETED, - Case.Events.CONTENT_TAG_ADDED, - Case.Events.CONTENT_TAG_DELETED, - Case.Events.CURRENT_CASE); - - 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.getCurrentCaseThrows(); - refresh(true); - tagResults.update(); - } catch (NoCurrentCaseException 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.getCurrentCaseThrows(); - refresh(true); - tagResults.update(); - } catch (NoCurrentCaseException 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(); - } - } - } - }; - - private final PropertyChangeListener weakPcl = WeakListeners.propertyChange(pcl, null); - - /** - * Constructor - * - * @param objId data source object id - */ - TagNameNodeFactory(long objId) { - this.filteringDSObjId = objId; - } - - @Override - protected void addNotify() { - IngestManager.getInstance().addIngestJobEventListener(INGEST_JOB_EVENTS_OF_INTEREST, weakPcl); - Case.addEventTypeSubscriber(CASE_EVENTS_OF_INTEREST, weakPcl); - tagResults.update(); - tagResults.addObserver(this); - } - - @Override - protected void finalize() throws Throwable { - super.finalize(); - IngestManager.getInstance().removeIngestJobEventListener(weakPcl); - Case.removeEventTypeSubscriber(CASE_EVENTS_OF_INTEREST, weakPcl); - tagResults.deleteObserver(this); - } - - @Override - protected boolean createKeys(List keys) { - try { - List tagNamesInUse; - if (UserPreferences.showOnlyCurrentUserTags()) { - String userName = System.getProperty(USER_NAME_PROPERTY); - tagNamesInUse = (filteringDSObjId > 0) - ? Case.getCurrentCaseThrows().getServices().getTagsManager().getTagNamesInUseForUser(filteringDSObjId, userName) - : Case.getCurrentCaseThrows().getServices().getTagsManager().getTagNamesInUseForUser(userName); - } else { - tagNamesInUse = (filteringDSObjId > 0) - ? Case.getCurrentCaseThrows().getServices().getTagsManager().getTagNamesInUse(filteringDSObjId) - : Case.getCurrentCaseThrows().getServices().getTagsManager().getTagNamesInUse(); - } - Collections.sort(tagNamesInUse, new Comparator() { - @Override - public int compare(TagName o1, TagName o2) { - return TagUtils.getDecoratedTagDisplayName(o1).compareTo(TagUtils.getDecoratedTagDisplayName(o2)); - } - }); - keys.addAll(tagNamesInUse); - } catch (TskCoreException | NoCurrentCaseException 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(TagUtils.getDecoratedTagDisplayName(tagName)); - updateDisplayName(); - if (tagName.getDisplayName().equals(TagsManager.getBookmarkTagDisplayName())) { - setIconBaseWithExtension(BOOKMARK_TAG_ICON_PATH); - } else { - setIconBaseWithExtension(ICON_PATH); - } - tagResults.addObserver(this); - } - - private void updateDisplayName() { - long tagsCount = 0; - try { - TagsManager tm = Case.getCurrentCaseThrows().getServices().getTagsManager(); - if (UserPreferences.showOnlyCurrentUserTags()) { - String userName = System.getProperty(USER_NAME_PROPERTY); - if (filteringDSObjId > 0) { - tagsCount = tm.getContentTagsCountByTagNameForUser(tagName, filteringDSObjId, userName); - tagsCount += tm.getBlackboardArtifactTagsCountByTagNameForUser(tagName, filteringDSObjId, userName); - } else { - tagsCount = tm.getContentTagsCountByTagNameForUser(tagName, userName); - tagsCount += tm.getBlackboardArtifactTagsCountByTagNameForUser(tagName, userName); - } - } else { - if (filteringDSObjId > 0) { - tagsCount = tm.getContentTagsCountByTagName(tagName, filteringDSObjId); - tagsCount += tm.getBlackboardArtifactTagsCountByTagName(tagName, filteringDSObjId); - } else { - tagsCount = tm.getContentTagsCountByTagName(tagName); - tagsCount += tm.getBlackboardArtifactTagsCountByTagName(tagName); - } - } - } catch (TskCoreException | NoCurrentCaseException ex) { - Logger.getLogger(TagNameNode.class.getName()).log(Level.SEVERE, "Failed to get tags count for " + tagName.getDisplayName() + " tag name", ex); //NON-NLS - } - setDisplayName(TagUtils.getDecoratedTagDisplayName(tagName) + " \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 accept(DisplayableItemNodeVisitor visitor) { - // See classes derived from DisplayableItemNodeVisitor - // for behavior added using the Visitor pattern. - return visitor.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 { - - 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 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, SelectionResponder { - - 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.LEAF, Lookups.singleton(tagName.getDisplayName() + " " + CONTENT_DISPLAY_NAME)); - this.tagName = tagName; - super.setName(CONTENT_DISPLAY_NAME); - updateDisplayName(); - this.setIconBaseWithExtension(ICON_PATH); - tagResults.addObserver(this); - } - - @Override - public void respondSelection(DataResultTopComponent dataResultPanel) { - dataResultPanel.displayTags(new TagsSearchParams(tagName, FILE, - filteringDataSourceObjId() > 0 ? filteringDataSourceObjId() : null)); - } - - private void updateDisplayName() { - long tagsCount = 0; - try { - - if (UserPreferences.showOnlyCurrentUserTags()) { - String userName = System.getProperty(USER_NAME_PROPERTY); - tagsCount = (filteringDSObjId > 0) - ? Case.getCurrentCaseThrows().getServices().getTagsManager().getContentTagsCountByTagNameForUser(tagName, filteringDSObjId, userName) - : Case.getCurrentCaseThrows().getServices().getTagsManager().getContentTagsCountByTagNameForUser(tagName, userName); - } else { - tagsCount = (filteringDSObjId > 0) - ? Case.getCurrentCaseThrows().getServices().getTagsManager().getContentTagsCountByTagName(tagName, filteringDSObjId) - : Case.getCurrentCaseThrows().getServices().getTagsManager().getContentTagsCountByTagName(tagName); - } - } catch (TskCoreException | NoCurrentCaseException 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 accept(DisplayableItemNodeVisitor visitor) { - return visitor.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 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, SelectionResponder { - - 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.LEAF, Lookups.singleton(tagName.getDisplayName() + " " + ARTIFACT_DISPLAY_NAME)); - this.tagName = tagName; - super.setName(ARTIFACT_DISPLAY_NAME); - this.setIconBaseWithExtension(ICON_PATH); - updateDisplayName(); - tagResults.addObserver(this); - } - - @Override - public void respondSelection(DataResultTopComponent dataResultPanel) { - dataResultPanel.displayTags(new TagsSearchParams(tagName, RESULT, - filteringDataSourceObjId() > 0 ? filteringDataSourceObjId() : null)); - } - - private void updateDisplayName() { - long tagsCount = 0; - try { - if (UserPreferences.showOnlyCurrentUserTags()) { - String userName = System.getProperty(USER_NAME_PROPERTY); - tagsCount = (filteringDSObjId > 0) - ? Case.getCurrentCaseThrows().getServices().getTagsManager().getBlackboardArtifactTagsCountByTagNameForUser(tagName, filteringDSObjId, userName) - : Case.getCurrentCaseThrows().getServices().getTagsManager().getBlackboardArtifactTagsCountByTagNameForUser(tagName, userName); - } else { - tagsCount = (filteringDSObjId > 0) - ? Case.getCurrentCaseThrows().getServices().getTagsManager().getBlackboardArtifactTagsCountByTagName(tagName, filteringDSObjId) - : Case.getCurrentCaseThrows().getServices().getTagsManager().getBlackboardArtifactTagsCountByTagName(tagName); - } - } catch (TskCoreException | NoCurrentCaseException 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 accept(DisplayableItemNodeVisitor visitor) { - return visitor.visit(this); - } - - @Override - public boolean isLeafTypeNode() { - return true; - } - - @Override - public void update(Observable o, Object arg) { - updateDisplayName(); - } - - @Override - public String getItemType() { - return getClass().getName(); + this.refresh(); } } } diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeFilterChildren.java b/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeFilterChildren.java index 8ceecea3cc..7f35282209 100644 --- a/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeFilterChildren.java +++ b/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeFilterChildren.java @@ -37,6 +37,7 @@ import org.sleuthkit.autopsy.datamodel.LayoutFileNode; import org.sleuthkit.autopsy.datamodel.LocalFileNode; import org.sleuthkit.autopsy.datamodel.LocalDirectoryNode; import org.sleuthkit.autopsy.datamodel.SlackFileNode; +import org.sleuthkit.autopsy.datamodel.Tags; import org.sleuthkit.autopsy.datamodel.ViewsNode; import org.sleuthkit.autopsy.datamodel.VirtualDirectoryNode; import org.sleuthkit.autopsy.datamodel.VolumeNode; @@ -92,6 +93,9 @@ class DirectoryTreeFilterChildren extends FilterNode.Children { } else if (origNode instanceof AnalysisResults.RootNode) { Node cloned = ((AnalysisResults.RootNode) origNode).clone(); return new Node[]{cloned}; + } else if (origNode instanceof Tags.RootNode) { + Node cloned = ((Tags.RootNode) origNode).clone(); + return new Node[]{cloned}; } else if (origNode instanceof ViewsNode) { Node cloned = ((ViewsNode) origNode).clone(); return new Node[]{cloned}; diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/actionhelpers/ExtractActionHelper.java b/Core/src/org/sleuthkit/autopsy/directorytree/actionhelpers/ExtractActionHelper.java index 386240ea86..bd7cc96a51 100644 --- a/Core/src/org/sleuthkit/autopsy/directorytree/actionhelpers/ExtractActionHelper.java +++ b/Core/src/org/sleuthkit/autopsy/directorytree/actionhelpers/ExtractActionHelper.java @@ -327,7 +327,7 @@ public class ExtractActionHelper { @Override protected ExtractFscContentVisitor getChildVisitor(File childFile, ProgressHandle progress, SwingWorker worker) { - return new UIExtractionVisitor(childFile, progress, worker, false); + return new UIExtractionVisitor<>(childFile, progress, worker, false); } 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 067caeaded..4fe5bc1c15 100644 --- a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/Bundle.properties-MERGED @@ -117,3 +117,5 @@ TagsDAO.tagColumns.sourceNameColLbl=Source Name TagsDAO.tagColumns.sourcePathColLbl=Source File Path TagsDAO.tagColumns.typeColLbl=Result Type TagsDAO.tagColumns.userNameColLbl=User Name +TagType_File_displayName=File Tags +TagType_Result_displayName=Result Tags diff --git a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/TagNameSearchParams.java b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/TagNameSearchParams.java new file mode 100644 index 0000000000..424cc61a81 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/TagNameSearchParams.java @@ -0,0 +1,86 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2022 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.TagName; + +/** + * + * Search param for a tag name. + */ +public class TagNameSearchParams { + + private static final String TYPE_ID = "TAG_TYPE"; + + /** + * @return The type id for this search parameter. + */ + public static String getTypeId() { + return TYPE_ID; + } + + private final TagName tagName; + private final Long dataSourceId; + + public TagNameSearchParams(TagName tagName, Long dataSourceId) { + this.dataSourceId = dataSourceId; + this.tagName = tagName; + } + + public TagName getTagName() { + return tagName; + } + + public Long getDataSourceId() { + return dataSourceId; + } + + @Override + public int hashCode() { + int hash = 3; + hash = 67 * hash + Objects.hashCode(this.tagName); + 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 TagNameSearchParams other = (TagNameSearchParams) obj; + if (!Objects.equals(this.tagName, other.tagName)) { + return false; + } + if (!Objects.equals(this.dataSourceId, other.dataSourceId)) { + return false; + } + return true; + } + + + +} diff --git a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/TagsDAO.java b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/TagsDAO.java index b469d3efc3..9f94b0d5d9 100755 --- a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/TagsDAO.java +++ b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/TagsDAO.java @@ -22,15 +22,20 @@ import org.sleuthkit.autopsy.mainui.datamodel.events.DAOEvent; import com.google.common.cache.Cache; import com.google.common.cache.CacheBuilder; import java.beans.PropertyChangeEvent; +import java.sql.SQLException; +import java.text.MessageFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Comparator; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Objects; 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.apache.commons.lang3.StringUtils; @@ -42,21 +47,29 @@ 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.casemodule.services.TagsManager; import org.sleuthkit.autopsy.core.UserPreferences; +import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.TimeZoneUtils; +import org.sleuthkit.autopsy.datamodel.Tags; import static org.sleuthkit.autopsy.mainui.datamodel.AbstractDAO.CACHE_DURATION; import static org.sleuthkit.autopsy.mainui.datamodel.AbstractDAO.CACHE_DURATION_UNITS; import static org.sleuthkit.autopsy.mainui.datamodel.AbstractDAO.CACHE_SIZE; import org.sleuthkit.autopsy.mainui.datamodel.TagsSearchParams.TagType; +import org.sleuthkit.autopsy.mainui.datamodel.TreeResultsDTO.TreeDisplayCount; import org.sleuthkit.autopsy.mainui.datamodel.TreeResultsDTO.TreeItemDTO; import org.sleuthkit.autopsy.mainui.datamodel.events.TagsEvent; import org.sleuthkit.autopsy.mainui.datamodel.events.TreeCounts; import org.sleuthkit.autopsy.mainui.datamodel.events.TreeEvent; import org.sleuthkit.autopsy.mainui.nodes.DAOFetcher; +import org.sleuthkit.autopsy.tags.TagUtils; import org.sleuthkit.datamodel.AbstractFile; +import org.sleuthkit.datamodel.Account; import org.sleuthkit.datamodel.BlackboardArtifactTag; +import org.sleuthkit.datamodel.CaseDbAccessManager; import org.sleuthkit.datamodel.Content; import org.sleuthkit.datamodel.ContentTag; +import org.sleuthkit.datamodel.SleuthkitCase; import org.sleuthkit.datamodel.Tag; import org.sleuthkit.datamodel.TagName; import org.sleuthkit.datamodel.TskCoreException; @@ -85,6 +98,8 @@ import org.sleuthkit.datamodel.TskCoreException; "TagsDAO.tagColumns.userNameColLbl=User Name"}) public class TagsDAO extends AbstractDAO { + private static final Logger logger = Logger.getLogger(TagsDAO.class.getName()); + private static final String USER_NAME_PROPERTY = "user.name"; //NON-NLS private static final List FILE_TAG_COLUMNS = Arrays.asList( @@ -122,14 +137,12 @@ public class TagsDAO extends AbstractDAO { return new ColumnKey(name, name, Bundle.TagsDAO_fileColumns_noDescription()); } - private final Cache, SearchResultsDTO> 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 final TreeCounts treeCounts = new TreeCounts<>(); public SearchResultsDTO getTags(TagsSearchParams key, long startItem, Long maxCount) throws ExecutionException, IllegalArgumentException { - if (key.getTagName() == null) { - throw new IllegalArgumentException("Must have non-null tag name"); - } else if (key.getDataSourceId() != null && key.getDataSourceId() <= 0) { + 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"); @@ -175,13 +188,14 @@ public class TagsDAO extends AbstractDAO { private SearchResultsDTO fetchResultTags(SearchParams cacheKey) throws NoCurrentCaseException, TskCoreException { Long dataSourceId = cacheKey.getParamData().getDataSourceId(); - TagName tagName = cacheKey.getParamData().getTagName(); + TagName tagNameId = cacheKey.getParamData().getTagName(); + TagsManager tm = Case.getCurrentCase().getServices().getTagsManager(); // 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); + ? tm.getBlackboardArtifactTagsByTagName(tagNameId, dataSourceId) + : tm.getBlackboardArtifactTagsByTagName(tagNameId); if (UserPreferences.showOnlyCurrentUserTags()) { String userName = System.getProperty(USER_NAME_PROPERTY); for (BlackboardArtifactTag tag : artifactTags) { @@ -233,13 +247,15 @@ public class TagsDAO extends AbstractDAO { private SearchResultsDTO fetchFileTags(SearchParams cacheKey) throws NoCurrentCaseException, TskCoreException { Long dataSourceId = cacheKey.getParamData().getDataSourceId(); - TagName tagName = cacheKey.getParamData().getTagName(); + TagName tagNameId = cacheKey.getParamData().getTagName(); + + TagsManager tm = Case.getCurrentCase().getServices().getTagsManager(); // 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); + ? tm.getContentTagsByTagName(tagNameId, dataSourceId) + : tm.getContentTagsByTagName(tagNameId); if (UserPreferences.showOnlyCurrentUserTags()) { String userName = System.getProperty(USER_NAME_PROPERTY); for (ContentTag tag : contentTags) { @@ -307,10 +323,10 @@ public class TagsDAO extends AbstractDAO { private TreeItemDTO getTreeItem(TagsEvent evt, TreeResultsDTO.TreeDisplayCount count) { return new TreeItemDTO<>( - TagsSearchParams.getTypeId(), - new TagsSearchParams(evt.getTagName(), evt.getTagType(), evt.getDataSourceId()), - evt.getTagName().getId(), - evt.getTagName().getDisplayName(), + TagsSearchParams.getTypeId(), + new TagsSearchParams(evt.getTagName(), evt.getTagType(), evt.getDataSourceId()), + evt.getTagName().getId(), + evt.getTagName().getDisplayName(), count); } @@ -345,9 +361,9 @@ public class TagsDAO extends AbstractDAO { Collection daoEvents = Collections.singletonList(data); - Collection treeEvents = this.treeCounts.enqueueAll(daoEvents).stream() - .map(arEvt -> new TreeEvent(getTreeItem(arEvt, TreeResultsDTO.TreeDisplayCount.INDETERMINATE), false)) - .collect(Collectors.toList()); + Collection treeEvents = daoEvents.stream() + .map(arEvt -> new TreeEvent(getTreeItem(arEvt, TreeResultsDTO.TreeDisplayCount.UNSPECIFIED), true)) + .collect(Collectors.toSet()); return Stream.of(daoEvents, treeEvents) .flatMap(lst -> lst.stream()) @@ -398,6 +414,208 @@ public class TagsDAO extends AbstractDAO { return null; } + /** + * Returns the counts of each tag name. + * + * @param dataSourceId The data source object id to filter on. + * + * @return The tree item results. + * + * @throws ExecutionException + */ + public TreeResultsDTO getNameCounts(Long dataSourceId) throws ExecutionException { + Set indeterminateTagNameIds = this.treeCounts.getEnqueued().stream() + .filter(evt -> dataSourceId == null || evt.getDataSourceId() == dataSourceId) + .map(evt -> evt.getTagName()) + .collect(Collectors.toSet()); + + Map tagNameCount = new HashMap<>(); + try { + TagsManager tm = Case.getCurrentCaseThrows().getServices().getTagsManager(); + + List tagNamesInUse; + if (UserPreferences.showOnlyCurrentUserTags()) { + String userName = System.getProperty(USER_NAME_PROPERTY); + tagNamesInUse = (dataSourceId != null) + ? tm.getTagNamesInUseForUser(dataSourceId, userName) + : tm.getTagNamesInUseForUser(userName); + } else { + tagNamesInUse = (dataSourceId != null) + ? Case.getCurrentCaseThrows().getServices().getTagsManager().getTagNamesInUse(dataSourceId) + : Case.getCurrentCaseThrows().getServices().getTagsManager().getTagNamesInUse(); + } + + for (TagName tagName : tagNamesInUse) { + if (indeterminateTagNameIds.contains(tagName)) { + tagNameCount.put(tagName, TreeDisplayCount.INDETERMINATE); + continue; + } + + long tagsCount; + if (UserPreferences.showOnlyCurrentUserTags()) { + String userName = System.getProperty(USER_NAME_PROPERTY); + if (dataSourceId != null) { + tagsCount = tm.getContentTagsCountByTagNameForUser(tagName, dataSourceId, userName); + tagsCount += tm.getBlackboardArtifactTagsCountByTagNameForUser(tagName, dataSourceId, userName); + } else { + tagsCount = tm.getContentTagsCountByTagNameForUser(tagName, userName); + tagsCount += tm.getBlackboardArtifactTagsCountByTagNameForUser(tagName, userName); + } + } else { + if (dataSourceId != null) { + tagsCount = tm.getContentTagsCountByTagName(tagName, dataSourceId); + tagsCount += tm.getBlackboardArtifactTagsCountByTagName(tagName, dataSourceId); + } else { + tagsCount = tm.getContentTagsCountByTagName(tagName); + tagsCount += tm.getBlackboardArtifactTagsCountByTagName(tagName); + } + } + + tagNameCount.put(tagName, TreeDisplayCount.getDeterminate(tagsCount)); + } + + } catch (NoCurrentCaseException | TskCoreException ex) { + throw new ExecutionException("An error occurred while fetching data artifact counts.", ex); + } + + List> tagNameParams = tagNameCount.entrySet().stream() + .map(e -> createTagNameTreeItem(e.getKey(), dataSourceId, e.getValue())) + .collect(Collectors.toList()); + + // return results + return new TreeResultsDTO<>(tagNameParams); + } + + /** + * Creates a tag name tree item. + * + * @param tagName The tag name. + * @param dataSourceId The data source object id or null if not present. + * @param treeDisplayCount The tree display count. + * + * @return The tree item dto. + */ + public TreeItemDTO createTagNameTreeItem(TagName tagName, Long dataSourceId, TreeResultsDTO.TreeDisplayCount treeDisplayCount) { + return new TreeItemDTO<>( + TagNameSearchParams.getTypeId(), + new TagNameSearchParams(tagName, dataSourceId), + tagName.getId(), + tagName.getDisplayName(), + treeDisplayCount + ); + } + + /** + * The count of content tags. + * + * @param dataSourceId The data source id where the content tag should + * appear or null. + * @param tagName The tag name. + * + * @return The count. + * + * @throws NoCurrentCaseException + * @throws TskCoreException + */ + private long getContentTagCount(Long dataSourceId, TagName tagName) throws NoCurrentCaseException, TskCoreException { + + if (UserPreferences.showOnlyCurrentUserTags()) { + String userName = System.getProperty(USER_NAME_PROPERTY); + return (dataSourceId != null) + ? Case.getCurrentCaseThrows().getServices().getTagsManager().getContentTagsCountByTagNameForUser(tagName, dataSourceId, userName) + : Case.getCurrentCaseThrows().getServices().getTagsManager().getContentTagsCountByTagNameForUser(tagName, userName); + } else { + return (dataSourceId != null) + ? Case.getCurrentCaseThrows().getServices().getTagsManager().getContentTagsCountByTagName(tagName, dataSourceId) + : Case.getCurrentCaseThrows().getServices().getTagsManager().getContentTagsCountByTagName(tagName); + } + } + + /** + * The count of result tags. + * + * @param dataSourceId The data source id where the result tag should appear + * or null. + * @param tagName The tag name. + * + * @return The count. + * + * @throws NoCurrentCaseException + * @throws TskCoreException + */ + private long getArtifactTagCount(Long dataSourceId, TagName tagName) throws NoCurrentCaseException, TskCoreException { + if (UserPreferences.showOnlyCurrentUserTags()) { + String userName = System.getProperty(USER_NAME_PROPERTY); + return (dataSourceId != null) + ? Case.getCurrentCaseThrows().getServices().getTagsManager().getBlackboardArtifactTagsCountByTagNameForUser(tagName, dataSourceId, userName) + : Case.getCurrentCaseThrows().getServices().getTagsManager().getBlackboardArtifactTagsCountByTagNameForUser(tagName, userName); + } else { + return (dataSourceId != null) + ? Case.getCurrentCaseThrows().getServices().getTagsManager().getBlackboardArtifactTagsCountByTagName(tagName, dataSourceId) + : Case.getCurrentCaseThrows().getServices().getTagsManager().getBlackboardArtifactTagsCountByTagName(tagName); + } + } + + /** + * Returns the counts of file and result type given the search params. + * + * @param searchParams The tag name search params. + * + * @return The tree item results. + * + * @throws ExecutionException + */ + public TreeResultsDTO getTypeCounts(TagNameSearchParams searchParams) throws ExecutionException { + Long dataSourceId = searchParams.getDataSourceId(); + TagName tagName = searchParams.getTagName(); + + Set indeterminateTagTypes = this.treeCounts.getEnqueued().stream() + .filter(evt -> (dataSourceId == null || Objects.equals(evt.getDataSourceId(), dataSourceId)) && Objects.equals(tagName, evt.getTagName())) + .map(evt -> evt.getTagType()) + .collect(Collectors.toSet()); + + try { + return new TreeResultsDTO<>(Arrays.asList( + createTagTypeTreeItem( + tagName, + TagType.FILE, + dataSourceId, + indeterminateTagTypes.contains(TagType.FILE) + ? TreeDisplayCount.INDETERMINATE + : TreeDisplayCount.getDeterminate(getContentTagCount(dataSourceId, tagName))), + createTagTypeTreeItem( + tagName, + TagType.RESULT, + dataSourceId, + indeterminateTagTypes.contains(TagType.RESULT) + ? TreeDisplayCount.INDETERMINATE + : TreeDisplayCount.getDeterminate(getArtifactTagCount(dataSourceId, tagName))) + )); + } catch (NoCurrentCaseException | TskCoreException ex) { + throw new ExecutionException("An error occurred while fetching tag type counts.", ex); + } + } + + /** + * Creates a tag type tree item. + * + * @param tagName The tag name. + * @param tagType The tag type. + * @param dataSourceId The data source object id or null if not present. + * @param treeDisplayCount The tree display count. + * + * @return The tree item dto. + */ + public TreeItemDTO createTagTypeTreeItem(TagName tagName, TagType tagType, Long dataSourceId, TreeResultsDTO.TreeDisplayCount treeDisplayCount) { + return new TreeItemDTO<>( + TagsSearchParams.getTypeId(), + new TagsSearchParams(tagName, tagType, dataSourceId), + tagName.getId() + "_" + tagType.name(), + tagType.getDisplayName(), + treeDisplayCount + ); + } + /** * Handles fetching and paging of data for allTags. */ diff --git a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/TagsSearchParams.java b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/TagsSearchParams.java index 5f5d862cf3..b0153d2810 100755 --- a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/TagsSearchParams.java +++ b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/TagsSearchParams.java @@ -19,12 +19,13 @@ package org.sleuthkit.autopsy.mainui.datamodel; import java.util.Objects; +import org.openide.util.NbBundle.Messages; import org.sleuthkit.datamodel.TagName; /** * Key for accessing data about tags from the DAO. */ -public class TagsSearchParams { +public class TagsSearchParams extends TagNameSearchParams { private static final String TYPE_ID = "TAG"; @@ -35,39 +36,40 @@ public class TagsSearchParams { return TYPE_ID; } + @Messages({ + "TagType_File_displayName=File Tags", + "TagType_Result_displayName=Result Tags",}) public enum TagType { - FILE, - RESULT; + FILE(Bundle.TagType_File_displayName()), + RESULT(Bundle.TagType_Result_displayName()); + + private final String displayName; + + TagType(String displayName) { + this.displayName = displayName; + } + + public String getDisplayName() { + return displayName; + } } private final TagType type; - private final TagName tagName; - private final Long dataSourceId; public TagsSearchParams(TagName tagName, TagType type, Long dataSourceId) { - this.tagName = tagName; + super(tagName, dataSourceId); this.type = type; - this.dataSourceId = dataSourceId; - } - - public TagName getTagName() { - return tagName; } public TagType getTagType() { return type; } - 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); + int hash = 3; + hash = 97 * hash + Objects.hashCode(this.type); + hash = 97 * hash + super.hashCode(); return hash; } @@ -83,16 +85,10 @@ public class TagsSearchParams { return false; } final TagsSearchParams other = (TagsSearchParams) obj; - if (!Objects.equals(this.tagName, other.tagName)) { + if (this.type != other.type) { return false; } - if (!Objects.equals(this.dataSourceId, other.dataSourceId)) { - return false; - } - if (!Objects.equals(this.type, other.type)) { - return false; - } - return true; + return super.equals(obj); } } diff --git a/Core/src/org/sleuthkit/autopsy/mainui/nodes/TagNameFactory.java b/Core/src/org/sleuthkit/autopsy/mainui/nodes/TagNameFactory.java new file mode 100644 index 0000000000..0a7d99f627 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/mainui/nodes/TagNameFactory.java @@ -0,0 +1,173 @@ +/* + * 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.Comparator; +import java.util.Objects; +import java.util.concurrent.ExecutionException; +import org.openide.nodes.Children; +import org.sleuthkit.autopsy.corecomponents.DataResultTopComponent; +import org.sleuthkit.autopsy.mainui.datamodel.MainDAO; +import org.sleuthkit.autopsy.mainui.datamodel.TagNameSearchParams; +import org.sleuthkit.autopsy.mainui.datamodel.TagsSearchParams; +import org.sleuthkit.autopsy.mainui.datamodel.TreeResultsDTO; +import org.sleuthkit.autopsy.mainui.datamodel.TreeResultsDTO.TreeItemDTO; +import org.sleuthkit.autopsy.mainui.datamodel.events.DAOAggregateEvent; +import org.sleuthkit.autopsy.mainui.datamodel.events.DAOEvent; +import org.sleuthkit.autopsy.mainui.datamodel.events.DeleteAnalysisResultEvent; +import org.sleuthkit.autopsy.mainui.datamodel.events.TreeEvent; + +/** + * Factory for displaying analysis result types in the tree. + */ +public class TagNameFactory extends TreeChildFactory { + + private static final String TAG_ICON = "org/sleuthkit/autopsy/images/tag-folder-blue-icon-16.png"; + + private final Long dataSourceId; + + /** + * Main constructor. + * + * @param dataSourceId The data source id to filter on or null if no filter. + */ + public TagNameFactory(Long dataSourceId) { + this.dataSourceId = dataSourceId; + } + + @Override + protected TreeResultsDTO getChildResults() throws IllegalArgumentException, ExecutionException { + return MainDAO.getInstance().getTagsDAO().getNameCounts(dataSourceId); + } + + @Override + protected TreeNode createNewNode(TreeResultsDTO.TreeItemDTO rowData) { + return new TagNameNode(rowData); + } + + @Override + protected TreeResultsDTO.TreeItemDTO getOrCreateRelevantChild(TreeEvent treeEvt) { + TreeResultsDTO.TreeItemDTO originalTreeItem = super.getTypedTreeItem(treeEvt, TagNameSearchParams.class); + + if (originalTreeItem != null + && (this.dataSourceId == null || Objects.equals(this.dataSourceId, originalTreeItem.getSearchParams().getDataSourceId()))) { + + return MainDAO.getInstance().getTagsDAO().createTagNameTreeItem( + originalTreeItem.getSearchParams().getTagName(), + dataSourceId, + originalTreeItem.getDisplayCount()); + } + return null; + } + + @Override + public int compare(TreeItemDTO o1, TreeItemDTO o2) { + return Comparator.comparing((TreeItemDTO tagTreeItem) -> tagTreeItem.getDisplayName()) + .compare(o1, o2); + } + + @Override + protected void handleDAOAggregateEvent(DAOAggregateEvent aggEvt) { + for (DAOEvent evt : aggEvt.getEvents()) { + if (evt instanceof DeleteAnalysisResultEvent && evt.getType() == DAOEvent.Type.TREE) { + super.update(); + return; + } + } + + super.handleDAOAggregateEvent(aggEvt); + } + + /** + * A node for a tag name. + */ + static class TagNameNode extends TreeNode { + + public TagNameNode(TreeResultsDTO.TreeItemDTO rowData) { + super(TagNameSearchParams.getTypeId() + "_" + Objects.toString(rowData.getId()), + TAG_ICON, + rowData, + Children.create(new TagTypeFactory(rowData.getSearchParams()), false), + getDefaultLookup(rowData)); + } + + } + + /** + * Factory displaying file type or result type underneath a tag name node. + */ + static class TagTypeFactory extends TreeChildFactory { + + private final TagNameSearchParams searchParams; + + TagTypeFactory(TagNameSearchParams searchParams) { + this.searchParams = searchParams; + } + + @Override + protected TreeNode createNewNode(TreeItemDTO rowData) { + return new TagsTypeNode(rowData); + } + + @Override + protected TreeResultsDTO getChildResults() throws IllegalArgumentException, ExecutionException { + return MainDAO.getInstance().getTagsDAO().getTypeCounts(searchParams); + } + + @Override + protected TreeItemDTO getOrCreateRelevantChild(TreeEvent treeEvt) { + TreeResultsDTO.TreeItemDTO originalTreeItem = super.getTypedTreeItem(treeEvt, TagsSearchParams.class); + + if (originalTreeItem != null + && Objects.equals(this.searchParams.getTagName(), originalTreeItem.getSearchParams().getTagName()) + && (this.searchParams.getDataSourceId() == null || Objects.equals(this.searchParams.getDataSourceId(), originalTreeItem.getSearchParams().getDataSourceId()))) { + + return MainDAO.getInstance().getTagsDAO().createTagTypeTreeItem( + searchParams.getTagName(), + originalTreeItem.getSearchParams().getTagType(), + searchParams.getDataSourceId(), + originalTreeItem.getDisplayCount()); + } + return null; + } + + @Override + public int compare(TreeItemDTO o1, TreeItemDTO o2) { + return Comparator.comparing((TreeItemDTO rowData) -> rowData.getSearchParams().getTagType() == TagsSearchParams.TagType.FILE ? 0 : 1) + .compare(o1, o2); + } + + } + + /** + * A tag type (i.e. File/Result) tree node. Clicking on this will go to + * results. + */ + static class TagsTypeNode extends TreeNode { + + private TagsTypeNode(TreeItemDTO rowData) { + super(TagsSearchParams.getTypeId() + "_" + Objects.toString(rowData.getId()), TAG_ICON, rowData); + } + + @Override + public void respondSelection(DataResultTopComponent dataResultPanel) { + dataResultPanel.displayTags(this.getItemData().getSearchParams()); + } + } +}