diff --git a/Core/src/org/sleuthkit/autopsy/communications/Bundle.properties b/Core/src/org/sleuthkit/autopsy/communications/Bundle.properties index af6d79d8ab..11b58aab80 100644 --- a/Core/src/org/sleuthkit/autopsy/communications/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/communications/Bundle.properties @@ -12,3 +12,5 @@ FiltersPanel.dateRangeLabel.text=Date Range: FiltersPanel.startCheckBox.text=Start: FiltersPanel.endCheckBox.text=End: FiltersPanel.refreshButton.text=Refresh +FiltersPanel.deviceRequiredLabel.text=Select at least one. +FiltersPanel.accountTypeRequiredLabel.text=Select at least one. diff --git a/Core/src/org/sleuthkit/autopsy/communications/FiltersPanel.form b/Core/src/org/sleuthkit/autopsy/communications/FiltersPanel.form index da402cb4e6..9ae8db86bb 100644 --- a/Core/src/org/sleuthkit/autopsy/communications/FiltersPanel.form +++ b/Core/src/org/sleuthkit/autopsy/communications/FiltersPanel.form @@ -87,9 +87,10 @@ - + - + + @@ -111,7 +112,10 @@ - + + + + @@ -166,6 +170,20 @@ + + + + + + + + + + + + + + @@ -176,6 +194,7 @@ + @@ -194,7 +213,10 @@ - + + + + @@ -261,6 +283,20 @@ + + + + + + + + + + + + + + diff --git a/Core/src/org/sleuthkit/autopsy/communications/FiltersPanel.java b/Core/src/org/sleuthkit/autopsy/communications/FiltersPanel.java index 93e3230576..44d46ceecd 100644 --- a/Core/src/org/sleuthkit/autopsy/communications/FiltersPanel.java +++ b/Core/src/org/sleuthkit/autopsy/communications/FiltersPanel.java @@ -19,6 +19,7 @@ package org.sleuthkit.autopsy.communications; import com.google.common.collect.ImmutableSet; +import java.awt.event.ItemListener; import java.beans.PropertyChangeListener; import java.time.LocalDate; import java.time.ZoneId; @@ -68,12 +69,25 @@ final public class FiltersPanel extends javax.swing.JPanel { @ThreadConfined(type = ThreadConfined.ThreadType.AWT) private final Map devicesMap = new HashMap<>(); + /** + * Listens to ingest events to enable refresh button + */ private final PropertyChangeListener ingestListener; + private boolean needsRefresh; + + /** + * Listen to check box state changes and validates that at least one box is + * selected for device and account type ( other wise there will be no + * results) + */ + private final ItemListener validationListener; @NbBundle.Messages({"refreshText=Refresh Results", "applyText=Apply"}) public FiltersPanel() { initComponents(); + deviceRequiredLabel.setVisible(false); + accountTypeRequiredLabel.setVisible(false); startDatePicker.setDate(LocalDate.now().minusWeeks(3)); endDatePicker.setDateToToday(); startDatePicker.getSettings().setVetoPolicy( @@ -88,7 +102,10 @@ final public class FiltersPanel extends javax.swing.JPanel { ); updateTimeZone(); + validationListener = itemEvent -> validateFilters(); + updateFilters(); + setAllDevicesSelected(true); UserPreferences.addChangeListener(preferenceChangeEvent -> { if (preferenceChangeEvent.getKey().equals(UserPreferences.DISPLAY_TIMES_IN_LOCAL_TIME)) { updateTimeZone(); @@ -99,7 +116,8 @@ final public class FiltersPanel extends javax.swing.JPanel { String eventType = pce.getPropertyName(); if (eventType.equals(DATA_ADDED.toString())) { updateFilters(); - refreshButton.setEnabled(true); + needsRefresh = true; + validateFilters(); } }; @@ -107,6 +125,24 @@ final public class FiltersPanel extends javax.swing.JPanel { refreshButton.addActionListener(e -> applyFilters()); } + /** + * Validate that filters are in a consistent state and will result in some + * results. Checks that at least one device and at least one account type is + * selected. Disables the apply and refresh button and shows warnings if the + * filters are not valid. + */ + private void validateFilters() { + boolean someDevice = devicesMap.values().stream().anyMatch(JCheckBox::isSelected); + boolean someAccountType = accountTypeMap.values().stream().anyMatch(JCheckBox::isSelected); + + deviceRequiredLabel.setVisible(someDevice == false); + accountTypeRequiredLabel.setVisible(someAccountType == false); + + applyFiltersButton.setEnabled(someDevice && someAccountType); + refreshButton.setEnabled(someDevice && someAccountType && needsRefresh); + + } + /** * Update the filter widgets, and apply them. */ @@ -171,6 +207,7 @@ final public class FiltersPanel extends javax.swing.JPanel { + "\"/>" + type.getDisplayName() + "", true ); + jCheckBox.addItemListener(validationListener); accountTypePane.add(jCheckBox); return jCheckBox; }); @@ -191,6 +228,7 @@ final public class FiltersPanel extends javax.swing.JPanel { //store the device id in the map, but display a datasource name in the UI. devicesMap.computeIfAbsent(dataSource.getDeviceId(), ds -> { final JCheckBox jCheckBox = new JCheckBox(dsName, false); + jCheckBox.addItemListener(validationListener); devicesPane.add(jCheckBox); return jCheckBox; }); @@ -203,7 +241,6 @@ final public class FiltersPanel extends javax.swing.JPanel { } } - /** * 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 @@ -217,9 +254,9 @@ final public class FiltersPanel extends javax.swing.JPanel { applyFiltersButton.setText(org.openide.util.NbBundle.getMessage(FiltersPanel.class, "FiltersPanel.applyFiltersButton.text")); // NOI18N applyFiltersButton.setPreferredSize(null); - filtersTitleLabel.setFont(new java.awt.Font("Tahoma", 0, 16)); // NOI18N filtersTitleLabel.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/communications/images/funnel.png"))); // NOI18N filtersTitleLabel.setText(org.openide.util.NbBundle.getMessage(FiltersPanel.class, "FiltersPanel.filtersTitleLabel.text")); // NOI18N + filtersTitleLabel.setFont(new java.awt.Font("Tahoma", 0, 16)); // NOI18N unCheckAllAccountTypesButton.setText(org.openide.util.NbBundle.getMessage(FiltersPanel.class, "FiltersPanel.unCheckAllAccountTypesButton.text")); // NOI18N unCheckAllAccountTypesButton.addActionListener(new java.awt.event.ActionListener() { @@ -241,15 +278,21 @@ final public class FiltersPanel extends javax.swing.JPanel { accountTypePane.setLayout(new javax.swing.BoxLayout(accountTypePane, javax.swing.BoxLayout.Y_AXIS)); jScrollPane3.setViewportView(accountTypePane); + accountTypeRequiredLabel.setForeground(new java.awt.Color(255, 0, 0)); + accountTypeRequiredLabel.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/images/error-icon-16.png"))); // NOI18N + accountTypeRequiredLabel.setText(org.openide.util.NbBundle.getMessage(FiltersPanel.class, "FiltersPanel.accountTypeRequiredLabel.text")); // NOI18N + accountTypeRequiredLabel.setHorizontalTextPosition(javax.swing.SwingConstants.LEFT); + javax.swing.GroupLayout jPanel2Layout = new javax.swing.GroupLayout(jPanel2); jPanel2.setLayout(jPanel2Layout); jPanel2Layout.setHorizontalGroup( jPanel2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, jPanel2Layout.createSequentialGroup() .addGroup(jPanel2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING) - .addGroup(javax.swing.GroupLayout.Alignment.LEADING, jPanel2Layout.createSequentialGroup() + .addGroup(jPanel2Layout.createSequentialGroup() .addComponent(accountTypesLabel) - .addGap(0, 0, Short.MAX_VALUE)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(accountTypeRequiredLabel)) .addGroup(jPanel2Layout.createSequentialGroup() .addContainerGap() .addGroup(jPanel2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING) @@ -264,7 +307,9 @@ final public class FiltersPanel extends javax.swing.JPanel { jPanel2Layout.setVerticalGroup( jPanel2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(jPanel2Layout.createSequentialGroup() - .addComponent(accountTypesLabel) + .addGroup(jPanel2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(accountTypesLabel) + .addComponent(accountTypeRequiredLabel)) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(jScrollPane3, javax.swing.GroupLayout.DEFAULT_SIZE, 245, Short.MAX_VALUE) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) @@ -297,13 +342,19 @@ final public class FiltersPanel extends javax.swing.JPanel { devicesPane.setLayout(new javax.swing.BoxLayout(devicesPane, javax.swing.BoxLayout.Y_AXIS)); jScrollPane2.setViewportView(devicesPane); + deviceRequiredLabel.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/images/error-icon-16.png"))); // NOI18N + deviceRequiredLabel.setText(org.openide.util.NbBundle.getMessage(FiltersPanel.class, "FiltersPanel.deviceRequiredLabel.text")); // NOI18N + deviceRequiredLabel.setForeground(new java.awt.Color(255, 0, 0)); + deviceRequiredLabel.setHorizontalTextPosition(javax.swing.SwingConstants.LEFT); + javax.swing.GroupLayout jPanel3Layout = new javax.swing.GroupLayout(jPanel3); jPanel3.setLayout(jPanel3Layout); jPanel3Layout.setHorizontalGroup( jPanel3Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(jPanel3Layout.createSequentialGroup() .addComponent(devicesLabel) - .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(deviceRequiredLabel)) .addGroup(jPanel3Layout.createSequentialGroup() .addContainerGap() .addGroup(jPanel3Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) @@ -317,7 +368,9 @@ final public class FiltersPanel extends javax.swing.JPanel { jPanel3Layout.setVerticalGroup( jPanel3Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(jPanel3Layout.createSequentialGroup() - .addComponent(devicesLabel) + .addGroup(jPanel3Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(devicesLabel) + .addComponent(deviceRequiredLabel)) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(jScrollPane2, javax.swing.GroupLayout.DEFAULT_SIZE, 96, Short.MAX_VALUE) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) @@ -430,7 +483,7 @@ final public class FiltersPanel extends javax.swing.JPanel { commsFilter.addAndFilter(getDateRangeFilter()); commsFilter.addAndFilter(new CommunicationsFilter.RelationshipTypeFilter( ImmutableSet.of(CALL_LOG, MESSAGE))); - + try { final CommunicationsManager commsManager = Case.getCurrentCase().getSleuthkitCase().getCommunicationsManager(); em.setRootContext(new AbstractNode(Children.create(new AccountsRootChildren(commsManager, commsFilter), true))); @@ -438,7 +491,8 @@ final public class FiltersPanel extends javax.swing.JPanel { logger.log(Level.SEVERE, "There was an error getting the CommunicationsManager for the current case.", ex); } - refreshButton.setEnabled(false); + needsRefresh = false; + validateFilters(); } /** @@ -449,9 +503,9 @@ final public class FiltersPanel extends javax.swing.JPanel { private DeviceFilter getDeviceFilter() { DeviceFilter deviceFilter = new DeviceFilter( devicesMap.entrySet().stream() - .filter(entry -> entry.getValue().isSelected()) - .map(Entry::getKey) - .collect(Collectors.toSet())); + .filter(entry -> entry.getValue().isSelected()) + .map(Entry::getKey) + .collect(Collectors.toSet())); return deviceFilter; } @@ -461,9 +515,11 @@ final public class FiltersPanel extends javax.swing.JPanel { * @return an AccountTypeFilter */ private AccountTypeFilter getAccountTypeFilter() { - AccountTypeFilter accountTypeFilter = new AccountTypeFilter(accountTypeMap.entrySet().stream() - .filter(entry -> entry.getValue().isSelected()) - .map(entry -> entry.getKey()).collect(Collectors.toSet())); + AccountTypeFilter accountTypeFilter = new AccountTypeFilter( + accountTypeMap.entrySet().stream() + .filter(entry -> entry.getValue().isSelected()) + .map(entry -> entry.getKey()) + .collect(Collectors.toSet())); return accountTypeFilter; } @@ -532,11 +588,13 @@ final public class FiltersPanel extends javax.swing.JPanel { // Variables declaration - do not modify//GEN-BEGIN:variables private final javax.swing.JPanel accountTypePane = new javax.swing.JPanel(); + private final javax.swing.JLabel accountTypeRequiredLabel = new javax.swing.JLabel(); private final javax.swing.JLabel accountTypesLabel = new javax.swing.JLabel(); private final javax.swing.JButton applyFiltersButton = new javax.swing.JButton(); private final javax.swing.JButton checkAllAccountTypesButton = new javax.swing.JButton(); private final javax.swing.JButton checkAllDevicesButton = new javax.swing.JButton(); private final javax.swing.JLabel dateRangeLabel = new javax.swing.JLabel(); + private final javax.swing.JLabel deviceRequiredLabel = new javax.swing.JLabel(); private final javax.swing.JLabel devicesLabel = new javax.swing.JLabel(); private final javax.swing.JPanel devicesPane = new javax.swing.JPanel(); private final javax.swing.JCheckBox endCheckBox = new javax.swing.JCheckBox(); diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/MessageContentViewer.java b/Core/src/org/sleuthkit/autopsy/contentviewers/MessageContentViewer.java index ec49948b78..df678a6635 100644 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/MessageContentViewer.java +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/MessageContentViewer.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2017 Basis Technology Corp. + * Copyright 2017 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -47,8 +47,10 @@ import org.sleuthkit.autopsy.directorytree.NewWindowViewAction; import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.BlackboardArtifact; import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_EMAIL_MSG; +import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_KEYWORD_HIT; import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_MESSAGE; import org.sleuthkit.datamodel.BlackboardAttribute; +import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ASSOCIATED_ARTIFACT; import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME; import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_RCVD; import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DIRECTION; @@ -73,6 +75,7 @@ public class MessageContentViewer extends javax.swing.JPanel implements DataCont private static final long serialVersionUID = 1L; private static final Logger LOGGER = Logger.getLogger(MessageContentViewer.class.getName()); + private static final BlackboardAttribute.Type TSK_ASSOCIATED_TYPE = new BlackboardAttribute.Type(TSK_ASSOCIATED_ARTIFACT); private static final int HDR_TAB_INDEX = 0; private static final int TEXT_TAB_INDEX = 1; @@ -92,9 +95,10 @@ public class MessageContentViewer extends javax.swing.JPanel implements DataCont /** * Creates new MessageContentViewer */ + @NbBundle.Messages("MessageContentViewer.AtrachmentsPanel.title=Attachments") public MessageContentViewer() { initComponents(); - drp = DataResultPanel.createInstanceUninitialized("Attachments", "", Node.EMPTY, 0, null); + drp = DataResultPanel.createInstanceUninitialized(Bundle.MessageContentViewer_AtrachmentsPanel_title(), "", Node.EMPTY, 0, null); attachmentsScrollPane.setViewportView(drp); msgbodyTabbedPane.setEnabledAt(ATTM_TAB_INDEX, true); @@ -106,8 +110,8 @@ public class MessageContentViewer extends javax.swing.JPanel implements DataCont drp.open(); drpExplorerManager = drp.getExplorerManager(); - drpExplorerManager.addPropertyChangeListener(evt - -> viewInNewWindowButton.setEnabled(drpExplorerManager.getSelectedNodes().length == 1)); + drpExplorerManager.addPropertyChangeListener(evt -> + viewInNewWindowButton.setEnabled(drpExplorerManager.getSelectedNodes().length == 1)); } /** @@ -406,6 +410,20 @@ public class MessageContentViewer extends javax.swing.JPanel implements DataCont return; } + /* + * If the artifact is a keyword hit, use the associated artifact as the + * one to show in this viewer + */ + if (artifact.getArtifactTypeID() == TSK_KEYWORD_HIT.getTypeID()) { + try { + getAssociatedArtifact(artifact).ifPresent(associatedArtifact -> { + artifact = associatedArtifact; + }); + } catch (TskCoreException ex) { + LOGGER.log(Level.SEVERE, "error getting associated artifact", ex); + } + } + if (artifact.getArtifactTypeID() == TSK_MESSAGE.getTypeID()) { displayMsg(); } else if (artifact.getArtifactTypeID() == TSK_EMAIL_MSG.getTypeID()) { @@ -415,6 +433,24 @@ public class MessageContentViewer extends javax.swing.JPanel implements DataCont } } + /** + * Get the artifact associated with the given artifact, if there is one. + * + * @param artifact The artifact to get the associated artifact from. Must + * not be null + * + * @throws TskCoreException If there is a critical error querying the DB. + * @return An optional containing the artifact associated with the given + * artifact, if there is one. + */ + private static Optional getAssociatedArtifact(final BlackboardArtifact artifact) throws TskCoreException { + BlackboardAttribute attribute = artifact.getAttribute(TSK_ASSOCIATED_TYPE); + if (attribute != null) { + return Optional.of(artifact.getSleuthkitCase().getArtifactByArtifactId(attribute.getValueLong())); + } + return Optional.empty(); + } + @Override @NbBundle.Messages("MessageContentViewer.title=Message") public String getTitle() { @@ -463,10 +499,37 @@ public class MessageContentViewer extends javax.swing.JPanel implements DataCont @Override public boolean isSupported(Node node) { - BlackboardArtifact artifact = node.getLookup().lookup(BlackboardArtifact.class); - return ((artifact != null) - && ((artifact.getArtifactTypeID() == TSK_EMAIL_MSG.getTypeID()) - || (artifact.getArtifactTypeID() == TSK_MESSAGE.getTypeID()))); + BlackboardArtifact nodeArtifact = node.getLookup().lookup(BlackboardArtifact.class); + + if (nodeArtifact == null) { + return false; + } + //if the artifact is a keyword hit, check if its associated artifact is a message or email. + if (nodeArtifact.getArtifactTypeID() == TSK_KEYWORD_HIT.getTypeID()) { + try { + if (getAssociatedArtifact(nodeArtifact).map(MessageContentViewer::isMessageArtifact).orElse(false)) { + return true; + } + } catch (TskCoreException ex) { + LOGGER.log(Level.SEVERE, "error getting associated artifact", ex); + } + } + return isMessageArtifact(nodeArtifact); + } + + /** + * Is the given artifact one that can be shown in this viewer? + * + * @param nodeArtifact An artifact that might be a message. Must not be + * null. + * + * @return True if the given artifact can be shown as a message in this + * viewer. + */ + private static boolean isMessageArtifact(BlackboardArtifact nodeArtifact) { + final int artifactTypeID = nodeArtifact.getArtifactTypeID(); + return artifactTypeID == TSK_EMAIL_MSG.getTypeID() + || artifactTypeID == TSK_MESSAGE.getTypeID(); } @Override diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/ExtractedContentViewer.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/ExtractedContentViewer.java index 315066cbbe..dbed68e660 100755 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/ExtractedContentViewer.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/ExtractedContentViewer.java @@ -34,6 +34,7 @@ import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.corecomponentinterfaces.DataContentViewer; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.datamodel.AbstractFile; +import org.sleuthkit.datamodel.Account; import org.sleuthkit.datamodel.BlackboardArtifact; import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_ACCOUNT; import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_KEYWORD_HIT; @@ -53,6 +54,7 @@ public class ExtractedContentViewer implements DataContentViewer { private static final long INVALID_DOCUMENT_ID = 0L; private static final BlackboardAttribute.Type TSK_ASSOCIATED_ARTIFACT_TYPE = new BlackboardAttribute.Type(TSK_ASSOCIATED_ARTIFACT); + public static final BlackboardAttribute.Type TSK_ACCOUNT_TYPE = new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ACCOUNT_TYPE); private ExtractedContentPanel panel; private volatile Node currentNode = null; @@ -173,14 +175,13 @@ public class ExtractedContentViewer implements DataContentViewer { } } panel.updateControls(currentSource); - + String contentName = ""; if (content != null) { contentName = content.getName(); } setPanel(contentName, sources); - - + } static private IndexedText getRawArtifactText(Lookup nodeLookup) throws TskCoreException { @@ -261,7 +262,7 @@ public class ExtractedContentViewer implements DataContentViewer { @Override public void resetComponent() { - + panel.resetDisplay(); currentNode = null; currentSource = null; @@ -274,14 +275,22 @@ public class ExtractedContentViewer implements DataContentViewer { } /* - * Is there a credit card artifact in the lookup + * Is there a credit card or keyword hit artifact in the lookup */ Collection artifacts = node.getLookup().lookupAll(BlackboardArtifact.class); if (artifacts != null) { for (BlackboardArtifact art : artifacts) { final int artifactTypeID = art.getArtifactTypeID(); - if (artifactTypeID == TSK_ACCOUNT.getTypeID() - || artifactTypeID == TSK_KEYWORD_HIT.getTypeID()) { + if (artifactTypeID == TSK_ACCOUNT.getTypeID()) { + try { + BlackboardAttribute attribute = art.getAttribute(TSK_ACCOUNT_TYPE); + if (attribute != null && Account.Type.CREDIT_CARD.getTypeName().equals(attribute.getValueString())) { + return true; + } + } catch (TskCoreException ex) { + logger.log(Level.SEVERE, "Error getting TSK_ACCOUNT_TYPE attribute from artifact " + art.getArtifactID(), ex); + } + } else if (artifactTypeID == TSK_KEYWORD_HIT.getTypeID()) { return true; } }