diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java index 340d847343..d6226a06d8 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java @@ -377,7 +377,9 @@ public class Case { * * @param eventNames The events the subscriber is interested in. * @param subscriber The subscriber (PropertyChangeListener) to add. + * @deprecated Use addEventTypeSubscriber instead. */ + @Deprecated public static void addEventSubscriber(Set eventNames, PropertyChangeListener subscriber) { eventPublisher.addSubscriber(eventNames, subscriber); } @@ -385,9 +387,23 @@ public class Case { /** * Adds a subscriber to specific case events. * - * @param eventName The event the subscriber is interested in. + * @param eventTypes The events the subscriber is interested in. * @param subscriber The subscriber (PropertyChangeListener) to add. */ + public static void addEventTypeSubscriber(Set eventTypes, PropertyChangeListener subscriber) { + eventTypes.forEach((Events event) -> { + eventPublisher.addSubscriber(event.toString(), subscriber); + }); + } + + /** + * Adds a subscriber to specific case events. + * + * @param eventName The event the subscriber is interested in. + * @param subscriber The subscriber (PropertyChangeListener) to add. + * @deprecated Use addEventTypeSubscriber instead. + */ + @Deprecated public static void addEventSubscriber(String eventName, PropertyChangeListener subscriber) { eventPublisher.addSubscriber(eventName, subscriber); } @@ -412,6 +428,18 @@ public class Case { eventPublisher.removeSubscriber(eventNames, subscriber); } + /** + * Removes a subscriber to specific case events. + * + * @param eventTypes The events the subscriber is no longer interested in. + * @param subscriber The subscriber (PropertyChangeListener) to remove. + */ + public static void removeEventTypeSubscriber(Set eventTypes, PropertyChangeListener subscriber) { + eventTypes.forEach((Events event) -> { + eventPublisher.removeSubscriber(event.toString(), subscriber); + }); + } + /** * Checks if a case display name is valid, i.e., does not include any * characters that cannot be used in file names. diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/CaseDeleteAction.java b/Core/src/org/sleuthkit/autopsy/casemodule/CaseDeleteAction.java index 2df3bfcc33..3ef30f2594 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/CaseDeleteAction.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/CaseDeleteAction.java @@ -20,6 +20,7 @@ package org.sleuthkit.autopsy.casemodule; import java.awt.event.ActionEvent; import java.beans.PropertyChangeEvent; +import java.util.EnumSet; import java.util.concurrent.ExecutionException; import java.util.logging.Level; import javax.swing.Action; @@ -49,7 +50,7 @@ final class CaseDeleteAction extends CallableSystemAction { CaseDeleteAction() { putValue(Action.NAME, NbBundle.getMessage(CaseDeleteAction.class, "CTL_CaseDeleteAction")); this.setEnabled(false); - Case.addEventSubscriber(Case.Events.CURRENT_CASE.toString(), (PropertyChangeEvent evt) -> { + Case.addEventTypeSubscriber(EnumSet.of(Case.Events.CURRENT_CASE), (PropertyChangeEvent evt) -> { setEnabled(null != evt.getNewValue() && UserPreferences.getMode() != UserPreferences.SelectedMode.REVIEW); }); } diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/CasePropertiesAction.java b/Core/src/org/sleuthkit/autopsy/casemodule/CasePropertiesAction.java index 7cd6a93b10..c50c561564 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/CasePropertiesAction.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/CasePropertiesAction.java @@ -22,6 +22,7 @@ import java.awt.Dimension; import java.awt.Toolkit; import java.awt.event.ActionEvent; import java.beans.PropertyChangeEvent; +import java.util.EnumSet; import javax.swing.Action; import javax.swing.JDialog; import javax.swing.SwingUtilities; @@ -42,7 +43,7 @@ final class CasePropertiesAction extends CallableSystemAction { CasePropertiesAction() { putValue(Action.NAME, NbBundle.getMessage(CasePropertiesAction.class, "CTL_CasePropertiesAction")); this.setEnabled(false); - Case.addEventSubscriber(Case.Events.CURRENT_CASE.toString(), (PropertyChangeEvent evt) -> { + Case.addEventTypeSubscriber(EnumSet.of(Case.Events.CURRENT_CASE), (PropertyChangeEvent evt) -> { setEnabled(null != evt.getNewValue()); }); } diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/CollaborationMonitor.java b/Core/src/org/sleuthkit/autopsy/casemodule/CollaborationMonitor.java index 6042c24d12..ab55b67c5b 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/CollaborationMonitor.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/CollaborationMonitor.java @@ -24,9 +24,8 @@ import java.beans.PropertyChangeListener; import java.io.Serializable; import java.time.Duration; import java.time.Instant; -import java.util.Arrays; +import java.util.EnumSet; import java.util.HashMap; -import java.util.HashSet; import java.util.Iterator; import java.util.Map; import java.util.Set; @@ -57,7 +56,8 @@ final class CollaborationMonitor { private static final String EVENT_CHANNEL_NAME = "%s-Collaboration-Monitor-Events"; //NON-NLS private static final String COLLABORATION_MONITOR_EVENT = "COLLABORATION_MONITOR_EVENT"; //NON-NLS - private static final Set CASE_EVENTS_OF_INTEREST = new HashSet<>(Arrays.asList(new String[]{Case.Events.ADDING_DATA_SOURCE.toString(), Case.Events.DATA_SOURCE_ADDED.toString(), Case.Events.ADDING_DATA_SOURCE_FAILED.toString()})); + private static final Set CASE_EVENTS_OF_INTEREST = EnumSet.of(Case.Events.ADDING_DATA_SOURCE, + Case.Events.DATA_SOURCE_ADDED, Case.Events.ADDING_DATA_SOURCE_FAILED); private static final int NUMBER_OF_PERIODIC_TASK_THREADS = 2; private static final String PERIODIC_TASK_THREAD_NAME = "collab-monitor-periodic-tasks-%d"; //NON-NLS private static final long HEARTBEAT_INTERVAL_MINUTES = 1; @@ -113,7 +113,7 @@ final class CollaborationMonitor { */ localTasksManager = new LocalTasksManager(); IngestManager.getInstance().addIngestJobEventListener(localTasksManager); - Case.addEventSubscriber(CASE_EVENTS_OF_INTEREST, localTasksManager); + Case.addEventTypeSubscriber(CASE_EVENTS_OF_INTEREST, localTasksManager); /** * Start periodic tasks that: @@ -141,7 +141,7 @@ final class CollaborationMonitor { } } - Case.removeEventSubscriber(CASE_EVENTS_OF_INTEREST, localTasksManager); + Case.removeEventTypeSubscriber(CASE_EVENTS_OF_INTEREST, localTasksManager); IngestManager.getInstance().removeIngestJobEventListener(localTasksManager); if (null != eventPublisher) { diff --git a/Core/src/org/sleuthkit/autopsy/coreutils/ImageUtils.java b/Core/src/org/sleuthkit/autopsy/coreutils/ImageUtils.java index eb6a16e6e0..eabc56e3bd 100755 --- a/Core/src/org/sleuthkit/autopsy/coreutils/ImageUtils.java +++ b/Core/src/org/sleuthkit/autopsy/coreutils/ImageUtils.java @@ -35,6 +35,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; +import java.util.EnumSet; import java.util.Iterator; import java.util.List; import static java.util.Objects.nonNull; @@ -153,7 +154,7 @@ public class ImageUtils { SUPPORTED_IMAGE_MIME_TYPES.removeIf("application/octet-stream"::equals); //NON-NLS //Clear the file map when the case changes, so we don't accidentaly get images from the old case. - Case.addEventSubscriber(Case.Events.CURRENT_CASE.toString(), evt -> cacheFileMap.clear()); + Case.addEventTypeSubscriber(EnumSet.of(Case.Events.CURRENT_CASE), evt -> cacheFileMap.clear()); } /** diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/AbstractAbstractFileNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/AbstractAbstractFileNode.java index a1d85d50df..44982682e9 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/AbstractAbstractFileNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/AbstractAbstractFileNode.java @@ -21,8 +21,10 @@ package org.sleuthkit.autopsy.datamodel; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.util.ArrayList; +import java.util.EnumSet; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.logging.Level; import java.util.stream.Collectors; import org.apache.commons.lang3.StringUtils; @@ -53,6 +55,9 @@ public abstract class AbstractAbstractFileNode extends A @NbBundle.Messages("AbstractAbstractFileNode.addFileProperty.desc=no description") private static final String NO_DESCR = AbstractAbstractFileNode_addFileProperty_desc(); + private static final Set CASE_EVENTS_OF_INTEREST = EnumSet.of(Case.Events.CURRENT_CASE, + Case.Events.CONTENT_TAG_ADDED, Case.Events.CONTENT_TAG_DELETED); + /** * @param abstractFile file to wrap */ @@ -67,13 +72,14 @@ public abstract class AbstractAbstractFileNode extends A IngestManager.getInstance().addIngestModuleEventListener(pcl); } } - // Listen for case events so that we can detect when case is closed - Case.addPropertyChangeListener(pcl); + // Listen for case events so that we can detect when the case is closed + // or when tags are added. + Case.addEventTypeSubscriber(CASE_EVENTS_OF_INTEREST, pcl); } private void removeListeners() { IngestManager.getInstance().removeIngestModuleEventListener(pcl); - Case.removePropertyChangeListener(pcl); + Case.removeEventTypeSubscriber(CASE_EVENTS_OF_INTEREST, pcl); } private final PropertyChangeListener pcl = (PropertyChangeEvent evt) -> { @@ -95,7 +101,11 @@ public abstract class AbstractAbstractFileNode extends A // If so, refresh our children. try { Children parentsChildren = getParentNode().getChildren(); - if (parentsChildren != null) { + // We only want to refresh our parents children if we are in the + // data sources branch of the tree. The parent nodes in other + // branches of the tree (e.g. File Types and Deleted Files) do + // not need to be refreshed. + if (parentsChildren instanceof ContentChildren) { ((ContentChildren) parentsChildren).refreshChildren(); parentsChildren.getNodesCount(); } diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java index 0e1c7cb8aa..33104bf47c 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java @@ -25,31 +25,29 @@ import java.beans.PropertyChangeListener; import java.text.MessageFormat; import java.util.ArrayList; import java.util.Arrays; -import java.util.Collection; +import java.util.EnumSet; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.logging.Level; import java.util.stream.Collectors; import javax.swing.Action; -import org.openide.nodes.Children; -import org.openide.nodes.Node; import org.openide.nodes.Sheet; import org.openide.util.Lookup; 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.events.BlackBoardArtifactTagAddedEvent; import org.sleuthkit.autopsy.casemodule.events.BlackBoardArtifactTagDeletedEvent; import org.sleuthkit.autopsy.casemodule.events.ContentTagAddedEvent; import org.sleuthkit.autopsy.casemodule.events.ContentTagDeletedEvent; -import static org.sleuthkit.autopsy.datamodel.DataModelActionsFactory.VIEW_IN_NEW_WINDOW; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; import static org.sleuthkit.autopsy.datamodel.DisplayableItemNode.findLinked; -import org.sleuthkit.autopsy.directorytree.NewWindowViewAction; import org.sleuthkit.autopsy.timeline.actions.ViewArtifactInTimelineAction; import org.sleuthkit.autopsy.timeline.actions.ViewFileInTimelineAction; import org.sleuthkit.datamodel.AbstractFile; @@ -68,6 +66,11 @@ import org.sleuthkit.datamodel.TskCoreException; public class BlackboardArtifactNode extends AbstractContentNode { private static final Logger LOGGER = Logger.getLogger(BlackboardArtifactNode.class.getName()); + private static 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 static Cache contentCache = CacheBuilder.newBuilder() .expireAfterWrite(1, TimeUnit.MINUTES). @@ -76,7 +79,7 @@ public class BlackboardArtifactNode extends AbstractContentNode> customProperties; - + /* * Artifact types which should have the full unique path of the associated * content as a property. @@ -128,8 +131,9 @@ public class BlackboardArtifactNode extends AbstractContentNode map, BlackboardAttribute attribute ) { + private void addEmailMsgProperty(Map map, BlackboardAttribute attribute) { final int attributeTypeID = attribute.getAttributeType().getTypeID(); @@ -523,23 +522,19 @@ public class BlackboardArtifactNode extends AbstractContentNode 160) { value = value.substring(0, 160) + "..."; } map.put(attribute.getAttributeType().getDisplayName(), value); - } - else if (attribute.getAttributeType().getValueType() == BlackboardAttribute.TSK_BLACKBOARD_ATTRIBUTE_VALUE_TYPE.DATETIME) { + } else if (attribute.getAttributeType().getValueType() == BlackboardAttribute.TSK_BLACKBOARD_ATTRIBUTE_VALUE_TYPE.DATETIME) { map.put(attribute.getAttributeType().getDisplayName(), ContentUtils.getStringTime(attribute.getValueLong(), associated)); - } - else { + } else { map.put(attribute.getAttributeType().getDisplayName(), attribute.getDisplayString()); } @@ -586,6 +581,6 @@ public class BlackboardArtifactNode extends AbstractContentNode T accept(ContentNodeVisitor v) { - return v.visit(this); + return v.visit(this); } } diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/DataSourcesNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/DataSourcesNode.java index 7ac9e37398..43ddd069a3 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/DataSourcesNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/DataSourcesNode.java @@ -22,6 +22,7 @@ import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.util.ArrayList; import java.util.Collections; +import java.util.EnumSet; import java.util.List; import java.util.logging.Level; import org.openide.nodes.Sheet; @@ -88,13 +89,13 @@ public class DataSourcesNode extends DisplayableItemNode { @Override protected void addNotify() { - Case.addPropertyChangeListener(pcl); + Case.addEventTypeSubscriber(EnumSet.of(Case.Events.DATA_SOURCE_ADDED), pcl); reloadKeys(); } @Override protected void removeNotify() { - Case.removePropertyChangeListener(pcl); + Case.removeEventTypeSubscriber(EnumSet.of(Case.Events.DATA_SOURCE_ADDED), pcl); currentKeys.clear(); setKeys(Collections.emptySet()); } diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/DeletedContent.java b/Core/src/org/sleuthkit/autopsy/datamodel/DeletedContent.java index 0b6ced4f16..9b6f36a56b 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/DeletedContent.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/DeletedContent.java @@ -22,9 +22,11 @@ import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.util.ArrayList; import java.util.Arrays; +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 javax.swing.JOptionPane; import javax.swing.SwingUtilities; @@ -173,68 +175,69 @@ public class DeletedContent implements AutopsyVisitableItem { * Listens for case and ingest invest. Updates observers when events are * fired. Other nodes are listening to this for changes. */ - private final class DeletedContentsChildrenObservable extends Observable { + private static final class DeletedContentsChildrenObservable extends Observable { + private static final Set CASE_EVENTS_OF_INTEREST = EnumSet.of( + Case.Events.DATA_SOURCE_ADDED, + Case.Events.CURRENT_CASE + ); DeletedContentsChildrenObservable() { IngestManager.getInstance().addIngestJobEventListener(pcl); IngestManager.getInstance().addIngestModuleEventListener(pcl); - Case.addPropertyChangeListener(pcl); + Case.addEventTypeSubscriber(CASE_EVENTS_OF_INTEREST, pcl); } private void removeListeners() { deleteObservers(); IngestManager.getInstance().removeIngestJobEventListener(pcl); IngestManager.getInstance().removeIngestModuleEventListener(pcl); - Case.removePropertyChangeListener(pcl); + Case.removeEventTypeSubscriber(CASE_EVENTS_OF_INTEREST, pcl); } - private final PropertyChangeListener pcl = new PropertyChangeListener() { - @Override - public void propertyChange(PropertyChangeEvent evt) { - String eventType = evt.getPropertyName(); - if (eventType.equals(IngestManager.IngestModuleEvent.CONTENT_CHANGED.toString())) { + private final PropertyChangeListener pcl = (PropertyChangeEvent evt) -> { + String eventType = evt.getPropertyName(); + if (eventType.equals(IngestManager.IngestModuleEvent.CONTENT_CHANGED.toString())) { + /** + * + // @@@ COULD CHECK If the new file is deleted + * before notifying... Checking for a current case is a + * stop gap measure + update(); 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(); + // new file was added + // @@@ COULD CHECK If the new file is deleted before notifying... + update(); + } catch (IllegalStateException notUsed) { /** - * + // @@@ COULD CHECK If the new file is deleted - * before notifying... Checking for a current case is a - * stop gap measure + update(); 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. + * Case is closed, do nothing. */ - try { - Case.getCurrentCase(); - // new file was added - // @@@ COULD CHECK If the new file is deleted before notifying... - update(); - } catch (IllegalStateException notUsed) { - /** - * Case is closed, do nothing. - */ - } - } else if (eventType.equals(IngestManager.IngestJobEvent.COMPLETED.toString()) - || eventType.equals(IngestManager.IngestJobEvent.CANCELLED.toString()) - || eventType.equals(Case.Events.DATA_SOURCE_ADDED.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(); - 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 we don't get called with a stale case handle - if (evt.getNewValue() == null) { - removeListeners(); - } - maxFilesDialogShown = false; } + } else if (eventType.equals(IngestManager.IngestJobEvent.COMPLETED.toString()) + || eventType.equals(IngestManager.IngestJobEvent.CANCELLED.toString()) + || eventType.equals(Case.Events.DATA_SOURCE_ADDED.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(); + 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 we don't get called with a stale case handle + if (evt.getNewValue() == null) { + removeListeners(); + } + maxFilesDialogShown = false; } }; diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/EmailExtracted.java b/Core/src/org/sleuthkit/autopsy/datamodel/EmailExtracted.java index ad740a5011..7ffcfc077c 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/EmailExtracted.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/EmailExtracted.java @@ -23,6 +23,7 @@ import java.beans.PropertyChangeListener; import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; +import java.util.EnumSet; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; @@ -285,7 +286,7 @@ public class EmailExtracted implements AutopsyVisitableItem { protected void addNotify() { IngestManager.getInstance().addIngestJobEventListener(pcl); IngestManager.getInstance().addIngestModuleEventListener(pcl); - Case.addPropertyChangeListener(pcl); + Case.addEventTypeSubscriber(EnumSet.of(Case.Events.CURRENT_CASE), pcl); emailResults.update(); emailResults.addObserver(this); } @@ -294,7 +295,7 @@ public class EmailExtracted implements AutopsyVisitableItem { protected void removeNotify() { IngestManager.getInstance().removeIngestJobEventListener(pcl); IngestManager.getInstance().removeIngestModuleEventListener(pcl); - Case.removePropertyChangeListener(pcl); + Case.removeEventTypeSubscriber(EnumSet.of(Case.Events.CURRENT_CASE), pcl); emailResults.deleteObserver(this); } diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/ExtractedContent.java b/Core/src/org/sleuthkit/autopsy/datamodel/ExtractedContent.java index 896cec5322..317cb42d10 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/ExtractedContent.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/ExtractedContent.java @@ -23,6 +23,7 @@ import java.beans.PropertyChangeListener; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; +import java.util.EnumSet; import java.util.HashMap; import java.util.List; import java.util.logging.Level; @@ -251,14 +252,14 @@ public class ExtractedContent implements AutopsyVisitableItem { protected void addNotify() { IngestManager.getInstance().addIngestJobEventListener(pcl); IngestManager.getInstance().addIngestModuleEventListener(pcl); - Case.addPropertyChangeListener(pcl); + Case.addEventTypeSubscriber(EnumSet.of(Case.Events.CURRENT_CASE), pcl); } @Override protected void removeNotify() { IngestManager.getInstance().removeIngestJobEventListener(pcl); IngestManager.getInstance().removeIngestModuleEventListener(pcl); - Case.removePropertyChangeListener(pcl); + Case.removeEventTypeSubscriber(EnumSet.of(Case.Events.CURRENT_CASE), pcl); typeNodeList.clear(); } @@ -309,7 +310,7 @@ public class ExtractedContent implements AutopsyVisitableItem { */ public class TypeNode extends DisplayableItemNode { - private BlackboardArtifact.Type type; + private final BlackboardArtifact.Type type; private long childCount = 0; TypeNode(BlackboardArtifact.Type type) { diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/FileSize.java b/Core/src/org/sleuthkit/autopsy/datamodel/FileSize.java index 54811f12c0..007c59ad41 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/FileSize.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/FileSize.java @@ -22,9 +22,11 @@ import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.util.ArrayList; import java.util.Arrays; +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.AbstractNode; import org.openide.nodes.ChildFactory; @@ -169,65 +171,64 @@ public class FileSize implements AutopsyVisitableItem { * Listens for case and ingest invest. Updates observers when events are * fired. Size-based nodes are listening to this for changes. */ - private final class FileSizeRootChildrenObservable extends Observable { + private static final class FileSizeRootChildrenObservable extends Observable { + + private static final Set CASE_EVENTS_OF_INTEREST = EnumSet.of(Case.Events.DATA_SOURCE_ADDED, Case.Events.CURRENT_CASE); FileSizeRootChildrenObservable() { IngestManager.getInstance().addIngestJobEventListener(pcl); IngestManager.getInstance().addIngestModuleEventListener(pcl); - Case.addPropertyChangeListener(pcl); + Case.addEventTypeSubscriber(CASE_EVENTS_OF_INTEREST, pcl); } private void removeListeners() { deleteObservers(); IngestManager.getInstance().removeIngestJobEventListener(pcl); IngestManager.getInstance().removeIngestModuleEventListener(pcl); - Case.removePropertyChangeListener(pcl); + Case.removeEventTypeSubscriber(CASE_EVENTS_OF_INTEREST, pcl); } - private final PropertyChangeListener pcl = new PropertyChangeListener() { - @Override - public void propertyChange(PropertyChangeEvent evt) { - String eventType = evt.getPropertyName(); + private final PropertyChangeListener pcl = (PropertyChangeEvent evt) -> { + String eventType = evt.getPropertyName(); - if (eventType.equals(IngestManager.IngestModuleEvent.CONTENT_CHANGED.toString())) { + if (eventType.equals(IngestManager.IngestModuleEvent.CONTENT_CHANGED.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 { + // new file was added + // @@@ could check the size here and only fire off updates if we know the file meets the min size criteria + Case.getCurrentCase(); + update(); + } catch (IllegalStateException notUsed) { /** - * 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. + * Case is closed, do nothing. */ - try { - // new file was added - // @@@ could check the size here and only fire off updates if we know the file meets the min size criteria - Case.getCurrentCase(); - update(); - } catch (IllegalStateException notUsed) { - /** - * Case is closed, do nothing. - */ - } - } else if (eventType.equals(IngestManager.IngestJobEvent.COMPLETED.toString()) - || eventType.equals(IngestManager.IngestJobEvent.CANCELLED.toString()) - || eventType.equals(Case.Events.DATA_SOURCE_ADDED.toString())) { + } + } else if (eventType.equals(IngestManager.IngestJobEvent.COMPLETED.toString()) + || eventType.equals(IngestManager.IngestJobEvent.CANCELLED.toString()) + || eventType.equals(Case.Events.DATA_SOURCE_ADDED.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(); + update(); + } catch (IllegalStateException notUsed) { /** - * 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. + * Case is closed, do nothing. */ - try { - Case.getCurrentCase(); - 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 we don't get called with a stale case handle - if (evt.getNewValue() == null) { - removeListeners(); - } + } + } else if (eventType.equals(Case.Events.CURRENT_CASE.toString())) { + // case was closed. Remove listeners so that we don't get called with a stale case handle + if (evt.getNewValue() == null) { + removeListeners(); } } }; diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/FileTypesByExtension.java b/Core/src/org/sleuthkit/autopsy/datamodel/FileTypesByExtension.java index d8ef2a2f5e..305da8e953 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/FileTypesByExtension.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/FileTypesByExtension.java @@ -21,10 +21,11 @@ package org.sleuthkit.autopsy.datamodel; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.util.Arrays; +import java.util.EnumSet; import java.util.List; import java.util.Observable; import java.util.Observer; -import java.util.function.Function; +import java.util.Set; import java.util.logging.Level; import java.util.stream.Collectors; import org.apache.commons.lang.StringUtils; @@ -39,8 +40,6 @@ import org.sleuthkit.autopsy.core.UserPreferences; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.datamodel.FileTypes.FileTypesKey; import org.sleuthkit.autopsy.ingest.IngestManager; -import org.sleuthkit.datamodel.AbstractFile; -import org.sleuthkit.datamodel.Content; import org.sleuthkit.datamodel.SleuthkitCase; import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.datamodel.TskData; @@ -75,9 +74,11 @@ public final class FileTypesByExtension implements AutopsyVisitableItem { private class FileTypesByExtObservable extends Observable { private final PropertyChangeListener pcl; + private final Set CASE_EVENTS_OF_INTEREST; private FileTypesByExtObservable() { super(); + this.CASE_EVENTS_OF_INTEREST = EnumSet.of(Case.Events.DATA_SOURCE_ADDED, Case.Events.CURRENT_CASE); this.pcl = (PropertyChangeEvent evt) -> { String eventType = evt.getPropertyName(); if (eventType.equals(IngestManager.IngestModuleEvent.CONTENT_CHANGED.toString()) @@ -109,15 +110,14 @@ public final class FileTypesByExtension implements AutopsyVisitableItem { IngestManager.getInstance().addIngestJobEventListener(pcl); IngestManager.getInstance().addIngestModuleEventListener(pcl); - Case.addPropertyChangeListener(pcl); - + Case.addEventTypeSubscriber(CASE_EVENTS_OF_INTEREST, pcl); } private void removeListeners() { deleteObservers(); IngestManager.getInstance().removeIngestJobEventListener(pcl); IngestManager.getInstance().removeIngestModuleEventListener(pcl); - Case.removePropertyChangeListener(pcl); + Case.removeEventTypeSubscriber(CASE_EVENTS_OF_INTEREST, pcl); } private void update() { diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/FileTypesByMimeType.java b/Core/src/org/sleuthkit/autopsy/datamodel/FileTypesByMimeType.java index e5bd41b6e8..9fe183a8a8 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/FileTypesByMimeType.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/FileTypesByMimeType.java @@ -24,11 +24,13 @@ import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; import java.util.Collections; +import java.util.EnumSet; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Observable; import java.util.Observer; +import java.util.Set; import java.util.logging.Level; import java.util.stream.Collectors; import org.apache.commons.lang3.StringUtils; @@ -44,7 +46,6 @@ import static org.sleuthkit.autopsy.core.UserPreferences.hideSlackFilesInViewsTr import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.datamodel.FileTypes.FileTypesKey; import org.sleuthkit.autopsy.ingest.IngestManager; -import org.sleuthkit.datamodel.Content; import org.sleuthkit.datamodel.SleuthkitCase; import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.datamodel.TskData; @@ -79,6 +80,8 @@ public final class FileTypesByMimeType extends Observable implements AutopsyVisi */ private final PropertyChangeListener pcl; + private static final Set CASE_EVENTS_OF_INTEREST = EnumSet.of(Case.Events.DATA_SOURCE_ADDED, Case.Events.CURRENT_CASE); + /** * Create the base expression used as the where clause in the queries for * files by mime type. Filters out certain kinds of files and directories, @@ -102,7 +105,7 @@ public final class FileTypesByMimeType extends Observable implements AutopsyVisi private void removeListeners() { deleteObservers(); IngestManager.getInstance().removeIngestJobEventListener(pcl); - Case.removePropertyChangeListener(pcl); + Case.removeEventTypeSubscriber(CASE_EVENTS_OF_INTEREST, pcl); } /** @@ -175,7 +178,7 @@ public final class FileTypesByMimeType extends Observable implements AutopsyVisi } }; IngestManager.getInstance().addIngestJobEventListener(pcl); - Case.addPropertyChangeListener(pcl); + Case.addEventTypeSubscriber(CASE_EVENTS_OF_INTEREST, pcl); populateHashMap(); } diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/HashsetHits.java b/Core/src/org/sleuthkit/autopsy/datamodel/HashsetHits.java index 2665894481..df3d239aef 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/HashsetHits.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/HashsetHits.java @@ -24,6 +24,7 @@ import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; import java.util.Collections; +import java.util.EnumSet; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashMap; @@ -252,7 +253,7 @@ public class HashsetHits implements AutopsyVisitableItem { protected void addNotify() { IngestManager.getInstance().addIngestJobEventListener(pcl); IngestManager.getInstance().addIngestModuleEventListener(pcl); - Case.addPropertyChangeListener(pcl); + Case.addEventTypeSubscriber(EnumSet.of(Case.Events.CURRENT_CASE), pcl); hashsetResults.update(); hashsetResults.addObserver(this); } @@ -261,7 +262,7 @@ public class HashsetHits implements AutopsyVisitableItem { protected void removeNotify() { IngestManager.getInstance().removeIngestJobEventListener(pcl); IngestManager.getInstance().removeIngestModuleEventListener(pcl); - Case.removePropertyChangeListener(pcl); + Case.removeEventTypeSubscriber(EnumSet.of(Case.Events.CURRENT_CASE), pcl); hashsetResults.deleteObserver(this); } diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/ImageNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/ImageNode.java index 682a3fa225..4efe1db364 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/ImageNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/ImageNode.java @@ -24,6 +24,7 @@ import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; import java.util.Collections; +import java.util.EnumSet; import java.util.List; import java.util.logging.Level; import javax.swing.Action; @@ -79,12 +80,12 @@ public class ImageNode extends AbstractContentNode { // Listen for ingest events so that we can detect new added files (e.g. carved) IngestManager.getInstance().addIngestModuleEventListener(pcl); // Listen for case events so that we can detect when case is closed - Case.addPropertyChangeListener(pcl); + Case.addEventTypeSubscriber(EnumSet.of(Case.Events.CURRENT_CASE), pcl); } private void removeListeners() { IngestManager.getInstance().removeIngestModuleEventListener(pcl); - Case.removePropertyChangeListener(pcl); + Case.removeEventTypeSubscriber(EnumSet.of(Case.Events.CURRENT_CASE), pcl); } /** diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/InterestingHits.java b/Core/src/org/sleuthkit/autopsy/datamodel/InterestingHits.java index d9fecdcdb2..7a0b6b9ad9 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/InterestingHits.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/InterestingHits.java @@ -24,6 +24,7 @@ import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; import java.util.Collections; +import java.util.EnumSet; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashMap; @@ -188,57 +189,54 @@ public class InterestingHits implements AutopsyVisitableItem { * nice methods for its startup and shutdown, so it seemed like a * cleaner place to register the property change listener. */ - private final PropertyChangeListener pcl = new PropertyChangeListener() { - @Override - public void propertyChange(PropertyChangeEvent evt) { - String eventType = evt.getPropertyName(); - if (eventType.equals(IngestManager.IngestModuleEvent.DATA_ADDED.toString())) { + private final PropertyChangeListener pcl = (PropertyChangeEvent evt) -> { + String eventType = evt.getPropertyName(); + if (eventType.equals(IngestManager.IngestModuleEvent.DATA_ADDED.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(); /** - * 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. + * Even with the check above, it is still possible that + * the case will be closed in a different thread before + * this code executes. If that happens, it is possible + * for the event to have a null oldValue. */ - try { - Case.getCurrentCase(); - /** - * Even with the check above, it is still possible that - * the case will be closed in a different thread before - * this code executes. If that happens, it is possible - * for the event to have a null oldValue. - */ - ModuleDataEvent eventData = (ModuleDataEvent) evt.getOldValue(); - if (null != eventData && (eventData.getBlackboardArtifactType().getTypeID() == BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_ARTIFACT_HIT.getTypeID() - || eventData.getBlackboardArtifactType().getTypeID() == BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_FILE_HIT.getTypeID())) { - interestingResults.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(); + ModuleDataEvent eventData = (ModuleDataEvent) evt.getOldValue(); + if (null != eventData && (eventData.getBlackboardArtifactType().getTypeID() == BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_ARTIFACT_HIT.getTypeID() + || eventData.getBlackboardArtifactType().getTypeID() == BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_FILE_HIT.getTypeID())) { interestingResults.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 we don't get called with a stale case handle - if (evt.getNewValue() == null) { - removeNotify(); - skCase = null; } + } 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(); + interestingResults.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 we don't get called with a stale case handle + if (evt.getNewValue() == null) { + removeNotify(); + skCase = null; } } }; @@ -247,7 +245,7 @@ public class InterestingHits implements AutopsyVisitableItem { protected void addNotify() { IngestManager.getInstance().addIngestJobEventListener(pcl); IngestManager.getInstance().addIngestModuleEventListener(pcl); - Case.addPropertyChangeListener(pcl); + Case.addEventTypeSubscriber(EnumSet.of(Case.Events.CURRENT_CASE), pcl); interestingResults.update(); interestingResults.addObserver(this); } @@ -256,7 +254,7 @@ public class InterestingHits implements AutopsyVisitableItem { protected void removeNotify() { IngestManager.getInstance().removeIngestJobEventListener(pcl); IngestManager.getInstance().removeIngestModuleEventListener(pcl); - Case.removePropertyChangeListener(pcl); + Case.removeEventTypeSubscriber(EnumSet.of(Case.Events.CURRENT_CASE), pcl); interestingResults.deleteObserver(this); } diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/KeywordHits.java b/Core/src/org/sleuthkit/autopsy/datamodel/KeywordHits.java index 343074ddc9..75873d135e 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/KeywordHits.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/KeywordHits.java @@ -24,6 +24,7 @@ import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; import java.util.Collections; +import java.util.EnumSet; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; @@ -455,7 +456,7 @@ public class KeywordHits implements AutopsyVisitableItem { protected void addNotify() { IngestManager.getInstance().addIngestJobEventListener(pcl); IngestManager.getInstance().addIngestModuleEventListener(pcl); - Case.addPropertyChangeListener(pcl); + Case.addEventTypeSubscriber(EnumSet.of(Case.Events.CURRENT_CASE), pcl); keywordResults.update(); super.addNotify(); } @@ -464,7 +465,7 @@ public class KeywordHits implements AutopsyVisitableItem { protected void removeNotify() { IngestManager.getInstance().removeIngestJobEventListener(pcl); IngestManager.getInstance().removeIngestModuleEventListener(pcl); - Case.removePropertyChangeListener(pcl); + Case.removeEventTypeSubscriber(EnumSet.of(Case.Events.CURRENT_CASE), pcl); super.removeNotify(); } diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/Reports.java b/Core/src/org/sleuthkit/autopsy/datamodel/Reports.java index c606984056..14cf3bfbd4 100755 --- a/Core/src/org/sleuthkit/autopsy/datamodel/Reports.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/Reports.java @@ -28,7 +28,9 @@ import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; +import java.util.EnumSet; import java.util.List; +import java.util.Set; import java.util.logging.Level; import javax.swing.AbstractAction; import javax.swing.Action; @@ -103,8 +105,10 @@ public final class Reports implements AutopsyVisitableItem { */ private static final class ReportNodeFactory extends ChildFactory { + private static final Set CASE_EVENTS_OF_INTEREST = EnumSet.of(Case.Events.REPORT_ADDED, Case.Events.REPORT_DELETED); + ReportNodeFactory() { - Case.addPropertyChangeListener((PropertyChangeEvent evt) -> { + Case.addEventTypeSubscriber(CASE_EVENTS_OF_INTEREST, (PropertyChangeEvent evt) -> { String eventType = evt.getPropertyName(); if (eventType.equals(Case.Events.REPORT_ADDED.toString()) || eventType.equals(Case.Events.REPORT_DELETED.toString())) { /** diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/Tags.java b/Core/src/org/sleuthkit/autopsy/datamodel/Tags.java index b8127fe5a7..64b77ac245 100755 --- a/Core/src/org/sleuthkit/autopsy/datamodel/Tags.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/Tags.java @@ -21,9 +21,11 @@ package org.sleuthkit.autopsy.datamodel; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.util.Collections; +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; @@ -118,6 +120,12 @@ public class Tags implements AutopsyVisitableItem { private class TagNameNodeFactory extends ChildFactory.Detachable implements Observer { + 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) { @@ -171,7 +179,7 @@ public class Tags implements AutopsyVisitableItem { protected void addNotify() { IngestManager.getInstance().addIngestJobEventListener(pcl); IngestManager.getInstance().addIngestModuleEventListener(pcl); - Case.addPropertyChangeListener(pcl); + Case.addEventTypeSubscriber(CASE_EVENTS_OF_INTEREST, pcl); tagResults.update(); tagResults.addObserver(this); } @@ -180,7 +188,7 @@ public class Tags implements AutopsyVisitableItem { protected void removeNotify() { IngestManager.getInstance().removeIngestJobEventListener(pcl); IngestManager.getInstance().removeIngestModuleEventListener(pcl); - Case.removePropertyChangeListener(pcl); + Case.removeEventTypeSubscriber(CASE_EVENTS_OF_INTEREST, pcl); tagResults.deleteObserver(this); } diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/VolumeNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/VolumeNode.java index ba37cd4f09..ee5a5713a3 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/VolumeNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/VolumeNode.java @@ -21,6 +21,7 @@ package org.sleuthkit.autopsy.datamodel; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.util.ArrayList; +import java.util.EnumSet; import java.util.List; import javax.swing.Action; import org.openide.nodes.Children; @@ -72,12 +73,12 @@ public class VolumeNode extends AbstractContentNode { // Listen for ingest events so that we can detect new added files (e.g. carved) IngestManager.getInstance().addIngestModuleEventListener(pcl); // Listen for case events so that we can detect when case is closed - Case.addPropertyChangeListener(pcl); + Case.addEventTypeSubscriber(EnumSet.of(Case.Events.CURRENT_CASE), pcl); } private void removeListeners() { IngestManager.getInstance().removeIngestModuleEventListener(pcl); - Case.removePropertyChangeListener(pcl); + Case.removeEventTypeSubscriber(EnumSet.of(Case.Events.CURRENT_CASE), pcl); } /* diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/accounts/Accounts.java b/Core/src/org/sleuthkit/autopsy/datamodel/accounts/Accounts.java index 6545ac0bef..e1ba5a2ca8 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/accounts/Accounts.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/accounts/Accounts.java @@ -32,6 +32,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; +import java.util.EnumSet; import java.util.HashSet; import java.util.List; import java.util.Objects; @@ -311,7 +312,7 @@ final public class Accounts implements AutopsyVisitableItem { protected void removeNotify() { IngestManager.getInstance().removeIngestJobEventListener(pcl); IngestManager.getInstance().removeIngestModuleEventListener(pcl); - Case.removePropertyChangeListener(pcl); + Case.removeEventTypeSubscriber(EnumSet.of(Case.Events.CURRENT_CASE), pcl); super.removeNotify(); } @@ -319,7 +320,7 @@ final public class Accounts implements AutopsyVisitableItem { protected void addNotify() { IngestManager.getInstance().addIngestJobEventListener(pcl); IngestManager.getInstance().addIngestModuleEventListener(pcl); - Case.addPropertyChangeListener(pcl); + Case.addEventTypeSubscriber(EnumSet.of(Case.Events.CURRENT_CASE), pcl); super.addNotify(); refreshKeys(); } diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.java b/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.java index bfb98e5220..03c265daee 100644 --- a/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.java +++ b/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.java @@ -26,6 +26,7 @@ import java.beans.PropertyVetoException; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; +import java.util.EnumSet; import java.util.HashSet; import java.util.LinkedList; import java.util.List; @@ -151,7 +152,7 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat } } }); - Case.addEventSubscriber(new HashSet<>(Arrays.asList(Case.Events.CURRENT_CASE.toString(), Case.Events.DATA_SOURCE_ADDED.toString())), this); + Case.addEventTypeSubscriber(EnumSet.of(Case.Events.CURRENT_CASE, Case.Events.DATA_SOURCE_ADDED), this); this.em.addPropertyChangeListener(this); IngestManager.getInstance().addIngestJobEventListener(this); IngestManager.getInstance().addIngestModuleEventListener(this); diff --git a/Core/src/org/sleuthkit/autopsy/filesearch/Bundle.properties b/Core/src/org/sleuthkit/autopsy/filesearch/Bundle.properties index c5fd4d48e4..68cfc25429 100644 --- a/Core/src/org/sleuthkit/autopsy/filesearch/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/filesearch/Bundle.properties @@ -56,3 +56,5 @@ SizeSearchPanel.sizeCompareComboBox.lessThan=less than MimeTypePanel.jLabel1.text=*Note: Multiple MIME types can be selected FileSearchPanel.searchButton.text=Search MimeTypePanel.mimeTypeCheckBox.text=MIME Type: +HashSearchPanel.md5CheckBox.text=MD5: +HashSearchPanel.emptyHashMsg.text=Must enter something for hash search. \ No newline at end of file diff --git a/Core/src/org/sleuthkit/autopsy/filesearch/DateSearchFilter.java b/Core/src/org/sleuthkit/autopsy/filesearch/DateSearchFilter.java index f7ae47cc73..205db56998 100644 --- a/Core/src/org/sleuthkit/autopsy/filesearch/DateSearchFilter.java +++ b/Core/src/org/sleuthkit/autopsy/filesearch/DateSearchFilter.java @@ -28,6 +28,7 @@ import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Calendar; import java.util.Date; +import java.util.EnumSet; import java.util.Iterator; import java.util.List; import java.util.Set; @@ -53,6 +54,9 @@ class DateSearchFilter extends AbstractFileSearchFilter { private static final DateFormat DATE_FORMAT = new SimpleDateFormat("MM/dd/yyyy"); private static final String SEPARATOR = "SEPARATOR"; //NON-NLS + private static final Set CASE_EVENTS_OF_INTEREST = EnumSet.of(Case.Events.CURRENT_CASE, + Case.Events.DATA_SOURCE_ADDED, Case.Events.DATA_SOURCE_DELETED); + /** * New DateSearchFilter with the default panel */ @@ -62,7 +66,7 @@ class DateSearchFilter extends AbstractFileSearchFilter { private DateSearchFilter(DateSearchPanel panel) { super(panel); - Case.addPropertyChangeListener(this.new CasePropertyChangeListener()); + Case.addEventTypeSubscriber(CASE_EVENTS_OF_INTEREST, this.new CasePropertyChangeListener()); } @Override diff --git a/Core/src/org/sleuthkit/autopsy/filesearch/FileSearchAction.java b/Core/src/org/sleuthkit/autopsy/filesearch/FileSearchAction.java index 603d9d1623..fb66a4b6d7 100644 --- a/Core/src/org/sleuthkit/autopsy/filesearch/FileSearchAction.java +++ b/Core/src/org/sleuthkit/autopsy/filesearch/FileSearchAction.java @@ -20,7 +20,7 @@ package org.sleuthkit.autopsy.filesearch; import java.awt.event.ActionEvent; import java.beans.PropertyChangeEvent; -import java.beans.PropertyChangeListener; +import java.util.EnumSet; import org.openide.util.HelpCtx; import org.openide.util.NbBundle; import org.openide.util.actions.CallableSystemAction; @@ -35,14 +35,10 @@ final class FileSearchAction extends CallableSystemAction implements FileSearchP FileSearchAction() { super(); setEnabled(Case.isCaseOpen()); - Case.addPropertyChangeListener(new PropertyChangeListener() { - @Override - public void propertyChange(PropertyChangeEvent evt) { - if (evt.getPropertyName().equals(Case.Events.CURRENT_CASE.toString())) { - setEnabled(evt.getNewValue() != null); - } + Case.addEventTypeSubscriber(EnumSet.of(Case.Events.CURRENT_CASE), (PropertyChangeEvent evt) -> { + if (evt.getPropertyName().equals(Case.Events.CURRENT_CASE.toString())) { + setEnabled(evt.getNewValue() != null); } - }); } diff --git a/Core/src/org/sleuthkit/autopsy/filesearch/FileSearchPanel.java b/Core/src/org/sleuthkit/autopsy/filesearch/FileSearchPanel.java index 23730c99c1..d7efb0b46f 100644 --- a/Core/src/org/sleuthkit/autopsy/filesearch/FileSearchPanel.java +++ b/Core/src/org/sleuthkit/autopsy/filesearch/FileSearchPanel.java @@ -94,6 +94,8 @@ class FileSearchPanel extends javax.swing.JPanel { this.filterAreas.add(new FilterArea(NbBundle.getMessage(this.getClass(), "FileSearchPanel.filterTitle.metadata"), metadataFilters)); this.filterAreas.add(new FilterArea(NbBundle.getMessage(this.getClass(), "FileSearchPanel.filterTitle.knownStatus"), new KnownStatusSearchFilter())); + + this.filterAreas.add(new FilterArea(NbBundle.getMessage(this.getClass(), "HashSearchPanel.md5CheckBox.text"), new HashSearchFilter())); for (FilterArea fa : this.filterAreas) { fa.setMaximumSize(new Dimension(Integer.MAX_VALUE, fa.getMinimumSize().height)); diff --git a/Core/src/org/sleuthkit/autopsy/filesearch/HashSearchFilter.java b/Core/src/org/sleuthkit/autopsy/filesearch/HashSearchFilter.java new file mode 100755 index 0000000000..951f4b206e --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/filesearch/HashSearchFilter.java @@ -0,0 +1,66 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2017 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.filesearch; + +import java.awt.event.ActionListener; +import org.openide.util.NbBundle; +import org.sleuthkit.autopsy.filesearch.FileSearchFilter.FilterValidationException; + +/** + * + */ +class HashSearchFilter extends AbstractFileSearchFilter { + + private static final String EMPTY_HASH_MESSAGE = NbBundle + .getMessage(HashSearchFilter.class, "HashSearchPanel.emptyHashMsg.text"); + + public HashSearchFilter() { + this(new HashSearchPanel()); + } + + public HashSearchFilter(HashSearchPanel component) { + super(component); + } + + @Override + public boolean isEnabled() { + return this.getComponent().getHashCheckBox().isSelected(); + } + + @Override + public String getPredicate() throws FilterValidationException { + String md5Hash = this.getComponent().getSearchTextField().getText(); + + if (md5Hash.isEmpty()) { + throw new FilterValidationException(EMPTY_HASH_MESSAGE); + } + + return "md5 = '" + md5Hash.toLowerCase() + "'"; //NON-NLS + } + + @Override + public void addActionListener(ActionListener l) { + getComponent().addActionListener(l); + } + + @Override + public boolean isValid() { + return !this.getComponent().getSearchTextField().getText().isEmpty(); + } +} diff --git a/Core/src/org/sleuthkit/autopsy/filesearch/HashSearchPanel.form b/Core/src/org/sleuthkit/autopsy/filesearch/HashSearchPanel.form new file mode 100755 index 0000000000..bb410a2aaa --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/filesearch/HashSearchPanel.form @@ -0,0 +1,101 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Core/src/org/sleuthkit/autopsy/filesearch/HashSearchPanel.java b/Core/src/org/sleuthkit/autopsy/filesearch/HashSearchPanel.java new file mode 100755 index 0000000000..22b8f74314 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/filesearch/HashSearchPanel.java @@ -0,0 +1,175 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2011-2017 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.filesearch; + +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import javax.swing.JCheckBox; +import javax.swing.JMenuItem; +import javax.swing.JTextField; +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; + +/** + * + */ +class HashSearchPanel extends javax.swing.JPanel { + + private static final long serialVersionUID = 1L; + + /** + * Creates new form HashSearchPanel + */ + HashSearchPanel() { + initComponents(); + customizeComponents(); + setComponentsEnabled(); + } + + private void customizeComponents() { + + searchTextField.setComponentPopupMenu(rightClickMenu); + ActionListener actList = new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + JMenuItem jmi = (JMenuItem) e.getSource(); + if (jmi.equals(cutMenuItem)) { + searchTextField.cut(); + } else if (jmi.equals(copyMenuItem)) { + searchTextField.copy(); + } else if (jmi.equals(pasteMenuItem)) { + searchTextField.paste(); + } else if (jmi.equals(selectAllMenuItem)) { + searchTextField.selectAll(); + } + } + }; + cutMenuItem.addActionListener(actList); + copyMenuItem.addActionListener(actList); + pasteMenuItem.addActionListener(actList); + selectAllMenuItem.addActionListener(actList); + this.searchTextField.getDocument().addDocumentListener(new DocumentListener() { + @Override + public void insertUpdate(DocumentEvent e) { + firePropertyChange(FileSearchPanel.EVENT.CHECKED.toString(), null, null); + } + + @Override + public void removeUpdate(DocumentEvent e) { + firePropertyChange(FileSearchPanel.EVENT.CHECKED.toString(), null, null); + } + + @Override + public void changedUpdate(DocumentEvent e) { + firePropertyChange(FileSearchPanel.EVENT.CHECKED.toString(), null, null); + } + }); + + } + + JCheckBox getHashCheckBox() { + return hashCheckBox; + } + + JTextField getSearchTextField() { + return searchTextField; + } + + void setComponentsEnabled() { + boolean enabled = hashCheckBox.isSelected(); + this.searchTextField.setEnabled(enabled); + } + + /** + * This method is called from within the constructor to initialize the form. + * WARNING: Do NOT modify this code. The content of this method is always + * regenerated by the Form Editor. + */ + @SuppressWarnings("unchecked") + // //GEN-BEGIN:initComponents + private void initComponents() { + + rightClickMenu = new javax.swing.JPopupMenu(); + cutMenuItem = new javax.swing.JMenuItem(); + copyMenuItem = new javax.swing.JMenuItem(); + pasteMenuItem = new javax.swing.JMenuItem(); + selectAllMenuItem = new javax.swing.JMenuItem(); + hashCheckBox = new javax.swing.JCheckBox(); + searchTextField = new javax.swing.JTextField(); + + cutMenuItem.setText(org.openide.util.NbBundle.getMessage(HashSearchPanel.class, "NameSearchPanel.cutMenuItem.text")); // NOI18N + rightClickMenu.add(cutMenuItem); + + copyMenuItem.setText(org.openide.util.NbBundle.getMessage(HashSearchPanel.class, "NameSearchPanel.copyMenuItem.text")); // NOI18N + rightClickMenu.add(copyMenuItem); + + pasteMenuItem.setText(org.openide.util.NbBundle.getMessage(HashSearchPanel.class, "NameSearchPanel.pasteMenuItem.text")); // NOI18N + rightClickMenu.add(pasteMenuItem); + + selectAllMenuItem.setText(org.openide.util.NbBundle.getMessage(HashSearchPanel.class, "NameSearchPanel.selectAllMenuItem.text")); // NOI18N + rightClickMenu.add(selectAllMenuItem); + + hashCheckBox.setFont(hashCheckBox.getFont().deriveFont(hashCheckBox.getFont().getStyle() & ~java.awt.Font.BOLD, 11)); + hashCheckBox.setText(org.openide.util.NbBundle.getMessage(HashSearchPanel.class, "HashSearchPanel.md5CheckBox.text")); // NOI18N + hashCheckBox.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + hashCheckBoxActionPerformed(evt); + } + }); + + searchTextField.setFont(searchTextField.getFont().deriveFont(searchTextField.getFont().getStyle() & ~java.awt.Font.BOLD, 11)); + + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); + this.setLayout(layout); + layout.setHorizontalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addGap(0, 0, 0) + .addComponent(hashCheckBox) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(searchTextField, javax.swing.GroupLayout.PREFERRED_SIZE, 247, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGap(0, 0, 0)) + ); + layout.setVerticalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(hashCheckBox) + .addComponent(searchTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) + ); + }// //GEN-END:initComponents + + private void hashCheckBoxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_hashCheckBoxActionPerformed + setComponentsEnabled(); + firePropertyChange(FileSearchPanel.EVENT.CHECKED.toString(), null, null); + }//GEN-LAST:event_hashCheckBoxActionPerformed + + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JMenuItem copyMenuItem; + private javax.swing.JMenuItem cutMenuItem; + private javax.swing.JCheckBox hashCheckBox; + private javax.swing.JMenuItem pasteMenuItem; + private javax.swing.JPopupMenu rightClickMenu; + private javax.swing.JTextField searchTextField; + private javax.swing.JMenuItem selectAllMenuItem; + // End of variables declaration//GEN-END:variables + + void addActionListener(ActionListener l) { + searchTextField.addActionListener(l); + } +} diff --git a/Core/src/org/sleuthkit/autopsy/ingest/IngestManager.java b/Core/src/org/sleuthkit/autopsy/ingest/IngestManager.java index 99f8e09fd8..e61f8d61f7 100644 --- a/Core/src/org/sleuthkit/autopsy/ingest/IngestManager.java +++ b/Core/src/org/sleuthkit/autopsy/ingest/IngestManager.java @@ -26,6 +26,7 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Date; +import java.util.EnumSet; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -229,7 +230,7 @@ public class IngestManager { * opened/closed) events. */ private void subscribeToCaseEvents() { - Case.addEventSubscriber(Case.Events.CURRENT_CASE.toString(), (PropertyChangeEvent event) -> { + Case.addEventTypeSubscriber(EnumSet.of(Case.Events.CURRENT_CASE), (PropertyChangeEvent event) -> { if (event.getNewValue() != null) { handleCaseOpened(); } else { diff --git a/Core/src/org/sleuthkit/autopsy/ingest/IngestMessagesToolbar.java b/Core/src/org/sleuthkit/autopsy/ingest/IngestMessagesToolbar.java index 20b791c64d..432cb48810 100644 --- a/Core/src/org/sleuthkit/autopsy/ingest/IngestMessagesToolbar.java +++ b/Core/src/org/sleuthkit/autopsy/ingest/IngestMessagesToolbar.java @@ -25,6 +25,7 @@ import java.awt.Font; import java.awt.Graphics; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; +import java.util.EnumSet; import javax.swing.JButton; import org.openide.util.NbBundle; import org.openide.windows.Mode; @@ -128,7 +129,7 @@ class IngestMessagesToolbar extends javax.swing.JPanel { } }); - Case.addPropertyChangeListener((PropertyChangeEvent evt) -> { + Case.addEventTypeSubscriber(EnumSet.of(Case.Events.CURRENT_CASE), (PropertyChangeEvent evt) -> { if (evt.getPropertyName().equals(Case.Events.CURRENT_CASE.toString())) { setEnabled(evt.getNewValue() != null && RuntimeProperties.runningWithGUI()); } diff --git a/Core/src/org/sleuthkit/autopsy/ingest/IngestMonitor.java b/Core/src/org/sleuthkit/autopsy/ingest/IngestMonitor.java index 771380c415..f6580a3cbc 100644 --- a/Core/src/org/sleuthkit/autopsy/ingest/IngestMonitor.java +++ b/Core/src/org/sleuthkit/autopsy/ingest/IngestMonitor.java @@ -23,6 +23,7 @@ import java.awt.event.ActionListener; import java.beans.PropertyChangeEvent; import java.io.File; import java.io.IOException; +import java.util.EnumSet; import java.util.logging.FileHandler; import java.util.logging.Level; import java.util.logging.SimpleFormatter; @@ -121,7 +122,7 @@ public final class IngestMonitor { MonitorTimerAction() { findRootDirectoryForCurrentCase(); - Case.addEventSubscriber(Case.Events.CURRENT_CASE.toString(), (PropertyChangeEvent evt) -> { + Case.addEventTypeSubscriber(EnumSet.of(Case.Events.CURRENT_CASE), (PropertyChangeEvent evt) -> { if (evt instanceof AutopsyEvent) { AutopsyEvent event = (AutopsyEvent) evt; if (AutopsyEvent.SourceType.LOCAL == event.getSourceType() && event.getPropertyName().equals(Case.Events.CURRENT_CASE.toString())) { diff --git a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbPanelSearchAction.java b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbPanelSearchAction.java index d7471df562..268f828fc7 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbPanelSearchAction.java +++ b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbPanelSearchAction.java @@ -23,7 +23,7 @@ import java.awt.event.ActionListener; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.beans.PropertyChangeEvent; -import java.beans.PropertyChangeListener; +import java.util.EnumSet; import org.openide.util.HelpCtx; import org.openide.util.NbBundle; import org.openide.util.actions.CallableSystemAction; @@ -43,13 +43,9 @@ class HashDbPanelSearchAction extends CallableSystemAction { HashDbPanelSearchAction() { super(); setEnabled(Case.isCaseOpen()); - Case.addPropertyChangeListener(new PropertyChangeListener() { - - @Override - public void propertyChange(PropertyChangeEvent evt) { - if (evt.getPropertyName().equals(Case.Events.CURRENT_CASE.toString())) { - setEnabled(evt.getNewValue() != null); - } + Case.addEventTypeSubscriber(EnumSet.of(Case.Events.CURRENT_CASE), (PropertyChangeEvent evt) -> { + if (evt.getPropertyName().equals(Case.Events.CURRENT_CASE.toString())) { + setEnabled(evt.getNewValue() != null); } }); } diff --git a/Core/src/org/sleuthkit/autopsy/report/ReportWizardAction.java b/Core/src/org/sleuthkit/autopsy/report/ReportWizardAction.java index 75c8f7c205..49f37008be 100644 --- a/Core/src/org/sleuthkit/autopsy/report/ReportWizardAction.java +++ b/Core/src/org/sleuthkit/autopsy/report/ReportWizardAction.java @@ -27,6 +27,7 @@ import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.beans.PropertyChangeEvent; import java.text.MessageFormat; +import java.util.EnumSet; import java.util.Map; import javax.swing.ImageIcon; import javax.swing.JButton; @@ -81,7 +82,7 @@ public final class ReportWizardAction extends CallableSystemAction implements Pr public ReportWizardAction() { setEnabled(false); - Case.addPropertyChangeListener((PropertyChangeEvent evt) -> { + Case.addEventTypeSubscriber(EnumSet.of(Case.Events.CURRENT_CASE), (PropertyChangeEvent evt) -> { if (evt.getPropertyName().equals(Case.Events.CURRENT_CASE.toString())) { Case newCase = (Case) evt.getNewValue(); setEnabled(newCase != null && RuntimeProperties.runningWithGUI()); diff --git a/CoreLibs/ivy.xml b/CoreLibs/ivy.xml index 8043007b52..bfd9112f9e 100644 --- a/CoreLibs/ivy.xml +++ b/CoreLibs/ivy.xml @@ -24,7 +24,7 @@ Note there is no namespace collision with ver 3 --> - + diff --git a/CoreLibs/nbproject/project.properties b/CoreLibs/nbproject/project.properties index 62c4d535e6..13a62b62ee 100644 --- a/CoreLibs/nbproject/project.properties +++ b/CoreLibs/nbproject/project.properties @@ -10,6 +10,7 @@ file.reference.commons-codec-1.10.jar=release/modules/ext/commons-codec-1.10.jar file.reference.commons-collections4-4.1.jar=release/modules/ext/commons-collections4-4.1.jar file.reference.commons-csv-1.4.jar=release/modules/ext/commons-csv-1.4.jar file.reference.commons-io-2.4.jar=release/modules/ext/commons-io-2.4.jar +file.reference.commons-io-2.5.jar=release/modules/ext/commons-io-2.5.jar file.reference.commons-lang-2.6.jar=release/modules/ext/commons-lang-2.6.jar file.reference.commons-lang3-3.0-javadoc.jar=release/modules/ext/commons-lang3-3.0-javadoc.jar file.reference.commons-lang3-3.0-sources.jar=release/modules/ext/commons-lang3-3.0-sources.jar @@ -74,6 +75,7 @@ file.reference.xmlbeans-2.6.0.jar=release/modules/ext/xmlbeans-2.6.0.jar javac.source=1.8 javac.compilerargs=-Xlint -Xlint:-serial javadoc.reference.commons-csv-1.4.jar=release/modules/ext/commons-csv-1.4-javadoc.jar +javadoc.reference.commons-io-2.5.jar=release/modules/ext/commons-io-2.5-javadoc.jar javadoc.reference.compiler-0.9.1.jar=release/modules/ext/compiler-0.9.1-javadoc.jar javadoc.reference.controlsfx-8.40.11.jar=release/modules/ext/controlsfx-8.40.11-javadoc.jar javadoc.reference.guava-19.0.jar=release/modules/ext/guava-19.0-javadoc.jar @@ -82,6 +84,7 @@ javadoc.reference.jfxtras-controls-8.0-r4.jar=release/modules/ext/jfxtras-contro javadoc.reference.jfxtras-fxml-8.0-r4.jar=release/modules/ext/jfxtras-fxml-8.0-r4-javadoc.jar nbm.needs.restart=true source.reference.commons-csv-1.4.jar=release/modules/ext/commons-csv-1.4-sources.jar +source.reference.commons-io-2.5.jar=release/modules/ext/commons-io-2.5-sources.jar source.reference.compiler-0.9.1.jar=release/modules/ext/compiler-0.9.1-sources.jar source.reference.controlsfx-8.40.11.jar=release/modules/ext/controlsfx-8.40.11-sources.jar source.reference.guava-19.0.jar=release/modules/ext/guava-19.0-sources.jar diff --git a/CoreLibs/nbproject/project.xml b/CoreLibs/nbproject/project.xml index 23a41edaf1..6fcef46ccd 100644 --- a/CoreLibs/nbproject/project.xml +++ b/CoreLibs/nbproject/project.xml @@ -699,18 +699,10 @@ ext/sigar-1.6.4.jar release/modules/ext/sigar-1.6.4.jar - - ext/xmlbeans-2.6.0.jar - release/modules/ext/xmlbeans-2.6.0.jar - ext/jna-3.4.0.jar release/modules/ext/jna-3.4.0.jar - - ext/poi-ooxml-schemas-3.15.jar - release/modules/ext/poi-ooxml-schemas-3.15.jar - ext/gson-1.4.jar release/modules/ext/gson-1.4.jar @@ -731,6 +723,10 @@ ext/imgscalr-lib-4.2.jar release/modules/ext/imgscalr-lib-4.2.jar + + ext/xmlbeans-2.6.0.jar + release/modules/ext/xmlbeans-2.6.0.jar + ext/common-io-3.2.jar release/modules/ext/common-io-3.2.jar @@ -764,12 +760,12 @@ release/modules/ext/joda-time-2.4-javadoc.jar - ext/jcalendarbutton-1.4.6.jar - release/modules/ext/jcalendarbutton-1.4.6.jar + ext/poi-excelant-3.15.jar + release/modules/ext/poi-excelant-3.15.jar - ext/poi-ooxml-3.15.jar - release/modules/ext/poi-ooxml-3.15.jar + ext/jcalendarbutton-1.4.6.jar + release/modules/ext/jcalendarbutton-1.4.6.jar ext/imageio-psd-3.2.jar @@ -779,18 +775,10 @@ ext/stax-api-1.0.1.jar release/modules/ext/stax-api-1.0.1.jar - - ext/commons-collections4-4.1.jar - release/modules/ext/commons-collections4-4.1.jar - ext/servlet-api-2.5.jar release/modules/ext/servlet-api-2.5.jar - - ext/poi-excelant-3.15.jar - release/modules/ext/poi-excelant-3.15.jar - ext/imageio-pcx-3.2.jar release/modules/ext/imageio-pcx-3.2.jar @@ -827,10 +815,6 @@ ext/geronimo-jms_1.1_spec-1.0.jar release/modules/ext/geronimo-jms_1.1_spec-1.0.jar - - ext/poi-scratchpad-3.15.jar - release/modules/ext/poi-scratchpad-3.15.jar - ext/joda-time-2.4-sources.jar release/modules/ext/joda-time-2.4-sources.jar @@ -839,14 +823,26 @@ ext/jfxtras-fxml-8.0-r4.jar release/modules/ext/jfxtras-fxml-8.0-r4.jar + + ext/poi-ooxml-3.15.jar + release/modules/ext/poi-ooxml-3.15.jar + ext/joda-time-2.4.jar release/modules/ext/joda-time-2.4.jar + + ext/commons-collections4-4.1.jar + release/modules/ext/commons-collections4-4.1.jar + ext/commons-logging-1.1.2-javadoc.jar release/modules/ext/commons-logging-1.1.2-javadoc.jar + + ext/commons-codec-1.10.jar + release/modules/ext/commons-codec-1.10.jar + ext/slf4j-simple-1.6.1.jar release/modules/ext/slf4j-simple-1.6.1.jar @@ -855,6 +851,18 @@ ext/guava-19.0.jar release/modules/ext/guava-19.0.jar + + ext/commons-io-2.5.jar + release/modules/ext/commons-io-2.5.jar + + + ext/poi-ooxml-schemas-3.15.jar + release/modules/ext/poi-ooxml-schemas-3.15.jar + + + ext/poi-scratchpad-3.15.jar + release/modules/ext/poi-scratchpad-3.15.jar + ext/imageio-bmp-3.2.jar release/modules/ext/imageio-bmp-3.2.jar @@ -879,10 +887,6 @@ ext/ant-1.8.2.jar release/modules/ext/ant-1.8.2.jar - - ext/commons-codec-1.10.jar - release/modules/ext/commons-codec-1.10.jar - ext/javassist-3.12.1.GA.jar release/modules/ext/javassist-3.12.1.GA.jar @@ -895,14 +899,6 @@ ext/commons-logging-1.1.2.jar release/modules/ext/commons-logging-1.1.2.jar - - ext/commons-io-2.4.jar - release/modules/ext/commons-io-2.4.jar - - - ext/poi-3.15.jar - release/modules/ext/poi-3.15.jar - ext/controlsfx-8.40.11.jar release/modules/ext/controlsfx-8.40.11.jar @@ -915,6 +911,10 @@ ext/javaee-api-5.0-2.jar release/modules/ext/javaee-api-5.0-2.jar + + ext/poi-3.15.jar + release/modules/ext/poi-3.15.jar + ext/common-image-3.2.jar release/modules/ext/common-image-3.2.jar diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestManager.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestManager.java index afae397553..0f36d63aea 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestManager.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestManager.java @@ -2646,8 +2646,6 @@ public final class AutoIngestManager extends Observable implements PropertyChang * back into hostNamesToRunningJobs as a result of * processing the job status update. */ - SYS_LOGGER.log(Level.WARNING, "Auto ingest node {0} timed out while processing folder {1}", - new Object[]{job.getNodeName(), job.getManifest().getFilePath().toString()}); hostNamesToRunningJobs.remove(job.getNodeName()); setChanged(); notifyObservers(Event.JOB_COMPLETED); diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestSystemLogger.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestSystemLogger.java index 08b3016eb5..184d5d350a 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestSystemLogger.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestSystemLogger.java @@ -35,7 +35,7 @@ import org.sleuthkit.autopsy.coreutils.PlatformUtil; */ final class AutoIngestSystemLogger { - private static final int LOG_SIZE = 10000000; // In bytes, zero is unlimited, set to roughly 10mb currently + private static final int LOG_SIZE = 50000000; // In bytes, zero is unlimited, set to roughly 10mb currently private static final int LOG_FILE_COUNT = 10; private static final Logger LOGGER = Logger.getLogger("AutoIngest"); //NON-NLS private static final String NEWLINE = System.lineSeparator(); diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/DropdownToolbar.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/DropdownToolbar.java index 3272b9dad1..1ad473dd01 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/DropdownToolbar.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/DropdownToolbar.java @@ -23,6 +23,7 @@ import java.awt.event.ActionListener; import java.awt.event.MouseEvent; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; +import java.util.EnumSet; import java.util.logging.Level; import javax.swing.SwingUtilities; import javax.swing.event.PopupMenuEvent; @@ -76,7 +77,7 @@ class DropdownToolbar extends javax.swing.JPanel { private void customizeComponents() { searchSettingsChangeListener = new SearchSettingsChangeListener(); KeywordSearch.getServer().addServerActionListener(searchSettingsChangeListener); - Case.addPropertyChangeListener(searchSettingsChangeListener); + Case.addEventTypeSubscriber(EnumSet.of(Case.Events.CURRENT_CASE), searchSettingsChangeListener); DropdownListSearchPanel listsPanel = DropdownListSearchPanel.getDefault(); listsPanel.addSearchButtonActionListener((ActionEvent e) -> { diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordCachedArtifact.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordCachedArtifact.java deleted file mode 100644 index 68f85dd29a..0000000000 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordCachedArtifact.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2011 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.keywordsearch; - -import java.util.Collection; -import java.util.HashMap; -import java.util.Map; -import org.sleuthkit.datamodel.BlackboardArtifact; -import org.sleuthkit.datamodel.BlackboardAttribute; - -/** - * result of writing keyword search result to blackboard (cached artifact and - * attributes) This is mainly to cache the attributes, so that we don't query - * the DB to get them back again. - */ -class KeywordCachedArtifact { - - private BlackboardArtifact artifact; - private Map attributes; - - KeywordCachedArtifact(BlackboardArtifact artifact) { - this.artifact = artifact; - attributes = new HashMap(); - } - - BlackboardArtifact getArtifact() { - return artifact; - } - - Collection getAttributes() { - return attributes.values(); - } - - BlackboardAttribute getAttribute(Integer attrTypeID) { - return attributes.get(attrTypeID); - } - - void add(BlackboardAttribute attribute) { - attributes.put(attribute.getAttributeType().getTypeID(), attribute); - } - - void add(Collection attributes) { - for (BlackboardAttribute attr : attributes) { - this.attributes.put(attr.getAttributeType().getTypeID(), attr); - } - } -} diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordHit.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordHit.java index f86b86b485..87ddac3d22 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordHit.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordHit.java @@ -18,17 +18,18 @@ */ package org.sleuthkit.autopsy.keywordsearch; +import java.util.Comparator; +import org.apache.commons.lang3.StringUtils; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.datamodel.BlackboardArtifact; -import org.sleuthkit.datamodel.Content; import org.sleuthkit.datamodel.SleuthkitCase; import org.sleuthkit.datamodel.TskCoreException; /** * Stores the fact that file or an artifact associated with a file had a keyword * hit. All instances make both the document id of the Solr document where the - * keyword was found and the file available to clients. Artifact keyword hits - * also make the artifact available to clients. + * keyword was found and the object Id available to clients. Artifact keyword + * hits also make the artifact available to clients. */ class KeywordHit implements Comparable { @@ -36,7 +37,7 @@ class KeywordHit implements Comparable { private final long solrObjectId; private final int chunkId; private final String snippet; - private final Content content; + private final long contentID; private final BlackboardArtifact artifact; private final String hit; @@ -44,14 +45,9 @@ class KeywordHit implements Comparable { return hit; } - KeywordHit(String solrDocumentId, String snippet) throws TskCoreException { - this(solrDocumentId, snippet, null); - } - KeywordHit(String solrDocumentId, String snippet, String hit) throws TskCoreException { - /** - * Store the Solr document id. - */ + this.snippet = StringUtils.stripToEmpty(snippet); + this.hit = hit; this.solrDocumentId = solrDocumentId; /** @@ -72,27 +68,19 @@ class KeywordHit implements Comparable { this.chunkId = 0; } - /** - * Look up the file associated with the keyword hit. If the high order - * bit of the object id is set, the hit was for an artifact. In this - * case, look up the artifact as well. + /* + * If the high order bit of the object id is set (ie, it is negative), + * the hit was in an artifact, look up the artifact. */ - SleuthkitCase caseDb = Case.getCurrentCase().getSleuthkitCase(); - long fileId; if (this.solrObjectId < 0) { + SleuthkitCase caseDb = Case.getCurrentCase().getSleuthkitCase(); this.artifact = caseDb.getBlackboardArtifact(this.solrObjectId); - fileId = artifact.getObjectID(); + contentID = artifact.getObjectID(); } else { + //else the object id is for content. this.artifact = null; - fileId = this.solrObjectId; + contentID = this.solrObjectId; } - this.content = caseDb.getContentById(fileId); - - /** - * Store the text snippet. - */ - this.snippet = snippet; - this.hit = hit; } String getSolrDocumentId() { @@ -103,24 +91,20 @@ class KeywordHit implements Comparable { return this.solrObjectId; } - boolean hasChunkId() { - return this.chunkId != 0; - } - int getChunkId() { return this.chunkId; } boolean hasSnippet() { - return !this.snippet.isEmpty(); + return StringUtils.isNotBlank(this.snippet); } String getSnippet() { return this.snippet; } - Content getContent() { - return this.content; + long getContentID() { + return this.contentID; } /** @@ -151,7 +135,7 @@ class KeywordHit implements Comparable { return false; } final KeywordHit other = (KeywordHit) obj; - return (this.solrObjectId == other.solrObjectId && this.chunkId == other.chunkId); + return this.compareTo(other) == 0; } @Override @@ -163,21 +147,8 @@ class KeywordHit implements Comparable { @Override public int compareTo(KeywordHit o) { - if (this.solrObjectId < o.solrObjectId) { - // Out object id is less than the other object id - return -1; - } else if (this.solrObjectId == o.solrObjectId) { - // Hits have same object id - if (this.chunkId < o.chunkId) { - // Our chunk id is lower than the other chunk id - return -1; - } else { - // Our chunk id is either greater than or equal to the other chunk id - return this.chunkId == o.chunkId ? 0 : 1; - } - } else { - // Our object id is greater than the other object id - return 1; - } + return Comparator.comparing(KeywordHit::getSolrObjectId) + .thenComparing(KeywordHit::getChunkId) + .compare(this, o); } } diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchQuery.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchQuery.java index cafd45b3e8..da269e2d2c 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchQuery.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchQuery.java @@ -18,8 +18,11 @@ */ package org.sleuthkit.autopsy.keywordsearch; +import org.sleuthkit.datamodel.BlackboardArtifact; +import org.sleuthkit.datamodel.Content; + /** - * Interface for kewyord search queries. + * Interface for kewyord search queries. */ interface KeywordSearchQuery { @@ -30,18 +33,20 @@ interface KeywordSearchQuery { * * @return true if the query passed validation */ - boolean validate(); + boolean validate(); /** * execute query and return results without publishing them return results * for all matching terms * - * @throws KeywordSearchModuleException error while executing Solr term query - * @throws NoOpenCoreException if query failed due to server error, this - * could be a notification to stop processing + * @throws KeywordSearchModuleException error while executing Solr term + * query + * @throws NoOpenCoreException if query failed due to server error, + * this could be a notification to stop + * processing * @return */ - QueryResults performQuery() throws KeywordSearchModuleException, NoOpenCoreException; + QueryResults performQuery() throws KeywordSearchModuleException, NoOpenCoreException; /** * Set an optional filter to narrow down the search Adding multiple filters @@ -49,54 +54,67 @@ interface KeywordSearchQuery { * * @param filter filter to set on the query */ - void addFilter(KeywordQueryFilter filter); + void addFilter(KeywordQueryFilter filter); /** * Set an optional SOLR field to narrow down the search * * @param field field to set on the query */ - void setField(String field); + void setField(String field); /** * Modify the query string to be searched as a substring instead of a whole * word - * - * @param isSubstring */ - void setSubstringQuery(); + void setSubstringQuery(); /** * escape the query string and use the escaped string in the query */ - void escape(); + void escape(); /** * * @return true if query was escaped */ - boolean isEscaped(); + boolean isEscaped(); /** * * @return true if query is a literal query (non regex) */ - boolean isLiteral(); + boolean isLiteral(); /** * return original keyword/query string * * @return the query String supplied originally */ - String getQueryString(); + String getQueryString(); /** * return escaped keyword/query string if escaping was done * * @return the escaped query string, or original string if no escaping done */ - String getEscapedQueryString(); - - KeywordCachedArtifact writeSingleFileHitsToBlackBoard(Keyword keyword, KeywordHit hit, String snippet, String listName); + String getEscapedQueryString(); + /** + * Converts the keyword hits for a given search term into artifacts. + * + * @param content The Content object associated with the hit. + * @param foundKeyword The keyword that was found by the search, this may be + * different than the Keyword that was searched if, for + * example, it was a RegexQuery. + * @param hit The keyword hit. + * @param snippet The document snippet that contains the hit. + * @param listName The name of the keyword list that contained the + * keyword for which the hit was found. + * + * + * @return The newly created artifact or Null if there was a problem + * creating it. + */ + BlackboardArtifact writeSingleFileHitsToBlackBoard(Content content, Keyword foundKeyword, KeywordHit hit, String snippet, String listName); } diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchResultFactory.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchResultFactory.java index b82e7778e1..b603f7b68c 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchResultFactory.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchResultFactory.java @@ -18,13 +18,10 @@ */ package org.sleuthkit.autopsy.keywordsearch; -import com.google.common.collect.SetMultimap; -import com.google.common.collect.TreeMultimap; import java.awt.EventQueue; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; -import java.util.Comparator; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; @@ -41,6 +38,7 @@ import org.openide.nodes.Children; import org.openide.nodes.Node; import org.openide.util.NbBundle; import org.openide.util.lookup.Lookups; +import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; import org.sleuthkit.autopsy.datamodel.AbstractAbstractFileNode; @@ -50,12 +48,13 @@ import org.sleuthkit.autopsy.datamodel.KeyValue; import org.sleuthkit.autopsy.datamodel.KeyValueNode; import org.sleuthkit.autopsy.keywordsearch.KeywordSearchResultFactory.KeyValueQueryContent; import org.sleuthkit.datamodel.AbstractFile; -import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.BlackboardAttribute; import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_KEYWORD; import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_KEYWORD_PREVIEW; import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_KEYWORD_REGEXP; import org.sleuthkit.datamodel.Content; +import org.sleuthkit.datamodel.SleuthkitCase; +import org.sleuthkit.datamodel.TskCoreException; /** * Node factory that performs the keyword search and creates children nodes for @@ -69,16 +68,16 @@ class KeywordSearchResultFactory extends ChildFactory { private static final Logger logger = Logger.getLogger(KeywordSearchResultFactory.class.getName()); //common properties (superset of all Node properties) to be displayed as columns - static final List COMMON_PROPERTIES - = Stream.concat( + static final List COMMON_PROPERTIES = + Stream.concat( Stream.of( TSK_KEYWORD, TSK_KEYWORD_REGEXP, TSK_KEYWORD_PREVIEW) - .map(BlackboardAttribute.ATTRIBUTE_TYPE::getDisplayName), + .map(BlackboardAttribute.ATTRIBUTE_TYPE::getDisplayName), Arrays.stream(AbstractAbstractFileNode.AbstractFilePropertyType.values()) - .map(Object::toString)) - .collect(Collectors.toList()); + .map(Object::toString)) + .collect(Collectors.toList()); private final Collection queryRequests; @@ -91,7 +90,7 @@ class KeywordSearchResultFactory extends ChildFactory { * properties are displayed as columns (since we are doing lazy child Node * load we need to preinitialize properties when sending parent Node) * - * @param toSet property set map for a Node + * @param toPopulate property set map for a Node */ @Override protected boolean createKeys(List toPopulate) { @@ -144,6 +143,13 @@ class KeywordSearchResultFactory extends ChildFactory { MessageNotifyUtil.Notify.error(Bundle.KeywordSearchResultFactory_query_exception_msg() + queryRequest.getQueryString(), ex.getCause().getMessage()); return false; } + SleuthkitCase tskCase = null; + try { + tskCase = Case.getCurrentCase().getSleuthkitCase(); + } catch (IllegalStateException ex) { + logger.log(Level.SEVERE, "There was no case open.", ex); //NON-NLS + return false; + } int hitNumber = 0; List tempList = new ArrayList<>(); @@ -153,8 +159,20 @@ class KeywordSearchResultFactory extends ChildFactory { * Get file properties. */ Map properties = new LinkedHashMap<>(); - Content content = hit.getContent(); - String contentName = content.getName(); + Content content = null; + String contentName = ""; + try { + content = tskCase.getContentById(hit.getContentID()); + if (content == null) { + logger.log(Level.SEVERE, "There was a error getting content by id."); //NON-NLS + return false; + } + } catch (TskCoreException ex) { + logger.log(Level.SEVERE, "There was a error getting content by id.", ex); //NON-NLS + return false; + } + + contentName = content.getName(); if (content instanceof AbstractFile) { AbstractFsContentNode.fillPropertyMap(properties, (AbstractFile) content); } else { @@ -222,6 +240,7 @@ class KeywordSearchResultFactory extends ChildFactory { //wrap in KeywordSearchFilterNode for the markup content, might need to override FilterNode for more customization return new KeywordSearchFilterNode(hits, kvNode); + } /** @@ -240,13 +259,14 @@ class KeywordSearchResultFactory extends ChildFactory { * NOTE Parameters are defined based on how they are currently used in * practice * - * @param name File name that has hit. - * @param map Contains content metadata, snippets, etc. (property - * map) - * @param id User incremented ID - * @param content File that had the hit. - * @param query Query used in search - * @param hits Full set of search results (for all files! @@@) + * @param name File name that has hit. + * @param map Contains content metadata, snippets, etc. + * (property map) + * @param id User incremented ID + * @param solrObjectId + * @param content File that had the hit. + * @param query Query used in search + * @param hits Full set of search results (for all files! @@@) */ KeyValueQueryContent(String name, Map map, int id, long solrObjectId, Content content, KeywordSearchQuery query, QueryResults hits) { super(name, map, id); @@ -278,13 +298,12 @@ class KeywordSearchResultFactory extends ChildFactory { * worker for writing results to bb, with progress bar, cancellation, and * central registry of workers to be stopped when case is closed */ - static class BlackboardResultWriter extends SwingWorker { + static class BlackboardResultWriter extends SwingWorker { private static final List writers = new ArrayList<>(); private ProgressHandle progress; private final KeywordSearchQuery query; private final QueryResults hits; - private Collection newArtifacts = new ArrayList<>(); private static final int QUERY_DISPLAY_LEN = 40; BlackboardResultWriter(QueryResults hits, String listName) { @@ -298,13 +317,13 @@ class KeywordSearchResultFactory extends ChildFactory { } @Override - protected Object doInBackground() throws Exception { + protected Void doInBackground() throws Exception { registerWriter(this); //register (synchronized on class) outside of writerLock to prevent deadlock final String queryStr = query.getQueryString(); final String queryDisp = queryStr.length() > QUERY_DISPLAY_LEN ? queryStr.substring(0, QUERY_DISPLAY_LEN - 1) + " ..." : queryStr; try { progress = ProgressHandle.createHandle(NbBundle.getMessage(this.getClass(), "KeywordSearchResultFactory.progress.saving", queryDisp), () -> BlackboardResultWriter.this.cancel(true)); - newArtifacts = hits.writeAllHitsToBlackBoard(progress, null, this, false); + hits.writeAllHitsToBlackBoard(progress, null, this, false); } finally { finalizeWorker(); } diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/LuceneQuery.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/LuceneQuery.java index 9f8686898f..3821501574 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/LuceneQuery.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/LuceneQuery.java @@ -20,7 +20,6 @@ package org.sleuthkit.autopsy.keywordsearch; import java.util.ArrayList; import java.util.Collection; -import java.util.Comparator; import java.util.List; import java.util.Map; import java.util.logging.Level; @@ -40,6 +39,7 @@ import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE; import org.sleuthkit.datamodel.BlackboardAttribute; import org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE; +import org.sleuthkit.datamodel.Content; import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.datamodel.TskException; @@ -192,15 +192,13 @@ class LuceneQuery implements KeywordSearchQuery { } @Override - public KeywordCachedArtifact writeSingleFileHitsToBlackBoard(Keyword foundKeyword, KeywordHit hit, String snippet, String listName) { + public BlackboardArtifact writeSingleFileHitsToBlackBoard(Content content, Keyword foundKeyword, KeywordHit hit, String snippet, String listName) { final String MODULE_NAME = KeywordSearchModuleFactory.getModuleName(); Collection attributes = new ArrayList<>(); BlackboardArtifact bba; - KeywordCachedArtifact writeResult; try { - bba = hit.getContent().newArtifact(ARTIFACT_TYPE.TSK_KEYWORD_HIT); - writeResult = new KeywordCachedArtifact(bba); + bba = content.newArtifact(ARTIFACT_TYPE.TSK_KEYWORD_HIT); } catch (TskCoreException e) { logger.log(Level.WARNING, "Error adding bb artifact for keyword hit", e); //NON-NLS return null; @@ -233,8 +231,7 @@ class LuceneQuery implements KeywordSearchQuery { try { bba.addAttributes(attributes); //write out to bb - writeResult.add(attributes); - return writeResult; + return bba; } catch (TskCoreException e) { logger.log(Level.WARNING, "Error adding bb attributes to artifact", e); //NON-NLS return null; diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/QueryResults.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/QueryResults.java index 410ec1a94c..48fdb38f10 100755 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/QueryResults.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/QueryResults.java @@ -31,6 +31,7 @@ import org.apache.commons.lang.StringUtils; import org.netbeans.api.progress.ProgressHandle; import org.netbeans.api.progress.aggregate.ProgressContributor; import org.openide.util.NbBundle; +import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.coreutils.EscapeUtil; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.ingest.IngestMessage; @@ -40,6 +41,8 @@ import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.BlackboardAttribute; import org.sleuthkit.datamodel.Content; +import org.sleuthkit.datamodel.SleuthkitCase; +import org.sleuthkit.datamodel.TskCoreException; /** * Stores the results from running a Solr query (which could contain multiple @@ -60,8 +63,6 @@ class QueryResults { */ private final Map> results = new HashMap<>(); - - QueryResults(KeywordSearchQuery query) { this.keywordSearchQuery = query; } @@ -70,8 +71,6 @@ class QueryResults { results.put(keyword, hits); } - - KeywordSearchQuery getQuery() { return keywordSearchQuery; } @@ -99,7 +98,7 @@ class QueryResults { * * @return The artifacts that were created. */ - Collection writeAllHitsToBlackBoard(ProgressHandle progress, ProgressContributor subProgress, SwingWorker worker, boolean notifyInbox) { + Collection writeAllHitsToBlackBoard(ProgressHandle progress, ProgressContributor subProgress, SwingWorker worker, boolean notifyInbox) { final Collection newArtifacts = new ArrayList<>(); if (progress != null) { progress.start(getKeywords().size()); @@ -145,14 +144,26 @@ class QueryResults { continue; } } - KeywordCachedArtifact writeResult = keywordSearchQuery.writeSingleFileHitsToBlackBoard(keyword, hit, snippet, keywordSearchQuery.getKeywordList().getName()); + Content content = null; + try { + SleuthkitCase tskCase = Case.getCurrentCase().getSleuthkitCase(); + content = tskCase.getContentById(hit.getContentID()); + } catch (TskCoreException | IllegalStateException tskCoreException) { + logger.log(Level.SEVERE, "Error adding artifact for keyword hit to blackboard", tskCoreException); //NON-NLS + return null; + } + BlackboardArtifact writeResult = keywordSearchQuery.writeSingleFileHitsToBlackBoard(content, keyword, hit, snippet, keywordSearchQuery.getKeywordList().getName()); if (writeResult != null) { - newArtifacts.add(writeResult.getArtifact()); + newArtifacts.add(writeResult); if (notifyInbox) { - writeSingleFileInboxMessage(writeResult, hit.getContent()); + try { + writeSingleFileInboxMessage(writeResult, content); + } catch (TskCoreException ex) { + logger.log(Level.WARNING, "Error posting message to Ingest Inbox", ex); //NON-NLS + } } } else { - logger.log(Level.WARNING, "BB artifact for keyword hit not written, file: {0}, hit: {1}", new Object[]{hit.getContent(), keyword.toString()}); //NON-NLS + logger.log(Level.WARNING, "BB artifact for keyword hit not written, file: {0}, hit: {1}", new Object[]{content, keyword.toString()}); //NON-NLS } } ++unitProgress; @@ -181,7 +192,6 @@ class QueryResults { * SolrObjectID-ChunkID pairs. */ private Collection getOneHitPerObject(Keyword keyword) { - HashMap hits = new HashMap<>(); // create a list of KeywordHits. KeywordHits with lowest chunkID is added the the list. @@ -196,12 +206,16 @@ class QueryResults { } /** - * Generate an ingest inbox message for given keyword in given file + * Generate and post an ingest inbox message for the given keyword in the + * given content. * - * @param written - * @param hitFile + * @param artifact The keyword hit artifact. + * @param hitContent The content that the hit is in. + * + * @throws TskCoreException If there is a problem generating or posting the + * inbox message. */ - private void writeSingleFileInboxMessage(KeywordCachedArtifact written, Content hitContent) { + private void writeSingleFileInboxMessage(BlackboardArtifact artifact, Content hitContent) throws TskCoreException { StringBuilder subjectSb = new StringBuilder(); StringBuilder detailsSb = new StringBuilder(); @@ -210,24 +224,24 @@ class QueryResults { } else { subjectSb.append(NbBundle.getMessage(this.getClass(), "KeywordSearchIngestModule.kwHitLbl")); } + String uniqueKey = null; - BlackboardAttribute attr = written.getAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_KEYWORD.getTypeID()); + BlackboardAttribute attr = artifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_KEYWORD)); if (attr != null) { final String keyword = attr.getValueString(); subjectSb.append(keyword); uniqueKey = keyword.toLowerCase(); + //details + detailsSb.append(""); //NON-NLS + //hit + detailsSb.append(""); //NON-NLS + detailsSb.append(NbBundle.getMessage(this.getClass(), "KeywordSearchIngestModule.kwHitThLbl")); + detailsSb.append(""); //NON-NLS + detailsSb.append(""); //NON-NLS } - //details - detailsSb.append("
").append(EscapeUtil.escapeHtml(keyword)).append("
"); //NON-NLS - //hit - detailsSb.append(""); //NON-NLS - detailsSb.append(NbBundle.getMessage(this.getClass(), "KeywordSearchIngestModule.kwHitThLbl")); - detailsSb.append(""); //NON-NLS - detailsSb.append(""); //NON-NLS - //preview - attr = written.getAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_KEYWORD_PREVIEW.getTypeID()); + attr = artifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_KEYWORD_PREVIEW)); if (attr != null) { detailsSb.append(""); //NON-NLS detailsSb.append(NbBundle.getMessage(this.getClass(), "KeywordSearchIngestModule.previewThLbl")); @@ -247,16 +261,17 @@ class QueryResults { detailsSb.append(""); //NON-NLS //list - attr = written.getAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SET_NAME.getTypeID()); + attr = artifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SET_NAME)); if (attr != null) { detailsSb.append(""); //NON-NLS detailsSb.append(NbBundle.getMessage(this.getClass(), "KeywordSearchIngestModule.listThLbl")); detailsSb.append(""); //NON-NLS detailsSb.append(""); //NON-NLS } + //regex if (!keywordSearchQuery.isLiteral()) { - attr = written.getAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_KEYWORD_REGEXP.getTypeID()); + attr = artifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_KEYWORD_REGEXP)); if (attr != null) { detailsSb.append(""); //NON-NLS detailsSb.append(NbBundle.getMessage(this.getClass(), "KeywordSearchIngestModule.regExThLbl")); @@ -264,9 +279,9 @@ class QueryResults { detailsSb.append(""); //NON-NLS } } + detailsSb.append("
").append(EscapeUtil.escapeHtml(attr.getValueString())).append("
").append(attr.getValueString()).append("
"); //NON-NLS - IngestServices.getInstance().postMessage(IngestMessage.createDataMessage(MODULE_NAME, subjectSb.toString(), detailsSb.toString(), uniqueKey, written.getArtifact())); + IngestServices.getInstance().postMessage(IngestMessage.createDataMessage(MODULE_NAME, subjectSb.toString(), detailsSb.toString(), uniqueKey, artifact)); } - } diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/RegexQuery.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/RegexQuery.java index 8d9ca309e5..6b91c9c2fb 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/RegexQuery.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/RegexQuery.java @@ -19,15 +19,11 @@ package org.sleuthkit.autopsy.keywordsearch; import com.google.common.base.CharMatcher; -import com.google.common.collect.ArrayListMultimap; -import com.google.common.collect.ListMultimap; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; -import java.util.HashSet; import java.util.List; import java.util.Map; -import java.util.Set; import java.util.logging.Level; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -52,6 +48,7 @@ import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.Account; import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.BlackboardAttribute; +import org.sleuthkit.datamodel.Content; import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.datamodel.TskData; @@ -84,8 +81,6 @@ final class RegexQuery implements KeywordSearchQuery { private final int MIN_EMAIL_ADDR_LENGTH = 8; - private final ListMultimap hitsMultiMap = ArrayListMultimap.create(); - // Lucene regular expressions do not support the following Java predefined // and POSIX character classes. There are other valid Java character classes // that are not supported by Lucene but we do not check for all of them. @@ -188,8 +183,9 @@ final class RegexQuery implements KeywordSearchQuery { solrQuery.setSort(SortClause.asc(Server.Schema.ID.toString())); String cursorMark = CursorMarkParams.CURSOR_MARK_START; - SolrDocumentList resultList ; + SolrDocumentList resultList; boolean allResultsProcessed = false; + QueryResults results = new QueryResults(this); while (!allResultsProcessed) { try { @@ -201,9 +197,15 @@ final class RegexQuery implements KeywordSearchQuery { try { List keywordHits = createKeywordHits(resultDoc); for (KeywordHit hit : keywordHits) { - hitsMultiMap.put(new Keyword(hit.getHit(), true, true, originalKeyword.getListName(), originalKeyword.getOriginalTerm()), hit); + Keyword keywordInstance = new Keyword(hit.getHit(), true, true, originalKeyword.getListName(), originalKeyword.getOriginalTerm()); + List hitsForKeyword = results.getResults(keywordInstance); + if (hitsForKeyword == null) { + hitsForKeyword = new ArrayList<>(); + results.addResult(keywordInstance, hitsForKeyword); + } + hitsForKeyword.add(hit); } - } catch (TskCoreException ex) { + } catch (TskCoreException ex) { LOGGER.log(Level.SEVERE, "Error creating keyword hits", ex); //NON-NLS } } @@ -218,10 +220,7 @@ final class RegexQuery implements KeywordSearchQuery { MessageNotifyUtil.Notify.error(NbBundle.getMessage(Server.class, "Server.query.exception.msg", keywordString), ex.getCause().getMessage()); } } - QueryResults results = new QueryResults(this); - for (Keyword k : hitsMultiMap.keySet()) { - results.addResult(k, hitsMultiMap.get(k)); - } + return results; } @@ -284,8 +283,8 @@ final class RegexQuery implements KeywordSearchQuery { } /* - * If searching for credit card account numbers, do a Luhn check - * on the term and discard it if it does not pass. + * If searching for credit card account numbers, do a Luhn + * check on the term and discard it if it does not pass. */ if (originalKeyword.getArtifactAttributeType() == BlackboardAttribute.ATTRIBUTE_TYPE.TSK_CARD_NUMBER) { Matcher ccnMatcher = CREDIT_CARD_NUM_PATTERN.matcher(hit); @@ -316,10 +315,14 @@ final class RegexQuery implements KeywordSearchQuery { } catch (TskCoreException ex) { throw ex; } catch (Throwable error) { - /* NOTE: Matcher.find() is known to throw StackOverflowError in rare cases (see JIRA-2700). - StackOverflowError is an error, not an exception, and therefore needs to be caught - as a Throwable. When this occurs we should re-throw the error as TskCoreException so that it is - logged by the calling method and move on to the next Solr document. */ + /* + * NOTE: Matcher.find() is known to throw StackOverflowError in rare + * cases (see JIRA-2700). StackOverflowError is an error, not an + * exception, and therefore needs to be caught as a Throwable. When + * this occurs we should re-throw the error as TskCoreException so + * that it is logged by the calling method and move on to the next + * Solr document. + */ throw new TskCoreException("Failed to create keyword hits for Solr document id " + docId + " due to " + error.getMessage()); } return hits; @@ -370,50 +373,15 @@ final class RegexQuery implements KeywordSearchQuery { return escapedQuery; } - /** - * Get a unique, comma separated list of document ids that match the given - * hit for the same object. - * - * @param keyword The keyword object that resulted in one or more hits. - * @param hit The specific hit for which we want to identify all other - * chunks that match the keyword - * - * @return A comma separated list of unique document ids. - */ - private String getDocumentIds(Keyword keyword, KeywordHit hit) { - Set documentIds = new HashSet<>(); - - for (KeywordHit h : hitsMultiMap.get(keyword)) { - // Add the document id only if it is for the same object as the - // given hit and we haven't already seen it. - if (h.getSolrObjectId() == hit.getSolrObjectId() && !documentIds.contains(h.getSolrDocumentId())) { - documentIds.add(h.getSolrDocumentId()); - } - } - - return StringUtils.join(documentIds, ","); - } - - /** - * Converts the keyword hits for a given search term into artifacts. - * - * @param foundKeyword The keyword that was found by the regex search. - * @param hit The keyword hit. - * @param snippet The document snippet that contains the hit - * @param listName The name of the keyword list that contained the - * keyword for which the hit was found. - * - * - * - * @return An object that wraps an artifact and a mapping by id of its - * attributes. - */ - // TODO: Are we actually making meaningful use of the KeywordCachedArtifact - // class? @Override - public KeywordCachedArtifact writeSingleFileHitsToBlackBoard(Keyword foundKeyword, KeywordHit hit, String snippet, String listName) { + public BlackboardArtifact writeSingleFileHitsToBlackBoard(Content content, Keyword foundKeyword, KeywordHit hit, String snippet, String listName) { final String MODULE_NAME = KeywordSearchModuleFactory.getModuleName(); + if (content == null) { + LOGGER.log(Level.WARNING, "Error adding artifact for keyword hit to blackboard"); //NON-NLS + return null; + } + /* * Create either a "plain vanilla" keyword hit artifact with keyword and * regex attributes, or a credit card account artifact with attributes @@ -426,8 +394,7 @@ final class RegexQuery implements KeywordSearchQuery { attributes.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_KEYWORD, MODULE_NAME, foundKeyword.getSearchTerm())); attributes.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_KEYWORD_REGEXP, MODULE_NAME, getQueryString())); try { - newArtifact = hit.getContent().newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_KEYWORD_HIT); - + newArtifact = content.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_KEYWORD_HIT); } catch (TskCoreException ex) { LOGGER.log(Level.SEVERE, "Error adding artifact for keyword hit to blackboard", ex); //NON-NLS return null; @@ -452,7 +419,7 @@ final class RegexQuery implements KeywordSearchQuery { if (hit.isArtifactHit()) { LOGGER.log(Level.SEVERE, String.format("Failed to parse credit card account number for artifact keyword hit: term = %s, snippet = '%s', artifact id = %d", foundKeyword.getSearchTerm(), hit.getSnippet(), hit.getArtifact().getArtifactID())); //NON-NLS } else { - LOGGER.log(Level.SEVERE, String.format("Failed to parse credit card account number for content keyword hit: term = %s, snippet = '%s', object id = %d", foundKeyword.getSearchTerm(), hit.getSnippet(), hit.getContent().getId())); //NON-NLS + LOGGER.log(Level.SEVERE, String.format("Failed to parse credit card account number for content keyword hit: term = %s, snippet = '%s', object id = %d", foundKeyword.getSearchTerm(), hit.getSnippet(), hit.getContentID())); //NON-NLS } return null; } @@ -488,8 +455,8 @@ final class RegexQuery implements KeywordSearchQuery { * document id to support showing just the chunk that contained the * hit. */ - if (hit.getContent() instanceof AbstractFile) { - AbstractFile file = (AbstractFile) hit.getContent(); + if (content instanceof AbstractFile) { + AbstractFile file = (AbstractFile) content; if (file.getType() == TskData.TSK_DB_FILES_TYPE_ENUM.UNUSED_BLOCKS || file.getType() == TskData.TSK_DB_FILES_TYPE_ENUM.UNALLOC_BLOCKS) { attributes.add(new BlackboardAttribute(KEYWORD_SEARCH_DOCUMENT_ID, MODULE_NAME, hit.getSolrDocumentId())); @@ -500,7 +467,7 @@ final class RegexQuery implements KeywordSearchQuery { * Create an account artifact. */ try { - newArtifact = hit.getContent().newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_ACCOUNT); + newArtifact = content.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_ACCOUNT); } catch (TskCoreException ex) { LOGGER.log(Level.SEVERE, "Error adding artifact for account to blackboard", ex); //NON-NLS return null; @@ -521,9 +488,7 @@ final class RegexQuery implements KeywordSearchQuery { try { newArtifact.addAttributes(attributes); - KeywordCachedArtifact writeResult = new KeywordCachedArtifact(newArtifact); - writeResult.add(attributes); - return writeResult; + return newArtifact; } catch (TskCoreException e) { LOGGER.log(Level.SEVERE, "Error adding bb attributes for terms search artifact", e); //NON-NLS return null; @@ -570,6 +535,7 @@ final class RegexQuery implements KeywordSearchQuery { * @param groupName The group name of the regular expression that was * used to parse the attribute data. * @param matcher A matcher for the snippet. + */ static private void addAttributeIfNotAlreadyCaptured(Map attributeMap, BlackboardAttribute.ATTRIBUTE_TYPE attrType, String groupName, Matcher matcher) { BlackboardAttribute.Type type = new BlackboardAttribute.Type(attrType); diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/SearchRunner.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/SearchRunner.java index 1a2ffd1200..c9b95a770b 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/SearchRunner.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/SearchRunner.java @@ -19,7 +19,6 @@ package org.sleuthkit.autopsy.keywordsearch; import java.util.ArrayList; -import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; @@ -46,7 +45,6 @@ import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; import org.sleuthkit.autopsy.coreutils.StopWatch; import org.sleuthkit.autopsy.ingest.IngestMessage; import org.sleuthkit.autopsy.ingest.IngestServices; -import org.sleuthkit.datamodel.BlackboardArtifact; /** * Singleton keyword search manager: Launches search threads for each job and @@ -482,9 +480,7 @@ public final class SearchRunner { if (!newResults.getKeywords().isEmpty()) { // Write results to BB - //new artifacts created, to report to listeners - Collection newArtifacts = new ArrayList<>(); - + //scale progress bar more more granular, per result sub-progress, within per keyword int totalUnits = newResults.getKeywords().size(); subProgresses[keywordsSearched].start(totalUnits); @@ -496,7 +492,7 @@ public final class SearchRunner { subProgresses[keywordsSearched].progress(keywordList.getName() + ": " + queryDisplayStr, unitProgress); // Create blackboard artifacts - newArtifacts = newResults.writeAllHitsToBlackBoard(null, subProgresses[keywordsSearched], this, keywordList.getIngestMessages()); + newResults.writeAllHitsToBlackBoard(null, subProgresses[keywordsSearched], this, keywordList.getIngestMessages()); } //if has results diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Server.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Server.java index 3a55aad646..c2072cfa54 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Server.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Server.java @@ -824,7 +824,7 @@ public class Server { return new Core(coreName, theCase.getCaseType(), index); - } catch (SolrServerException | SolrException | IOException ex) { + } catch (Exception ex) { throw new KeywordSearchModuleException(NbBundle.getMessage(this.getClass(), "Server.openCore.exception.cantOpen.msg"), ex); } } @@ -1231,6 +1231,8 @@ public class Server { // the server to access a core needs to be built from a URL with the // core in it, and is only good for core-specific operations private final HttpSolrServer solrCore; + + private final int QUERY_TIMEOUT_MILLISECONDS = 86400000; // 24 Hours = 86,400,000 Milliseconds private Core(String name, CaseType caseType, Index index) { this.name = name; @@ -1240,7 +1242,8 @@ public class Server { this.solrCore = new HttpSolrServer(currentSolrServer.getBaseURL() + "/" + name); //NON-NLS //TODO test these settings - //solrCore.setSoTimeout(1000 * 60); // socket read timeout, make large enough so can index larger files + // socket read timeout, make large enough so can index larger files + solrCore.setSoTimeout(QUERY_TIMEOUT_MILLISECONDS); //solrCore.setConnectionTimeout(1000); solrCore.setDefaultMaxConnectionsPerHost(2); solrCore.setMaxTotalConnections(5); diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/TermsComponentQuery.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/TermsComponentQuery.java index f4413e3f2a..62c7dd601d 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/TermsComponentQuery.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/TermsComponentQuery.java @@ -42,6 +42,7 @@ import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE; import org.sleuthkit.datamodel.BlackboardAttribute; import org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE; +import org.sleuthkit.datamodel.Content; import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.datamodel.TskData; @@ -96,7 +97,7 @@ final class TermsComponentQuery implements KeywordSearchQuery { + "(?:\\?" // end sentinal: ? //NON-NLS + "(?.)" //longitudinal redundancy check //NON-NLS + "?)?)?)?)?)?");//close nested optional groups //NON-NLS - static final Pattern CREDIT_CARD_TRACK2_PATTERN = Pattern.compile( + static final Pattern CREDIT_CARD_TRACK2_PATTERN = Pattern.compile( /* * Track 2 is numeric plus six punctuation symbolls :;<=>? * @@ -115,7 +116,7 @@ final class TermsComponentQuery implements KeywordSearchQuery { + "(?:[:;<=>?]" //end sentinel //NON-NLS + "(?.)" //longitudinal redundancy check //NON-NLS + "?)?)?)?)?)?"); //close nested optional groups //NON-NLS - static final BlackboardAttribute.Type KEYWORD_SEARCH_DOCUMENT_ID = new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_KEYWORD_SEARCH_DOCUMENT_ID); + static final BlackboardAttribute.Type KEYWORD_SEARCH_DOCUMENT_ID = new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_KEYWORD_SEARCH_DOCUMENT_ID); /** * Constructs an object that implements a regex query that will be performed @@ -315,27 +316,12 @@ final class TermsComponentQuery implements KeywordSearchQuery { } results.addResult(new Keyword(term.getTerm(), false, true, originalKeyword.getListName(), originalKeyword.getOriginalTerm()), new ArrayList<>(termHits)); } - return results; + return results; } - /** - * Converts the keyword hits for a given search term into artifacts. - * - * @param foundKeyword The keyword that was found by the search. - * @param hit The keyword hit. - * @param snippet The document snippet that contains the hit - * @param listName The name of the keyword list that contained the keyword - * for which the hit was found. - * - * - * - * @return An object that wraps an artifact and a mapping by id of its - * attributes. - */ - // TODO: Are we actually making meaningful use of the KeywordCachedArtifact - // class? + @Override - public KeywordCachedArtifact writeSingleFileHitsToBlackBoard(Keyword foundKeyword, KeywordHit hit, String snippet, String listName) { + public BlackboardArtifact writeSingleFileHitsToBlackBoard(Content content, Keyword foundKeyword, KeywordHit hit, String snippet, String listName) { /* * Create either a "plain vanilla" keyword hit artifact with keyword and * regex attributes, or a credit card account artifact with attributes @@ -347,9 +333,9 @@ final class TermsComponentQuery implements KeywordSearchQuery { if (originalKeyword.getArtifactAttributeType() != ATTRIBUTE_TYPE.TSK_CARD_NUMBER) { attributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_KEYWORD, MODULE_NAME, foundKeyword.getSearchTerm())); attributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_KEYWORD_REGEXP, MODULE_NAME, originalKeyword.getSearchTerm())); - + try { - newArtifact = hit.getContent().newArtifact(ARTIFACT_TYPE.TSK_KEYWORD_HIT); + newArtifact = content.newArtifact(ARTIFACT_TYPE.TSK_KEYWORD_HIT); } catch (TskCoreException ex) { LOGGER.log(Level.SEVERE, "Error adding artifact for keyword hit to blackboard", ex); //NON-NLS @@ -375,7 +361,7 @@ final class TermsComponentQuery implements KeywordSearchQuery { if (hit.isArtifactHit()) { LOGGER.log(Level.SEVERE, String.format("Failed to parse credit card account number for artifact keyword hit: term = %s, snippet = '%s', artifact id = %d", searchTerm, hit.getSnippet(), hit.getArtifact().getArtifactID())); //NON-NLS } else { - LOGGER.log(Level.SEVERE, String.format("Failed to parse credit card account number for content keyword hit: term = %s, snippet = '%s', object id = %d", searchTerm, hit.getSnippet(), hit.getContent().getId())); //NON-NLS + LOGGER.log(Level.SEVERE, String.format("Failed to parse credit card account number for content keyword hit: term = %s, snippet = '%s', object id = %d", searchTerm, hit.getSnippet(), hit.getContentID())); //NON-NLS } return null; } @@ -411,8 +397,8 @@ final class TermsComponentQuery implements KeywordSearchQuery { * document id to support showing just the chunk that contained the * hit. */ - if (hit.getContent() instanceof AbstractFile) { - AbstractFile file = (AbstractFile) hit.getContent(); + if (content instanceof AbstractFile) { + AbstractFile file = (AbstractFile)content; if (file.getType() == TskData.TSK_DB_FILES_TYPE_ENUM.UNUSED_BLOCKS || file.getType() == TskData.TSK_DB_FILES_TYPE_ENUM.UNALLOC_BLOCKS) { attributes.add(new BlackboardAttribute(KEYWORD_SEARCH_DOCUMENT_ID, MODULE_NAME, hit.getSolrDocumentId())); @@ -423,7 +409,7 @@ final class TermsComponentQuery implements KeywordSearchQuery { * Create an account artifact. */ try { - newArtifact = hit.getContent().newArtifact(ARTIFACT_TYPE.TSK_ACCOUNT); + newArtifact = content.newArtifact(ARTIFACT_TYPE.TSK_ACCOUNT); } catch (TskCoreException ex) { LOGGER.log(Level.SEVERE, "Error adding artifact for account to blackboard", ex); //NON-NLS return null; @@ -442,12 +428,10 @@ final class TermsComponentQuery implements KeywordSearchQuery { // TermsComponentQuery is now being used exclusively for substring searches. attributes.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_KEYWORD_SEARCH_TYPE, MODULE_NAME, KeywordSearch.QueryType.SUBSTRING.ordinal())); - + try { newArtifact.addAttributes(attributes); - KeywordCachedArtifact writeResult = new KeywordCachedArtifact(newArtifact); - writeResult.add(attributes); - return writeResult; + return newArtifact; } catch (TskCoreException e) { LOGGER.log(Level.SEVERE, "Error adding bb attributes for terms search artifact", e); //NON-NLS return null; diff --git a/build-windows-installer.xml b/build-windows-installer.xml index 1e608b1326..e3f0401632 100644 --- a/build-windows-installer.xml +++ b/build-windows-installer.xml @@ -170,7 +170,7 @@ - + diff --git a/build.xml b/build.xml index b0d244b119..685f285fda 100755 --- a/build.xml +++ b/build.xml @@ -92,7 +92,7 @@ - + diff --git a/nbproject/project.properties b/nbproject/project.properties index 8df7ff47a0..6293589ab0 100644 --- a/nbproject/project.properties +++ b/nbproject/project.properties @@ -4,7 +4,7 @@ app.title=Autopsy ### lowercase version of above app.name=${branding.token} ### if left unset, version will default to today's date -app.version=4.4.1 +app.version=4.4.2 ### build.type must be one of: DEVELOPMENT, RELEASE #build.type=RELEASE build.type=DEVELOPMENT @@ -16,7 +16,7 @@ update_versions=false #custom JVM options #Note: can be higher on 64 bit systems, should be in sync with build.xml # for Japanese version add: -J-Duser.language=ja -run.args.extra=-J-Xms24m -J-XX:MaxPermSize=128M -J-Xverify:none +run.args.extra=-J-Xms24m -J-XX:MaxPermSize=128M -J-Xverify:none -J-XX:+UseG1GC -J-XX:+UseStringDeduplication auxiliary.org-netbeans-modules-apisupport-installer.license-type=apache.v2 auxiliary.org-netbeans-modules-apisupport-installer.os-linux=false auxiliary.org-netbeans-modules-apisupport-installer.os-macosx=false