From 2ec73212d45bf6b158e7ad13f7c010081e5e7d11 Mon Sep 17 00:00:00 2001 From: Ann Priestman Date: Tue, 12 Sep 2017 09:57:39 -0400 Subject: [PATCH 01/14] Known bad can be automatically added when setting a tag to BAD --- .../datamodel/AbstractSqlEamDb.java | 45 +++++++++++- .../datamodel/EamArtifactUtil.java | 70 +++++++++++++++++++ .../centralrepository/datamodel/EamDb.java | 9 +++ .../datamodel/SqliteEamDb.java | 19 ++++- .../eventlisteners/CaseEventListener.java | 16 ++++- .../optionspanel/ManageTagsDialog.java | 45 ++++++++++++ 6 files changed, 198 insertions(+), 6 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/AbstractSqlEamDb.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/AbstractSqlEamDb.java index 475f6459d5..4792616d88 100755 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/AbstractSqlEamDb.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/AbstractSqlEamDb.java @@ -33,12 +33,13 @@ import java.time.LocalDate; import java.util.HashMap; import java.util.Map; import java.util.Set; -import java.util.logging.Level; -import javafx.animation.KeyValue; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.datamodel.TskData; +import org.sleuthkit.datamodel.BlackboardArtifactTag; +import org.sleuthkit.datamodel.TagName; +import org.sleuthkit.datamodel.ContentTag; /** * @@ -1063,7 +1064,8 @@ public abstract class AbstractSqlEamDb implements EamDb { /** * Sets an eamArtifact instance as knownStatus = "Bad". If eamArtifact - * exists, it is updated. If eamArtifact does not exist nothing happens + * exists, it is updated. If eamArtifact does not exist it is added + * with knownStatus = "Bad" * * @param eamArtifact Artifact containing exactly one (1) ArtifactInstance. */ @@ -1149,6 +1151,43 @@ public abstract class AbstractSqlEamDb implements EamDb { EamDbUtil.closeConnection(conn); } } + + /** + * Set knownBad status for all files/artifacts in the given case that + * are tagged with the given tag name. + * Files/artifacts that are not already in the database will be added. + * @param tagName The name of the tag to search for + * @param curCase The case to search in + */ + @Override + public void setArtifactsKnownBadByTag(String tagNameString, Case curCase) throws EamDbException{ + try{ + TagName tagName = curCase.getServices().getTagsManager().getDisplayNamesToTagNamesMap().get(tagNameString); + + // First find any matching artifacts + List artifactTags = curCase.getSleuthkitCase().getBlackboardArtifactTagsByTagName(tagName); + System.out.println("\n####### There are " + artifactTags.size() + " matching artifact tags for tag " + tagNameString); + + for(BlackboardArtifactTag bbTag:artifactTags){ + List convertedArtifacts = EamArtifactUtil.fromBlackboardArtifact(bbTag.getArtifact(), true, getCorrelationTypes(), true); + for (EamArtifact eamArtifact : convertedArtifacts) { + setArtifactInstanceKnownBad(eamArtifact); + } + } + + // Now search for files + List fileTags = curCase.getSleuthkitCase().getContentTagsByTagName(tagName); + System.out.println("\n####### There are " + fileTags.size() + " matching file tags for tag " + tagNameString); + for(ContentTag contentTag:fileTags){ + final EamArtifact eamArtifact = EamArtifactUtil.getEamArtifactFromContent(contentTag.getContent(), + TskData.FileKnown.BAD, ""); + setArtifactInstanceKnownBad(eamArtifact); + } + } catch (Exception ex){ + // fix fix + } + + } /** * Gets list of matching eamArtifact instances that have knownStatus = diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/EamArtifactUtil.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/EamArtifactUtil.java index a582418911..670067ac0a 100755 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/EamArtifactUtil.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/EamArtifactUtil.java @@ -27,6 +27,7 @@ import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.datamodel.AbstractFile; 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; import org.sleuthkit.datamodel.TskDataException; @@ -192,4 +193,73 @@ public class EamArtifactUtil { return null; } } + + /** + * Create an EamArtifact from the given Content. + * Will return null if an artifact can not be created. Does not + * add the artifact to the database. + * + * @param content The content object + * @param knownStatus Unknown, known bad, or known + * @param comment The comment for the new artifact (generally used for a tag comment) + * @return The new EamArtifact or null if creation failed + */ + public static EamArtifact getEamArtifactFromContent(Content content, TskData.FileKnown knownStatus, String comment){ + + if(! (content instanceof AbstractFile)){ + return null; + } + + final AbstractFile af = (AbstractFile) content; + + if ((af.getType() == TskData.TSK_DB_FILES_TYPE_ENUM.UNALLOC_BLOCKS) + || (af.getType() == TskData.TSK_DB_FILES_TYPE_ENUM.UNUSED_BLOCKS) + || (af.getType() == TskData.TSK_DB_FILES_TYPE_ENUM.SLACK) + || (af.getKnown() == TskData.FileKnown.KNOWN) + || (af.isDir() == true) + || (!af.isMetaFlagSet(TskData.TSK_FS_META_FLAG_ENUM.ALLOC))) { + return null; + } + + String dsName; + try { + dsName = af.getDataSource().getName(); + } catch (TskCoreException ex) { + LOGGER.log(Level.SEVERE, "Error, unable to get name of data source from abstract file.", ex); + return null; + } + + // We need a hash to make the artifact + String md5 = af.getMd5Hash(); + if (md5 == null || md5.isEmpty()) { + return null; + } + + String deviceId; + try { + deviceId = Case.getCurrentCase().getSleuthkitCase().getDataSource(af.getDataSource().getId()).getDeviceId(); + } catch (TskCoreException | TskDataException ex) { + LOGGER.log(Level.SEVERE, "Error, failed to get deviceID or data source from current case.", ex); + return null; + } + + EamArtifact eamArtifact; + try { + EamArtifact.Type filesType = EamDb.getInstance().getCorrelationTypeById(EamArtifact.FILES_TYPE_ID); + eamArtifact = new EamArtifact(filesType, af.getMd5Hash()); + EamArtifactInstance cei = new EamArtifactInstance( + new EamCase(Case.getCurrentCase().getName(), Case.getCurrentCase().getDisplayName()), + new EamDataSource(deviceId, dsName), + af.getParentPath() + af.getName(), + comment, + TskData.FileKnown.BAD, + EamArtifactInstance.GlobalStatus.LOCAL + ); + eamArtifact.addInstance(cei); + return eamArtifact; + } catch (EamDbException ex) { + LOGGER.log(Level.SEVERE, "Error, unable to get FILES correlation type.", ex); + return null; + } + } } diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/EamDb.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/EamDb.java index 4d89314ffe..f6df682910 100755 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/EamDb.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/EamDb.java @@ -326,6 +326,15 @@ public interface EamDb { */ void setArtifactInstanceKnownBad(EamArtifact eamArtifact) throws EamDbException; + /** + * Set knownBad status for all files/artifacts in the given case that + * are tagged with the given tag name. + * Files/artifacts that are not already in the database will be added. + * @param tagName The name of the tag to search for + * @param curCase The case to search in + */ + void setArtifactsKnownBadByTag(String tagNameString, Case curCase) throws EamDbException; + /** * Gets list of matching eamArtifact instances that have knownStatus = * "Bad". diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/SqliteEamDb.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/SqliteEamDb.java index 01d2083af4..4d96c22910 100755 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/SqliteEamDb.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/SqliteEamDb.java @@ -599,7 +599,24 @@ public class SqliteEamDb extends AbstractSqlEamDb { } finally { releaseExclusiveLock(); } - } + } + + /** + * Set knownBad status for all files/artifacts in the given case that + * are tagged with the given tag name. + * Files/artifacts that are not already in the database will be added. + * @param tagName The name of the tag to search for + * @param curCase The case to search in + */ + @Override + public void setArtifactsKnownBadByTag(String tagNameString, Case curCase) throws EamDbException{ + try{ + acquireExclusiveLock(); + super.setArtifactsKnownBadByTag(tagNameString, curCase); + } finally { + releaseExclusiveLock(); + } + } /** * Gets list of matching eamArtifact instances that have knownStatus = diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/eventlisteners/CaseEventListener.java b/Core/src/org/sleuthkit/autopsy/centralrepository/eventlisteners/CaseEventListener.java index 7c4201012f..8a07143e68 100755 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/eventlisteners/CaseEventListener.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/eventlisteners/CaseEventListener.java @@ -75,9 +75,21 @@ public class CaseEventListener implements PropertyChangeListener { final ContentTagAddedEvent tagAddedEvent = (ContentTagAddedEvent) evt; final ContentTag tagAdded = tagAddedEvent.getAddedTag(); // TODO: detect failed cast and break if so. - final AbstractFile af = (AbstractFile) tagAdded.getContent(); + //final AbstractFile af = (AbstractFile) tagAdded.getContent(); final TagName tagName = tagAdded.getName(); + + if (dbManager.getBadTags().contains(tagName.getDisplayName())) { + final EamArtifact eamArtifact = EamArtifactUtil.getEamArtifactFromContent(tagAdded.getContent(), + TskData.FileKnown.BAD, tagAdded.getComment()); + // send update to Central Repository db + Runnable r = new BadFileTagRunner(eamArtifact); + // TODO: send r into a thread pool instead + Thread t = new Thread(r); + t.start(); + } + + /* if ((af.getType() == TskData.TSK_DB_FILES_TYPE_ENUM.UNALLOC_BLOCKS) || (af.getType() == TskData.TSK_DB_FILES_TYPE_ENUM.UNUSED_BLOCKS) || (af.getType() == TskData.TSK_DB_FILES_TYPE_ENUM.SLACK) @@ -129,7 +141,7 @@ public class CaseEventListener implements PropertyChangeListener { } catch (EamDbException ex) { LOGGER.log(Level.SEVERE, "Error, unable to get FILES correlation type during CONTENT_TAG_ADDED event.", ex); } - } + }*/ } // CONTENT_TAG_ADDED break; diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/ManageTagsDialog.java b/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/ManageTagsDialog.java index a5c0b87b1b..b42be9bd06 100755 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/ManageTagsDialog.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/ManageTagsDialog.java @@ -27,8 +27,12 @@ import java.util.logging.Level; import java.util.stream.Collectors; import javax.swing.JFrame; import javax.swing.table.DefaultTableModel; +import javax.swing.event.TableModelEvent; +import javax.swing.event.TableModelListener; +import javax.swing.JOptionPane; import org.openide.util.NbBundle.Messages; import org.openide.windows.WindowManager; +import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.services.TagsManager; import org.sleuthkit.autopsy.centralrepository.datamodel.EamDb; import org.sleuthkit.autopsy.centralrepository.datamodel.EamDbException; @@ -92,6 +96,8 @@ final class ManageTagsDialog extends javax.swing.JDialog { boolean enabled = badTags.contains(tagName); model.addRow(new Object[]{tagName, enabled}); } + CheckBoxModelListener listener = new CheckBoxModelListener(); + model.addTableModelListener(listener); } private void display() { @@ -230,6 +236,45 @@ final class ManageTagsDialog extends javax.swing.JDialog { } return true; } + + public class CheckBoxModelListener implements TableModelListener { + + @Override + public void tableChanged(TableModelEvent e) { + int row = e.getFirstRow(); + int column = e.getColumn(); + if (column == 1) { + DefaultTableModel model = (DefaultTableModel) e.getSource(); + String columnName = model.getColumnName(column); + String tagName = (String) model.getValueAt(row, 0); + Boolean checked = (Boolean) model.getValueAt(row, column); + if (checked) { + System.out.println(tagName + " " + columnName + ": " + true); + + + if(Case.isCaseOpen()){ + int dialogButton = JOptionPane.YES_NO_OPTION; + // The actual idea: Flag any files/artifacts that are already in the central repo and that match + // this thing? Or maye not that first part. However it already works + int dialogResult = JOptionPane.showConfirmDialog (null, "Tag the things??","Warning",dialogButton); + if(dialogResult == JOptionPane.YES_OPTION){ + try{ + EamDb.getInstance().setArtifactsKnownBadByTag(tagName, Case.getCurrentCase()); + } catch (Exception ex) { // fix fix fix + ex.printStackTrace(); + } + } + } else { + System.out.println(" No case open"); + } + + + } else { + System.out.println(tagName + " " + columnName + ": " + false); + } + } + } + } // Variables declaration - do not modify//GEN-BEGIN:variables private javax.swing.ButtonGroup buttonGroup1; From ef9168e3523e02142907605bc8684a691279b9ef Mon Sep 17 00:00:00 2001 From: Ann Priestman Date: Tue, 12 Sep 2017 14:41:34 -0400 Subject: [PATCH 02/14] Add dialog message. Cleanup. --- .../eventlisteners/CaseEventListener.java | 55 ------------------- .../optionspanel/ManageTagsDialog.java | 21 +++---- 2 files changed, 9 insertions(+), 67 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/eventlisteners/CaseEventListener.java b/Core/src/org/sleuthkit/autopsy/centralrepository/eventlisteners/CaseEventListener.java index 69311c65d1..55bb3f1e39 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/eventlisteners/CaseEventListener.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/eventlisteners/CaseEventListener.java @@ -30,11 +30,9 @@ import org.sleuthkit.autopsy.casemodule.events.BlackBoardArtifactTagDeletedEvent import org.sleuthkit.autopsy.casemodule.events.ContentTagAddedEvent; import org.sleuthkit.autopsy.casemodule.events.ContentTagDeletedEvent; import org.sleuthkit.autopsy.casemodule.events.DataSourceAddedEvent; -import org.sleuthkit.autopsy.casemodule.services.Services; import org.sleuthkit.autopsy.casemodule.services.TagsManager; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.centralrepository.datamodel.EamArtifact; -import org.sleuthkit.autopsy.centralrepository.datamodel.EamArtifactInstance; import org.sleuthkit.autopsy.centralrepository.datamodel.EamArtifactUtil; import org.sleuthkit.autopsy.centralrepository.datamodel.EamCase; import org.sleuthkit.autopsy.centralrepository.datamodel.EamDataSource; @@ -46,7 +44,6 @@ import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.BlackboardArtifactTag; import org.sleuthkit.datamodel.Content; import org.sleuthkit.datamodel.ContentTag; -import org.sleuthkit.datamodel.TagName; import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.datamodel.TskData; import org.sleuthkit.datamodel.TskDataException; @@ -150,58 +147,6 @@ public class CaseEventListener implements PropertyChangeListener { // TODO: send r into a thread pool instead Thread t = new Thread(r); t.start(); - - /* - if ((af.getType() == TskData.TSK_DB_FILES_TYPE_ENUM.UNALLOC_BLOCKS) - || (af.getType() == TskData.TSK_DB_FILES_TYPE_ENUM.UNUSED_BLOCKS) - || (af.getType() == TskData.TSK_DB_FILES_TYPE_ENUM.SLACK) - || (af.getKnown() == TskData.FileKnown.KNOWN) - || (af.isDir() == true) - || (!af.isMetaFlagSet(TskData.TSK_FS_META_FLAG_ENUM.ALLOC))) { - break; - } - - String dsName; - try { - dsName = af.getDataSource().getName(); - } catch (TskCoreException ex) { - LOGGER.log(Level.SEVERE, "Error, unable to get name of data source from abstract file during CONTENT_TAG_ADDED event.", ex); - return; - } - - String md5 = af.getMd5Hash(); - if (md5 == null || md5.isEmpty()) { - return; - } - String deviceId; - try { - deviceId = Case.getCurrentCase().getSleuthkitCase().getDataSource(af.getDataSource().getId()).getDeviceId(); - } catch (TskCoreException | TskDataException ex) { - LOGGER.log(Level.SEVERE, "Error, failed to get deviceID or data source from current case.", ex); - return; - } - - EamArtifact eamArtifact; - try { - EamArtifact.Type filesType = dbManager.getCorrelationTypeById(EamArtifact.FILES_TYPE_ID); - eamArtifact = new EamArtifact(filesType, af.getMd5Hash()); - EamArtifactInstance cei = new EamArtifactInstance( - new EamCase(Case.getCurrentCase().getName(), Case.getCurrentCase().getDisplayName()), - new EamDataSource(deviceId, dsName), - af.getParentPath() + af.getName(), - comment, - knownStatus, - EamArtifactInstance.GlobalStatus.LOCAL - ); - eamArtifact.addInstance(cei); - // send update to Central Repository db - Runnable r = new KnownStatusChangeRunner(eamArtifact, knownStatus); - // TODO: send r into a thread pool instead - Thread t = new Thread(r); - t.start(); - } catch (EamDbException ex) { - LOGGER.log(Level.SEVERE, "Error, unable to get FILES correlation type during CONTENT_TAG_ADDED/CONTENT_TAG_DELETED event.", ex); - } */ } // CONTENT_TAG_ADDED, CONTENT_TAG_DELETED break; diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/ManageTagsDialog.java b/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/ManageTagsDialog.java index b42be9bd06..a9e4dd0447 100755 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/ManageTagsDialog.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/ManageTagsDialog.java @@ -238,25 +238,28 @@ final class ManageTagsDialog extends javax.swing.JDialog { } public class CheckBoxModelListener implements TableModelListener { - +// For files/artifacts in the current case that are already tagged, + @Messages({"ManageTagsDialog.updateCurrentCase.msg=Mark as known bad any files/artifacts in the current case that have this tag?", + "ManageTagsDialog.updateCurrentCase.title=Update current case?"}) + @Override public void tableChanged(TableModelEvent e) { int row = e.getFirstRow(); int column = e.getColumn(); if (column == 1) { DefaultTableModel model = (DefaultTableModel) e.getSource(); - String columnName = model.getColumnName(column); String tagName = (String) model.getValueAt(row, 0); Boolean checked = (Boolean) model.getValueAt(row, column); if (checked) { - System.out.println(tagName + " " + columnName + ": " + true); - - if(Case.isCaseOpen()){ int dialogButton = JOptionPane.YES_NO_OPTION; // The actual idea: Flag any files/artifacts that are already in the central repo and that match // this thing? Or maye not that first part. However it already works - int dialogResult = JOptionPane.showConfirmDialog (null, "Tag the things??","Warning",dialogButton); + int dialogResult = JOptionPane.showConfirmDialog ( + null, + Bundle.ManageTagsDialog_updateCurrentCase_msg(), + Bundle.ManageTagsDialog_updateCurrentCase_title(), + dialogButton); if(dialogResult == JOptionPane.YES_OPTION){ try{ EamDb.getInstance().setArtifactsKnownBadByTag(tagName, Case.getCurrentCase()); @@ -264,13 +267,7 @@ final class ManageTagsDialog extends javax.swing.JDialog { ex.printStackTrace(); } } - } else { - System.out.println(" No case open"); } - - - } else { - System.out.println(tagName + " " + columnName + ": " + false); } } } From b8d60f663a83ab23054afc0f81fa055d335635ed Mon Sep 17 00:00:00 2001 From: Ann Priestman Date: Tue, 12 Sep 2017 14:58:52 -0400 Subject: [PATCH 03/14] Cleanup --- .../datamodel/AbstractSqlEamDb.java | 8 +++----- .../optionspanel/ManageTagsDialog.java | 17 +++++++++++------ 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/AbstractSqlEamDb.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/AbstractSqlEamDb.java index 79c1525553..b1516948b9 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/AbstractSqlEamDb.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/AbstractSqlEamDb.java @@ -1063,9 +1063,9 @@ public abstract class AbstractSqlEamDb implements EamDb { } /** - * Sets an eamArtifact instance as knownStatus = "Bad". If eamArtifact + * Sets an eamArtifact instance to the given knownStatus. If eamArtifact * exists, it is updated. If eamArtifact does not exist it is added - * with knownStatus = "Bad" + * with the given status. * * @param eamArtifact Artifact containing exactly one (1) ArtifactInstance. * @param FileKnown The status to change the artifact to @@ -1166,8 +1166,7 @@ public abstract class AbstractSqlEamDb implements EamDb { TagName tagName = curCase.getServices().getTagsManager().getDisplayNamesToTagNamesMap().get(tagNameString); // First find any matching artifacts - List artifactTags = curCase.getSleuthkitCase().getBlackboardArtifactTagsByTagName(tagName); - System.out.println("\n####### There are " + artifactTags.size() + " matching artifact tags for tag " + tagNameString); + List artifactTags = curCase.getSleuthkitCase().getBlackboardArtifactTagsByTagName(tagName); for(BlackboardArtifactTag bbTag:artifactTags){ List convertedArtifacts = EamArtifactUtil.fromBlackboardArtifact(bbTag.getArtifact(), true, getCorrelationTypes(), true); @@ -1178,7 +1177,6 @@ public abstract class AbstractSqlEamDb implements EamDb { // Now search for files List fileTags = curCase.getSleuthkitCase().getContentTagsByTagName(tagName); - System.out.println("\n####### There are " + fileTags.size() + " matching file tags for tag " + tagNameString); for(ContentTag contentTag:fileTags){ final EamArtifact eamArtifact = EamArtifactUtil.getEamArtifactFromContent(contentTag.getContent(), TskData.FileKnown.BAD, ""); diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/ManageTagsDialog.java b/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/ManageTagsDialog.java index a9e4dd0447..d11681bc7d 100755 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/ManageTagsDialog.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/ManageTagsDialog.java @@ -237,10 +237,14 @@ final class ManageTagsDialog extends javax.swing.JDialog { return true; } + /** + * If the user sets a tag to "Implies known bad", give them the option to update + * any existing tagged items (in the current case only) in the central repo. + */ public class CheckBoxModelListener implements TableModelListener { -// For files/artifacts in the current case that are already tagged, @Messages({"ManageTagsDialog.updateCurrentCase.msg=Mark as known bad any files/artifacts in the current case that have this tag?", - "ManageTagsDialog.updateCurrentCase.title=Update current case?"}) + "ManageTagsDialog.updateCurrentCase.title=Update current case?", + "ManageTagsDialog.updateCurrentCase.error=Error updating existing Central Repository entries"}) @Override public void tableChanged(TableModelEvent e) { @@ -251,10 +255,10 @@ final class ManageTagsDialog extends javax.swing.JDialog { String tagName = (String) model.getValueAt(row, 0); Boolean checked = (Boolean) model.getValueAt(row, column); if (checked) { + + // Don't do anything if there's no case open if(Case.isCaseOpen()){ int dialogButton = JOptionPane.YES_NO_OPTION; - // The actual idea: Flag any files/artifacts that are already in the central repo and that match - // this thing? Or maye not that first part. However it already works int dialogResult = JOptionPane.showConfirmDialog ( null, Bundle.ManageTagsDialog_updateCurrentCase_msg(), @@ -263,8 +267,9 @@ final class ManageTagsDialog extends javax.swing.JDialog { if(dialogResult == JOptionPane.YES_OPTION){ try{ EamDb.getInstance().setArtifactsKnownBadByTag(tagName, Case.getCurrentCase()); - } catch (Exception ex) { // fix fix fix - ex.printStackTrace(); + } catch (EamDbException ex) { + LOGGER.log(Level.SEVERE, "Failed to apply known bad status to current case", ex); + JOptionPane.showMessageDialog(null, Bundle.ManageTagsDialog_updateCurrentCase_error()); } } } From ea39eed2a9f41dd96e273657b2e965b1f8a05e45 Mon Sep 17 00:00:00 2001 From: Ann Priestman Date: Tue, 12 Sep 2017 15:06:00 -0400 Subject: [PATCH 04/14] Cleanup --- .../centralrepository/datamodel/AbstractSqlEamDb.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/AbstractSqlEamDb.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/AbstractSqlEamDb.java index b1516948b9..fadf162586 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/AbstractSqlEamDb.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/AbstractSqlEamDb.java @@ -40,6 +40,7 @@ import org.sleuthkit.datamodel.TskData; import org.sleuthkit.datamodel.BlackboardArtifactTag; import org.sleuthkit.datamodel.TagName; import org.sleuthkit.datamodel.ContentTag; +import org.sleuthkit.datamodel.TskCoreException; /** * @@ -1182,8 +1183,8 @@ public abstract class AbstractSqlEamDb implements EamDb { TskData.FileKnown.BAD, ""); setArtifactInstanceKnownStatus(eamArtifact, TskData.FileKnown.BAD); } - } catch (Exception ex){ - // fix fix + } catch (TskCoreException ex){ + throw new EamDbException("Error updating artifacts", ex); } } From f21b64a7b594c83b21ff8dc293ee2cbb93aee39a Mon Sep 17 00:00:00 2001 From: Richard Cordovano Date: Tue, 12 Sep 2017 18:33:24 -0400 Subject: [PATCH 05/14] Change auto ingest events channel name --- .../autopsy/experimental/autoingest/AutoIngestManager.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestManager.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestManager.java index d5632bbfa7..990df2da58 100755 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestManager.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestManager.java @@ -124,7 +124,7 @@ public final class AutoIngestManager extends Observable implements PropertyChang private static int DEFAULT_JOB_PRIORITY = 0; private static final String AUTO_INGEST_THREAD_NAME = "AIM-job-processing-%d"; private static final String LOCAL_HOST_NAME = NetworkUtils.getLocalHostName(); - private static final String EVENT_CHANNEL_NAME = "Auto-Ingest-Manager-Events"; + private static final String EVENT_CHANNEL_NAME = "Auto-Ingest-Events"; private static final Set EVENT_LIST = new HashSet<>(Arrays.asList(new String[]{ Event.JOB_STATUS_UPDATED.toString(), Event.JOB_COMPLETED.toString(), From e1b4b4abfa67840bc69e04ed0306466dbe33c078 Mon Sep 17 00:00:00 2001 From: Richard Cordovano Date: Tue, 12 Sep 2017 19:10:55 -0400 Subject: [PATCH 06/14] Fix undocumented format parameter warnings for message strings --- .../autopsy/experimental/autoingest/AutoIngestControlPanel.java | 1 + .../sleuthkit/autopsy/keywordsearch/ExtractedContentPanel.java | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestControlPanel.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestControlPanel.java index bdd6310064..d9b7bec40f 100755 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestControlPanel.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestControlPanel.java @@ -304,6 +304,7 @@ public final class AutoIngestControlPanel extends JPanel implements Observer { * text box. */ @Messages({ + "# {0} - case db status", "# {1} - search svc Status", "# {2} - coord svc Status", "# {3} - msg broker status", "AutoIngestControlPanel.tbServicesStatusMessage.Message=Case databases {0}, keyword search {1}, coordination {2}, messaging {3} ", "AutoIngestControlPanel.tbServicesStatusMessage.Message.Up=up", "AutoIngestControlPanel.tbServicesStatusMessage.Message.Down=down", diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/ExtractedContentPanel.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/ExtractedContentPanel.java index d9bcd47332..d63163ff61 100755 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/ExtractedContentPanel.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/ExtractedContentPanel.java @@ -637,7 +637,7 @@ class ExtractedContentPanel extends javax.swing.JPanel { } @Override - @NbBundle.Messages({"# 0 - Content name", + @NbBundle.Messages({"# {0} - Content name", "ExtractedContentPanel.SetMarkup.progress.loading=Loading text for {0}"}) protected String doInBackground() throws Exception { progress = ProgressHandle.createHandle(Bundle.ExtractedContentPanel_SetMarkup_progress_loading(contentName)); From 3d718485470c85a7fb8e3d21222d55f228bda567 Mon Sep 17 00:00:00 2001 From: Richard Cordovano Date: Tue, 12 Sep 2017 19:45:08 -0400 Subject: [PATCH 07/14] Change auto ingest events channel name --- .../autopsy/experimental/autoingest/AutoIngestManager.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestManager.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestManager.java index 990df2da58..d5632bbfa7 100755 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestManager.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestManager.java @@ -124,7 +124,7 @@ public final class AutoIngestManager extends Observable implements PropertyChang private static int DEFAULT_JOB_PRIORITY = 0; private static final String AUTO_INGEST_THREAD_NAME = "AIM-job-processing-%d"; private static final String LOCAL_HOST_NAME = NetworkUtils.getLocalHostName(); - private static final String EVENT_CHANNEL_NAME = "Auto-Ingest-Events"; + private static final String EVENT_CHANNEL_NAME = "Auto-Ingest-Manager-Events"; private static final Set EVENT_LIST = new HashSet<>(Arrays.asList(new String[]{ Event.JOB_STATUS_UPDATED.toString(), Event.JOB_COMPLETED.toString(), From 150d394d9f6f6c592e32211cb092048d6e7f42bb Mon Sep 17 00:00:00 2001 From: Ann Priestman Date: Wed, 13 Sep 2017 08:43:23 -0400 Subject: [PATCH 08/14] Refactoring --- .../datamodel/AbstractSqlEamDb.java | 39 ----------------- .../centralrepository/datamodel/EamDb.java | 9 ---- .../datamodel/SqliteEamDb.java | 17 -------- .../optionspanel/ManageTagsDialog.java | 43 ++++++++++++++++++- 4 files changed, 42 insertions(+), 66 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/AbstractSqlEamDb.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/AbstractSqlEamDb.java index fadf162586..1f5af1104c 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/AbstractSqlEamDb.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/AbstractSqlEamDb.java @@ -37,10 +37,6 @@ import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.datamodel.TskData; -import org.sleuthkit.datamodel.BlackboardArtifactTag; -import org.sleuthkit.datamodel.TagName; -import org.sleuthkit.datamodel.ContentTag; -import org.sleuthkit.datamodel.TskCoreException; /** * @@ -1153,41 +1149,6 @@ public abstract class AbstractSqlEamDb implements EamDb { EamDbUtil.closeConnection(conn); } } - - /** - * Set knownBad status for all files/artifacts in the given case that - * are tagged with the given tag name. - * Files/artifacts that are not already in the database will be added. - * @param tagName The name of the tag to search for - * @param curCase The case to search in - */ - @Override - public void setArtifactsKnownBadByTag(String tagNameString, Case curCase) throws EamDbException{ - try{ - TagName tagName = curCase.getServices().getTagsManager().getDisplayNamesToTagNamesMap().get(tagNameString); - - // First find any matching artifacts - List artifactTags = curCase.getSleuthkitCase().getBlackboardArtifactTagsByTagName(tagName); - - for(BlackboardArtifactTag bbTag:artifactTags){ - List convertedArtifacts = EamArtifactUtil.fromBlackboardArtifact(bbTag.getArtifact(), true, getCorrelationTypes(), true); - for (EamArtifact eamArtifact : convertedArtifacts) { - setArtifactInstanceKnownStatus(eamArtifact,TskData.FileKnown.BAD); - } - } - - // Now search for files - List fileTags = curCase.getSleuthkitCase().getContentTagsByTagName(tagName); - for(ContentTag contentTag:fileTags){ - final EamArtifact eamArtifact = EamArtifactUtil.getEamArtifactFromContent(contentTag.getContent(), - TskData.FileKnown.BAD, ""); - setArtifactInstanceKnownStatus(eamArtifact, TskData.FileKnown.BAD); - } - } catch (TskCoreException ex){ - throw new EamDbException("Error updating artifacts", ex); - } - - } /** * Gets list of matching eamArtifact instances that have knownStatus = diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/EamDb.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/EamDb.java index 41136f55a2..fff8bbf540 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/EamDb.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/EamDb.java @@ -327,15 +327,6 @@ public interface EamDb { * @param FileKnown The status to change the artifact to */ void setArtifactInstanceKnownStatus(EamArtifact eamArtifact, TskData.FileKnown knownStatus) throws EamDbException; - - /** - * Set knownBad status for all files/artifacts in the given case that - * are tagged with the given tag name. - * Files/artifacts that are not already in the database will be added. - * @param tagName The name of the tag to search for - * @param curCase The case to search in - */ - void setArtifactsKnownBadByTag(String tagNameString, Case curCase) throws EamDbException; /** * Gets list of matching eamArtifact instances that have knownStatus = diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/SqliteEamDb.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/SqliteEamDb.java index 3513b845a0..1e1bc7227e 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/SqliteEamDb.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/SqliteEamDb.java @@ -602,23 +602,6 @@ public class SqliteEamDb extends AbstractSqlEamDb { } } - /** - * Set knownBad status for all files/artifacts in the given case that - * are tagged with the given tag name. - * Files/artifacts that are not already in the database will be added. - * @param tagName The name of the tag to search for - * @param curCase The case to search in - */ - @Override - public void setArtifactsKnownBadByTag(String tagNameString, Case curCase) throws EamDbException{ - try{ - acquireExclusiveLock(); - super.setArtifactsKnownBadByTag(tagNameString, curCase); - } finally { - releaseExclusiveLock(); - } - } - /** * Gets list of matching eamArtifact instances that have knownStatus = * "Bad". diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/ManageTagsDialog.java b/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/ManageTagsDialog.java index d11681bc7d..f2473a4dfe 100755 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/ManageTagsDialog.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/ManageTagsDialog.java @@ -36,8 +36,14 @@ import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.services.TagsManager; import org.sleuthkit.autopsy.centralrepository.datamodel.EamDb; import org.sleuthkit.autopsy.centralrepository.datamodel.EamDbException; +import org.sleuthkit.autopsy.centralrepository.datamodel.EamArtifact; +import org.sleuthkit.autopsy.centralrepository.datamodel.EamArtifactUtil; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.datamodel.TskCoreException; +import org.sleuthkit.datamodel.BlackboardArtifactTag; +import org.sleuthkit.datamodel.TagName; +import org.sleuthkit.datamodel.ContentTag; +import org.sleuthkit.datamodel.TskData; /** * Instances of this class allow a user to select an existing hash database and @@ -266,7 +272,7 @@ final class ManageTagsDialog extends javax.swing.JDialog { dialogButton); if(dialogResult == JOptionPane.YES_OPTION){ try{ - EamDb.getInstance().setArtifactsKnownBadByTag(tagName, Case.getCurrentCase()); + setArtifactsKnownBadByTag(tagName, Case.getCurrentCase()); } catch (EamDbException ex) { LOGGER.log(Level.SEVERE, "Failed to apply known bad status to current case", ex); JOptionPane.showMessageDialog(null, Bundle.ManageTagsDialog_updateCurrentCase_error()); @@ -277,6 +283,41 @@ final class ManageTagsDialog extends javax.swing.JDialog { } } } + + /** + * Set knownBad status for all files/artifacts in the given case that + * are tagged with the given tag name. + * Files/artifacts that are not already in the database will be added. + * @param tagName The name of the tag to search for + * @param curCase The case to search in + */ + public void setArtifactsKnownBadByTag(String tagNameString, Case curCase) throws EamDbException{ + try{ + TagName tagName = curCase.getServices().getTagsManager().getDisplayNamesToTagNamesMap().get(tagNameString); + + // First find any matching artifacts + List artifactTags = curCase.getSleuthkitCase().getBlackboardArtifactTagsByTagName(tagName); + + for(BlackboardArtifactTag bbTag:artifactTags){ + List convertedArtifacts = EamArtifactUtil.fromBlackboardArtifact(bbTag.getArtifact(), true, + EamDb.getInstance().getCorrelationTypes(), true); + for (EamArtifact eamArtifact : convertedArtifacts) { + EamDb.getInstance().setArtifactInstanceKnownStatus(eamArtifact,TskData.FileKnown.BAD); + } + } + + // Now search for files + List fileTags = curCase.getSleuthkitCase().getContentTagsByTagName(tagName); + for(ContentTag contentTag:fileTags){ + final EamArtifact eamArtifact = EamArtifactUtil.getEamArtifactFromContent(contentTag.getContent(), + TskData.FileKnown.BAD, ""); + EamDb.getInstance().setArtifactInstanceKnownStatus(eamArtifact, TskData.FileKnown.BAD); + } + } catch (TskCoreException ex){ + throw new EamDbException("Error updating artifacts", ex); + } + + } // Variables declaration - do not modify//GEN-BEGIN:variables private javax.swing.ButtonGroup buttonGroup1; From 71170bc27e4b6b439ec6654f5326e1bcb415ab06 Mon Sep 17 00:00:00 2001 From: Ann Priestman Date: Wed, 13 Sep 2017 09:12:07 -0400 Subject: [PATCH 09/14] Update Manage Tags in the central repo docs --- docs/doxygen-user/central_repo.dox | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/doxygen-user/central_repo.dox b/docs/doxygen-user/central_repo.dox index c59732c9bd..66b446023a 100644 --- a/docs/doxygen-user/central_repo.dox +++ b/docs/doxygen-user/central_repo.dox @@ -68,6 +68,9 @@ and added to the list of Interesting Items. \image html central_repo_manage_tags.png +If a case is open, checking the Implies Known Bad checkbox will give you the option to add the known bad status to anything in the current case +that has already been tagged. For example, if you create a tag named "Alpha", tag a few items and then go into Manage Tags and check the box for the Alpha tag, you can optionally choose to have the status for those tagged items changed in the Central Repository. The effect is the same as if you had checked the box in Manage Tags before tagging the items. Note that data from any previous cases will not be changed. + By default there is a tag called "Evidence" as the only tag associated with this module. To associate one or more tag(s) with this module, check the Correlate box next to the tag name(s) and click OK. To create additional tags, use the Tags options panel. From 36e6ccb8a6c3dc7db44aa6af920f589db5bf155a Mon Sep 17 00:00:00 2001 From: "U-BASIS\\dgrove" Date: Wed, 13 Sep 2017 12:27:50 -0400 Subject: [PATCH 10/14] Additional attributes supported for node data. --- .../autoingest/AutoIngestControlPanel.java | 12 +- .../autoingest/AutoIngestDashboard.form | 11 +- .../autoingest/AutoIngestDashboard.java | 16 +- .../autoingest/AutoIngestJob.java | 148 ++--------- ...obNodeData.java => AutoIngestJobData.java} | 212 +++++++++++++--- ...n.java => AutoIngestJobDataException.java} | 6 +- .../autoingest/AutoIngestManager.java | 230 ++++++++++-------- .../autoingest/AutoIngestMonitor.java | 75 +++++- .../experimental/autoingest/Bundle.properties | 2 +- 9 files changed, 416 insertions(+), 296 deletions(-) rename Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/{AutoIngestJobNodeData.java => AutoIngestJobData.java} (68%) rename Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/{AutoIngestJobNodeDataException.java => AutoIngestJobDataException.java} (86%) diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestControlPanel.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestControlPanel.java index bdd6310064..6d27bac166 100755 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestControlPanel.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestControlPanel.java @@ -1076,7 +1076,7 @@ public final class AutoIngestControlPanel extends JPanel implements Observer { * @return True or fale. */ private boolean isLocalJob(AutoIngestJob job) { - return job.getNodeName().equals(LOCAL_HOST_NAME); + return job.getNodeData().getProcessingHost().equals(LOCAL_HOST_NAME); } /** @@ -1145,19 +1145,19 @@ public final class AutoIngestControlPanel extends JPanel implements Observer { tableModel.setRowCount(0); for (AutoIngestJob job : jobs) { AutoIngestJob.StageDetails status = job.getStageDetails(); - AutoIngestJobNodeData nodeData = job.getNodeData(); + AutoIngestJobData nodeData = job.getNodeData(); tableModel.addRow(new Object[]{ nodeData.getCaseName(), // CASE nodeData.getDataSourcePath().getFileName(), // DATA_SOURCE - job.getNodeName(), // HOST_NAME + job.getNodeData().getProcessingHost(), // HOST_NAME nodeData.getManifestFileDate(), // CREATED_TIME - job.getStageStartDate(), // STARTED_TIME + job.getNodeData().getProcessingStageStartDate(), // STARTED_TIME nodeData.getCompletedDate(), // COMPLETED_TIME status.getDescription(), // ACTIVITY nodeData.getErrorsOccurred(), // STATUS ((Date.from(Instant.now()).getTime()) - (status.getStartDate().getTime())), // ACTIVITY_TIME - job.getCaseDirectoryPath(), // CASE_DIRECTORY_PATH - job.getNodeName().equals(LOCAL_HOST_NAME), // IS_LOCAL_JOB + job.getNodeData().getCaseDirectoryPath(), // CASE_DIRECTORY_PATH + job.getNodeData().getProcessingHost().equals(LOCAL_HOST_NAME), // IS_LOCAL_JOB nodeData.getManifestFilePath()}); // MANIFEST_FILE_PATH } } catch (Exception ex) { diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboard.form b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboard.form index 502fab6f16..ba1b540bd0 100755 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboard.form +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboard.form @@ -1,6 +1,15 @@
+ + + + + + + + + @@ -41,7 +50,7 @@ - + diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboard.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboard.java index ba07eaf42b..472d8ea45f 100755 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboard.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboard.java @@ -468,18 +468,18 @@ public final class AutoIngestDashboard extends JPanel implements Observer { continue; } AutoIngestJob.StageDetails status = job.getStageDetails(); - AutoIngestJobNodeData nodeData = job.getNodeData(); + AutoIngestJobData nodeData = job.getNodeData(); tableModel.addRow(new Object[]{ nodeData.getCaseName(), // CASE nodeData.getDataSourcePath().getFileName(), // DATA_SOURCE - job.getNodeName(), // HOST_NAME + job.getNodeData().getProcessingHost(), // HOST_NAME nodeData.getManifestFileDate(), // CREATED_TIME - job.getStageStartDate(), // STARTED_TIME + job.getNodeData().getProcessingStageStartDate(), // STARTED_TIME nodeData.getCompletedDate(), // COMPLETED_TIME status.getDescription(), // ACTIVITY nodeData.getErrorsOccurred(), // STATUS ((Date.from(Instant.now()).getTime()) - (status.getStartDate().getTime())), // ACTIVITY_TIME - job.getCaseDirectoryPath(), // CASE_DIRECTORY_PATH + job.getNodeData().getCaseDirectoryPath(), // CASE_DIRECTORY_PATH nodeData.getManifestFilePath()//DLG: , // MANIFEST_FILE_PATH //DLG: job }); // JOB @@ -547,7 +547,7 @@ public final class AutoIngestDashboard extends JPanel implements Observer { */ private enum JobsTableModelColumns { - // DLG: Go through the bundles.properties file and delete any unused key-value pairs. + // DLG: Go through the bundle.properties file and delete any unused key-value pairs. CASE(NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.JobsTableModel.ColumnHeader.Case")), DATA_SOURCE(NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.JobsTableModel.ColumnHeader.ImageFolder")), HOST_NAME(NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.JobsTableModel.ColumnHeader.HostName")), @@ -662,6 +662,7 @@ public final class AutoIngestDashboard extends JPanel implements Observer { // //GEN-BEGIN:initComponents private void initComponents() { + jButton1 = new javax.swing.JButton(); pendingScrollPane = new javax.swing.JScrollPane(); pendingTable = new javax.swing.JTable(); runningScrollPane = new javax.swing.JScrollPane(); @@ -676,6 +677,8 @@ public final class AutoIngestDashboard extends JPanel implements Observer { tbServicesStatusMessage = new javax.swing.JTextField(); prioritizeButton = new javax.swing.JButton(); + org.openide.awt.Mnemonics.setLocalizedText(jButton1, org.openide.util.NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.jButton1.text")); // NOI18N + pendingTable.setModel(pendingTableModel); pendingTable.setToolTipText(org.openide.util.NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.pendingTable.toolTipText")); // NOI18N pendingTable.setAutoResizeMode(javax.swing.JTable.AUTO_RESIZE_ALL_COLUMNS); @@ -843,8 +846,6 @@ public final class AutoIngestDashboard extends JPanel implements Observer { jobsSnapshot = autoIngestMonitor.prioritizeJob(manifestFilePath); refreshTables(jobsSnapshot); } catch (AutoIngestMonitor.AutoIngestMonitorException ex) { - // DLG: DONE! Log the exception and do a popup with a user-friendly - // message explaining that the operation failed String errorMessage = String.format(NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.PrioritizeError"), manifestFilePath); logger.log(Level.SEVERE, errorMessage, ex); MessageNotifyUtil.Message.error(errorMessage); @@ -856,6 +857,7 @@ public final class AutoIngestDashboard extends JPanel implements Observer { // Variables declaration - do not modify//GEN-BEGIN:variables private javax.swing.JScrollPane completedScrollPane; private javax.swing.JTable completedTable; + private javax.swing.JButton jButton1; private javax.swing.JLabel lbCompleted; private javax.swing.JLabel lbPending; private javax.swing.JLabel lbRunning; diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJob.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJob.java index 1bd4194ebd..3df00a24dd 100755 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJob.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJob.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2015-2017 Basis Technology Corp. + * Copyright 2011-2017 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -20,7 +20,6 @@ package org.sleuthkit.autopsy.experimental.autoingest; import java.io.Serializable; import java.nio.file.Path; -import java.nio.file.Paths; import java.time.Instant; import java.util.Comparator; import java.util.Date; @@ -30,6 +29,7 @@ import javax.annotation.concurrent.Immutable; import javax.annotation.concurrent.ThreadSafe; import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessor; import org.sleuthkit.autopsy.coreutils.NetworkUtils; +import org.sleuthkit.autopsy.experimental.autoingest.AutoIngestJobData.ProcessingStage; import org.sleuthkit.autopsy.ingest.IngestJob; /** @@ -41,14 +41,7 @@ public final class AutoIngestJob implements Comparable, Serializa private static final long serialVersionUID = 1L; private static final String LOCAL_HOST_NAME = NetworkUtils.getLocalHostName(); - private final AutoIngestJobNodeData nodeData; - private final String nodeName; - @GuardedBy("this") - private String caseDirectoryPath; // DLG: Replace with AutoIngestJobNodeData.caseDirectoryPath - @GuardedBy("this") - private Stage stage; - @GuardedBy("this") - private Date stageStartDate; + private final AutoIngestJobData nodeData; @GuardedBy("this") transient private DataSourceProcessor dataSourceProcessor; @GuardedBy("this") @@ -67,29 +60,9 @@ public final class AutoIngestJob implements Comparable, Serializa * AutoIngestJob class. * * @param nodeData The node data. - * @param caseDirectoryPath The path to the case directory for the job, may - * be null. - * @param nodeName If the job is in progress, the node doing the - * processing, otherwise the locla host. - * @param stage The processing stage for display purposes. */ - /* - * DLG: We need a contrucotr that takes just the node data. When we have - * added the case dierectory path, the host name and the stage data to the - * ZK nodes, we probably cna use that constructor only. I'm thinking this - * because we will creater node data with initial values when we first - * discover the nodes, and then we will continue to update it. - */ - AutoIngestJob(AutoIngestJobNodeData nodeData, Path caseDirectoryPath, String nodeName, Stage stage) { + AutoIngestJob(AutoIngestJobData nodeData) { this.nodeData = nodeData; - if (null != caseDirectoryPath) { - this.caseDirectoryPath = caseDirectoryPath.toString(); - } else { - this.caseDirectoryPath = ""; - } - this.nodeName = nodeName; - this.stage = stage; - this.stageStartDate = nodeData.getManifestFileDate(); } /** @@ -97,68 +70,27 @@ public final class AutoIngestJob implements Comparable, Serializa * * @return The node data. */ - AutoIngestJobNodeData getNodeData() { + AutoIngestJobData getNodeData() { return this.nodeData; } - /** - * Queries whether or not a case directory path has been set for this auto - * ingest job. - * - * @return True or false - */ - synchronized boolean hasCaseDirectoryPath() { - return (false == this.caseDirectoryPath.isEmpty()); - } - - /** - * Sets the path to the case directory of the case associated with this job. - * - * @param caseDirectoryPath The path to the case directory. - */ - synchronized void setCaseDirectoryPath(Path caseDirectoryPath) { - this.caseDirectoryPath = caseDirectoryPath.toString(); - } - - /** - * Gets the path to the case directory of the case associated with this job, - * may be null. - * - * @return The case directory path or null if the case directory has not - * been created yet. - */ - synchronized Path getCaseDirectoryPath() { - if (!caseDirectoryPath.isEmpty()) { - return Paths.get(caseDirectoryPath); - } else { - return null; - } - } - - synchronized void setStage(Stage newStage) { + synchronized void setStage(ProcessingStage newStage) { setStage(newStage, Date.from(Instant.now())); } - synchronized void setStage(Stage newState, Date stateStartedDate) { - if (Stage.CANCELING == this.stage && Stage.COMPLETED != newState) { + synchronized void setStage(ProcessingStage newStage, Date stateStartedDate) { + if (ProcessingStage.CANCELING == this.nodeData.getProcessingStage() && ProcessingStage.COMPLETED != newStage) { return; } - this.stage = newState; - this.stageStartDate = stateStartedDate; - } - - synchronized Stage getStage() { - return this.stage; - } - - synchronized Date getStageStartDate() { - return this.stageStartDate; + this.nodeData.setProcessingStage(newStage); + this.nodeData.setProcessingStageStartDate(stateStartedDate); } synchronized StageDetails getStageDetails() { String description; Date startDate; - if (Stage.CANCELING != this.stage && null != this.ingestJob) { + ProcessingStage stage = nodeData.getProcessingStage(); + if (ProcessingStage.CANCELING != stage && null != this.ingestJob) { IngestJob.ProgressSnapshot progress = this.ingestJob.getSnapshot(); IngestJob.DataSourceIngestModuleHandle ingestModuleHandle = progress.runningDataSourceIngestModule(); if (null != ingestModuleHandle) { @@ -171,7 +103,7 @@ public final class AutoIngestJob implements Comparable, Serializa if (!ingestModuleHandle.isCancelled()) { description = ingestModuleHandle.displayName(); } else { - description = String.format(Stage.CANCELING_MODULE.getDisplayText(), ingestModuleHandle.displayName()); + description = String.format(ProcessingStage.CANCELING_MODULE.getDisplayText(), ingestModuleHandle.displayName()); } } else { /** @@ -182,12 +114,12 @@ public final class AutoIngestJob implements Comparable, Serializa * parallel. For example, there is an ingest job created to * ingest each extracted virtual machine. */ - description = Stage.ANALYZING_FILES.getDisplayText(); + description = ProcessingStage.ANALYZING_FILES.getDisplayText(); startDate = progress.fileIngestStartTime(); } } else { - description = this.stage.getDisplayText(); - startDate = this.stageStartDate; + description = stage.getDisplayText(); + startDate = this.nodeData.getProcessingStageStartDate(); } return new StageDetails(description, startDate); } @@ -205,7 +137,7 @@ public final class AutoIngestJob implements Comparable, Serializa } synchronized void cancel() { - setStage(Stage.CANCELING); + setStage(ProcessingStage.CANCELING); canceled = true; nodeData.setErrorsOccurred(true); if (null != dataSourceProcessor) { @@ -221,7 +153,7 @@ public final class AutoIngestJob implements Comparable, Serializa } synchronized void setCompleted() { - setStage(Stage.COMPLETED); + setStage(ProcessingStage.COMPLETED); completed = true; } @@ -229,10 +161,6 @@ public final class AutoIngestJob implements Comparable, Serializa return completed; } - String getNodeName() { - return nodeName; - } - @Override public boolean equals(Object obj) { if (!(obj instanceof AutoIngestJob)) { @@ -250,7 +178,7 @@ public final class AutoIngestJob implements Comparable, Serializa @Override public int hashCode() { - int hash = 71 * (Objects.hashCode(this.caseDirectoryPath)); + int hash = 71 * (Objects.hashCode(this.nodeData.getCaseDirectoryPath())); return hash; } @@ -262,11 +190,9 @@ public final class AutoIngestJob implements Comparable, Serializa return -date1.compareTo(date2); } - // DLG: Add a toString override @Override public String toString() { - // DLG: FINISH ME! - return ""; + return nodeData.getCaseDirectoryPath().toString(); } /** @@ -277,7 +203,7 @@ public final class AutoIngestJob implements Comparable, Serializa @Override public int compare(AutoIngestJob o1, AutoIngestJob o2) { - return -o1.getStageStartDate().compareTo(o2.getStageStartDate()); + return -o1.getNodeData().getProcessingStageStartDate().compareTo(o2.getNodeData().getProcessingStageStartDate()); } } @@ -305,9 +231,9 @@ public final class AutoIngestJob implements Comparable, Serializa @Override public int compare(AutoIngestJob o1, AutoIngestJob o2) { - if (o1.getNodeName().equalsIgnoreCase(LOCAL_HOST_NAME)) { + if (o1.getNodeData().getProcessingHost().equalsIgnoreCase(LOCAL_HOST_NAME)) { return -1; // o1 is for current case, float to top - } else if (o2.getNodeName().equalsIgnoreCase(LOCAL_HOST_NAME)) { + } else if (o2.getNodeData().getProcessingHost().equalsIgnoreCase(LOCAL_HOST_NAME)) { return 1; // o2 is for current case, float to top } else { String caseName1 = o1.getNodeData().getCaseName(); @@ -318,34 +244,6 @@ public final class AutoIngestJob implements Comparable, Serializa } } - enum Stage { - - PENDING("Pending"), - STARTING("Starting"), - UPDATING_SHARED_CONFIG("Updating shared configuration"), - CHECKING_SERVICES("Checking services"), - OPENING_CASE("Opening case"), - IDENTIFYING_DATA_SOURCE("Identifying data source type"), - ADDING_DATA_SOURCE("Adding data source"), - ANALYZING_DATA_SOURCE("Analyzing data source"), - ANALYZING_FILES("Analyzing files"), - EXPORTING_FILES("Exporting files"), - CANCELING_MODULE("Canceling module"), - CANCELING("Canceling"), - COMPLETED("Completed"); - - private final String displayText; - - private Stage(String displayText) { - this.displayText = displayText; - } - - String getDisplayText() { - return displayText; - } - - } - @Immutable static final class StageDetails { diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobNodeData.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobData.java similarity index 68% rename from Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobNodeData.java rename to Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobData.java index 25ea226a7f..42efa2238c 100755 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobNodeData.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobData.java @@ -28,7 +28,7 @@ import javax.lang.model.type.TypeKind; /** * A coordination service node data transfer object for an auto ingest job. */ -final class AutoIngestJobNodeData implements Serializable { +final class AutoIngestJobData implements Serializable { private static final long serialVersionUID = 1L; private static final int NODE_DATA_VERSION = 1; @@ -39,7 +39,7 @@ final class AutoIngestJobNodeData implements Serializable { * Version 0 fields. */ private final boolean coordSvcNodeDataWasSet; - private ProcessingStatus status; + private int processingStatus; private int priority; private int numberOfCrashes; private long completedDate; @@ -55,26 +55,26 @@ final class AutoIngestJobNodeData implements Serializable { private long manifestFileDate; private String manifestFilePath; private String dataSourcePath; - private String processingStage; + private byte processingStage; private long processingStageStartDate; private String processingHost; //DLG: Add caseDirectoryPath from AutoIngestJob /* - * DLG: Rename class to AutoIngestJobNodeData - Add String + * DLG: DONE! Rename class to AutoIngestJobData - Add String * caseDirectoryPath. Needed to locate case auto ingest log and later, for * case deletion * - * Add String processingStage, long processingStageStartDate, String + * DLG: Add String processingStage, long processingStageStartDate, String * processingHost fields. These three fields are needed to populate running * jobs table; use of auto ingest job data is not enough, because there * would be no data until a status event was received by the auto ingest * monitor. * - * Update the AutoIngestManager code that creates ZK nodes for auto ingest + * DLG: Update the AutoIngestManager code that creates ZK nodes for auto ingest * jobs to write the new fields described above to new nodes * - * Update the AutoIngestManager code that publishes auto ingest status + * DLG: Update the AutoIngestManager code that publishes auto ingest status * events for the current job to update the the processing status fields * described above in addition to publishing AutoIngestJobStatusEvents. * Probably also need to write this data initially when a jo becomes the @@ -87,27 +87,18 @@ final class AutoIngestJobNodeData implements Serializable { * * @param nodeData The raw bytes received from the coordination service. */ - AutoIngestJobNodeData(byte[] nodeData) throws AutoIngestJobNodeDataException { + AutoIngestJobData(byte[] nodeData) throws AutoIngestJobDataException { ByteBuffer buffer = ByteBuffer.wrap(nodeData); this.coordSvcNodeDataWasSet = buffer.hasRemaining(); if (this.coordSvcNodeDataWasSet) { - int rawStatus = buffer.getInt(); - if (ProcessingStatus.PENDING.ordinal() == rawStatus) { - this.status = ProcessingStatus.PENDING; - } else if (ProcessingStatus.PROCESSING.ordinal() == rawStatus) { - this.status = ProcessingStatus.PROCESSING; - } else if (ProcessingStatus.COMPLETED.ordinal() == rawStatus) { - this.status = ProcessingStatus.COMPLETED; - } else if (ProcessingStatus.DELETED.ordinal() == rawStatus) { - this.status = ProcessingStatus.DELETED; - } + this.processingStatus = buffer.getInt(); this.priority = buffer.getInt(); this.numberOfCrashes = buffer.getInt(); this.completedDate = buffer.getLong(); int errorFlag = buffer.getInt(); this.errorsOccurred = (1 == errorFlag); } else { - this.status = ProcessingStatus.PENDING; + this.processingStatus = ProcessingStatus.PENDING.ordinal(); this.priority = DEFAULT_PRIORITY; this.numberOfCrashes = 0; this.completedDate = 0L; @@ -121,17 +112,17 @@ final class AutoIngestJobNodeData implements Serializable { */ this.version = buffer.getInt(); if (this.version > NODE_DATA_VERSION) { - throw new AutoIngestJobNodeDataException(String.format("Node data version %d is not suppored.", this.version)); + throw new AutoIngestJobDataException(String.format("Node data version %d is not suppored.", this.version)); } this.deviceId = getStringFromBuffer(buffer, TypeKind.BYTE); this.caseName = getStringFromBuffer(buffer, TypeKind.BYTE); - //DLG: this.caseDirectoryPath = getStringFromBuffer(buffer, TypeKind.SHORT); + this.caseDirectoryPath = getStringFromBuffer(buffer, TypeKind.SHORT); this.manifestFileDate = buffer.getLong(); this.manifestFilePath = getStringFromBuffer(buffer, TypeKind.SHORT); this.dataSourcePath = getStringFromBuffer(buffer, TypeKind.SHORT); - //DLG: this.processingStage = getStringFromBuffer(buffer, TypeKind.BYTE); - //DLG: this.processingStageStartDate = buffer.getLong(); - //DLG: this.processingHost = getStringFromBuffer(buffer, TypeKind.SHORT); + this.processingStage = buffer.get(); + this.processingStageStartDate = buffer.getLong(); + this.processingHost = getStringFromBuffer(buffer, TypeKind.SHORT); } else { this.version = 0; this.deviceId = ""; @@ -140,7 +131,7 @@ final class AutoIngestJobNodeData implements Serializable { this.manifestFileDate = 0L; this.manifestFilePath = ""; this.dataSourcePath = ""; - this.processingStage = ""; + this.processingStage = (byte)ProcessingStage.PENDING.ordinal(); this.processingStageStartDate = 0L; this.processingHost = ""; } @@ -159,9 +150,9 @@ final class AutoIngestJobNodeData implements Serializable { * completed. * @param errorsOccurred Boolean to determine if errors have occurred. */ - AutoIngestJobNodeData(Manifest manifest, ProcessingStatus status, int priority, int numberOfCrashes, Date completedDate, boolean errorOccurred) { + AutoIngestJobData(Manifest manifest, ProcessingStatus status, int priority, int numberOfCrashes, Date completedDate, boolean errorOccurred) { this.coordSvcNodeDataWasSet = false; - this.status = status; + this.processingStatus = status.ordinal(); this.priority = priority; this.numberOfCrashes = numberOfCrashes; this.completedDate = completedDate.getTime(); @@ -170,9 +161,13 @@ final class AutoIngestJobNodeData implements Serializable { this.version = NODE_DATA_VERSION; this.deviceId = manifest.getDeviceId(); this.caseName = manifest.getCaseName(); + this.caseDirectoryPath = ""; //DLG: this.manifestFileDate = manifest.getDateFileCreated().getTime(); this.manifestFilePath = manifest.getFilePath().toString(); this.dataSourcePath = manifest.getDataSourcePath().toString(); + this.processingStage = (byte)ProcessingStage.PENDING.ordinal(); //DLG: + this.processingStageStartDate = 0L; //DLG: + this.processingHost = ""; //DLG: } /** @@ -191,17 +186,17 @@ final class AutoIngestJobNodeData implements Serializable { * * @return The processing status of the manifest. */ - ProcessingStatus getStatus() { - return this.status; + ProcessingStatus getProcessingStatus() { + return ProcessingStatus.values()[this.processingStatus]; } /** * Sets the processing status of the manifest * - * @param status The processing status of the manifest. + * @param processingSatus The processing status of the manifest. */ - void setStatus(ProcessingStatus status) { - this.status = status; + void setProcessingStatus(ProcessingStatus processingStatus) { + this.processingStatus = processingStatus.ordinal(); } /** @@ -342,6 +337,44 @@ final class AutoIngestJobNodeData implements Serializable { this.caseName = caseName; } + /** + * Queries whether or not a case directory path has been set for this auto + * ingest job. + * + * @return True or false + */ + synchronized boolean hasCaseDirectoryPath() { + return (false == this.caseDirectoryPath.isEmpty()); + } + + /** + * Sets the path to the case directory of the case associated with this job. + * + * @param caseDirectoryPath The path to the case directory. + */ + synchronized void setCaseDirectoryPath(Path caseDirectoryPath) { + if(caseDirectoryPath == null) { + this.caseDirectoryPath = ""; + } else { + this.caseDirectoryPath = caseDirectoryPath.toString(); + } + } + + /** + * Gets the path to the case directory of the case associated with this job, + * may be null. + * + * @return The case directory path or null if the case directory has not + * been created yet. + */ + synchronized Path getCaseDirectoryPath() { + if (!caseDirectoryPath.isEmpty()) { + return Paths.get(caseDirectoryPath); + } else { + return null; + } + } + /** * Gets the date the manifest was created. * @@ -417,6 +450,83 @@ final class AutoIngestJobNodeData implements Serializable { } } + /** + * Get the processing stage. + * + * @return The processing stage. + */ + ProcessingStage getProcessingStage() { + return ProcessingStage.values()[this.processingStage]; + } + + /** + * Set the processing stage. + * + * @param processingStage The processing stage. + */ + void setProcessingStage(ProcessingStage processingStage) { + this.processingStage = (byte)processingStage.ordinal(); + } + + /** + * Get the processing stage start date. + * + * @return The processing stage start date. + */ + Date getProcessingStageStartDate() { + return new Date(this.processingStageStartDate); + } + + /** + * Set the processing stage start date. + * + * @param processingStageStartDate The processing stage start date. + */ + void setProcessingStageStartDate(Date processingStageStartDate) { + this.processingStageStartDate = processingStageStartDate.getTime(); + } + + /** + * Get the processing host. + * + * @return The processing host. + */ + String getProcessingHost() { + return this.processingHost; + } + + /** + * Set the processing host. + * + * @param processingHost The processing host. + */ + void setProcessingHost(String processingHost) { + this.processingHost = processingHost; + } + + /** + * This method will upgrade the node data to the latest version. + * + * @param manifest The manifest. + * @param caseDirectoryPath The case directory path. + * @param processingHost The host name. + * @param processingStage The processing stage. + */ + public void upgradeNode(Manifest manifest, Path caseDirectoryPath, String processingHost, ProcessingStage processingStage) { + if(this.version < NODE_DATA_VERSION) { + this.setVersion(NODE_DATA_VERSION); + this.setDeviceId(manifest.getDeviceId()); + this.setCaseName(manifest.getCaseName()); + this.setCaseDirectoryPath(caseDirectoryPath); + this.setManifestFileDate(manifest.getDateFileCreated()); + this.setManifestFilePath(manifest.getFilePath()); + this.setDataSourcePath(manifest.getDataSourcePath()); + this.setProcessingStage(processingStage); + this.setProcessingStageStartDate(manifest.getDateFileCreated()); + this.setProcessingHost(processingHost); + } + } + /** * Gets the node data as raw bytes that can be sent to the coordination * service. @@ -427,7 +537,7 @@ final class AutoIngestJobNodeData implements Serializable { ByteBuffer buffer = ByteBuffer.allocate(MAX_POSSIBLE_NODE_DATA_SIZE); // Write data (compatible with version 0) - buffer.putInt(this.status.ordinal()); + buffer.putInt(this.processingStatus); buffer.putInt(this.priority); buffer.putInt(this.numberOfCrashes); buffer.putLong(this.completedDate); @@ -440,13 +550,13 @@ final class AutoIngestJobNodeData implements Serializable { // Write data putStringIntoBuffer(deviceId, buffer, TypeKind.BYTE); putStringIntoBuffer(caseName, buffer, TypeKind.BYTE); - //DLG: putStringIntoBuffer(caseDirectoryPath, buffer, TypeKind.SHORT); + putStringIntoBuffer(caseDirectoryPath, buffer, TypeKind.SHORT); buffer.putLong(this.manifestFileDate); putStringIntoBuffer(manifestFilePath, buffer, TypeKind.SHORT); putStringIntoBuffer(dataSourcePath, buffer, TypeKind.SHORT); - //DLG: putStringIntoBuffer(processingStage, buffer, TypeKind.BYTE); - //DLG: buffer.putLong(this.processingStageStartDate); - //DLG: putStringIntoBuffer(processingHost, buffer, TypeKind.SHORT); + buffer.put(this.processingStage); + buffer.putLong(this.processingStageStartDate); + putStringIntoBuffer(processingHost, buffer, TypeKind.SHORT); } // Prepare the array @@ -502,4 +612,32 @@ final class AutoIngestJobNodeData implements Serializable { DELETED } + enum ProcessingStage { + + PENDING("Pending"), + STARTING("Starting"), + UPDATING_SHARED_CONFIG("Updating shared configuration"), + CHECKING_SERVICES("Checking services"), + OPENING_CASE("Opening case"), + IDENTIFYING_DATA_SOURCE("Identifying data source type"), + ADDING_DATA_SOURCE("Adding data source"), + ANALYZING_DATA_SOURCE("Analyzing data source"), + ANALYZING_FILES("Analyzing files"), + EXPORTING_FILES("Exporting files"), + CANCELING_MODULE("Canceling module"), + CANCELING("Canceling"), + COMPLETED("Completed"); + + private final String displayText; + + private ProcessingStage(String displayText) { + this.displayText = displayText; + } + + String getDisplayText() { + return displayText; + } + + } + } diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobNodeDataException.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobDataException.java similarity index 86% rename from Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobNodeDataException.java rename to Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobDataException.java index 6618062e50..c6676d2e8a 100755 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobNodeDataException.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobDataException.java @@ -21,7 +21,7 @@ package org.sleuthkit.autopsy.experimental.autoingest; /** * Exception thrown when a manifest node contains incompatible data. */ -public class AutoIngestJobNodeDataException extends Exception { +public class AutoIngestJobDataException extends Exception { /** * Constructs an exception thrown when a manifest node contains incompatible @@ -29,7 +29,7 @@ public class AutoIngestJobNodeDataException extends Exception { * * @param message An error message. */ - public AutoIngestJobNodeDataException(String message) { + public AutoIngestJobDataException(String message) { super(message); } @@ -40,7 +40,7 @@ public class AutoIngestJobNodeDataException extends Exception { * @param message An error message. * @param cause An exception that caused this exception to be thrown. */ - public AutoIngestJobNodeDataException(String message, Throwable cause) { + public AutoIngestJobDataException(String message, Throwable cause) { super(message, cause); } } diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestManager.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestManager.java index d5632bbfa7..383807347e 100755 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestManager.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestManager.java @@ -61,6 +61,7 @@ import java.util.stream.Collectors; import javax.annotation.concurrent.GuardedBy; import javax.annotation.concurrent.Immutable; import javax.annotation.concurrent.ThreadSafe; +import org.openide.util.Exceptions; import org.openide.util.Lookup; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.Case.CaseType; @@ -84,16 +85,17 @@ import org.sleuthkit.autopsy.experimental.autoingest.AutoIngestAlertFile.AutoIng import org.sleuthkit.autopsy.experimental.autoingest.AutoIngestJobLogger.AutoIngestJobLoggerException; import org.sleuthkit.autopsy.experimental.autoingest.FileExporter.FileExportException; import org.sleuthkit.autopsy.experimental.autoingest.ManifestFileParser.ManifestFileParserException; -import org.sleuthkit.autopsy.experimental.autoingest.AutoIngestJobNodeData.ProcessingStatus; -import static org.sleuthkit.autopsy.experimental.autoingest.AutoIngestJobNodeData.ProcessingStatus.COMPLETED; -import static org.sleuthkit.autopsy.experimental.autoingest.AutoIngestJobNodeData.ProcessingStatus.DELETED; -import static org.sleuthkit.autopsy.experimental.autoingest.AutoIngestJobNodeData.ProcessingStatus.PENDING; -import static org.sleuthkit.autopsy.experimental.autoingest.AutoIngestJobNodeData.ProcessingStatus.PROCESSING; +import org.sleuthkit.autopsy.experimental.autoingest.AutoIngestJobData.ProcessingStatus; +import static org.sleuthkit.autopsy.experimental.autoingest.AutoIngestJobData.ProcessingStatus.COMPLETED; +import static org.sleuthkit.autopsy.experimental.autoingest.AutoIngestJobData.ProcessingStatus.DELETED; +import static org.sleuthkit.autopsy.experimental.autoingest.AutoIngestJobData.ProcessingStatus.PENDING; +import static org.sleuthkit.autopsy.experimental.autoingest.AutoIngestJobData.ProcessingStatus.PROCESSING; import org.sleuthkit.autopsy.experimental.configuration.AutoIngestUserPreferences; import org.sleuthkit.autopsy.experimental.configuration.SharedConfiguration; import org.sleuthkit.autopsy.experimental.configuration.SharedConfiguration.SharedConfigurationException; import org.sleuthkit.autopsy.datasourceprocessors.AutoIngestDataSourceProcessor; import org.sleuthkit.autopsy.datasourceprocessors.AutoIngestDataSourceProcessor.AutoIngestDataSourceProcessorException; +import org.sleuthkit.autopsy.experimental.autoingest.AutoIngestJobData.ProcessingStage; import org.sleuthkit.autopsy.ingest.IngestJob; import org.sleuthkit.autopsy.ingest.IngestJob.CancellationReason; import org.sleuthkit.autopsy.ingest.IngestJobSettings; @@ -286,7 +288,7 @@ public final class AutoIngestManager extends Observable implements PropertyChang * @param event A job started from another auto ingest node. */ private void handleRemoteJobStartedEvent(AutoIngestJobStartedEvent event) { - String hostName = event.getJob().getNodeName(); + String hostName = event.getJob().getNodeData().getProcessingHost(); hostNamesToLastMsgTime.put(hostName, Instant.now()); synchronized (jobsLock) { Path manifestFilePath = event.getJob().getNodeData().getManifestFilePath(); @@ -298,7 +300,7 @@ public final class AutoIngestManager extends Observable implements PropertyChang } } } - hostNamesToRunningJobs.put(event.getJob().getNodeName(), event.getJob()); + hostNamesToRunningJobs.put(hostName, event.getJob()); setChanged(); notifyObservers(Event.JOB_STARTED); } @@ -313,7 +315,7 @@ public final class AutoIngestManager extends Observable implements PropertyChang * @param event An job status event from another auto ingest node. */ private void handleRemoteJobStatusEvent(AutoIngestJobStatusEvent event) { - String hostName = event.getJob().getNodeName(); + String hostName = event.getJob().getNodeData().getProcessingHost(); hostNamesToLastMsgTime.put(hostName, Instant.now()); hostNamesToRunningJobs.put(hostName, event.getJob()); setChanged(); @@ -331,7 +333,7 @@ public final class AutoIngestManager extends Observable implements PropertyChang * @param event An job completed event from another auto ingest node. */ private void handleRemoteJobCompletedEvent(AutoIngestJobCompletedEvent event) { - String hostName = event.getJob().getNodeName(); + String hostName = event.getJob().getNodeData().getProcessingHost(); hostNamesToLastMsgTime.put(hostName, Instant.now()); hostNamesToRunningJobs.remove(hostName); if (event.shouldRetry() == false) { @@ -543,10 +545,10 @@ public final class AutoIngestManager extends Observable implements PropertyChang for (AutoIngestJob job : prioritizedJobs) { String manifestNodePath = job.getNodeData().getManifestFilePath().toString(); try { - AutoIngestJobNodeData nodeData = new AutoIngestJobNodeData(coordinationService.getNodeData(CoordinationService.CategoryNode.MANIFESTS, manifestNodePath)); + AutoIngestJobData nodeData = new AutoIngestJobData(coordinationService.getNodeData(CoordinationService.CategoryNode.MANIFESTS, manifestNodePath)); nodeData.setPriority(maxPriority); coordinationService.setNodeData(CoordinationService.CategoryNode.MANIFESTS, manifestNodePath, nodeData.toArray()); - } catch (AutoIngestJobNodeDataException ex) { + } catch (AutoIngestJobDataException ex) { SYS_LOGGER.log(Level.WARNING, String.format("Unable to use node data for %s", manifestNodePath), ex); } catch (CoordinationServiceException ex) { SYS_LOGGER.log(Level.SEVERE, String.format("Coordination service error while prioritizing %s", manifestNodePath), ex); @@ -592,10 +594,10 @@ public final class AutoIngestManager extends Observable implements PropertyChang ++maxPriority; String manifestNodePath = prioritizedJob.getNodeData().getManifestFilePath().toString(); try { - AutoIngestJobNodeData nodeData = new AutoIngestJobNodeData(coordinationService.getNodeData(CoordinationService.CategoryNode.MANIFESTS, manifestNodePath)); + AutoIngestJobData nodeData = new AutoIngestJobData(coordinationService.getNodeData(CoordinationService.CategoryNode.MANIFESTS, manifestNodePath)); nodeData.setPriority(maxPriority); coordinationService.setNodeData(CoordinationService.CategoryNode.MANIFESTS, manifestNodePath, nodeData.toArray()); - } catch (AutoIngestJobNodeDataException ex) { + } catch (AutoIngestJobDataException ex) { SYS_LOGGER.log(Level.WARNING, String.format("Unable to use node data for %s", manifestPath), ex); } catch (CoordinationServiceException ex) { SYS_LOGGER.log(Level.SEVERE, String.format("Coordination service error while prioritizing %s", manifestNodePath), ex); @@ -634,16 +636,16 @@ public final class AutoIngestManager extends Observable implements PropertyChang } } - if (null != completedJob && null != completedJob.getCaseDirectoryPath()) { + if (null != completedJob && null != completedJob.getNodeData().getCaseDirectoryPath()) { try { - AutoIngestJobNodeData nodeData = completedJob.getNodeData(); - nodeData.setStatus(PENDING); + AutoIngestJobData nodeData = completedJob.getNodeData(); + nodeData.setProcessingStatus(PENDING); nodeData.setPriority(DEFAULT_JOB_PRIORITY); nodeData.setNumberOfCrashes(0); nodeData.setCompletedDate(new Date(0)); nodeData.setErrorsOccurred(true); coordinationService.setNodeData(CoordinationService.CategoryNode.MANIFESTS, manifestPath.toString(), nodeData.toArray()); - pendingJobs.add(new AutoIngestJob(nodeData, completedJob.getCaseDirectoryPath(), LOCAL_HOST_NAME, AutoIngestJob.Stage.PENDING)); + pendingJobs.add(new AutoIngestJob(nodeData)); } catch (CoordinationServiceException ex) { SYS_LOGGER.log(Level.SEVERE, String.format("Coordination service error while reprocessing %s", manifestPath), ex); completedJobs.add(completedJob); @@ -733,10 +735,10 @@ public final class AutoIngestManager extends Observable implements PropertyChang */ for (Path manifestPath : manifestPaths) { try { - AutoIngestJobNodeData nodeData = new AutoIngestJobNodeData(coordinationService.getNodeData(CoordinationService.CategoryNode.MANIFESTS, manifestPath.toString())); - nodeData.setStatus(AutoIngestJobNodeData.ProcessingStatus.DELETED); + AutoIngestJobData nodeData = new AutoIngestJobData(coordinationService.getNodeData(CoordinationService.CategoryNode.MANIFESTS, manifestPath.toString())); + nodeData.setProcessingStatus(AutoIngestJobData.ProcessingStatus.DELETED); coordinationService.setNodeData(CoordinationService.CategoryNode.MANIFESTS, manifestPath.toString(), nodeData.toArray()); - } catch (AutoIngestJobNodeDataException ex) { + } catch (AutoIngestJobDataException ex) { SYS_LOGGER.log(Level.WARNING, String.format("Unable to use node data for %s", manifestPath), ex); } catch (InterruptedException | CoordinationServiceException ex) { SYS_LOGGER.log(Level.SEVERE, String.format("Error attempting to set delete flag on manifest data for %s for case %s at %s", manifestPath, caseName, caseDirectoryPath), ex); @@ -834,7 +836,7 @@ public final class AutoIngestManager extends Observable implements PropertyChang if (null != ingestJob) { IngestJob.DataSourceIngestModuleHandle moduleHandle = ingestJob.getSnapshot().runningDataSourceIngestModule(); if (null != moduleHandle) { - currentJob.setStage(AutoIngestJob.Stage.CANCELING_MODULE); + currentJob.setStage(AutoIngestJobData.ProcessingStage.CANCELING_MODULE); moduleHandle.cancel(); SYS_LOGGER.log(Level.INFO, "Cancelling {0} module for manifest {1}", new Object[]{moduleHandle.displayName(), currentJob.getNodeData().getManifestFilePath()}); } @@ -1027,34 +1029,18 @@ public final class AutoIngestManager extends Observable implements PropertyChang byte[] rawData = coordinationService.getNodeData(CoordinationService.CategoryNode.MANIFESTS, manifestPath.toString()); if (null != rawData) { try { - AutoIngestJobNodeData nodeData = new AutoIngestJobNodeData(rawData); - if(nodeData.getVersion() < 1) { - /* - * A version '0' node doesn't have a sufficient - * amount of data to populate the jobs tables, - * so we will it here. - */ - nodeData.setDeviceId(manifest.getDeviceId()); - nodeData.setCaseName(manifest.getCaseName()); - nodeData.setManifestFileDate(manifest.getDateFileCreated()); - nodeData.setManifestFilePath(manifest.getFilePath()); - nodeData.setDataSourcePath(manifest.getDataSourcePath()); - nodeData.setVersion(1); - rawData = nodeData.toArray(); - coordinationService.setNodeData(CoordinationService.CategoryNode.MANIFESTS, manifestPath.toString(), rawData); - } - + AutoIngestJobData nodeData = new AutoIngestJobData(rawData); if (nodeData.coordSvcNodeDataWasSet()) { - ProcessingStatus processingStatus = nodeData.getStatus(); + ProcessingStatus processingStatus = nodeData.getProcessingStatus(); switch (processingStatus) { case PENDING: - addPendingJob(nodeData); + addPendingJob(nodeData, manifest); break; case PROCESSING: - doRecoveryIfCrashed(nodeData); + doRecoveryIfCrashed(nodeData, manifest); break; case COMPLETED: - addCompletedJob(nodeData); + addCompletedJob(nodeData, manifest); break; case DELETED: // Do nothing - we dont'want to add it to any job list or do recovery @@ -1066,14 +1052,14 @@ public final class AutoIngestManager extends Observable implements PropertyChang } else { addNewPendingJob(manifest); } - } catch(AutoIngestJobNodeDataException ex) { + } catch(AutoIngestJobDataException ex) { SYS_LOGGER.log(Level.WARNING, String.format("Unable to use node data for %s", manifestPath), ex); } } else { addNewPendingJob(manifest); } } catch (CoordinationServiceException ex) { - SYS_LOGGER.log(Level.SEVERE, String.format("Error getting node data for %s", manifestPath), ex); + SYS_LOGGER.log(Level.SEVERE, String.format("Error transmitting node data for %s", manifestPath), ex); return CONTINUE; } catch (InterruptedException ex) { Thread.currentThread().interrupt(); @@ -1088,17 +1074,46 @@ public final class AutoIngestManager extends Observable implements PropertyChang } } + /** + * This method will push a node to the coordination service if it does + * not exist. If the node is an older version, it will be upgraded prior + * to being pushed. + * + * @param nodeData The node data to upgrade. + * @param manifest The manifest. + * @param caseDirectoryPath The case directory path. + * @param processingStage The processing stage. + * + * @throws CoordinationServiceException + * @throws InterruptedException + */ + private void pushNodeToCoordinationService(AutoIngestJobData nodeData, Manifest manifest, Path caseDirectoryPath, ProcessingStage processingStage) + throws CoordinationServiceException, InterruptedException { + if(nodeData.getVersion() < 1) { + nodeData.upgradeNode(manifest, caseDirectoryPath, LOCAL_HOST_NAME, processingStage); + } + if(!nodeData.coordSvcNodeDataWasSet()) { + byte[] rawData = nodeData.toArray(); + coordinationService.setNodeData(CoordinationService.CategoryNode.MANIFESTS, manifest.getFilePath().toString(), rawData); + } + } + /** * Adds a job to process a manifest to the pending jobs queue. * * @param nodeData The data stored in the coordination service node for * the manifest. + * @param manifest The manifest for upgrading the node. + * + * @throws CoordinationServiceException + * @throws InterruptedException */ - private void addPendingJob(AutoIngestJobNodeData nodeData) { - Path caseDirectory = PathUtils.findCaseDirectory(rootOutputDirectory, nodeData.getCaseName()); + private void addPendingJob(AutoIngestJobData nodeData, Manifest manifest) throws CoordinationServiceException, InterruptedException { + Path caseDirectoryPath = PathUtils.findCaseDirectory(rootOutputDirectory, manifest.getCaseName()); nodeData.setCompletedDate(new Date(0)); nodeData.setErrorsOccurred(false); - newPendingJobsList.add(new AutoIngestJob(nodeData, caseDirectory, LOCAL_HOST_NAME, AutoIngestJob.Stage.PENDING)); + pushNodeToCoordinationService(nodeData, manifest, caseDirectoryPath, ProcessingStage.PENDING); + newPendingJobsList.add(new AutoIngestJob(nodeData)); } /** @@ -1116,9 +1131,9 @@ public final class AutoIngestManager extends Observable implements PropertyChang // Is use of Curator.create().forPath() possible instead? try (Lock manifestLock = coordinationService.tryGetExclusiveLock(CoordinationService.CategoryNode.MANIFESTS, manifest.getFilePath().toString())) { if (null != manifestLock) { - AutoIngestJobNodeData newNodeData = new AutoIngestJobNodeData(manifest, PENDING, DEFAULT_JOB_PRIORITY, 0, new Date(0), false); - coordinationService.setNodeData(CoordinationService.CategoryNode.MANIFESTS, manifest.getFilePath().toString(), newNodeData.toArray()); - newPendingJobsList.add(new AutoIngestJob(newNodeData, null, LOCAL_HOST_NAME, AutoIngestJob.Stage.PENDING)); + AutoIngestJobData newNodeData = new AutoIngestJobData(manifest, PENDING, DEFAULT_JOB_PRIORITY, 0, new Date(0), false); + pushNodeToCoordinationService(newNodeData, manifest, null, ProcessingStage.PENDING); + newPendingJobsList.add(new AutoIngestJob(newNodeData)); } } catch (CoordinationServiceException ex) { SYS_LOGGER.log(Level.SEVERE, String.format("Error attempting to set node data for %s", manifest.getFilePath()), ex); @@ -1133,33 +1148,35 @@ public final class AutoIngestManager extends Observable implements PropertyChang * the node that was processing the job crashed and the processing * status was not updated. * - * @param nodeData + * @param nodeData The node data. + * @param manifest The manifest for upgrading the node. * * @throws InterruptedException if the thread running the input * directory scan task is interrupted while * blocked, i.e., if auto ingest is * shutting down. */ - private void doRecoveryIfCrashed(AutoIngestJobNodeData nodeData) throws InterruptedException { + private void doRecoveryIfCrashed(AutoIngestJobData nodeData, Manifest manifest) throws InterruptedException { String manifestPath = nodeData.getManifestFilePath().toString(); try { Lock manifestLock = coordinationService.tryGetExclusiveLock(CoordinationService.CategoryNode.MANIFESTS, manifestPath); if (null != manifestLock) { try { - if (nodeData.coordSvcNodeDataWasSet() && ProcessingStatus.PROCESSING == nodeData.getStatus()) { + if (nodeData.coordSvcNodeDataWasSet() && ProcessingStatus.PROCESSING == nodeData.getProcessingStatus()) { SYS_LOGGER.log(Level.SEVERE, "Attempting crash recovery for {0}", manifestPath); int numberOfCrashes = nodeData.getNumberOfCrashes(); ++numberOfCrashes; + Path caseDirectoryPath = PathUtils.findCaseDirectory(rootOutputDirectory, manifest.getCaseName()); nodeData.setNumberOfCrashes(numberOfCrashes); nodeData.setCompletedDate(new Date(0)); nodeData.setErrorsOccurred(true); if (numberOfCrashes <= AutoIngestUserPreferences.getMaxNumTimesToProcessImage()) { - nodeData.setStatus(PENDING); - Path caseDirectoryPath = PathUtils.findCaseDirectory(rootOutputDirectory, nodeData.getCaseName()); - newPendingJobsList.add(new AutoIngestJob(nodeData, caseDirectoryPath, LOCAL_HOST_NAME, AutoIngestJob.Stage.PENDING)); - if (null != caseDirectoryPath) { + nodeData.setProcessingStatus(PENDING); + pushNodeToCoordinationService(nodeData, manifest, caseDirectoryPath, ProcessingStage.PENDING); + newPendingJobsList.add(new AutoIngestJob(nodeData)); + if (null != nodeData.getCaseDirectoryPath()) { try { - AutoIngestAlertFile.create(caseDirectoryPath); + AutoIngestAlertFile.create(nodeData.getCaseDirectoryPath()); } catch (AutoIngestAlertFileException ex) { SYS_LOGGER.log(Level.SEVERE, String.format("Error creating alert file for crashed job for %s", manifestPath), ex); } @@ -1170,17 +1187,17 @@ public final class AutoIngestManager extends Observable implements PropertyChang } } } else { - nodeData.setStatus(COMPLETED); - Path caseDirectoryPath = PathUtils.findCaseDirectory(rootOutputDirectory, nodeData.getCaseName()); - newCompletedJobsList.add(new AutoIngestJob(nodeData, caseDirectoryPath, LOCAL_HOST_NAME, AutoIngestJob.Stage.COMPLETED)); - if (null != caseDirectoryPath) { + nodeData.setProcessingStatus(COMPLETED); + pushNodeToCoordinationService(nodeData, manifest, caseDirectoryPath, ProcessingStage.COMPLETED); + newCompletedJobsList.add(new AutoIngestJob(nodeData)); + if (null != nodeData.getCaseDirectoryPath()) { try { - AutoIngestAlertFile.create(caseDirectoryPath); + AutoIngestAlertFile.create(nodeData.getCaseDirectoryPath()); } catch (AutoIngestAlertFileException ex) { SYS_LOGGER.log(Level.SEVERE, String.format("Error creating alert file for crashed job for %s", manifestPath), ex); } try { - new AutoIngestJobLogger(nodeData.getManifestFilePath(), nodeData.getDataSourceFileName(), caseDirectoryPath).logCrashRecoveryNoRetry(); + new AutoIngestJobLogger(nodeData.getManifestFilePath(), nodeData.getDataSourceFileName(), nodeData.getCaseDirectoryPath()).logCrashRecoveryNoRetry(); } catch (AutoIngestJobLoggerException ex) { SYS_LOGGER.log(Level.SEVERE, String.format("Error creating case auto ingest log entry for crashed job for %s", manifestPath), ex); } @@ -1210,11 +1227,16 @@ public final class AutoIngestManager extends Observable implements PropertyChang * * @param nodeData The data stored in the coordination service node for * the manifest. + * @param manifest The manifest for upgrading the node. + * + * @throws CoordinationServiceException + * @throws InterruptedException */ - private void addCompletedJob(AutoIngestJobNodeData nodeData) { - Path caseDirectoryPath = PathUtils.findCaseDirectory(rootOutputDirectory, nodeData.getCaseName()); + private void addCompletedJob(AutoIngestJobData nodeData, Manifest manifest) throws CoordinationServiceException, InterruptedException { + Path caseDirectoryPath = PathUtils.findCaseDirectory(rootOutputDirectory, manifest.getCaseName()); if (null != caseDirectoryPath) { - newCompletedJobsList.add(new AutoIngestJob(nodeData, caseDirectoryPath, LOCAL_HOST_NAME, AutoIngestJob.Stage.COMPLETED)); + pushNodeToCoordinationService(nodeData, manifest, caseDirectoryPath, ProcessingStage.COMPLETED); + newCompletedJobsList.add(new AutoIngestJob(nodeData)); } else { SYS_LOGGER.log(Level.WARNING, String.format("Job completed for %s, but cannot find case directory, ignoring job", nodeData.getManifestFilePath())); } @@ -1549,7 +1571,7 @@ public final class AutoIngestManager extends Observable implements PropertyChang return; } processJob(); - } catch (AutoIngestJobNodeDataException ex) { + } catch (AutoIngestJobDataException ex) { SYS_LOGGER.log(Level.WARNING, String.format("Unable to use node data"), ex); } finally { manifestLock.release(); @@ -1650,8 +1672,8 @@ public final class AutoIngestManager extends Observable implements PropertyChang } try { - AutoIngestJobNodeData nodeData = new AutoIngestJobNodeData(coordinationService.getNodeData(CoordinationService.CategoryNode.MANIFESTS, manifestPath.toString())); - if (!nodeData.getStatus().equals(PENDING)) { + AutoIngestJobData nodeData = new AutoIngestJobData(coordinationService.getNodeData(CoordinationService.CategoryNode.MANIFESTS, manifestPath.toString())); + if (!nodeData.getProcessingStatus().equals(PENDING)) { /* * Due to a timing issue or a missed event, a * non-pending job has ended up on the pending queue. @@ -1677,7 +1699,7 @@ public final class AutoIngestManager extends Observable implements PropertyChang iterator.remove(); currentJob = job; break; - } catch (AutoIngestJobNodeDataException ex) { + } catch (AutoIngestJobDataException ex) { SYS_LOGGER.log(Level.WARNING, String.format("Unable to use node data for %s", manifestPath), ex); } } @@ -1723,13 +1745,13 @@ public final class AutoIngestManager extends Observable implements PropertyChang * i.e., if auto ingest is * shutting down. */ - private void processJob() throws CoordinationServiceException, SharedConfigurationException, ServicesMonitorException, DatabaseServerDownException, KeywordSearchServerDownException, CaseManagementException, AnalysisStartupException, FileExportException, AutoIngestAlertFileException, AutoIngestJobLoggerException, InterruptedException, AutoIngestDataSourceProcessor.AutoIngestDataSourceProcessorException, AutoIngestJobNodeDataException { + private void processJob() throws CoordinationServiceException, SharedConfigurationException, ServicesMonitorException, DatabaseServerDownException, KeywordSearchServerDownException, CaseManagementException, AnalysisStartupException, FileExportException, AutoIngestAlertFileException, AutoIngestJobLoggerException, InterruptedException, AutoIngestDataSourceProcessor.AutoIngestDataSourceProcessorException, AutoIngestJobDataException { Path manifestPath = currentJob.getNodeData().getManifestFilePath(); - AutoIngestJobNodeData nodeData = new AutoIngestJobNodeData(coordinationService.getNodeData(CoordinationService.CategoryNode.MANIFESTS, manifestPath.toString())); - nodeData.setStatus(PROCESSING); + AutoIngestJobData nodeData = new AutoIngestJobData(coordinationService.getNodeData(CoordinationService.CategoryNode.MANIFESTS, manifestPath.toString())); + nodeData.setProcessingStatus(PROCESSING); coordinationService.setNodeData(CoordinationService.CategoryNode.MANIFESTS, manifestPath.toString(), nodeData.toArray()); SYS_LOGGER.log(Level.INFO, "Started processing of {0}", manifestPath); - currentJob.setStage(AutoIngestJob.Stage.STARTING); + currentJob.setStage(AutoIngestJobData.ProcessingStage.STARTING); setChanged(); notifyObservers(Event.JOB_STARTED); eventPublisher.publishRemotely(new AutoIngestJobStartedEvent(currentJob)); @@ -1744,23 +1766,23 @@ public final class AutoIngestManager extends Observable implements PropertyChang currentJob.cancel(); } - nodeData = new AutoIngestJobNodeData(coordinationService.getNodeData(CoordinationService.CategoryNode.MANIFESTS, manifestPath.toString())); + nodeData = new AutoIngestJobData(coordinationService.getNodeData(CoordinationService.CategoryNode.MANIFESTS, manifestPath.toString())); if (currentJob.isCompleted() || currentJob.isCanceled()) { - nodeData.setStatus(COMPLETED); + nodeData.setProcessingStatus(COMPLETED); Date completedDate = new Date(); currentJob.getNodeData().setCompletedDate(completedDate); nodeData.setCompletedDate(currentJob.getNodeData().getCompletedDate()); nodeData.setErrorsOccurred(currentJob.getNodeData().getErrorsOccurred()); } else { // The job may get retried - nodeData.setStatus(PENDING); + nodeData.setProcessingStatus(PENDING); } coordinationService.setNodeData(CoordinationService.CategoryNode.MANIFESTS, manifestPath.toString(), nodeData.toArray()); boolean retry = (!currentJob.isCanceled() && !currentJob.isCompleted()); SYS_LOGGER.log(Level.INFO, "Completed processing of {0}, retry = {1}", new Object[]{manifestPath, retry}); if (currentJob.isCanceled()) { - Path caseDirectoryPath = currentJob.getCaseDirectoryPath(); + Path caseDirectoryPath = currentJob.getNodeData().getCaseDirectoryPath(); if (null != caseDirectoryPath) { AutoIngestAlertFile.create(caseDirectoryPath); // Do this first, it is more important than the case log AutoIngestJobLogger jobLogger = new AutoIngestJobLogger(nodeData.getManifestFilePath(), nodeData.getDataSourceFileName(), caseDirectoryPath); @@ -1830,7 +1852,7 @@ public final class AutoIngestManager extends Observable implements PropertyChang try { Case.closeCurrentCase(); } catch (CaseActionException ex) { - AutoIngestJobNodeData nodeData = currentJob.getNodeData(); + AutoIngestJobData nodeData = currentJob.getNodeData(); throw new CaseManagementException(String.format("Error closing case %s for %s", nodeData.getCaseName(), nodeData.getManifestFilePath()), ex); } } @@ -1851,7 +1873,7 @@ public final class AutoIngestManager extends Observable implements PropertyChang if (AutoIngestUserPreferences.getSharedConfigEnabled()) { Path manifestPath = currentJob.getNodeData().getManifestFilePath(); SYS_LOGGER.log(Level.INFO, "Downloading shared configuration for {0}", manifestPath); - currentJob.setStage(AutoIngestJob.Stage.UPDATING_SHARED_CONFIG); + currentJob.setStage(AutoIngestJobData.ProcessingStage.UPDATING_SHARED_CONFIG); new SharedConfiguration().downloadConfiguration(); } } @@ -1869,7 +1891,7 @@ public final class AutoIngestManager extends Observable implements PropertyChang private void verifyRequiredSevicesAreRunning() throws ServicesMonitorException, DatabaseServerDownException, KeywordSearchServerDownException { Path manifestPath = currentJob.getNodeData().getManifestFilePath(); SYS_LOGGER.log(Level.INFO, "Checking services availability for {0}", manifestPath); - currentJob.setStage(AutoIngestJob.Stage.CHECKING_SERVICES); + currentJob.setStage(AutoIngestJobData.ProcessingStage.CHECKING_SERVICES); if (!isServiceUp(ServicesMonitor.Service.REMOTE_CASE_DATABASE.toString())) { throw new DatabaseServerDownException("Case database server is down"); } @@ -1913,10 +1935,10 @@ public final class AutoIngestManager extends Observable implements PropertyChang * if auto ingest is shutting down. */ private Case openCase() throws CoordinationServiceException, CaseManagementException, InterruptedException { - AutoIngestJobNodeData nodeData = currentJob.getNodeData(); + AutoIngestJobData nodeData = currentJob.getNodeData(); String caseName = nodeData.getCaseName(); SYS_LOGGER.log(Level.INFO, "Opening case {0} for {1}", new Object[]{caseName, nodeData.getManifestFilePath()}); - currentJob.setStage(AutoIngestJob.Stage.OPENING_CASE); + currentJob.setStage(AutoIngestJobData.ProcessingStage.OPENING_CASE); /* * Acquire and hold a case name lock so that only one node at as * time can scan the output directory at a time. This prevents @@ -1939,7 +1961,7 @@ public final class AutoIngestManager extends Observable implements PropertyChang */ Thread.sleep(AutoIngestUserPreferences.getSecondsToSleepBetweenCases() * 1000); } - currentJob.setCaseDirectoryPath(caseDirectoryPath); + currentJob.getNodeData().setCaseDirectoryPath(caseDirectoryPath); Case caseForJob = Case.getCurrentCase(); SYS_LOGGER.log(Level.INFO, "Opened case {0} for {1}", new Object[]{caseForJob.getName(), nodeData.getManifestFilePath()}); return caseForJob; @@ -2030,7 +2052,7 @@ public final class AutoIngestManager extends Observable implements PropertyChang DataSource dataSource = identifyDataSource(caseForJob); if (null == dataSource) { - currentJob.setStage(AutoIngestJob.Stage.COMPLETED); + currentJob.setStage(AutoIngestJobData.ProcessingStage.COMPLETED); return; } @@ -2040,7 +2062,7 @@ public final class AutoIngestManager extends Observable implements PropertyChang runDataSourceProcessor(caseForJob, dataSource); if (dataSource.getContent().isEmpty()) { - currentJob.setStage(AutoIngestJob.Stage.COMPLETED); + currentJob.setStage(AutoIngestJobData.ProcessingStage.COMPLETED); return; } @@ -2082,11 +2104,11 @@ public final class AutoIngestManager extends Observable implements PropertyChang * if auto ingest is shutting down. */ private DataSource identifyDataSource(Case caseForJob) throws AutoIngestAlertFileException, AutoIngestJobLoggerException, InterruptedException { - AutoIngestJobNodeData nodeData = currentJob.getNodeData(); + AutoIngestJobData nodeData = currentJob.getNodeData(); Path manifestPath = nodeData.getManifestFilePath(); SYS_LOGGER.log(Level.INFO, "Identifying data source for {0} ", manifestPath); - currentJob.setStage(AutoIngestJob.Stage.IDENTIFYING_DATA_SOURCE); - Path caseDirectoryPath = currentJob.getCaseDirectoryPath(); + currentJob.setStage(AutoIngestJobData.ProcessingStage.IDENTIFYING_DATA_SOURCE); + Path caseDirectoryPath = currentJob.getNodeData().getCaseDirectoryPath(); AutoIngestJobLogger jobLogger = new AutoIngestJobLogger(manifestPath, nodeData.getDataSourceFileName(), caseDirectoryPath); Path dataSourcePath = nodeData.getDataSourcePath(); File dataSource = dataSourcePath.toFile(); @@ -2118,14 +2140,14 @@ public final class AutoIngestManager extends Observable implements PropertyChang * ingest is shutting down. */ private void runDataSourceProcessor(Case caseForJob, DataSource dataSource) throws InterruptedException, AutoIngestAlertFileException, AutoIngestJobLoggerException, AutoIngestDataSourceProcessor.AutoIngestDataSourceProcessorException { - AutoIngestJobNodeData nodeData = currentJob.getNodeData(); + AutoIngestJobData nodeData = currentJob.getNodeData(); Path manifestPath = nodeData.getManifestFilePath(); SYS_LOGGER.log(Level.INFO, "Adding data source for {0} ", manifestPath); - currentJob.setStage(AutoIngestJob.Stage.ADDING_DATA_SOURCE); + currentJob.setStage(AutoIngestJobData.ProcessingStage.ADDING_DATA_SOURCE); UUID taskId = UUID.randomUUID(); DataSourceProcessorCallback callBack = new AddDataSourceCallback(caseForJob, dataSource, taskId); DataSourceProcessorProgressMonitor progressMonitor = new DoNothingDSPProgressMonitor(); - Path caseDirectoryPath = currentJob.getCaseDirectoryPath(); + Path caseDirectoryPath = currentJob.getNodeData().getCaseDirectoryPath(); AutoIngestJobLogger jobLogger = new AutoIngestJobLogger(manifestPath, nodeData.getDataSourceFileName(), caseDirectoryPath); try { caseForJob.notifyAddingDataSource(taskId); @@ -2211,9 +2233,9 @@ public final class AutoIngestManager extends Observable implements PropertyChang * ingest is shutting down. */ private void logDataSourceProcessorResult(DataSource dataSource) throws AutoIngestAlertFileException, AutoIngestJobLoggerException, InterruptedException { - AutoIngestJobNodeData nodeData = currentJob.getNodeData(); + AutoIngestJobData nodeData = currentJob.getNodeData(); Path manifestPath = nodeData.getManifestFilePath(); - Path caseDirectoryPath = currentJob.getCaseDirectoryPath(); + Path caseDirectoryPath = currentJob.getNodeData().getCaseDirectoryPath(); AutoIngestJobLogger jobLogger = new AutoIngestJobLogger(manifestPath, nodeData.getDataSourceFileName(), caseDirectoryPath); DataSourceProcessorResult resultCode = dataSource.getResultDataSourceProcessorResultCode(); if (null != resultCode) { @@ -2283,11 +2305,11 @@ public final class AutoIngestManager extends Observable implements PropertyChang * ingest is shutting down. */ private void analyze(DataSource dataSource) throws AnalysisStartupException, AutoIngestAlertFileException, AutoIngestJobLoggerException, InterruptedException { - AutoIngestJobNodeData nodeData = currentJob.getNodeData(); + AutoIngestJobData nodeData = currentJob.getNodeData(); Path manifestPath = nodeData.getManifestFilePath(); SYS_LOGGER.log(Level.INFO, "Starting ingest modules analysis for {0} ", manifestPath); - currentJob.setStage(AutoIngestJob.Stage.ANALYZING_DATA_SOURCE); - Path caseDirectoryPath = currentJob.getCaseDirectoryPath(); + currentJob.setStage(AutoIngestJobData.ProcessingStage.ANALYZING_DATA_SOURCE); + Path caseDirectoryPath = currentJob.getNodeData().getCaseDirectoryPath(); AutoIngestJobLogger jobLogger = new AutoIngestJobLogger(manifestPath, nodeData.getDataSourceFileName(), caseDirectoryPath); IngestJobEventListener ingestJobEventListener = new IngestJobEventListener(); IngestManager.getInstance().addIngestJobEventListener(ingestJobEventListener); @@ -2322,7 +2344,7 @@ public final class AutoIngestManager extends Observable implements PropertyChang } jobLogger.logAnalysisCompleted(); } else { - currentJob.setStage(AutoIngestJob.Stage.CANCELING); + currentJob.setStage(AutoIngestJobData.ProcessingStage.CANCELING); currentJob.getNodeData().setErrorsOccurred(true); AutoIngestAlertFile.create(caseDirectoryPath); // Do this first, it is more important than the case log jobLogger.logAnalysisCancelled(); @@ -2382,11 +2404,11 @@ public final class AutoIngestManager extends Observable implements PropertyChang * ingest is shutting down. */ private void exportFiles(DataSource dataSource) throws FileExportException, AutoIngestAlertFileException, AutoIngestJobLoggerException, InterruptedException { - AutoIngestJobNodeData nodeData = currentJob.getNodeData(); + AutoIngestJobData nodeData = currentJob.getNodeData(); Path manifestPath = nodeData.getManifestFilePath(); SYS_LOGGER.log(Level.INFO, "Exporting files for {0}", manifestPath); - currentJob.setStage(AutoIngestJob.Stage.EXPORTING_FILES); - Path caseDirectoryPath = currentJob.getCaseDirectoryPath(); + currentJob.setStage(AutoIngestJobData.ProcessingStage.EXPORTING_FILES); + Path caseDirectoryPath = currentJob.getNodeData().getCaseDirectoryPath(); AutoIngestJobLogger jobLogger = new AutoIngestJobLogger(manifestPath, nodeData.getDataSourceFileName(), caseDirectoryPath); try { FileExporter fileExporter = new FileExporter(); @@ -2665,7 +2687,7 @@ public final class AutoIngestManager extends Observable implements PropertyChang // check whether any remote nodes have timed out for (AutoIngestJob job : hostNamesToRunningJobs.values()) { - if (isStale(hostNamesToLastMsgTime.get(job.getNodeName()))) { + if (isStale(hostNamesToLastMsgTime.get(job.getNodeData().getProcessingHost()))) { // remove the job from remote job running map. /* * NOTE: there is theoretically a check-then-act race @@ -2677,7 +2699,7 @@ public final class AutoIngestManager extends Observable implements PropertyChang * back into hostNamesToRunningJobs as a result of * processing the job status update. */ - hostNamesToRunningJobs.remove(job.getNodeName()); + hostNamesToRunningJobs.remove(job.getNodeData().getProcessingHost()); setChanged(); notifyObservers(Event.JOB_COMPLETED); } diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestMonitor.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestMonitor.java index 20478a1935..f84ca42522 100755 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestMonitor.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestMonitor.java @@ -19,6 +19,7 @@ package org.sleuthkit.autopsy.experimental.autoingest; import com.google.common.util.concurrent.ThreadFactoryBuilder; +import java.awt.Cursor; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.nio.file.Path; @@ -33,12 +34,14 @@ import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.logging.Level; import javax.annotation.concurrent.GuardedBy; +import org.openide.util.Exceptions; import org.sleuthkit.autopsy.coordinationservice.CoordinationService; import org.sleuthkit.autopsy.coordinationservice.CoordinationService.CoordinationServiceException; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.NetworkUtils; import org.sleuthkit.autopsy.events.AutopsyEventException; import org.sleuthkit.autopsy.events.AutopsyEventPublisher; +import org.sleuthkit.autopsy.experimental.autoingest.AutoIngestJobData.ProcessingStatus; /** * An auto ingest monitor responsible for monitoring and reporting the @@ -140,8 +143,10 @@ public final class AutoIngestMonitor extends Observable implements PropertyChang */ private void handleJobStartedEvent(AutoIngestJobStartedEvent event) { synchronized (jobsLock) { - // DLG: Remove job from pending queue, if present - // DLG: Add job to running jobs list + // DLG: TEST! Remove job from pending queue, if present + // DLG: TEST! Add job to running jobs list + jobsSnapshot.removePendingJob(event.getJob()); + jobsSnapshot.addOrReplaceRunningJob(event.getJob()); setChanged(); notifyObservers(jobsSnapshot); } @@ -154,7 +159,9 @@ public final class AutoIngestMonitor extends Observable implements PropertyChang */ private void handleJobStatusEvent(AutoIngestJobStatusEvent event) { synchronized (jobsLock) { - // DLG: Replace job in running list with job from event + // DLG: TEST! Replace job in running list with job from event + jobsSnapshot.getRunningJobs().remove((AutoIngestJob)event.getOldValue()); + jobsSnapshot.getRunningJobs().add(event.getJob()); setChanged(); notifyObservers(jobsSnapshot); } @@ -167,8 +174,10 @@ public final class AutoIngestMonitor extends Observable implements PropertyChang */ private void handleJobCompletedEvent(AutoIngestJobCompletedEvent event) { synchronized (jobsLock) { - // DLG: Remove job from event from running list, if present - // DLG: Add job to completed list + // DLG: TEST! Remove job from event from running list, if present + // DLG: TEST! Add job to completed list + jobsSnapshot.removeRunningJob(event.getJob()); + jobsSnapshot.addOrReplaceCompletedJob(event.getJob()); setChanged(); notifyObservers(jobsSnapshot); } @@ -181,8 +190,22 @@ public final class AutoIngestMonitor extends Observable implements PropertyChang */ private void handleCasePrioritizationEvent(AutoIngestCasePrioritizedEvent event) { synchronized (jobsLock) { - // DLG: Replace job in pending queue with job from event - // DLG: See 'bnPrioritizeCaseActionPerformed(ActionEvent)' in the AutoIngestControlPanel class!!! + // DLG: TEST! Replace job in pending queue with job from event + + jobsSnapshot.getPendingJobs().remove((AutoIngestJob)event.getOldValue()); + jobsSnapshot.getPendingJobs().add((AutoIngestJob)event.getNewValue()); + + /* DLG: List pendingJobsList = jobsSnapshot.getPendingJobs(); + for(int i=0; i < pendingJobsList.size(); i++) { + AutoIngestJob job = pendingJobsList.get(i); + if(job.getNodeName().equalsIgnoreCase(event.getNodeName())) { + if(job.getNodeData().getCaseName().equalsIgnoreCase(event.getCaseName())) { + int newPriority = ((AutoIngestJob)event.getNewValue()).getNodeData().getPriority(); + jobsSnapshot.getPendingJobs().get(i).getNodeData().setPriority(newPriority); + } + } + }*/ + setChanged(); notifyObservers(jobsSnapshot); } @@ -236,9 +259,37 @@ public final class AutoIngestMonitor extends Observable implements PropertyChang JobsSnapshot newJobsSnapshot = new JobsSnapshot(); List nodeList = coordinationService.getNodeList(CoordinationService.CategoryNode.MANIFESTS); for (String node : nodeList) { - // DLG: Do not need a lock here - // DLG: Get the node data and construct a AutoIngestJobNodeData object (rename AutoIngestJobNodeData => AutoIngestJobData) - // DLG: Construct an AutoIngestJob object from the AutoIngestJobNodeData object, need new AutoIngestJob constructor + try { + // DLG: DONE! Do not need a lock here + + // DLG: DONE! Get the node data and construct an AutoIngestJobData object (DONE! rename AutoIngestJobData => AutoIngestJobData) + AutoIngestJobData nodeData = new AutoIngestJobData(coordinationService.getNodeData(CoordinationService.CategoryNode.MANIFESTS, node)); + + // DLG: DONE! Construct an AutoIngestJob object from the AutoIngestJobData object, need new AutoIngestJob constructor + AutoIngestJob job = new AutoIngestJob(nodeData); + ProcessingStatus processingStatus = nodeData.getProcessingStatus(); + switch (processingStatus) { + case PENDING: + newJobsSnapshot.addOrReplacePendingJob(job); + break; + case PROCESSING: + newJobsSnapshot.addOrReplaceRunningJob(job); + break; + case COMPLETED: + newJobsSnapshot.addOrReplaceCompletedJob(job); + break; + case DELETED: + // Do nothing - we dont'want to add it to any job list or do recovery + break; + default: + LOGGER.log(Level.SEVERE, "Unknown AutoIngestJobData.ProcessingStatus"); + break; + } + } catch (InterruptedException ex) { + LOGGER.log(Level.SEVERE, "Unexpected interrupt while retrieving coordination service node data for '{0}'", node); + } catch (AutoIngestJobDataException ex) { + LOGGER.log(Level.WARNING, String.format("Unable to use node data for '%s'", node), ex); + } } return newJobsSnapshot; } catch (CoordinationServiceException ex) { @@ -277,10 +328,10 @@ public final class AutoIngestMonitor extends Observable implements PropertyChang ++highestPriority; String manifestNodePath = prioritizedJob.getNodeData().getManifestFilePath().toString(); try { - AutoIngestJobNodeData nodeData = new AutoIngestJobNodeData(coordinationService.getNodeData(CoordinationService.CategoryNode.MANIFESTS, manifestNodePath)); + AutoIngestJobData nodeData = new AutoIngestJobData(coordinationService.getNodeData(CoordinationService.CategoryNode.MANIFESTS, manifestNodePath)); nodeData.setPriority(highestPriority); coordinationService.setNodeData(CoordinationService.CategoryNode.MANIFESTS, manifestNodePath, nodeData.toArray()); - } catch (AutoIngestJobNodeDataException | CoordinationServiceException | InterruptedException ex) { + } catch (AutoIngestJobDataException | CoordinationServiceException | InterruptedException ex) { throw new AutoIngestMonitorException("Error bumping priority for job " + prioritizedJob.toString(), ex); } prioritizedJob.getNodeData().setPriority(highestPriority); diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/Bundle.properties b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/Bundle.properties index 31fa2c11f0..a22410a70e 100755 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/Bundle.properties +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/Bundle.properties @@ -27,7 +27,6 @@ AutoIngestDashboard.DeletionFailed=Deletion failed for job AutoIngestDashboard.ShowLogFailed.Title=Unable to display case log AutoIngestDashboard.ShowLogFailed.Message=Case log file does not exist AutoIngestDashboard.bnPrioritizeCase.toolTipText=Move all images associated with a case to top of Pending queue. -AutoIngestDashboard.bnPrioritizeCase.text=Prioritize Case AutoIngestDashboard.ExitConsequences=This will cancel any currently running job on this host. Exiting while a job is running potentially leaves the case in an inconsistent or corrupted state. AutoIngestDashboard.ExitingStatus=Exiting... AutoIngestDashboard.OK=OK @@ -275,3 +274,4 @@ AutoIngestDashboard.prioritizeButton.toolTipText=Prioritizes the selected job AutoIngestDashboard.prioritizeButton.text=&Prioritize AutoIngestDashboard.refreshButton.toolTipText=Refresh displayed tables AutoIngestDashboard.refreshButton.text=&Refresh +AutoIngestDashboard.jButton1.text=jButton1 From 4f8b8af6c85e3ba26717e55edc0aa0d885807145 Mon Sep 17 00:00:00 2001 From: Richard Cordovano Date: Wed, 13 Sep 2017 15:43:26 -0400 Subject: [PATCH 11/14] Remove AutoIngestJobData field from AutoIngestJob --- .../autoingest/AutoIngestControlPanel.java | 35 +-- .../autoingest/AutoIngestDashboard.java | 25 +- .../autoingest/AutoIngestJob.java | 279 ++++++++++++++---- .../autoingest/AutoIngestJobData.java | 46 +-- .../autoingest/AutoIngestManager.java | 241 ++++++++------- .../autoingest/AutoIngestMonitor.java | 12 +- .../experimental/autoingest/Manifest.java | 15 + 7 files changed, 403 insertions(+), 250 deletions(-) diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestControlPanel.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestControlPanel.java index 6d27bac166..538afac282 100755 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestControlPanel.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestControlPanel.java @@ -253,12 +253,12 @@ public final class AutoIngestControlPanel extends JPanel implements Observer { * controlling automated ingest for a single node within the cluster. */ private AutoIngestControlPanel() { - + //Disable the main window so they can only use the dashboard (if we used setVisible the taskBar icon would go away) WindowManager.getDefault().getMainWindow().setEnabled(false); - + manager = AutoIngestManager.getInstance(); - + pendingTableModel = new DefaultTableModel(JobsTableModelColumns.headers, 0) { private static final long serialVersionUID = 1L; @@ -669,8 +669,7 @@ public final class AutoIngestControlPanel extends JPanel implements Observer { @Messages({ "AutoIngestControlPanel.AutoIngestStartupError=Failed to start automated ingest. Verify Multi-user Settings.", "AutoIngestControlPanel.AutoIngestStartupFailed.Message=Failed to start automated ingest.\nPlease see auto ingest system log for details.", - "AutoIngestControlPanel.AutoIngestStartupFailed.Title=Automated Ingest Error", - }) + "AutoIngestControlPanel.AutoIngestStartupFailed.Title=Automated Ingest Error",}) private void startUp() { /* @@ -812,8 +811,7 @@ public final class AutoIngestControlPanel extends JPanel implements Observer { "AutoIngestControlPanel.PauseDueToWriteStateFilesFailure=Paused, unable to write to shared images or cases location.", "AutoIngestControlPanel.PauseDueToSharedConfigError=Paused, unable to update shared configuration.", "AutoIngestControlPanel.PauseDueToIngestJobStartFailure=Paused, unable to start ingest job processing.", - "AutoIngestControlPanel.PauseDueToFileExporterError=Paused, unable to load File Exporter settings.", - }) + "AutoIngestControlPanel.PauseDueToFileExporterError=Paused, unable to load File Exporter settings.",}) @Override public void update(Observable o, Object arg) { @@ -1076,7 +1074,7 @@ public final class AutoIngestControlPanel extends JPanel implements Observer { * @return True or fale. */ private boolean isLocalJob(AutoIngestJob job) { - return job.getNodeData().getProcessingHost().equals(LOCAL_HOST_NAME); + return job.getNodeName().equals(LOCAL_HOST_NAME); // RJCTODO: Is getProcessingHost a better name? } /** @@ -1145,20 +1143,19 @@ public final class AutoIngestControlPanel extends JPanel implements Observer { tableModel.setRowCount(0); for (AutoIngestJob job : jobs) { AutoIngestJob.StageDetails status = job.getStageDetails(); - AutoIngestJobData nodeData = job.getNodeData(); tableModel.addRow(new Object[]{ - nodeData.getCaseName(), // CASE - nodeData.getDataSourcePath().getFileName(), // DATA_SOURCE - job.getNodeData().getProcessingHost(), // HOST_NAME - nodeData.getManifestFileDate(), // CREATED_TIME - job.getNodeData().getProcessingStageStartDate(), // STARTED_TIME - nodeData.getCompletedDate(), // COMPLETED_TIME + job.getManifest().getCaseName(), // CASE + job.getManifest().getDataSourcePath().getFileName(), // DATA_SOURCE + job.getNodeName(), // HOST_NAME + job.getManifest().getDateFileCreated(), // CREATED_TIME + job.getStageStartDate(), // STARTED_TIME + job.getCompletedDate(), // COMPLETED_TIME status.getDescription(), // ACTIVITY - nodeData.getErrorsOccurred(), // STATUS + job.hasErrors(), // STATUS ((Date.from(Instant.now()).getTime()) - (status.getStartDate().getTime())), // ACTIVITY_TIME - job.getNodeData().getCaseDirectoryPath(), // CASE_DIRECTORY_PATH - job.getNodeData().getProcessingHost().equals(LOCAL_HOST_NAME), // IS_LOCAL_JOB - nodeData.getManifestFilePath()}); // MANIFEST_FILE_PATH + job.getCaseDirectoryPath(), // CASE_DIRECTORY_PATH + job.getNodeName().equals(LOCAL_HOST_NAME), // IS_LOCAL_JOB + job.getManifest().getFilePath()}); // MANIFEST_FILE_PATH } } catch (Exception ex) { SYS_LOGGER.log(Level.SEVERE, "Dashboard error refreshing table", ex); diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboard.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboard.java index 472d8ea45f..c11d82c594 100755 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboard.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboard.java @@ -462,27 +462,26 @@ public final class AutoIngestDashboard extends JPanel implements Observer { Path currentRow = getSelectedEntry(table, tableModel); tableModel.setRowCount(0); for (AutoIngestJob job : jobs) { - if (job.getNodeData().getVersion() < 1) { + if (job.getVersion() < 1) { // Ignore version '0' nodes since they don't carry enough // data to populate the table. continue; } AutoIngestJob.StageDetails status = job.getStageDetails(); - AutoIngestJobData nodeData = job.getNodeData(); tableModel.addRow(new Object[]{ - nodeData.getCaseName(), // CASE - nodeData.getDataSourcePath().getFileName(), // DATA_SOURCE - job.getNodeData().getProcessingHost(), // HOST_NAME - nodeData.getManifestFileDate(), // CREATED_TIME - job.getNodeData().getProcessingStageStartDate(), // STARTED_TIME - nodeData.getCompletedDate(), // COMPLETED_TIME + job.getManifest().getCaseName(), // CASE + job.getManifest().getDataSourcePath().getFileName(), // DATA_SOURCE + job.getNodeName(), // HOST_NAME + job.getManifest().getDateFileCreated(), // CREATED_TIME + job.getStageStartDate(), // STARTED_TIME // RJCTODO: add "processing" to method names? + job.getCompletedDate(), // COMPLETED_TIME status.getDescription(), // ACTIVITY - nodeData.getErrorsOccurred(), // STATUS + job.hasErrors(), // STATUS //RJCTODO: Change name to getErrorsOccurred for consistency? ((Date.from(Instant.now()).getTime()) - (status.getStartDate().getTime())), // ACTIVITY_TIME - job.getNodeData().getCaseDirectoryPath(), // CASE_DIRECTORY_PATH - nodeData.getManifestFilePath()//DLG: , // MANIFEST_FILE_PATH - //DLG: job - }); // JOB + job.getCaseDirectoryPath(), // CASE_DIRECTORY_PATH + job.getManifest().getFilePath()//DLG: , // MANIFEST_FILE_PATH + //DLG: Put job object in the table RJCTODO + }); } setSelectedEntry(table, tableModel, currentRow); } catch (Exception ex) { diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJob.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJob.java index 3df00a24dd..b44dcdcf78 100755 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJob.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJob.java @@ -20,7 +20,9 @@ package org.sleuthkit.autopsy.experimental.autoingest; import java.io.Serializable; import java.nio.file.Path; +import java.nio.file.Paths; import java.time.Instant; +import java.util.Collections; import java.util.Comparator; import java.util.Date; import java.util.Objects; @@ -29,7 +31,6 @@ import javax.annotation.concurrent.Immutable; import javax.annotation.concurrent.ThreadSafe; import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessor; import org.sleuthkit.autopsy.coreutils.NetworkUtils; -import org.sleuthkit.autopsy.experimental.autoingest.AutoIngestJobData.ProcessingStage; import org.sleuthkit.autopsy.ingest.IngestJob; /** @@ -40,16 +41,65 @@ import org.sleuthkit.autopsy.ingest.IngestJob; public final class AutoIngestJob implements Comparable, Serializable { private static final long serialVersionUID = 1L; + private static final int CURRENT_VERSION = 1; private static final String LOCAL_HOST_NAME = NetworkUtils.getLocalHostName(); - private final AutoIngestJobData nodeData; + private final Manifest manifest; + private final String nodeName; + @GuardedBy("this") + private String caseDirectoryPath; + @GuardedBy("this") + private Integer priority; + @GuardedBy("this") + private Stage stage; + @GuardedBy("this") + private Date stageStartDate; @GuardedBy("this") transient private DataSourceProcessor dataSourceProcessor; @GuardedBy("this") transient private IngestJob ingestJob; @GuardedBy("this") - transient private boolean canceled; + transient private boolean cancelled; @GuardedBy("this") transient private boolean completed; + @GuardedBy("this") + private Date completedDate; + @GuardedBy("this") + private boolean errorsOccurred; + private final int version; + + /** + * Constructs an automated ingest job for a manifest. The manifest specifies + * a co-located data source and a case to which the data source is to be + * added. + * + * @param manifest The manifest + * @param caseDirectoryPath The path to the case directory for the job, may + * be null. + * @param priority The priority of the job. The higher the number, + * the higher the priority. + * @param nodeName If the job is in progress, the node doing the + * processing, otherwise the locla host. + * @param stage The processing stage for display purposes. + * @param completedDate The date when the job was completed. Use the + * epoch (January 1, 1970, 00:00:00 GMT) to + * indicate the the job is not completed, i.e., new + * Date(0L). + */ + AutoIngestJob(Manifest manifest, Path caseDirectoryPath, int priority, String nodeName, Stage stage, Date completedDate, boolean errorsOccurred) { + this.manifest = manifest; + if (null != caseDirectoryPath) { + this.caseDirectoryPath = caseDirectoryPath.toString(); + } else { + this.caseDirectoryPath = ""; + } + this.priority = priority; + this.nodeName = nodeName; + this.stage = stage; + this.stageStartDate = manifest.getDateFileCreated(); + this.completedDate = completedDate; + this.errorsOccurred = errorsOccurred; + this.version = AutoIngestJob.CURRENT_VERSION; + } /** * Constructs an automated ingest job for a manifest. The manifest specifies @@ -59,38 +109,107 @@ public final class AutoIngestJob implements Comparable, Serializa * Note: Manifest objects will be phased out and no longer be part of the * AutoIngestJob class. * - * @param nodeData The node data. + * @param nodeData The node data. */ AutoIngestJob(AutoIngestJobData nodeData) { - this.nodeData = nodeData; + this.manifest = new Manifest(nodeData.getManifestFilePath(), nodeData.getManifestFileDate(), nodeData.getCaseName(), nodeData.getDeviceId(), nodeData.getDataSourcePath(), Collections.emptyMap()); + this.caseDirectoryPath = nodeData.getCaseDirectoryPath().toString(); + this.priority = nodeData.getPriority(); // RJCTODO: This should probably go into the manifest... + this.nodeName = nodeData.getProcessingHost(); + // this.stage = nodeData.getProcessingStage(); // RJCTODO + this.stageStartDate = nodeData.getProcessingStageStartDate(); + this.completedDate = nodeData.getCompletedDate(); + this.errorsOccurred = nodeData.getErrorsOccurred(); + this.version = nodeData.getVersion(); } /** - * Gets the auto ingest job node data. + * Gets the auto ingest jobmanifest. * - * @return The node data. + * @return The manifest. */ - AutoIngestJobData getNodeData() { - return this.nodeData; + Manifest getManifest() { + return this.manifest; } - synchronized void setStage(ProcessingStage newStage) { + /** + * Queries whether or not a case directory path has been set for this auto + * ingest job. + * + * @return True or false + */ + synchronized boolean hasCaseDirectoryPath() { + return (false == this.caseDirectoryPath.isEmpty()); + } + + /** + * Sets the path to the case directory of the case associated with this job. + * + * @param caseDirectoryPath The path to the case directory. + */ + synchronized void setCaseDirectoryPath(Path caseDirectoryPath) { + this.caseDirectoryPath = caseDirectoryPath.toString(); + } + + /** + * Gets the path to the case directory of the case associated with this job, + * may be null. + * + * @return The case directory path or null if the case directory has not + * been created yet. + */ + synchronized Path getCaseDirectoryPath() { + if (!caseDirectoryPath.isEmpty()) { + return Paths.get(caseDirectoryPath); + } else { + return null; + } + } + + /** + * Sets the priority of the job. A higher number indicates a higher + * priority. + * + * @param priority The priority. + */ + synchronized void setPriority(Integer priority) { + this.priority = priority; + } + + /** + * Gets the priority of the job. A higher number indicates a higher + * priority. + * + * @return The priority. + */ + synchronized Integer getPriority() { + return this.priority; + } + + synchronized void setStage(Stage newStage) { setStage(newStage, Date.from(Instant.now())); } - synchronized void setStage(ProcessingStage newStage, Date stateStartedDate) { - if (ProcessingStage.CANCELING == this.nodeData.getProcessingStage() && ProcessingStage.COMPLETED != newStage) { + synchronized void setStage(Stage newStage, Date stageStartDate) { + if (Stage.CANCELLING == this.stage && Stage.COMPLETED != newStage) { return; } - this.nodeData.setProcessingStage(newStage); - this.nodeData.setProcessingStageStartDate(stateStartedDate); + this.stage = newStage; + this.stageStartDate = stageStartDate; + } + + synchronized Stage getStage() { + return this.stage; + } + + synchronized Date getStageStartDate() { + return this.stageStartDate; } synchronized StageDetails getStageDetails() { String description; Date startDate; - ProcessingStage stage = nodeData.getProcessingStage(); - if (ProcessingStage.CANCELING != stage && null != this.ingestJob) { + if (Stage.CANCELLING != this.stage && null != this.ingestJob) { IngestJob.ProgressSnapshot progress = this.ingestJob.getSnapshot(); IngestJob.DataSourceIngestModuleHandle ingestModuleHandle = progress.runningDataSourceIngestModule(); if (null != ingestModuleHandle) { @@ -103,7 +222,7 @@ public final class AutoIngestJob implements Comparable, Serializa if (!ingestModuleHandle.isCancelled()) { description = ingestModuleHandle.displayName(); } else { - description = String.format(ProcessingStage.CANCELING_MODULE.getDisplayText(), ingestModuleHandle.displayName()); + description = String.format(Stage.CANCELLING_MODULE.getDisplayText(), ingestModuleHandle.displayName()); } } else { /** @@ -114,12 +233,12 @@ public final class AutoIngestJob implements Comparable, Serializa * parallel. For example, there is an ingest job created to * ingest each extracted virtual machine. */ - description = ProcessingStage.ANALYZING_FILES.getDisplayText(); + description = Stage.ANALYZING_FILES.getDisplayText(); startDate = progress.fileIngestStartTime(); } } else { - description = stage.getDisplayText(); - startDate = this.nodeData.getProcessingStageStartDate(); + description = this.stage.getDisplayText(); + startDate = this.stageStartDate; } return new StageDetails(description, startDate); } @@ -137,9 +256,9 @@ public final class AutoIngestJob implements Comparable, Serializa } synchronized void cancel() { - setStage(ProcessingStage.CANCELING); - canceled = true; - nodeData.setErrorsOccurred(true); + setStage(Stage.CANCELLING); + cancelled = true; + errorsOccurred = true; if (null != dataSourceProcessor) { dataSourceProcessor.cancel(); } @@ -149,11 +268,11 @@ public final class AutoIngestJob implements Comparable, Serializa } synchronized boolean isCanceled() { - return canceled; + return cancelled; } synchronized void setCompleted() { - setStage(ProcessingStage.COMPLETED); + setStage(Stage.COMPLETED); completed = true; } @@ -161,6 +280,52 @@ public final class AutoIngestJob implements Comparable, Serializa return completed; } + /** + * Sets the date the job was completed, with or without cancellation or + * errors. + * + * @param completedDate The completion date. + */ + synchronized void setCompletedDate(Date completedDate) { + this.completedDate = completedDate; + } + + /** + * Gets the date the job was completed, with or without cancellation or + * errors. + * + * @return True or false. + */ + synchronized Date getCompletedDate() { + return completedDate; + } + + /** + * Sets whether or not errors occurred during the processing of the job. + * + * @param errorsOccurred True or false; + */ + synchronized void setErrorsOccurred(boolean errorsOccurred) { + this.errorsOccurred = errorsOccurred; + } + + /** + * Queries whether or not errors occurred during the processing of the job. + * + * @return True or false. + */ + synchronized boolean hasErrors() { + return this.errorsOccurred; + } + + String getNodeName() { + return nodeName; + } + + int getVersion() { + return this.version; + } + @Override public boolean equals(Object obj) { if (!(obj instanceof AutoIngestJob)) { @@ -169,32 +334,20 @@ public final class AutoIngestJob implements Comparable, Serializa if (obj == this) { return true; } - - Path manifestPath1 = this.getNodeData().getManifestFilePath(); - Path manifestPath2 = ((AutoIngestJob) obj).getNodeData().getManifestFilePath(); - - return manifestPath1.equals(manifestPath2); + return this.getManifest().getFilePath().equals(((AutoIngestJob) obj).getManifest().getFilePath()); } @Override public int hashCode() { - int hash = 71 * (Objects.hashCode(this.nodeData.getCaseDirectoryPath())); + int hash = 71 * (Objects.hashCode(this.caseDirectoryPath)); return hash; } @Override public int compareTo(AutoIngestJob o) { - Date date1 = this.getNodeData().getManifestFileDate(); - Date date2 = o.getNodeData().getManifestFileDate(); - - return -date1.compareTo(date2); + return -this.getManifest().getDateFileCreated().compareTo(o.getManifest().getDateFileCreated()); } - @Override - public String toString() { - return nodeData.getCaseDirectoryPath().toString(); - } - /** * Custom comparator that allows us to sort List on reverse * chronological date modified (descending) @@ -203,8 +356,9 @@ public final class AutoIngestJob implements Comparable, Serializa @Override public int compare(AutoIngestJob o1, AutoIngestJob o2) { - return -o1.getNodeData().getProcessingStageStartDate().compareTo(o2.getNodeData().getProcessingStageStartDate()); + return -o1.getStageStartDate().compareTo(o2.getStageStartDate()); } + } /** @@ -214,10 +368,7 @@ public final class AutoIngestJob implements Comparable, Serializa @Override public int compare(AutoIngestJob job, AutoIngestJob anotherJob) { - Integer priority1 = job.getNodeData().getPriority(); - Integer priority2 = anotherJob.getNodeData().getPriority(); - - return -priority1.compareTo(priority2); + return -(job.getPriority().compareTo(anotherJob.getPriority())); } } @@ -231,17 +382,43 @@ public final class AutoIngestJob implements Comparable, Serializa @Override public int compare(AutoIngestJob o1, AutoIngestJob o2) { - if (o1.getNodeData().getProcessingHost().equalsIgnoreCase(LOCAL_HOST_NAME)) { + if (o1.getNodeName().equalsIgnoreCase(LOCAL_HOST_NAME)) { return -1; // o1 is for current case, float to top - } else if (o2.getNodeData().getProcessingHost().equalsIgnoreCase(LOCAL_HOST_NAME)) { + } else if (o2.getNodeName().equalsIgnoreCase(LOCAL_HOST_NAME)) { return 1; // o2 is for current case, float to top } else { - String caseName1 = o1.getNodeData().getCaseName(); - String caseName2 = o2.getNodeData().getCaseName(); - - return caseName1.compareToIgnoreCase(caseName2); + return o1.getManifest().getCaseName().compareToIgnoreCase(o2.getManifest().getCaseName()); } } + + } + + enum Stage { + + PENDING("Pending"), + STARTING("Starting"), + UPDATING_SHARED_CONFIG("Updating shared configuration"), + CHECKING_SERVICES("Checking services"), + OPENING_CASE("Opening case"), + IDENTIFYING_DATA_SOURCE("Identifying data source type"), + ADDING_DATA_SOURCE("Adding data source"), + ANALYZING_DATA_SOURCE("Analyzing data source"), + ANALYZING_FILES("Analyzing files"), + EXPORTING_FILES("Exporting files"), + CANCELLING_MODULE("Cancelling module"), + CANCELLING("Cancelling"), + COMPLETED("Completed"); + + private final String displayText; + + private Stage(String displayText) { + this.displayText = displayText; + } + + String getDisplayText() { + return displayText; + } + } @Immutable diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobData.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobData.java index 42efa2238c..46a7348657 100755 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobData.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobData.java @@ -131,7 +131,7 @@ final class AutoIngestJobData implements Serializable { this.manifestFileDate = 0L; this.manifestFilePath = ""; this.dataSourcePath = ""; - this.processingStage = (byte)ProcessingStage.PENDING.ordinal(); + this.processingStage = (byte)AutoIngestJob.Stage.PENDING.ordinal(); this.processingStageStartDate = 0L; this.processingHost = ""; } @@ -161,13 +161,13 @@ final class AutoIngestJobData implements Serializable { this.version = NODE_DATA_VERSION; this.deviceId = manifest.getDeviceId(); this.caseName = manifest.getCaseName(); - this.caseDirectoryPath = ""; //DLG: + this.caseDirectoryPath = ""; //DLG: RJCTODO: completed job has a case directory this.manifestFileDate = manifest.getDateFileCreated().getTime(); this.manifestFilePath = manifest.getFilePath().toString(); this.dataSourcePath = manifest.getDataSourcePath().toString(); - this.processingStage = (byte)ProcessingStage.PENDING.ordinal(); //DLG: - this.processingStageStartDate = 0L; //DLG: - this.processingHost = ""; //DLG: + this.processingStage = (byte)AutoIngestJob.Stage.PENDING.ordinal(); + this.processingStageStartDate = 0L; + this.processingHost = ""; } /** @@ -455,8 +455,8 @@ final class AutoIngestJobData implements Serializable { * * @return The processing stage. */ - ProcessingStage getProcessingStage() { - return ProcessingStage.values()[this.processingStage]; + AutoIngestJob.Stage getProcessingStage() { + return AutoIngestJob.Stage.values()[this.processingStage]; } /** @@ -464,7 +464,7 @@ final class AutoIngestJobData implements Serializable { * * @param processingStage The processing stage. */ - void setProcessingStage(ProcessingStage processingStage) { + void setProcessingStage(AutoIngestJob.Stage processingStage) { this.processingStage = (byte)processingStage.ordinal(); } @@ -512,7 +512,7 @@ final class AutoIngestJobData implements Serializable { * @param processingHost The host name. * @param processingStage The processing stage. */ - public void upgradeNode(Manifest manifest, Path caseDirectoryPath, String processingHost, ProcessingStage processingStage) { + public void upgradeNode(Manifest manifest, Path caseDirectoryPath, String processingHost, AutoIngestJob.Stage processingStage) { if(this.version < NODE_DATA_VERSION) { this.setVersion(NODE_DATA_VERSION); this.setDeviceId(manifest.getDeviceId()); @@ -612,32 +612,4 @@ final class AutoIngestJobData implements Serializable { DELETED } - enum ProcessingStage { - - PENDING("Pending"), - STARTING("Starting"), - UPDATING_SHARED_CONFIG("Updating shared configuration"), - CHECKING_SERVICES("Checking services"), - OPENING_CASE("Opening case"), - IDENTIFYING_DATA_SOURCE("Identifying data source type"), - ADDING_DATA_SOURCE("Adding data source"), - ANALYZING_DATA_SOURCE("Analyzing data source"), - ANALYZING_FILES("Analyzing files"), - EXPORTING_FILES("Exporting files"), - CANCELING_MODULE("Canceling module"), - CANCELING("Canceling"), - COMPLETED("Completed"); - - private final String displayText; - - private ProcessingStage(String displayText) { - this.displayText = displayText; - } - - String getDisplayText() { - return displayText; - } - - } - } diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestManager.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestManager.java index 383807347e..29c33662d9 100755 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestManager.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestManager.java @@ -95,7 +95,6 @@ import org.sleuthkit.autopsy.experimental.configuration.SharedConfiguration; import org.sleuthkit.autopsy.experimental.configuration.SharedConfiguration.SharedConfigurationException; import org.sleuthkit.autopsy.datasourceprocessors.AutoIngestDataSourceProcessor; import org.sleuthkit.autopsy.datasourceprocessors.AutoIngestDataSourceProcessor.AutoIngestDataSourceProcessorException; -import org.sleuthkit.autopsy.experimental.autoingest.AutoIngestJobData.ProcessingStage; import org.sleuthkit.autopsy.ingest.IngestJob; import org.sleuthkit.autopsy.ingest.IngestJob.CancellationReason; import org.sleuthkit.autopsy.ingest.IngestJobSettings; @@ -288,13 +287,13 @@ public final class AutoIngestManager extends Observable implements PropertyChang * @param event A job started from another auto ingest node. */ private void handleRemoteJobStartedEvent(AutoIngestJobStartedEvent event) { - String hostName = event.getJob().getNodeData().getProcessingHost(); + String hostName = event.getJob().getNodeName(); hostNamesToLastMsgTime.put(hostName, Instant.now()); synchronized (jobsLock) { - Path manifestFilePath = event.getJob().getNodeData().getManifestFilePath(); + Path manifestFilePath = event.getJob().getManifest().getFilePath(); for (Iterator iterator = pendingJobs.iterator(); iterator.hasNext();) { AutoIngestJob pendingJob = iterator.next(); - if (pendingJob.getNodeData().getManifestFilePath().equals(manifestFilePath)) { + if (pendingJob.getManifest().getFilePath().equals(manifestFilePath)) { iterator.remove(); break; } @@ -315,7 +314,7 @@ public final class AutoIngestManager extends Observable implements PropertyChang * @param event An job status event from another auto ingest node. */ private void handleRemoteJobStatusEvent(AutoIngestJobStatusEvent event) { - String hostName = event.getJob().getNodeData().getProcessingHost(); + String hostName = event.getJob().getNodeName(); hostNamesToLastMsgTime.put(hostName, Instant.now()); hostNamesToRunningJobs.put(hostName, event.getJob()); setChanged(); @@ -333,7 +332,7 @@ public final class AutoIngestManager extends Observable implements PropertyChang * @param event An job completed event from another auto ingest node. */ private void handleRemoteJobCompletedEvent(AutoIngestJobCompletedEvent event) { - String hostName = event.getJob().getNodeData().getProcessingHost(); + String hostName = event.getJob().getNodeName(); hostNamesToLastMsgTime.put(hostName, Instant.now()); hostNamesToRunningJobs.remove(hostName); if (event.shouldRetry() == false) { @@ -533,17 +532,17 @@ public final class AutoIngestManager extends Observable implements PropertyChang int maxPriority = 0; synchronized (jobsLock) { for (AutoIngestJob job : pendingJobs) { - if (job.getNodeData().getPriority() > maxPriority) { - maxPriority = job.getNodeData().getPriority(); + if (job.getPriority() > maxPriority) { + maxPriority = job.getPriority(); } - if (job.getNodeData().getCaseName().equals(caseName)) { + if (job.getManifest().getCaseName().equals(caseName)) { prioritizedJobs.add(job); } } if (!prioritizedJobs.isEmpty()) { ++maxPriority; for (AutoIngestJob job : prioritizedJobs) { - String manifestNodePath = job.getNodeData().getManifestFilePath().toString(); + String manifestNodePath = job.getManifest().getFilePath().toString(); try { AutoIngestJobData nodeData = new AutoIngestJobData(coordinationService.getNodeData(CoordinationService.CategoryNode.MANIFESTS, manifestNodePath)); nodeData.setPriority(maxPriority); @@ -555,7 +554,7 @@ public final class AutoIngestManager extends Observable implements PropertyChang } catch (InterruptedException ex) { SYS_LOGGER.log(Level.SEVERE, "Unexpected interrupt while updating coordination service node data for {0}", manifestNodePath); } - job.getNodeData().setPriority(maxPriority); + job.setPriority(maxPriority); } } @@ -583,16 +582,16 @@ public final class AutoIngestManager extends Observable implements PropertyChang AutoIngestJob prioritizedJob = null; synchronized (jobsLock) { for (AutoIngestJob job : pendingJobs) { - if (job.getNodeData().getPriority() > maxPriority) { - maxPriority = job.getNodeData().getPriority(); + if (job.getPriority() > maxPriority) { + maxPriority = job.getPriority(); } - if (job.getNodeData().getManifestFilePath().equals(manifestPath)) { + if (job.getManifest().getFilePath().equals(manifestPath)) { prioritizedJob = job; } } if (null != prioritizedJob) { ++maxPriority; - String manifestNodePath = prioritizedJob.getNodeData().getManifestFilePath().toString(); + String manifestNodePath = prioritizedJob.getManifest().getFilePath().toString(); try { AutoIngestJobData nodeData = new AutoIngestJobData(coordinationService.getNodeData(CoordinationService.CategoryNode.MANIFESTS, manifestNodePath)); nodeData.setPriority(maxPriority); @@ -604,14 +603,14 @@ public final class AutoIngestManager extends Observable implements PropertyChang } catch (InterruptedException ex) { SYS_LOGGER.log(Level.SEVERE, "Unexpected interrupt while updating coordination service node data for {0}", manifestNodePath); } - prioritizedJob.getNodeData().setPriority(maxPriority); + prioritizedJob.setPriority(maxPriority); } Collections.sort(pendingJobs, new AutoIngestJob.PriorityComparator()); } if (null != prioritizedJob) { - final String caseName = prioritizedJob.getNodeData().getCaseName(); + final String caseName = prioritizedJob.getManifest().getCaseName(); new Thread(() -> { eventPublisher.publishRemotely(new AutoIngestCasePrioritizedEvent(LOCAL_HOST_NAME, caseName)); }).start(); @@ -629,21 +628,16 @@ public final class AutoIngestManager extends Observable implements PropertyChang synchronized (jobsLock) { for (Iterator iterator = completedJobs.iterator(); iterator.hasNext();) { AutoIngestJob job = iterator.next(); - if (job.getNodeData().getManifestFilePath().equals(manifestPath)) { + if (job.getManifest().getFilePath().equals(manifestPath)) { completedJob = job; iterator.remove(); break; } } - if (null != completedJob && null != completedJob.getNodeData().getCaseDirectoryPath()) { + if (null != completedJob && null != completedJob.getCaseDirectoryPath()) { try { - AutoIngestJobData nodeData = completedJob.getNodeData(); - nodeData.setProcessingStatus(PENDING); - nodeData.setPriority(DEFAULT_JOB_PRIORITY); - nodeData.setNumberOfCrashes(0); - nodeData.setCompletedDate(new Date(0)); - nodeData.setErrorsOccurred(true); + AutoIngestJobData nodeData = new AutoIngestJobData(completedJob.getManifest(), PENDING, DEFAULT_JOB_PRIORITY, 0, new Date(0), true); coordinationService.setNodeData(CoordinationService.CategoryNode.MANIFESTS, manifestPath.toString(), nodeData.toArray()); pendingJobs.add(new AutoIngestJob(nodeData)); } catch (CoordinationServiceException ex) { @@ -796,7 +790,7 @@ public final class AutoIngestManager extends Observable implements PropertyChang private void removeJobs(Set manifestPaths, List jobs) { for (Iterator iterator = jobs.iterator(); iterator.hasNext();) { AutoIngestJob job = iterator.next(); - Path manifestPath = job.getNodeData().getManifestFilePath(); + Path manifestPath = job.getManifest().getFilePath(); if (manifestPaths.contains(manifestPath)) { iterator.remove(); } @@ -817,7 +811,7 @@ public final class AutoIngestManager extends Observable implements PropertyChang synchronized (jobsLock) { if (null != currentJob) { currentJob.cancel(); - SYS_LOGGER.log(Level.INFO, "Cancelling automated ingest for manifest {0}", currentJob.getNodeData().getManifestFilePath()); + SYS_LOGGER.log(Level.INFO, "Cancelling automated ingest for manifest {0}", currentJob.getManifest().getFilePath()); } } } @@ -836,9 +830,9 @@ public final class AutoIngestManager extends Observable implements PropertyChang if (null != ingestJob) { IngestJob.DataSourceIngestModuleHandle moduleHandle = ingestJob.getSnapshot().runningDataSourceIngestModule(); if (null != moduleHandle) { - currentJob.setStage(AutoIngestJobData.ProcessingStage.CANCELING_MODULE); + currentJob.setStage(AutoIngestJob.Stage.CANCELLING_MODULE); moduleHandle.cancel(); - SYS_LOGGER.log(Level.INFO, "Cancelling {0} module for manifest {1}", new Object[]{moduleHandle.displayName(), currentJob.getNodeData().getManifestFilePath()}); + SYS_LOGGER.log(Level.INFO, "Cancelling {0} module for manifest {1}", new Object[]{moduleHandle.displayName(), currentJob.getManifest().getFilePath()}); } } } @@ -1052,7 +1046,7 @@ public final class AutoIngestManager extends Observable implements PropertyChang } else { addNewPendingJob(manifest); } - } catch(AutoIngestJobDataException ex) { + } catch (AutoIngestJobDataException ex) { SYS_LOGGER.log(Level.WARNING, String.format("Unable to use node data for %s", manifestPath), ex); } } else { @@ -1078,21 +1072,21 @@ public final class AutoIngestManager extends Observable implements PropertyChang * This method will push a node to the coordination service if it does * not exist. If the node is an older version, it will be upgraded prior * to being pushed. - * - * @param nodeData The node data to upgrade. - * @param manifest The manifest. + * + * @param nodeData The node data to upgrade. + * @param manifest The manifest. * @param caseDirectoryPath The case directory path. - * @param processingStage The processing stage. - * + * @param processingStage The processing stage. + * * @throws CoordinationServiceException - * @throws InterruptedException + * @throws InterruptedException */ - private void pushNodeToCoordinationService(AutoIngestJobData nodeData, Manifest manifest, Path caseDirectoryPath, ProcessingStage processingStage) + private void pushNodeToCoordinationService(AutoIngestJobData nodeData, Manifest manifest, Path caseDirectoryPath, AutoIngestJob.Stage processingStage) throws CoordinationServiceException, InterruptedException { - if(nodeData.getVersion() < 1) { + if (nodeData.getVersion() < 1) { nodeData.upgradeNode(manifest, caseDirectoryPath, LOCAL_HOST_NAME, processingStage); } - if(!nodeData.coordSvcNodeDataWasSet()) { + if (!nodeData.coordSvcNodeDataWasSet()) { byte[] rawData = nodeData.toArray(); coordinationService.setNodeData(CoordinationService.CategoryNode.MANIFESTS, manifest.getFilePath().toString(), rawData); } @@ -1104,15 +1098,15 @@ public final class AutoIngestManager extends Observable implements PropertyChang * @param nodeData The data stored in the coordination service node for * the manifest. * @param manifest The manifest for upgrading the node. - * + * * @throws CoordinationServiceException - * @throws InterruptedException + * @throws InterruptedException */ private void addPendingJob(AutoIngestJobData nodeData, Manifest manifest) throws CoordinationServiceException, InterruptedException { Path caseDirectoryPath = PathUtils.findCaseDirectory(rootOutputDirectory, manifest.getCaseName()); nodeData.setCompletedDate(new Date(0)); nodeData.setErrorsOccurred(false); - pushNodeToCoordinationService(nodeData, manifest, caseDirectoryPath, ProcessingStage.PENDING); + pushNodeToCoordinationService(nodeData, manifest, caseDirectoryPath, AutoIngestJob.Stage.PENDING); newPendingJobsList.add(new AutoIngestJob(nodeData)); } @@ -1132,7 +1126,7 @@ public final class AutoIngestManager extends Observable implements PropertyChang try (Lock manifestLock = coordinationService.tryGetExclusiveLock(CoordinationService.CategoryNode.MANIFESTS, manifest.getFilePath().toString())) { if (null != manifestLock) { AutoIngestJobData newNodeData = new AutoIngestJobData(manifest, PENDING, DEFAULT_JOB_PRIORITY, 0, new Date(0), false); - pushNodeToCoordinationService(newNodeData, manifest, null, ProcessingStage.PENDING); + pushNodeToCoordinationService(newNodeData, manifest, null, AutoIngestJob.Stage.PENDING); newPendingJobsList.add(new AutoIngestJob(newNodeData)); } } catch (CoordinationServiceException ex) { @@ -1172,7 +1166,7 @@ public final class AutoIngestManager extends Observable implements PropertyChang nodeData.setErrorsOccurred(true); if (numberOfCrashes <= AutoIngestUserPreferences.getMaxNumTimesToProcessImage()) { nodeData.setProcessingStatus(PENDING); - pushNodeToCoordinationService(nodeData, manifest, caseDirectoryPath, ProcessingStage.PENDING); + pushNodeToCoordinationService(nodeData, manifest, caseDirectoryPath, AutoIngestJob.Stage.PENDING); newPendingJobsList.add(new AutoIngestJob(nodeData)); if (null != nodeData.getCaseDirectoryPath()) { try { @@ -1188,7 +1182,7 @@ public final class AutoIngestManager extends Observable implements PropertyChang } } else { nodeData.setProcessingStatus(COMPLETED); - pushNodeToCoordinationService(nodeData, manifest, caseDirectoryPath, ProcessingStage.COMPLETED); + pushNodeToCoordinationService(nodeData, manifest, caseDirectoryPath, AutoIngestJob.Stage.COMPLETED); newCompletedJobsList.add(new AutoIngestJob(nodeData)); if (null != nodeData.getCaseDirectoryPath()) { try { @@ -1228,14 +1222,14 @@ public final class AutoIngestManager extends Observable implements PropertyChang * @param nodeData The data stored in the coordination service node for * the manifest. * @param manifest The manifest for upgrading the node. - * + * * @throws CoordinationServiceException - * @throws InterruptedException + * @throws InterruptedException */ private void addCompletedJob(AutoIngestJobData nodeData, Manifest manifest) throws CoordinationServiceException, InterruptedException { Path caseDirectoryPath = PathUtils.findCaseDirectory(rootOutputDirectory, manifest.getCaseName()); if (null != caseDirectoryPath) { - pushNodeToCoordinationService(nodeData, manifest, caseDirectoryPath, ProcessingStage.COMPLETED); + pushNodeToCoordinationService(nodeData, manifest, caseDirectoryPath, AutoIngestJob.Stage.COMPLETED); newCompletedJobsList.add(new AutoIngestJob(nodeData)); } else { SYS_LOGGER.log(Level.WARNING, String.format("Job completed for %s, but cannot find case directory, ignoring job", nodeData.getManifestFilePath())); @@ -1620,13 +1614,13 @@ public final class AutoIngestManager extends Observable implements PropertyChang synchronized (jobsLock) { manifestLock = dequeueAndLockNextJob(true); if (null != manifestLock) { - SYS_LOGGER.log(Level.INFO, "Dequeued job for {0}", currentJob.getNodeData().getManifestFilePath()); + SYS_LOGGER.log(Level.INFO, "Dequeued job for {0}", currentJob.getManifest().getFilePath()); } else { SYS_LOGGER.log(Level.INFO, "No ready job"); SYS_LOGGER.log(Level.INFO, "Checking pending jobs queue for ready job, not enforcing max jobs per case"); manifestLock = dequeueAndLockNextJob(false); if (null != manifestLock) { - SYS_LOGGER.log(Level.INFO, "Dequeued job for {0}", currentJob.getNodeData().getManifestFilePath()); + SYS_LOGGER.log(Level.INFO, "Dequeued job for {0}", currentJob.getManifest().getFilePath()); } else { SYS_LOGGER.log(Level.INFO, "No ready job"); } @@ -1659,7 +1653,7 @@ public final class AutoIngestManager extends Observable implements PropertyChang Iterator iterator = pendingJobs.iterator(); while (iterator.hasNext()) { AutoIngestJob job = iterator.next(); - Path manifestPath = job.getNodeData().getManifestFilePath(); + Path manifestPath = job.getManifest().getFilePath(); manifestLock = coordinationService.tryGetExclusiveLock(CoordinationService.CategoryNode.MANIFESTS, manifestPath.toString()); if (null == manifestLock) { /* @@ -1676,8 +1670,8 @@ public final class AutoIngestManager extends Observable implements PropertyChang if (!nodeData.getProcessingStatus().equals(PENDING)) { /* * Due to a timing issue or a missed event, a - * non-pending job has ended up on the pending queue. - * Skip the job and remove it from the queue. + * non-pending job has ended up on the pending + * queue. Skip the job and remove it from the queue. */ iterator.remove(); continue; @@ -1686,7 +1680,7 @@ public final class AutoIngestManager extends Observable implements PropertyChang if (enforceMaxJobsPerCase) { int currentJobsForCase = 0; for (AutoIngestJob runningJob : hostNamesToRunningJobs.values()) { - if (0 == job.getNodeData().getCaseName().compareTo(runningJob.getNodeData().getCaseName())) { + if (0 == job.getManifest().getCaseName().compareTo(runningJob.getManifest().getCaseName())) { ++currentJobsForCase; } } @@ -1746,12 +1740,12 @@ public final class AutoIngestManager extends Observable implements PropertyChang * shutting down. */ private void processJob() throws CoordinationServiceException, SharedConfigurationException, ServicesMonitorException, DatabaseServerDownException, KeywordSearchServerDownException, CaseManagementException, AnalysisStartupException, FileExportException, AutoIngestAlertFileException, AutoIngestJobLoggerException, InterruptedException, AutoIngestDataSourceProcessor.AutoIngestDataSourceProcessorException, AutoIngestJobDataException { - Path manifestPath = currentJob.getNodeData().getManifestFilePath(); + Path manifestPath = currentJob.getManifest().getFilePath(); AutoIngestJobData nodeData = new AutoIngestJobData(coordinationService.getNodeData(CoordinationService.CategoryNode.MANIFESTS, manifestPath.toString())); nodeData.setProcessingStatus(PROCESSING); coordinationService.setNodeData(CoordinationService.CategoryNode.MANIFESTS, manifestPath.toString(), nodeData.toArray()); SYS_LOGGER.log(Level.INFO, "Started processing of {0}", manifestPath); - currentJob.setStage(AutoIngestJobData.ProcessingStage.STARTING); + currentJob.setStage(AutoIngestJob.Stage.STARTING); setChanged(); notifyObservers(Event.JOB_STARTED); eventPublisher.publishRemotely(new AutoIngestJobStartedEvent(currentJob)); @@ -1770,9 +1764,9 @@ public final class AutoIngestManager extends Observable implements PropertyChang if (currentJob.isCompleted() || currentJob.isCanceled()) { nodeData.setProcessingStatus(COMPLETED); Date completedDate = new Date(); - currentJob.getNodeData().setCompletedDate(completedDate); - nodeData.setCompletedDate(currentJob.getNodeData().getCompletedDate()); - nodeData.setErrorsOccurred(currentJob.getNodeData().getErrorsOccurred()); + currentJob.setCompletedDate(completedDate); + nodeData.setCompletedDate(currentJob.getCompletedDate()); + nodeData.setErrorsOccurred(currentJob.hasErrors()); } else { // The job may get retried nodeData.setProcessingStatus(PENDING); @@ -1782,7 +1776,7 @@ public final class AutoIngestManager extends Observable implements PropertyChang boolean retry = (!currentJob.isCanceled() && !currentJob.isCompleted()); SYS_LOGGER.log(Level.INFO, "Completed processing of {0}, retry = {1}", new Object[]{manifestPath, retry}); if (currentJob.isCanceled()) { - Path caseDirectoryPath = currentJob.getNodeData().getCaseDirectoryPath(); + Path caseDirectoryPath = currentJob.getCaseDirectoryPath(); if (null != caseDirectoryPath) { AutoIngestAlertFile.create(caseDirectoryPath); // Do this first, it is more important than the case log AutoIngestJobLogger jobLogger = new AutoIngestJobLogger(nodeData.getManifestFilePath(), nodeData.getDataSourceFileName(), caseDirectoryPath); @@ -1852,8 +1846,8 @@ public final class AutoIngestManager extends Observable implements PropertyChang try { Case.closeCurrentCase(); } catch (CaseActionException ex) { - AutoIngestJobData nodeData = currentJob.getNodeData(); - throw new CaseManagementException(String.format("Error closing case %s for %s", nodeData.getCaseName(), nodeData.getManifestFilePath()), ex); + Manifest manifest = currentJob.getManifest(); + throw new CaseManagementException(String.format("Error closing case %s for %s", manifest.getCaseName(), manifest.getFilePath()), ex); } } } @@ -1871,9 +1865,9 @@ public final class AutoIngestManager extends Observable implements PropertyChang */ private void updateConfiguration() throws SharedConfigurationException, InterruptedException { if (AutoIngestUserPreferences.getSharedConfigEnabled()) { - Path manifestPath = currentJob.getNodeData().getManifestFilePath(); + Path manifestPath = currentJob.getManifest().getFilePath(); SYS_LOGGER.log(Level.INFO, "Downloading shared configuration for {0}", manifestPath); - currentJob.setStage(AutoIngestJobData.ProcessingStage.UPDATING_SHARED_CONFIG); + currentJob.setStage(AutoIngestJob.Stage.UPDATING_SHARED_CONFIG); new SharedConfiguration().downloadConfiguration(); } } @@ -1889,9 +1883,9 @@ public final class AutoIngestManager extends Observable implements PropertyChang * down. */ private void verifyRequiredSevicesAreRunning() throws ServicesMonitorException, DatabaseServerDownException, KeywordSearchServerDownException { - Path manifestPath = currentJob.getNodeData().getManifestFilePath(); + Path manifestPath = currentJob.getManifest().getFilePath(); SYS_LOGGER.log(Level.INFO, "Checking services availability for {0}", manifestPath); - currentJob.setStage(AutoIngestJobData.ProcessingStage.CHECKING_SERVICES); + currentJob.setStage(AutoIngestJob.Stage.CHECKING_SERVICES); if (!isServiceUp(ServicesMonitor.Service.REMOTE_CASE_DATABASE.toString())) { throw new DatabaseServerDownException("Case database server is down"); } @@ -1935,10 +1929,10 @@ public final class AutoIngestManager extends Observable implements PropertyChang * if auto ingest is shutting down. */ private Case openCase() throws CoordinationServiceException, CaseManagementException, InterruptedException { - AutoIngestJobData nodeData = currentJob.getNodeData(); - String caseName = nodeData.getCaseName(); - SYS_LOGGER.log(Level.INFO, "Opening case {0} for {1}", new Object[]{caseName, nodeData.getManifestFilePath()}); - currentJob.setStage(AutoIngestJobData.ProcessingStage.OPENING_CASE); + Manifest manifest = currentJob.getManifest(); + String caseName = manifest.getCaseName(); + SYS_LOGGER.log(Level.INFO, "Opening case {0} for {1}", new Object[]{caseName, manifest.getFilePath()}); + currentJob.setStage(AutoIngestJob.Stage.OPENING_CASE); /* * Acquire and hold a case name lock so that only one node at as * time can scan the output directory at a time. This prevents @@ -1961,26 +1955,26 @@ public final class AutoIngestManager extends Observable implements PropertyChang */ Thread.sleep(AutoIngestUserPreferences.getSecondsToSleepBetweenCases() * 1000); } - currentJob.getNodeData().setCaseDirectoryPath(caseDirectoryPath); + currentJob.setCaseDirectoryPath(caseDirectoryPath); Case caseForJob = Case.getCurrentCase(); - SYS_LOGGER.log(Level.INFO, "Opened case {0} for {1}", new Object[]{caseForJob.getName(), nodeData.getManifestFilePath()}); + SYS_LOGGER.log(Level.INFO, "Opened case {0} for {1}", new Object[]{caseForJob.getName(), manifest.getFilePath()}); return caseForJob; } catch (CaseActionException ex) { - throw new CaseManagementException(String.format("Error creating or opening case %s for %s", caseName, nodeData.getManifestFilePath()), ex); + throw new CaseManagementException(String.format("Error creating or opening case %s for %s", caseName, manifest.getFilePath()), ex); } catch (IllegalStateException ex) { /* * Deal with the unfortunate fact that * Case.getCurrentCase throws IllegalStateException. */ - throw new CaseManagementException(String.format("Error getting current case %s for %s", caseName, nodeData.getManifestFilePath()), ex); + throw new CaseManagementException(String.format("Error getting current case %s for %s", caseName, manifest.getFilePath()), ex); } } else { - throw new CaseManagementException(String.format("Timed out acquiring case name lock for %s for %s", caseName, nodeData.getManifestFilePath())); + throw new CaseManagementException(String.format("Timed out acquiring case name lock for %s for %s", caseName, manifest.getFilePath())); } } } - + /** * Runs the ingest process for the current job. * @@ -2007,7 +2001,6 @@ public final class AutoIngestManager extends Observable implements PropertyChang * ingest is shutting down. */ private void runIngestForJob(Case caseForJob) throws CoordinationServiceException, AnalysisStartupException, FileExportException, AutoIngestAlertFileException, AutoIngestJobLoggerException, InterruptedException, AutoIngestDataSourceProcessor.AutoIngestDataSourceProcessorException { - Path manifestPath = currentJob.getNodeData().getManifestFilePath(); try { if (currentJob.isCanceled() || jobProcessingTaskFuture.isCancelled()) { return; @@ -2052,7 +2045,7 @@ public final class AutoIngestManager extends Observable implements PropertyChang DataSource dataSource = identifyDataSource(caseForJob); if (null == dataSource) { - currentJob.setStage(AutoIngestJobData.ProcessingStage.COMPLETED); + currentJob.setStage(AutoIngestJob.Stage.COMPLETED); return; } @@ -2062,7 +2055,7 @@ public final class AutoIngestManager extends Observable implements PropertyChang runDataSourceProcessor(caseForJob, dataSource); if (dataSource.getContent().isEmpty()) { - currentJob.setStage(AutoIngestJobData.ProcessingStage.COMPLETED); + currentJob.setStage(AutoIngestJob.Stage.COMPLETED); return; } @@ -2104,22 +2097,22 @@ public final class AutoIngestManager extends Observable implements PropertyChang * if auto ingest is shutting down. */ private DataSource identifyDataSource(Case caseForJob) throws AutoIngestAlertFileException, AutoIngestJobLoggerException, InterruptedException { - AutoIngestJobData nodeData = currentJob.getNodeData(); - Path manifestPath = nodeData.getManifestFilePath(); + Manifest manifest = currentJob.getManifest(); + Path manifestPath = manifest.getFilePath(); SYS_LOGGER.log(Level.INFO, "Identifying data source for {0} ", manifestPath); - currentJob.setStage(AutoIngestJobData.ProcessingStage.IDENTIFYING_DATA_SOURCE); - Path caseDirectoryPath = currentJob.getNodeData().getCaseDirectoryPath(); - AutoIngestJobLogger jobLogger = new AutoIngestJobLogger(manifestPath, nodeData.getDataSourceFileName(), caseDirectoryPath); - Path dataSourcePath = nodeData.getDataSourcePath(); + currentJob.setStage(AutoIngestJob.Stage.IDENTIFYING_DATA_SOURCE); + Path caseDirectoryPath = currentJob.getCaseDirectoryPath(); + AutoIngestJobLogger jobLogger = new AutoIngestJobLogger(manifestPath, manifest.getDataSourceFileName(), caseDirectoryPath); + Path dataSourcePath = manifest.getDataSourcePath(); File dataSource = dataSourcePath.toFile(); if (!dataSource.exists()) { SYS_LOGGER.log(Level.SEVERE, "Missing data source for {0}", manifestPath); - currentJob.getNodeData().setErrorsOccurred(true); + currentJob.setErrorsOccurred(true); AutoIngestAlertFile.create(caseDirectoryPath); // Do this first, it is more important than the case log jobLogger.logMissingDataSource(); return null; } - String deviceId = nodeData.getDeviceId(); + String deviceId = manifest.getDeviceId(); return new DataSource(deviceId, dataSourcePath); } @@ -2140,15 +2133,15 @@ public final class AutoIngestManager extends Observable implements PropertyChang * ingest is shutting down. */ private void runDataSourceProcessor(Case caseForJob, DataSource dataSource) throws InterruptedException, AutoIngestAlertFileException, AutoIngestJobLoggerException, AutoIngestDataSourceProcessor.AutoIngestDataSourceProcessorException { - AutoIngestJobData nodeData = currentJob.getNodeData(); - Path manifestPath = nodeData.getManifestFilePath(); + Manifest manifest = currentJob.getManifest(); + Path manifestPath = manifest.getFilePath(); SYS_LOGGER.log(Level.INFO, "Adding data source for {0} ", manifestPath); - currentJob.setStage(AutoIngestJobData.ProcessingStage.ADDING_DATA_SOURCE); + currentJob.setStage(AutoIngestJob.Stage.ADDING_DATA_SOURCE); UUID taskId = UUID.randomUUID(); DataSourceProcessorCallback callBack = new AddDataSourceCallback(caseForJob, dataSource, taskId); DataSourceProcessorProgressMonitor progressMonitor = new DoNothingDSPProgressMonitor(); - Path caseDirectoryPath = currentJob.getNodeData().getCaseDirectoryPath(); - AutoIngestJobLogger jobLogger = new AutoIngestJobLogger(manifestPath, nodeData.getDataSourceFileName(), caseDirectoryPath); + Path caseDirectoryPath = currentJob.getCaseDirectoryPath(); + AutoIngestJobLogger jobLogger = new AutoIngestJobLogger(manifestPath, manifest.getDataSourceFileName(), caseDirectoryPath); try { caseForJob.notifyAddingDataSource(taskId); @@ -2173,7 +2166,7 @@ public final class AutoIngestManager extends Observable implements PropertyChang if (validDataSourceProcessorsMap.isEmpty()) { // This should never happen. We should add all unsupported data sources as logical files. AutoIngestAlertFile.create(caseDirectoryPath); - currentJob.getNodeData().setErrorsOccurred(true); + currentJob.setErrorsOccurred(true); jobLogger.logFailedToIdentifyDataSource(); SYS_LOGGER.log(Level.WARNING, "Unsupported data source {0} for {1}", new Object[]{dataSource.getPath(), manifestPath}); // NON-NLS return; @@ -2199,7 +2192,7 @@ public final class AutoIngestManager extends Observable implements PropertyChang // if a DSP fails even if a later one succeeds since we expected to be able to process // the data source which each DSP on the list. AutoIngestAlertFile.create(caseDirectoryPath); - currentJob.getNodeData().setErrorsOccurred(true); + currentJob.setErrorsOccurred(true); jobLogger.logDataSourceProcessorError(selectedProcessor.getDataSourceType()); SYS_LOGGER.log(Level.SEVERE, "Exception while processing {0} with data source processor {1}", new Object[]{dataSource.getPath(), selectedProcessor.getDataSourceType()}); } @@ -2233,17 +2226,17 @@ public final class AutoIngestManager extends Observable implements PropertyChang * ingest is shutting down. */ private void logDataSourceProcessorResult(DataSource dataSource) throws AutoIngestAlertFileException, AutoIngestJobLoggerException, InterruptedException { - AutoIngestJobData nodeData = currentJob.getNodeData(); - Path manifestPath = nodeData.getManifestFilePath(); - Path caseDirectoryPath = currentJob.getNodeData().getCaseDirectoryPath(); - AutoIngestJobLogger jobLogger = new AutoIngestJobLogger(manifestPath, nodeData.getDataSourceFileName(), caseDirectoryPath); + Manifest manifest = currentJob.getManifest(); + Path manifestPath = manifest.getFilePath(); + Path caseDirectoryPath = currentJob.getCaseDirectoryPath(); + AutoIngestJobLogger jobLogger = new AutoIngestJobLogger(manifestPath, manifest.getDataSourceFileName(), caseDirectoryPath); DataSourceProcessorResult resultCode = dataSource.getResultDataSourceProcessorResultCode(); if (null != resultCode) { switch (resultCode) { case NO_ERRORS: jobLogger.logDataSourceAdded(); if (dataSource.getContent().isEmpty()) { - currentJob.getNodeData().setErrorsOccurred(true); + currentJob.setErrorsOccurred(true); AutoIngestAlertFile.create(caseDirectoryPath); // Do this first, it is more important than the case log jobLogger.logNoDataSourceContent(); } @@ -2255,7 +2248,7 @@ public final class AutoIngestManager extends Observable implements PropertyChang } jobLogger.logDataSourceAdded(); if (dataSource.getContent().isEmpty()) { - currentJob.getNodeData().setErrorsOccurred(true); + currentJob.setErrorsOccurred(true); AutoIngestAlertFile.create(caseDirectoryPath); // Do this first, it is more important than the case log jobLogger.logNoDataSourceContent(); } @@ -2265,7 +2258,7 @@ public final class AutoIngestManager extends Observable implements PropertyChang for (String errorMessage : dataSource.getDataSourceProcessorErrorMessages()) { SYS_LOGGER.log(Level.SEVERE, "Critical error running data source processor for {0}: {1}", new Object[]{manifestPath, errorMessage}); } - currentJob.getNodeData().setErrorsOccurred(true); + currentJob.setErrorsOccurred(true); AutoIngestAlertFile.create(caseDirectoryPath); // Do this first, it is more important than the case log jobLogger.logFailedToAddDataSource(); break; @@ -2279,7 +2272,7 @@ public final class AutoIngestManager extends Observable implements PropertyChang * cancelCurrentJob. */ SYS_LOGGER.log(Level.WARNING, "Cancellation while waiting for data source processor for {0}", manifestPath); - currentJob.getNodeData().setErrorsOccurred(true); + currentJob.setErrorsOccurred(true); AutoIngestAlertFile.create(caseDirectoryPath); // Do this first, it is more important than the case log jobLogger.logDataSourceProcessorCancelled(); } @@ -2305,12 +2298,12 @@ public final class AutoIngestManager extends Observable implements PropertyChang * ingest is shutting down. */ private void analyze(DataSource dataSource) throws AnalysisStartupException, AutoIngestAlertFileException, AutoIngestJobLoggerException, InterruptedException { - AutoIngestJobData nodeData = currentJob.getNodeData(); - Path manifestPath = nodeData.getManifestFilePath(); + Manifest manifest = currentJob.getManifest(); + Path manifestPath = manifest.getFilePath(); SYS_LOGGER.log(Level.INFO, "Starting ingest modules analysis for {0} ", manifestPath); - currentJob.setStage(AutoIngestJobData.ProcessingStage.ANALYZING_DATA_SOURCE); - Path caseDirectoryPath = currentJob.getNodeData().getCaseDirectoryPath(); - AutoIngestJobLogger jobLogger = new AutoIngestJobLogger(manifestPath, nodeData.getDataSourceFileName(), caseDirectoryPath); + currentJob.setStage(AutoIngestJob.Stage.ANALYZING_DATA_SOURCE); + Path caseDirectoryPath = currentJob.getCaseDirectoryPath(); + AutoIngestJobLogger jobLogger = new AutoIngestJobLogger(manifestPath, manifest.getDataSourceFileName(), caseDirectoryPath); IngestJobEventListener ingestJobEventListener = new IngestJobEventListener(); IngestManager.getInstance().addIngestJobEventListener(ingestJobEventListener); try { @@ -2335,7 +2328,7 @@ public final class AutoIngestManager extends Observable implements PropertyChang List cancelledModules = snapshot.getCancelledDataSourceIngestModules(); if (!cancelledModules.isEmpty()) { SYS_LOGGER.log(Level.WARNING, String.format("Ingest module(s) cancelled for %s", manifestPath)); - currentJob.getNodeData().setErrorsOccurred(true); + currentJob.setErrorsOccurred(true); AutoIngestAlertFile.create(caseDirectoryPath); // Do this first, it is more important than the case log for (String module : snapshot.getCancelledDataSourceIngestModules()) { SYS_LOGGER.log(Level.WARNING, String.format("%s ingest module cancelled for %s", module, manifestPath)); @@ -2344,8 +2337,8 @@ public final class AutoIngestManager extends Observable implements PropertyChang } jobLogger.logAnalysisCompleted(); } else { - currentJob.setStage(AutoIngestJobData.ProcessingStage.CANCELING); - currentJob.getNodeData().setErrorsOccurred(true); + currentJob.setStage(AutoIngestJob.Stage.CANCELLING); + currentJob.setErrorsOccurred(true); AutoIngestAlertFile.create(caseDirectoryPath); // Do this first, it is more important than the case log jobLogger.logAnalysisCancelled(); CancellationReason cancellationReason = snapshot.getCancellationReason(); @@ -2358,13 +2351,13 @@ public final class AutoIngestManager extends Observable implements PropertyChang for (IngestModuleError error : ingestJobStartResult.getModuleErrors()) { SYS_LOGGER.log(Level.SEVERE, String.format("%s ingest module startup error for %s", error.getModuleDisplayName(), manifestPath), error.getThrowable()); } - currentJob.getNodeData().setErrorsOccurred(true); + currentJob.setErrorsOccurred(true); AutoIngestAlertFile.create(caseDirectoryPath); // Do this first, it is more important than the case log jobLogger.logIngestModuleStartupErrors(); throw new AnalysisStartupException(String.format("Error(s) during ingest module startup for %s", manifestPath)); } else { SYS_LOGGER.log(Level.SEVERE, String.format("Ingest manager ingest job start error for %s", manifestPath), ingestJobStartResult.getStartupException()); - currentJob.getNodeData().setErrorsOccurred(true); + currentJob.setErrorsOccurred(true); AutoIngestAlertFile.create(caseDirectoryPath); // Do this first, it is more important than the case log jobLogger.logAnalysisStartupError(); throw new AnalysisStartupException("Ingest manager error starting job", ingestJobStartResult.getStartupException()); @@ -2373,7 +2366,7 @@ public final class AutoIngestManager extends Observable implements PropertyChang for (String warning : settingsWarnings) { SYS_LOGGER.log(Level.SEVERE, "Ingest job settings error for {0}: {1}", new Object[]{manifestPath, warning}); } - currentJob.getNodeData().setErrorsOccurred(true); + currentJob.setErrorsOccurred(true); AutoIngestAlertFile.create(caseDirectoryPath); // Do this first, it is more important than the case log jobLogger.logIngestJobSettingsErrors(); throw new AnalysisStartupException("Error(s) in ingest job settings"); @@ -2404,16 +2397,16 @@ public final class AutoIngestManager extends Observable implements PropertyChang * ingest is shutting down. */ private void exportFiles(DataSource dataSource) throws FileExportException, AutoIngestAlertFileException, AutoIngestJobLoggerException, InterruptedException { - AutoIngestJobData nodeData = currentJob.getNodeData(); - Path manifestPath = nodeData.getManifestFilePath(); + Manifest manifest = currentJob.getManifest(); + Path manifestPath = manifest.getFilePath(); SYS_LOGGER.log(Level.INFO, "Exporting files for {0}", manifestPath); - currentJob.setStage(AutoIngestJobData.ProcessingStage.EXPORTING_FILES); - Path caseDirectoryPath = currentJob.getNodeData().getCaseDirectoryPath(); - AutoIngestJobLogger jobLogger = new AutoIngestJobLogger(manifestPath, nodeData.getDataSourceFileName(), caseDirectoryPath); + currentJob.setStage(AutoIngestJob.Stage.EXPORTING_FILES); + Path caseDirectoryPath = currentJob.getCaseDirectoryPath(); + AutoIngestJobLogger jobLogger = new AutoIngestJobLogger(manifestPath, manifest.getDataSourceFileName(), caseDirectoryPath); try { FileExporter fileExporter = new FileExporter(); if (fileExporter.isEnabled()) { - fileExporter.process(nodeData.getDeviceId(), dataSource.getContent(), currentJob::isCanceled); + fileExporter.process(manifest.getDeviceId(), dataSource.getContent(), currentJob::isCanceled); jobLogger.logFileExportCompleted(); } else { SYS_LOGGER.log(Level.WARNING, "Exporting files not enabled for {0}", manifestPath); @@ -2421,7 +2414,7 @@ public final class AutoIngestManager extends Observable implements PropertyChang } } catch (FileExportException ex) { SYS_LOGGER.log(Level.SEVERE, String.format("Error doing file export for %s", manifestPath), ex); - currentJob.getNodeData().setErrorsOccurred(true); + currentJob.setErrorsOccurred(true); AutoIngestAlertFile.create(caseDirectoryPath); // Do this first, it is more important than the case log jobLogger.logFileExportError(); } @@ -2668,8 +2661,8 @@ public final class AutoIngestManager extends Observable implements PropertyChang boolean isError = false; if (getErrorState().equals(ErrorState.NONE)) { if (currentJob != null) { - message = "Processing " + currentJob.getNodeData().getDataSourceFileName() - + " for case " + currentJob.getNodeData().getCaseName(); + message = "Processing " + currentJob.getManifest().getDataSourceFileName() + + " for case " + currentJob.getManifest().getCaseName(); } else { message = "Paused or waiting for next case"; } @@ -2687,7 +2680,7 @@ public final class AutoIngestManager extends Observable implements PropertyChang // check whether any remote nodes have timed out for (AutoIngestJob job : hostNamesToRunningJobs.values()) { - if (isStale(hostNamesToLastMsgTime.get(job.getNodeData().getProcessingHost()))) { + if (isStale(hostNamesToLastMsgTime.get(job.getNodeName()))) { // remove the job from remote job running map. /* * NOTE: there is theoretically a check-then-act race @@ -2699,7 +2692,7 @@ public final class AutoIngestManager extends Observable implements PropertyChang * back into hostNamesToRunningJobs as a result of * processing the job status update. */ - hostNamesToRunningJobs.remove(job.getNodeData().getProcessingHost()); + hostNamesToRunningJobs.remove(job.getNodeName()); setChanged(); notifyObservers(Event.JOB_COMPLETED); } diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestMonitor.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestMonitor.java index f84ca42522..5abc0163d2 100755 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestMonitor.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestMonitor.java @@ -312,10 +312,10 @@ public final class AutoIngestMonitor extends Observable implements PropertyChang * the pending jobs queue. */ for (AutoIngestJob job : jobsSnapshot.getPendingJobs()) { - if (job.getNodeData().getPriority() > highestPriority) { - highestPriority = job.getNodeData().getPriority(); + if (job.getPriority() > highestPriority) { + highestPriority = job.getPriority(); } - if (job.getNodeData().getManifestFilePath().equals(manifestFilePath)) { + if (job.getManifest().getFilePath().equals(manifestFilePath)) { prioritizedJob = job; } } @@ -326,7 +326,7 @@ public final class AutoIngestMonitor extends Observable implements PropertyChang */ if (null != prioritizedJob) { ++highestPriority; - String manifestNodePath = prioritizedJob.getNodeData().getManifestFilePath().toString(); + String manifestNodePath = prioritizedJob.getManifest().getFilePath().toString(); try { AutoIngestJobData nodeData = new AutoIngestJobData(coordinationService.getNodeData(CoordinationService.CategoryNode.MANIFESTS, manifestNodePath)); nodeData.setPriority(highestPriority); @@ -334,14 +334,14 @@ public final class AutoIngestMonitor extends Observable implements PropertyChang } catch (AutoIngestJobDataException | CoordinationServiceException | InterruptedException ex) { throw new AutoIngestMonitorException("Error bumping priority for job " + prioritizedJob.toString(), ex); } - prioritizedJob.getNodeData().setPriority(highestPriority); + prioritizedJob.setPriority(highestPriority); } /* * Publish a prioritization event. */ if (null != prioritizedJob) { - final String caseName = prioritizedJob.getNodeData().getCaseName(); + final String caseName = prioritizedJob.getManifest().getCaseName(); new Thread(() -> { eventPublisher.publishRemotely(new AutoIngestCasePrioritizedEvent(LOCAL_HOST_NAME, caseName)); }).start(); diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/Manifest.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/Manifest.java index da195239f9..0be566d47c 100755 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/Manifest.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/Manifest.java @@ -40,6 +40,7 @@ public final class Manifest implements Serializable { private final String dataSourcePath; private final Map manifestProperties; + // RJCTODO public Manifest(Path manifestFilePath, String caseName, String deviceId, Path dataSourcePath, Map manifestProperties) throws IOException { this.filePath = manifestFilePath.toString(); BasicFileAttributes attrs = Files.readAttributes(manifestFilePath, BasicFileAttributes.class); @@ -54,6 +55,20 @@ public final class Manifest implements Serializable { this.manifestProperties = new HashMap<>(manifestProperties); } + // RJCTODO + public Manifest(Path manifestFilePath, Date dateFileCreated, String caseName, String deviceId, Path dataSourcePath, Map manifestProperties) { + this.filePath = manifestFilePath.toString(); + this.dateFileCreated = dateFileCreated; + this.caseName = caseName; + this.deviceId = deviceId; + if (dataSourcePath != null) { + this.dataSourcePath = dataSourcePath.toString(); + } else { + this.dataSourcePath = ""; + } + this.manifestProperties = new HashMap<>(manifestProperties); + } + public Path getFilePath() { return Paths.get(this.filePath); } From 7d7300f3517fc19f496ea17b31578c24315bd7a7 Mon Sep 17 00:00:00 2001 From: Ann Priestman Date: Thu, 14 Sep 2017 15:41:02 -0400 Subject: [PATCH 12/14] Add wait cursor --- .../optionspanel/ManageTagsDialog.java | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/ManageTagsDialog.java b/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/ManageTagsDialog.java index f2473a4dfe..49d9ab6d79 100755 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/ManageTagsDialog.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/ManageTagsDialog.java @@ -18,6 +18,7 @@ */ package org.sleuthkit.autopsy.centralrepository.optionspanel; +import java.awt.Cursor; import java.awt.Dimension; import java.awt.Toolkit; import java.util.ArrayList; @@ -102,7 +103,7 @@ final class ManageTagsDialog extends javax.swing.JDialog { boolean enabled = badTags.contains(tagName); model.addRow(new Object[]{tagName, enabled}); } - CheckBoxModelListener listener = new CheckBoxModelListener(); + CheckBoxModelListener listener = new CheckBoxModelListener(this); model.addTableModelListener(listener); } @@ -252,6 +253,11 @@ final class ManageTagsDialog extends javax.swing.JDialog { "ManageTagsDialog.updateCurrentCase.title=Update current case?", "ManageTagsDialog.updateCurrentCase.error=Error updating existing Central Repository entries"}) + javax.swing.JDialog dialog; + public CheckBoxModelListener(javax.swing.JDialog dialog){ + this.dialog = dialog; + } + @Override public void tableChanged(TableModelEvent e) { int row = e.getFirstRow(); @@ -272,10 +278,13 @@ final class ManageTagsDialog extends javax.swing.JDialog { dialogButton); if(dialogResult == JOptionPane.YES_OPTION){ try{ + dialog.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); setArtifactsKnownBadByTag(tagName, Case.getCurrentCase()); } catch (EamDbException ex) { LOGGER.log(Level.SEVERE, "Failed to apply known bad status to current case", ex); JOptionPane.showMessageDialog(null, Bundle.ManageTagsDialog_updateCurrentCase_error()); + } finally { + dialog.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); } } } From a43cebeca6ad2790ecf5eada43a2e53b2b51b72b Mon Sep 17 00:00:00 2001 From: Richard Cordovano Date: Thu, 14 Sep 2017 19:51:37 -0400 Subject: [PATCH 13/14] AIN/AID 1.0/AID 2.0 revisions --- .../autoingest/AutoIngestControlPanel.java | 34 +- .../autoingest/AutoIngestDashboard.java | 22 +- .../autoingest/AutoIngestJob.java | 155 +++-- .../autoingest/AutoIngestJobData.java | 615 ----------------- .../AutoIngestJobDataException.java | 46 -- .../autoingest/AutoIngestJobNodeData.java | 570 ++++++++++++++++ .../autoingest/AutoIngestManager.java | 630 +++++++++++------- .../autoingest/AutoIngestMonitor.java | 44 +- .../autoingest/AutopsyManifestFileParser.java | 114 +--- .../experimental/autoingest/Manifest.java | 21 +- 10 files changed, 1093 insertions(+), 1158 deletions(-) delete mode 100755 Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobData.java delete mode 100755 Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobDataException.java create mode 100755 Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobNodeData.java diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestControlPanel.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestControlPanel.java index 78de48530b..8f80022834 100755 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestControlPanel.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestControlPanel.java @@ -304,7 +304,7 @@ public final class AutoIngestControlPanel extends JPanel implements Observer { * text box. */ @Messages({ - "# {0} - case db status", "# {1} - search svc Status", "# {2} - coord svc Status", "# {3} - msg broker status", + "# {0} - case db status", "# {1} - search svc Status", "# {2} - coord svc Status", "# {3} - msg broker status", "AutoIngestControlPanel.tbServicesStatusMessage.Message=Case databases {0}, keyword search {1}, coordination {2}, messaging {3} ", "AutoIngestControlPanel.tbServicesStatusMessage.Message.Up=up", "AutoIngestControlPanel.tbServicesStatusMessage.Message.Down=down", @@ -679,7 +679,7 @@ public final class AutoIngestControlPanel extends JPanel implements Observer { try { manager.startUp(); autoIngestStarted = true; - } catch (AutoIngestManager.AutoIngestManagerStartupException ex) { + } catch (AutoIngestManager.AutoIngestManagerException ex) { SYS_LOGGER.log(Level.SEVERE, "Dashboard error starting up auto ingest", ex); tbStatusMessage.setText(NbBundle.getMessage(AutoIngestControlPanel.class, "AutoIngestControlPanel.AutoIngestStartupError")); manager = null; @@ -982,7 +982,7 @@ public final class AutoIngestControlPanel extends JPanel implements Observer { List completedJobs = new ArrayList<>(); manager.getJobs(pendingJobs, runningJobs, completedJobs); // Sort the completed jobs list by completed date - Collections.sort(completedJobs, new AutoIngestJob.ReverseDateCompletedComparator()); + Collections.sort(completedJobs, new AutoIngestJob.ReverseCompletedDateComparator()); EventQueue.invokeLater(new RefreshComponentsTask(pendingJobs, runningJobs, completedJobs)); } } @@ -1075,7 +1075,7 @@ public final class AutoIngestControlPanel extends JPanel implements Observer { * @return True or fale. */ private boolean isLocalJob(AutoIngestJob job) { - return job.getNodeName().equals(LOCAL_HOST_NAME); // RJCTODO: Is getProcessingHost a better name? + return job.getProcessingHostName().equals(LOCAL_HOST_NAME); } /** @@ -1147,15 +1147,15 @@ public final class AutoIngestControlPanel extends JPanel implements Observer { tableModel.addRow(new Object[]{ job.getManifest().getCaseName(), // CASE job.getManifest().getDataSourcePath().getFileName(), // DATA_SOURCE - job.getNodeName(), // HOST_NAME + job.getProcessingHostName(), // HOST_NAME job.getManifest().getDateFileCreated(), // CREATED_TIME - job.getStageStartDate(), // STARTED_TIME + job.getProcessingStageStartDate(), // STARTED_TIME job.getCompletedDate(), // COMPLETED_TIME status.getDescription(), // ACTIVITY - job.hasErrors(), // STATUS +job.getErrorsOccurred(), // STATUS ((Date.from(Instant.now()).getTime()) - (status.getStartDate().getTime())), // ACTIVITY_TIME job.getCaseDirectoryPath(), // CASE_DIRECTORY_PATH - job.getNodeName().equals(LOCAL_HOST_NAME), // IS_LOCAL_JOB + job.getProcessingHostName().equals(LOCAL_HOST_NAME), // IS_LOCAL_JOB job.getManifest().getFilePath()}); // MANIFEST_FILE_PATH } } catch (Exception ex) { @@ -1701,11 +1701,17 @@ public final class AutoIngestControlPanel extends JPanel implements Observer { * * @param evt The button click event. */ + @Messages({"AutoIngestControlPanel.casePrioritization.errorMessage=An error occurred when prioritizing the case. Some or all jobs may not have been prioritized."}) private void bnPrioritizeCaseActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_bnPrioritizeCaseActionPerformed if (pendingTableModel.getRowCount() > 0 && pendingTable.getSelectedRow() >= 0) { this.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); String caseName = (pendingTableModel.getValueAt(pendingTable.getSelectedRow(), JobsTableModelColumns.CASE.ordinal())).toString(); - manager.prioritizeCase(caseName); + try { + manager.prioritizeCase(caseName); + } catch (AutoIngestManager.AutoIngestManagerException ex) { + SYS_LOGGER.log(Level.SEVERE, "Error prioritizing a case", ex); + MessageNotifyUtil.Message.error(Bundle.AutoIngestControlPanel_casePrioritization_errorMessage()); + } refreshTables(); pendingTable.clearSelection(); enablePendingTableButtons(false); @@ -1753,12 +1759,18 @@ public final class AutoIngestControlPanel extends JPanel implements Observer { options[0]); } }//GEN-LAST:event_bnShowCaseLogActionPerformed - + + @Messages({"AutoIngestControlPanel.jobPrioritization.errorMessage=An error occurred when prioritizing the job."}) private void bnPrioritizeJobActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_bnPrioritizeJobActionPerformed if (pendingTableModel.getRowCount() > 0 && pendingTable.getSelectedRow() >= 0) { this.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); Path manifestFilePath = (Path) (pendingTableModel.getValueAt(pendingTable.getSelectedRow(), JobsTableModelColumns.MANIFEST_FILE_PATH.ordinal())); - manager.prioritizeJob(manifestFilePath); + try { + manager.prioritizeJob(manifestFilePath); + } catch (AutoIngestManager.AutoIngestManagerException ex) { + SYS_LOGGER.log(Level.SEVERE, "Error prioritizing a case", ex); + MessageNotifyUtil.Message.error(Bundle.AutoIngestControlPanel_jobPrioritization_errorMessage()); + } refreshTables(); pendingTable.clearSelection(); enablePendingTableButtons(false); diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboard.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboard.java index c11d82c594..6782140e7e 100755 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboard.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboard.java @@ -31,7 +31,6 @@ import java.util.logging.Level; import javax.swing.DefaultListSelectionModel; import java.awt.Color; import java.beans.PropertyChangeEvent; -import java.util.Collections; import javax.swing.JPanel; import javax.swing.JTable; import javax.swing.SwingWorker; @@ -438,11 +437,9 @@ public final class AutoIngestDashboard extends JPanel implements Observer { List pendingJobs = jobsSnapshot.getPendingJobs(); List runningJobs = jobsSnapshot.getRunningJobs(); List completedJobs = jobsSnapshot.getCompletedJobs(); - - // DLG: DONE! Do the appropriate sorts for each table. - Collections.sort(pendingJobs, new AutoIngestJob.PriorityComparator()); - runningJobs.sort(new AutoIngestJob.AlphabeticalComparator()); - + pendingJobs.sort(new AutoIngestJob.PriorityComparator()); + runningJobs.sort(new AutoIngestJob.CaseNameAndProcessingHostComparator()); + completedJobs.sort(new AutoIngestJob.ReverseCompletedDateComparator()); refreshTable(pendingJobs, pendingTable, pendingTableModel); refreshTable(runningJobs, runningTable, runningTableModel); refreshTable(completedJobs, completedTable, completedTableModel); @@ -470,17 +467,16 @@ public final class AutoIngestDashboard extends JPanel implements Observer { AutoIngestJob.StageDetails status = job.getStageDetails(); tableModel.addRow(new Object[]{ job.getManifest().getCaseName(), // CASE - job.getManifest().getDataSourcePath().getFileName(), // DATA_SOURCE - job.getNodeName(), // HOST_NAME + job.getManifest().getDataSourcePath().getFileName(), job.getProcessingHostName(), // HOST_NAME job.getManifest().getDateFileCreated(), // CREATED_TIME - job.getStageStartDate(), // STARTED_TIME // RJCTODO: add "processing" to method names? + job.getProcessingStageStartDate(), // STARTED_TIME job.getCompletedDate(), // COMPLETED_TIME status.getDescription(), // ACTIVITY - job.hasErrors(), // STATUS //RJCTODO: Change name to getErrorsOccurred for consistency? + job.getErrorsOccurred(), // STATUS ((Date.from(Instant.now()).getTime()) - (status.getStartDate().getTime())), // ACTIVITY_TIME job.getCaseDirectoryPath(), // CASE_DIRECTORY_PATH - job.getManifest().getFilePath()//DLG: , // MANIFEST_FILE_PATH - //DLG: Put job object in the table RJCTODO + job.getManifest().getFilePath() // MANIFEST_FILE_PATH + //DLG: Put job object in the table }); } setSelectedEntry(table, tableModel, currentRow); @@ -591,7 +587,7 @@ public final class AutoIngestDashboard extends JPanel implements Observer { STAGE_TIME.getColumnHeader(), CASE_DIRECTORY_PATH.getColumnHeader(), MANIFEST_FILE_PATH.getColumnHeader() //DLG: , - //DLG: JOB.getColumnHeader() + //DLG: JOB.getColumnHeader() }; }; diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJob.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJob.java index b44dcdcf78..d4986c82c7 100755 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJob.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJob.java @@ -42,7 +42,9 @@ public final class AutoIngestJob implements Comparable, Serializa private static final long serialVersionUID = 1L; private static final int CURRENT_VERSION = 1; + private static final int DEFAULT_PRIORITY = 0; private static final String LOCAL_HOST_NAME = NetworkUtils.getLocalHostName(); + private final int version; private final Manifest manifest; private final String nodeName; @GuardedBy("this") @@ -54,6 +56,8 @@ public final class AutoIngestJob implements Comparable, Serializa @GuardedBy("this") private Date stageStartDate; @GuardedBy("this") + private StageDetails stageDetails; + @GuardedBy("this") transient private DataSourceProcessor dataSourceProcessor; @GuardedBy("this") transient private IngestJob ingestJob; @@ -65,40 +69,34 @@ public final class AutoIngestJob implements Comparable, Serializa private Date completedDate; @GuardedBy("this") private boolean errorsOccurred; - private final int version; + @GuardedBy("this") + private ProcessingStatus processingStatus; + @GuardedBy("this") + private int numberOfCrashes; /** - * Constructs an automated ingest job for a manifest. The manifest specifies - * a co-located data source and a case to which the data source is to be - * added. + * Constructs a new automated ingest job for a manifest. All job state not + * specified in the manifest is set to the default state for a new job. * - * @param manifest The manifest - * @param caseDirectoryPath The path to the case directory for the job, may - * be null. - * @param priority The priority of the job. The higher the number, - * the higher the priority. - * @param nodeName If the job is in progress, the node doing the - * processing, otherwise the locla host. - * @param stage The processing stage for display purposes. - * @param completedDate The date when the job was completed. Use the - * epoch (January 1, 1970, 00:00:00 GMT) to - * indicate the the job is not completed, i.e., new - * Date(0L). + * @param manifest The manifest. */ - AutoIngestJob(Manifest manifest, Path caseDirectoryPath, int priority, String nodeName, Stage stage, Date completedDate, boolean errorsOccurred) { + AutoIngestJob(Manifest manifest) { + this.version = CURRENT_VERSION; this.manifest = manifest; - if (null != caseDirectoryPath) { - this.caseDirectoryPath = caseDirectoryPath.toString(); - } else { - this.caseDirectoryPath = ""; - } - this.priority = priority; - this.nodeName = nodeName; - this.stage = stage; + this.nodeName = AutoIngestJob.LOCAL_HOST_NAME; + this.caseDirectoryPath = ""; + this.priority = DEFAULT_PRIORITY; + this.stage = Stage.PENDING; this.stageStartDate = manifest.getDateFileCreated(); - this.completedDate = completedDate; - this.errorsOccurred = errorsOccurred; - this.version = AutoIngestJob.CURRENT_VERSION; + this.stageDetails = this.getStageDetails(); + this.dataSourceProcessor = null; + this.ingestJob = null; + this.cancelled = false; + this.completed = false; + this.completedDate = new Date(0); + this.errorsOccurred = false; + this.processingStatus = ProcessingStatus.PENDING; + this.numberOfCrashes = 0; } /** @@ -111,20 +109,27 @@ public final class AutoIngestJob implements Comparable, Serializa * * @param nodeData The node data. */ - AutoIngestJob(AutoIngestJobData nodeData) { + AutoIngestJob(AutoIngestJobNodeData nodeData) { + this.version = nodeData.getVersion(); this.manifest = new Manifest(nodeData.getManifestFilePath(), nodeData.getManifestFileDate(), nodeData.getCaseName(), nodeData.getDeviceId(), nodeData.getDataSourcePath(), Collections.emptyMap()); + this.nodeName = nodeData.getProcessingHostName(); this.caseDirectoryPath = nodeData.getCaseDirectoryPath().toString(); - this.priority = nodeData.getPriority(); // RJCTODO: This should probably go into the manifest... - this.nodeName = nodeData.getProcessingHost(); - // this.stage = nodeData.getProcessingStage(); // RJCTODO + this.priority = nodeData.getPriority(); + this.stage = nodeData.getProcessingStage(); this.stageStartDate = nodeData.getProcessingStageStartDate(); + this.stageDetails = this.getStageDetails(); + this.dataSourceProcessor = null; + this.ingestJob = null; + this.cancelled = false; + this.completed = false; this.completedDate = nodeData.getCompletedDate(); this.errorsOccurred = nodeData.getErrorsOccurred(); - this.version = nodeData.getVersion(); + this.processingStatus = nodeData.getProcessingStatus(); + this.numberOfCrashes = nodeData.getNumberOfCrashes(); } /** - * Gets the auto ingest jobmanifest. + * Gets the auto ingest job manifest. * * @return The manifest. */ @@ -132,16 +137,6 @@ public final class AutoIngestJob implements Comparable, Serializa return this.manifest; } - /** - * Queries whether or not a case directory path has been set for this auto - * ingest job. - * - * @return True or false - */ - synchronized boolean hasCaseDirectoryPath() { - return (false == this.caseDirectoryPath.isEmpty()); - } - /** * Sets the path to the case directory of the case associated with this job. * @@ -187,23 +182,19 @@ public final class AutoIngestJob implements Comparable, Serializa } synchronized void setStage(Stage newStage) { - setStage(newStage, Date.from(Instant.now())); - } - - synchronized void setStage(Stage newStage, Date stageStartDate) { if (Stage.CANCELLING == this.stage && Stage.COMPLETED != newStage) { return; } this.stage = newStage; - this.stageStartDate = stageStartDate; + this.stageStartDate = Date.from(Instant.now()); } - synchronized Stage getStage() { + synchronized Stage getProcessingStage() { return this.stage; } - synchronized Date getStageStartDate() { - return this.stageStartDate; + synchronized Date getProcessingStageStartDate() { + return new Date(this.stageStartDate.getTime()); } synchronized StageDetails getStageDetails() { @@ -240,9 +231,14 @@ public final class AutoIngestJob implements Comparable, Serializa description = this.stage.getDisplayText(); startDate = this.stageStartDate; } - return new StageDetails(description, startDate); + this.stageDetails = new StageDetails(description, startDate); + return this.stageDetails; } + synchronized void setStageDetails(StageDetails stageDetails) { + this.stageDetails = stageDetails; + } + synchronized void setDataSourceProcessor(DataSourceProcessor dataSourceProcessor) { this.dataSourceProcessor = dataSourceProcessor; } @@ -287,7 +283,7 @@ public final class AutoIngestJob implements Comparable, Serializa * @param completedDate The completion date. */ synchronized void setCompletedDate(Date completedDate) { - this.completedDate = completedDate; + this.completedDate = new Date(completedDate.getTime()); } /** @@ -297,7 +293,7 @@ public final class AutoIngestJob implements Comparable, Serializa * @return True or false. */ synchronized Date getCompletedDate() { - return completedDate; + return new Date(completedDate.getTime()); } /** @@ -314,18 +310,34 @@ public final class AutoIngestJob implements Comparable, Serializa * * @return True or false. */ - synchronized boolean hasErrors() { + synchronized boolean getErrorsOccurred() { return this.errorsOccurred; } - String getNodeName() { + synchronized String getProcessingHostName() { return nodeName; } int getVersion() { return this.version; } - + + synchronized ProcessingStatus getProcessingStatus() { + return this.processingStatus; + } + + synchronized void setProcessingStatus(ProcessingStatus processingStatus) { + this.processingStatus = processingStatus; + } + + synchronized int getNumberOfCrashes() { + return this.numberOfCrashes; + } + + synchronized void setNumberOfCrashes(int numberOfCrashes) { + this.numberOfCrashes = numberOfCrashes; + } + @Override public boolean equals(Object obj) { if (!(obj instanceof AutoIngestJob)) { @@ -352,11 +364,11 @@ public final class AutoIngestJob implements Comparable, Serializa * Custom comparator that allows us to sort List on reverse * chronological date modified (descending) */ - static class ReverseDateCompletedComparator implements Comparator { + static class ReverseCompletedDateComparator implements Comparator { @Override public int compare(AutoIngestJob o1, AutoIngestJob o2) { - return -o1.getStageStartDate().compareTo(o2.getStageStartDate()); + return -o1.getCompletedDate().compareTo(o2.getCompletedDate()); } } @@ -378,13 +390,13 @@ public final class AutoIngestJob implements Comparable, Serializa * alphabetically except for jobs for the current host, which are placed at * the top of the list. */ - static class AlphabeticalComparator implements Comparator { + static class CaseNameAndProcessingHostComparator implements Comparator { @Override public int compare(AutoIngestJob o1, AutoIngestJob o2) { - if (o1.getNodeName().equalsIgnoreCase(LOCAL_HOST_NAME)) { + if (o1.getProcessingHostName().equalsIgnoreCase(LOCAL_HOST_NAME)) { return -1; // o1 is for current case, float to top - } else if (o2.getNodeName().equalsIgnoreCase(LOCAL_HOST_NAME)) { + } else if (o2.getProcessingHostName().equalsIgnoreCase(LOCAL_HOST_NAME)) { return 1; // o2 is for current case, float to top } else { return o1.getManifest().getCaseName().compareToIgnoreCase(o2.getManifest().getCaseName()); @@ -393,6 +405,16 @@ public final class AutoIngestJob implements Comparable, Serializa } + /** + * Processing status for the auto ingest job for the manifest. + */ + enum ProcessingStatus { + PENDING, + PROCESSING, + COMPLETED, + DELETED + } + enum Stage { PENDING("Pending"), @@ -422,12 +444,13 @@ public final class AutoIngestJob implements Comparable, Serializa } @Immutable - static final class StageDetails { + static final class StageDetails implements Serializable { + private static final long serialVersionUID = 1L; private final String description; private final Date startDate; - private StageDetails(String description, Date startDate) { + StageDetails(String description, Date startDate) { this.description = description; this.startDate = startDate; } @@ -437,7 +460,7 @@ public final class AutoIngestJob implements Comparable, Serializa } Date getStartDate() { - return this.startDate; + return new Date(this.startDate.getTime()); } } diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobData.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobData.java deleted file mode 100755 index 46a7348657..0000000000 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobData.java +++ /dev/null @@ -1,615 +0,0 @@ -/* - * 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.experimental.autoingest; - -import java.io.Serializable; -import java.nio.ByteBuffer; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.Date; -import javax.lang.model.type.TypeKind; - -/** - * A coordination service node data transfer object for an auto ingest job. - */ -final class AutoIngestJobData implements Serializable { - - private static final long serialVersionUID = 1L; - private static final int NODE_DATA_VERSION = 1; - private static final int MAX_POSSIBLE_NODE_DATA_SIZE = 131493; - private static final int DEFAULT_PRIORITY = 0; - - /* - * Version 0 fields. - */ - private final boolean coordSvcNodeDataWasSet; - private int processingStatus; - private int priority; - private int numberOfCrashes; - private long completedDate; - private boolean errorsOccurred; - - /* - * Version 1 fields. - */ - private int version; - private String deviceId; - private String caseName; - private String caseDirectoryPath; - private long manifestFileDate; - private String manifestFilePath; - private String dataSourcePath; - private byte processingStage; - private long processingStageStartDate; - private String processingHost; - - //DLG: Add caseDirectoryPath from AutoIngestJob - /* - * DLG: DONE! Rename class to AutoIngestJobData - Add String - * caseDirectoryPath. Needed to locate case auto ingest log and later, for - * case deletion - * - * DLG: Add String processingStage, long processingStageStartDate, String - * processingHost fields. These three fields are needed to populate running - * jobs table; use of auto ingest job data is not enough, because there - * would be no data until a status event was received by the auto ingest - * monitor. - * - * DLG: Update the AutoIngestManager code that creates ZK nodes for auto ingest - * jobs to write the new fields described above to new nodes - * - * DLG: Update the AutoIngestManager code that publishes auto ingest status - * events for the current job to update the the processing status fields - * described above in addition to publishing AutoIngestJobStatusEvents. - * Probably also need to write this data initially when a jo becomes the - * current job. - */ - /** - * Constructs a coordination service node data data transfer object for an - * auto ingest manifest from the raw bytes obtained from the coordination - * service. - * - * @param nodeData The raw bytes received from the coordination service. - */ - AutoIngestJobData(byte[] nodeData) throws AutoIngestJobDataException { - ByteBuffer buffer = ByteBuffer.wrap(nodeData); - this.coordSvcNodeDataWasSet = buffer.hasRemaining(); - if (this.coordSvcNodeDataWasSet) { - this.processingStatus = buffer.getInt(); - this.priority = buffer.getInt(); - this.numberOfCrashes = buffer.getInt(); - this.completedDate = buffer.getLong(); - int errorFlag = buffer.getInt(); - this.errorsOccurred = (1 == errorFlag); - } else { - this.processingStatus = ProcessingStatus.PENDING.ordinal(); - this.priority = DEFAULT_PRIORITY; - this.numberOfCrashes = 0; - this.completedDate = 0L; - this.errorsOccurred = false; - } - - if (buffer.hasRemaining()) { - /* - * There are more than 24 bytes in the buffer, so we assume the - * version is greater than '0'. - */ - this.version = buffer.getInt(); - if (this.version > NODE_DATA_VERSION) { - throw new AutoIngestJobDataException(String.format("Node data version %d is not suppored.", this.version)); - } - this.deviceId = getStringFromBuffer(buffer, TypeKind.BYTE); - this.caseName = getStringFromBuffer(buffer, TypeKind.BYTE); - this.caseDirectoryPath = getStringFromBuffer(buffer, TypeKind.SHORT); - this.manifestFileDate = buffer.getLong(); - this.manifestFilePath = getStringFromBuffer(buffer, TypeKind.SHORT); - this.dataSourcePath = getStringFromBuffer(buffer, TypeKind.SHORT); - this.processingStage = buffer.get(); - this.processingStageStartDate = buffer.getLong(); - this.processingHost = getStringFromBuffer(buffer, TypeKind.SHORT); - } else { - this.version = 0; - this.deviceId = ""; - this.caseName = ""; - this.caseDirectoryPath = ""; - this.manifestFileDate = 0L; - this.manifestFilePath = ""; - this.dataSourcePath = ""; - this.processingStage = (byte)AutoIngestJob.Stage.PENDING.ordinal(); - this.processingStageStartDate = 0L; - this.processingHost = ""; - } - } - - /** - * Constructs a coordination service node data data transfer object for an - * auto ingest manifest from values provided by the auto ingest system. - * - * @param manifest The manifest - * @param status The processing status of the manifest. - * @param priority The priority of the manifest. - * @param numberOfCrashes The number of times auto ingest jobs for the - * manifest have crashed during processing. - * @param completedDate The date the auto ingest job for the manifest was - * completed. - * @param errorsOccurred Boolean to determine if errors have occurred. - */ - AutoIngestJobData(Manifest manifest, ProcessingStatus status, int priority, int numberOfCrashes, Date completedDate, boolean errorOccurred) { - this.coordSvcNodeDataWasSet = false; - this.processingStatus = status.ordinal(); - this.priority = priority; - this.numberOfCrashes = numberOfCrashes; - this.completedDate = completedDate.getTime(); - this.errorsOccurred = errorOccurred; - - this.version = NODE_DATA_VERSION; - this.deviceId = manifest.getDeviceId(); - this.caseName = manifest.getCaseName(); - this.caseDirectoryPath = ""; //DLG: RJCTODO: completed job has a case directory - this.manifestFileDate = manifest.getDateFileCreated().getTime(); - this.manifestFilePath = manifest.getFilePath().toString(); - this.dataSourcePath = manifest.getDataSourcePath().toString(); - this.processingStage = (byte)AutoIngestJob.Stage.PENDING.ordinal(); - this.processingStageStartDate = 0L; - this.processingHost = ""; - } - - /** - * Indicates whether or not the coordination service node data was set, - * i.e., this object was constructed from raw bytes from the ccordination - * service node for the manifest. - * - * @return True or false. - */ - boolean coordSvcNodeDataWasSet() { - return this.coordSvcNodeDataWasSet; - } - - /** - * Gets the processing status of the manifest - * - * @return The processing status of the manifest. - */ - ProcessingStatus getProcessingStatus() { - return ProcessingStatus.values()[this.processingStatus]; - } - - /** - * Sets the processing status of the manifest - * - * @param processingSatus The processing status of the manifest. - */ - void setProcessingStatus(ProcessingStatus processingStatus) { - this.processingStatus = processingStatus.ordinal(); - } - - /** - * Gets the priority of the manifest. - * - * @return The priority of the manifest. - */ - int getPriority() { - return this.priority; - } - - /** - * Sets the priority of the manifest. A higher number indicates a higheer - * priority. - * - * @param priority The priority of the manifest. - */ - void setPriority(int priority) { - this.priority = priority; - } - - /** - * Gets the number of times auto ingest jobs for the manifest have crashed - * during processing. - * - * @return The number of times auto ingest jobs for the manifest have - * crashed during processing. - */ - int getNumberOfCrashes() { - return this.numberOfCrashes; - } - - /** - * Sets the number of times auto ingest jobs for the manifest have crashed - * during processing. - * - * @param numberOfCrashes The number of times auto ingest jobs for the - * manifest have crashed during processing. - */ - void setNumberOfCrashes(int numberOfCrashes) { - this.numberOfCrashes = numberOfCrashes; - } - - /** - * Gets the date the auto ingest job for the manifest was completed. - * - * @return The date the auto ingest job for the manifest was completed. The - * epoch (January 1, 1970, 00:00:00 GMT) indicates the date is not - * set, i.e., Date.getTime() returns 0L. - */ - Date getCompletedDate() { - return new Date(this.completedDate); - } - - /** - * Sets the date the auto ingest job for the manifest was completed. - * - * @param completedDate The date the auto ingest job for the manifest was - * completed. Use the epoch (January 1, 1970, 00:00:00 - * GMT) to indicate the date is not set, i.e., new - * Date(0L). - */ - void setCompletedDate(Date completedDate) { - this.completedDate = completedDate.getTime(); - } - - /** - * Queries whether or not any errors occurred during the processing of the - * auto ingest job for the manifest. - * - * @return True or false. - */ - boolean getErrorsOccurred() { - return this.errorsOccurred; - } - - /** - * Sets whether or not any errors occurred during the processing of the auto - * ingest job for the manifest. - * - * @param errorsOccurred True or false. - */ - void setErrorsOccurred(boolean errorsOccurred) { - this.errorsOccurred = errorsOccurred; - } - - /** - * Get the node data version. - * - * @return The node data version. - */ - int getVersion() { - return this.version; - } - - /** - * Set the node data version. - * - * @param version The node data version. - */ - void setVersion(int version) { - this.version = version; - } - - /** - * Get the device ID. - * - * @return The device ID. - */ - String getDeviceId() { - return this.deviceId; - } - - /** - * Set the device ID. - * - * @param deviceId The device ID. - */ - void setDeviceId(String deviceId) { - this.deviceId = deviceId; - } - - /** - * Get the case name. - * - * @return The case name. - */ - String getCaseName() { - return this.caseName; - } - - /** - * Set the case name. - * - * @param caseName The case name. - */ - void setCaseName(String caseName) { - this.caseName = caseName; - } - - /** - * Queries whether or not a case directory path has been set for this auto - * ingest job. - * - * @return True or false - */ - synchronized boolean hasCaseDirectoryPath() { - return (false == this.caseDirectoryPath.isEmpty()); - } - - /** - * Sets the path to the case directory of the case associated with this job. - * - * @param caseDirectoryPath The path to the case directory. - */ - synchronized void setCaseDirectoryPath(Path caseDirectoryPath) { - if(caseDirectoryPath == null) { - this.caseDirectoryPath = ""; - } else { - this.caseDirectoryPath = caseDirectoryPath.toString(); - } - } - - /** - * Gets the path to the case directory of the case associated with this job, - * may be null. - * - * @return The case directory path or null if the case directory has not - * been created yet. - */ - synchronized Path getCaseDirectoryPath() { - if (!caseDirectoryPath.isEmpty()) { - return Paths.get(caseDirectoryPath); - } else { - return null; - } - } - - /** - * Gets the date the manifest was created. - * - * @return The date the manifest was created. The epoch (January 1, 1970, - * 00:00:00 GMT) indicates the date is not set, i.e., Date.getTime() - * returns 0L. - */ - Date getManifestFileDate() { - return new Date(this.manifestFileDate); - } - - /** - * Sets the date the manifest was created. - * - * @param manifestFileDate The date the manifest was created. Use the epoch - * (January 1, 1970, 00:00:00 GMT) to indicate the - * date is not set, i.e., new Date(0L). - */ - void setManifestFileDate(Date manifestFileDate) { - this.manifestFileDate = manifestFileDate.getTime(); - } - - /** - * Get the manifest file path. - * - * @return The manifest file path. - */ - Path getManifestFilePath() { - return Paths.get(this.manifestFilePath); - } - - /** - * Set the manifest file path. - * - * @param manifestFilePath The manifest file path. - */ - void setManifestFilePath(Path manifestFilePath) { - if (manifestFilePath != null) { - this.manifestFilePath = manifestFilePath.toString(); - } else { - this.manifestFilePath = ""; - } - } - - /** - * Get the data source path. - * - * @return The data source path. - */ - Path getDataSourcePath() { - return Paths.get(dataSourcePath); - } - - /** - * Get the file name portion of the data source path. - * - * @return The data source file name. - */ - public String getDataSourceFileName() { - return Paths.get(dataSourcePath).getFileName().toString(); - } - - /** - * Set the data source path. - * - * @param dataSourcePath The data source path. - */ - void setDataSourcePath(Path dataSourcePath) { - if (dataSourcePath != null) { - this.dataSourcePath = dataSourcePath.toString(); - } else { - this.dataSourcePath = ""; - } - } - - /** - * Get the processing stage. - * - * @return The processing stage. - */ - AutoIngestJob.Stage getProcessingStage() { - return AutoIngestJob.Stage.values()[this.processingStage]; - } - - /** - * Set the processing stage. - * - * @param processingStage The processing stage. - */ - void setProcessingStage(AutoIngestJob.Stage processingStage) { - this.processingStage = (byte)processingStage.ordinal(); - } - - /** - * Get the processing stage start date. - * - * @return The processing stage start date. - */ - Date getProcessingStageStartDate() { - return new Date(this.processingStageStartDate); - } - - /** - * Set the processing stage start date. - * - * @param processingStageStartDate The processing stage start date. - */ - void setProcessingStageStartDate(Date processingStageStartDate) { - this.processingStageStartDate = processingStageStartDate.getTime(); - } - - /** - * Get the processing host. - * - * @return The processing host. - */ - String getProcessingHost() { - return this.processingHost; - } - - /** - * Set the processing host. - * - * @param processingHost The processing host. - */ - void setProcessingHost(String processingHost) { - this.processingHost = processingHost; - } - - /** - * This method will upgrade the node data to the latest version. - * - * @param manifest The manifest. - * @param caseDirectoryPath The case directory path. - * @param processingHost The host name. - * @param processingStage The processing stage. - */ - public void upgradeNode(Manifest manifest, Path caseDirectoryPath, String processingHost, AutoIngestJob.Stage processingStage) { - if(this.version < NODE_DATA_VERSION) { - this.setVersion(NODE_DATA_VERSION); - this.setDeviceId(manifest.getDeviceId()); - this.setCaseName(manifest.getCaseName()); - this.setCaseDirectoryPath(caseDirectoryPath); - this.setManifestFileDate(manifest.getDateFileCreated()); - this.setManifestFilePath(manifest.getFilePath()); - this.setDataSourcePath(manifest.getDataSourcePath()); - this.setProcessingStage(processingStage); - this.setProcessingStageStartDate(manifest.getDateFileCreated()); - this.setProcessingHost(processingHost); - } - } - - /** - * Gets the node data as raw bytes that can be sent to the coordination - * service. - * - * @return The manifest node data as a byte array. - */ - byte[] toArray() { - ByteBuffer buffer = ByteBuffer.allocate(MAX_POSSIBLE_NODE_DATA_SIZE); - - // Write data (compatible with version 0) - buffer.putInt(this.processingStatus); - buffer.putInt(this.priority); - buffer.putInt(this.numberOfCrashes); - buffer.putLong(this.completedDate); - buffer.putInt(this.errorsOccurred ? 1 : 0); - - if (this.version > 0) { - // Write version - buffer.putInt(this.version); - - // Write data - putStringIntoBuffer(deviceId, buffer, TypeKind.BYTE); - putStringIntoBuffer(caseName, buffer, TypeKind.BYTE); - putStringIntoBuffer(caseDirectoryPath, buffer, TypeKind.SHORT); - buffer.putLong(this.manifestFileDate); - putStringIntoBuffer(manifestFilePath, buffer, TypeKind.SHORT); - putStringIntoBuffer(dataSourcePath, buffer, TypeKind.SHORT); - buffer.put(this.processingStage); - buffer.putLong(this.processingStageStartDate); - putStringIntoBuffer(processingHost, buffer, TypeKind.SHORT); - } - - // Prepare the array - byte[] array = new byte[buffer.position()]; - buffer.rewind(); - buffer.get(array, 0, array.length); - - return array; - } - - private String getStringFromBuffer(ByteBuffer buffer, TypeKind lengthType) { - int length = 0; - String output = ""; - - switch (lengthType) { - case BYTE: - length = buffer.get(); - break; - case SHORT: - length = buffer.getShort(); - break; - } - - if (length > 0) { - byte[] array = new byte[length]; - buffer.get(array, 0, length); - output = new String(array); - } - - return output; - } - - private void putStringIntoBuffer(String stringValue, ByteBuffer buffer, TypeKind lengthType) { - switch (lengthType) { - case BYTE: - buffer.put((byte) stringValue.length()); - break; - case SHORT: - buffer.putShort((short) stringValue.length()); - break; - } - - buffer.put(stringValue.getBytes()); - } - - /** - * Processing status for the auto ingest job for the manifest. - */ - enum ProcessingStatus { - PENDING, - PROCESSING, - COMPLETED, - DELETED - } - -} diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobDataException.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobDataException.java deleted file mode 100755 index c6676d2e8a..0000000000 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobDataException.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * 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.experimental.autoingest; - -/** - * Exception thrown when a manifest node contains incompatible data. - */ -public class AutoIngestJobDataException extends Exception { - - /** - * Constructs an exception thrown when a manifest node contains incompatible - * data. - * - * @param message An error message. - */ - public AutoIngestJobDataException(String message) { - super(message); - } - - /** - * Constructs an exception thrown when a manifest node contains incompatible - * data. - * - * @param message An error message. - * @param cause An exception that caused this exception to be thrown. - */ - public AutoIngestJobDataException(String message, Throwable cause) { - super(message, cause); - } -} diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobNodeData.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobNodeData.java new file mode 100755 index 0000000000..df2e321af2 --- /dev/null +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobNodeData.java @@ -0,0 +1,570 @@ +/* + * 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.experimental.autoingest; + +import java.nio.BufferUnderflowException; +import java.nio.ByteBuffer; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Date; +import javax.lang.model.type.TypeKind; + +/** + * An object that converts auto ingest job data for an auto ingest job + * coordination service node to and from byte arrays. + */ +final class AutoIngestJobNodeData { + + private static final int CURRENT_VERSION = 1; + private static final int MAX_POSSIBLE_NODE_DATA_SIZE = 131629; + private static final int DEFAULT_PRIORITY = 0; + + /* + * Version 0 fields. + */ + private int processingStatus; + private int priority; + private int numberOfCrashes; + private long completedDate; + private boolean errorsOccurred; + + /* + * Version 1 fields. + */ + private int version; + private String manifestFilePath; + private long manifestFileDate; + private String caseName; + private String deviceId; + private String dataSourcePath; + private String caseDirectoryPath; + private String processingHostName; + private byte processingStage; + private long processingStageStartDate; + private String processingStageDetailsDescription; + private long processingStageDetailsStartDate; + + /** + * Uses an auto ingest job to construct an object that converts auto ingest + * job data for an auto ingest job coordination service node to and from + * byte arrays. + * + * @param job The job. + */ + AutoIngestJobNodeData(AutoIngestJob job) { + setProcessingStatus(job.getProcessingStatus()); + setPriority(job.getPriority()); + setNumberOfCrashes(numberOfCrashes); // RJCTODO + setCompletedDate(job.getCompletedDate()); + setErrorsOccurred(job.getErrorsOccurred()); + this.version = CURRENT_VERSION; + Manifest manifest = job.getManifest(); + setManifestFilePath(manifest.getFilePath()); + setManifestFileDate(manifest.getDateFileCreated()); + setCaseName(manifest.getCaseName()); + setDeviceId(manifest.getDeviceId()); + setDataSourcePath(manifest.getDataSourcePath()); + setCaseDirectoryPath(job.getCaseDirectoryPath()); + setProcessingHostName(job.getProcessingHostName()); + setProcessingStage(job.getProcessingStage()); + setProcessingStageStartDate(job.getProcessingStageStartDate()); + setProcessingStageDetails(job.getStageDetails()); + } + + /** + * Uses a coordination service node data to construct an object that + * converts auto ingest job data for an auto ingest job coordination service + * node to and from byte arrays. + * + * @param nodeData The raw bytes received from the coordination service. + */ + AutoIngestJobNodeData(byte[] nodeData) throws InvalidDataException { + if (null == nodeData || nodeData.length == 0) { + throw new InvalidDataException(null == nodeData ? "Null nodeData byte array" : "Zero-length nodeData byte array"); + } + + /* + * Set default values for all fields. + */ + this.processingStatus = AutoIngestJob.ProcessingStatus.PENDING.ordinal(); + this.priority = DEFAULT_PRIORITY; + this.numberOfCrashes = 0; + this.completedDate = 0L; + this.errorsOccurred = false; + this.version = CURRENT_VERSION; + this.manifestFilePath = ""; + this.manifestFileDate = 0L; + this.caseName = ""; + this.deviceId = ""; + this.dataSourcePath = ""; + this.caseDirectoryPath = ""; + this.processingHostName = ""; + this.processingStage = (byte) AutoIngestJob.Stage.PENDING.ordinal(); + this.processingStageStartDate = 0L; + this.processingStageDetailsDescription = ""; + this.processingStageDetailsStartDate = 0L; + + /* + * Get fields from node data. + */ + ByteBuffer buffer = ByteBuffer.wrap(nodeData); + try { + if (buffer.hasRemaining()) { + /* + * Get version 0 fields. + */ + this.processingStatus = buffer.getInt(); + this.priority = buffer.getInt(); + this.numberOfCrashes = buffer.getInt(); + this.completedDate = buffer.getLong(); + int errorFlag = buffer.getInt(); + this.errorsOccurred = (1 == errorFlag); + } + + if (buffer.hasRemaining()) { + /* + * Get version 1 fields. + */ + this.version = buffer.getInt(); + this.deviceId = getStringFromBuffer(buffer, TypeKind.BYTE); + this.caseName = getStringFromBuffer(buffer, TypeKind.BYTE); + this.caseDirectoryPath = getStringFromBuffer(buffer, TypeKind.SHORT); + this.manifestFileDate = buffer.getLong(); + this.manifestFilePath = getStringFromBuffer(buffer, TypeKind.SHORT); + this.dataSourcePath = getStringFromBuffer(buffer, TypeKind.SHORT); + this.processingStage = buffer.get(); + this.processingStageStartDate = buffer.getLong(); + this.processingStageDetailsDescription = getStringFromBuffer(buffer, TypeKind.BYTE); + this.processingStageDetailsStartDate = buffer.getLong();; + this.processingHostName = getStringFromBuffer(buffer, TypeKind.SHORT); + } + + } catch (BufferUnderflowException ex) { + throw new InvalidDataException("Node data is incomplete", ex); + } + } + + /** + * Gets the processing status of the job. + * + * @return The processing status. + */ + AutoIngestJob.ProcessingStatus getProcessingStatus() { + return AutoIngestJob.ProcessingStatus.values()[this.processingStatus]; + } + + /** + * Sets the processing status of the job. + * + * @param processingSatus The processing status. + */ + void setProcessingStatus(AutoIngestJob.ProcessingStatus processingStatus) { + this.processingStatus = processingStatus.ordinal(); + } + + /** + * Gets the priority of the job. + * + * @return The priority. + */ + int getPriority() { + return this.priority; + } + + /** + * Sets the priority of the job. A higher number indicates a higheer + * priority. + * + * @param priority The priority. + */ + void setPriority(int priority) { + this.priority = priority; + } + + /** + * Gets the number of times the job has crashed during processing. + * + * @return The number of crashes. + */ + int getNumberOfCrashes() { + return this.numberOfCrashes; + } + + /** + * Sets the number of times the job has crashed during processing. + * + * @param numberOfCrashes The number of crashes. + */ + void setNumberOfCrashes(int numberOfCrashes) { + this.numberOfCrashes = numberOfCrashes; + } + + /** + * Gets the date the job was completed. A completion date equal to the epoch + * (January 1, 1970, 00:00:00 GMT), i.e., Date.getTime() returns 0L, + * indicates the job has not been completed. + * + * @return The job completion date. + */ + Date getCompletedDate() { + return new Date(this.completedDate); + } + + /** + * Sets the date the job was completed. A completion date equal to the epoch + * (January 1, 1970, 00:00:00 GMT), i.e., Date.getTime() returns 0L, + * indicates the job has not been completed. + * + * @param completedDate The job completion date. + */ + void setCompletedDate(Date completedDate) { + this.completedDate = completedDate.getTime(); + } + + /** + * Gets whether or not any errors occurred during the processing of the job. + * + * @return True or false. + */ + boolean getErrorsOccurred() { + return this.errorsOccurred; + } + + /** + * Sets whether or not any errors occurred during the processing of job. + * + * @param errorsOccurred True or false. + */ + void setErrorsOccurred(boolean errorsOccurred) { + this.errorsOccurred = errorsOccurred; + } + + /** + * Gets the node data version number. + * + * @return The version number. + */ + int getVersion() { + return this.version; + } + + /** + * Gets the device ID of the device associated with the data source for the + * job. + * + * @return The device ID. + */ + String getDeviceId() { + return this.deviceId; + } + + /** + * Sets the device ID of the device associated with the data source for the + * job. + * + * @param deviceId The device ID. + */ + void setDeviceId(String deviceId) { + this.deviceId = deviceId; + } + + /** + * Gets the case name. + * + * @return The case name. + */ + String getCaseName() { + return this.caseName; + } + + /** + * Sets the case name. + * + * @param caseName The case name. + */ + void setCaseName(String caseName) { + this.caseName = caseName; + } + + /** + * Sets the path to the case directory of the case associated with the job. + * + * @param caseDirectoryPath The path to the case directory. + */ + synchronized void setCaseDirectoryPath(Path caseDirectoryPath) { + if (caseDirectoryPath == null) { + this.caseDirectoryPath = ""; + } else { + this.caseDirectoryPath = caseDirectoryPath.toString(); + } + } + + /** + * Gets the path to the case directory of the case associated with the job. + * + * @return The case directory path or null if the case directory has not + * been created yet. + */ + synchronized Path getCaseDirectoryPath() { + if (!caseDirectoryPath.isEmpty()) { + return Paths.get(caseDirectoryPath); + } else { + return null; + } + } + + /** + * Gets the date the manifest was created. + * + * @return The date the manifest was created. + */ + Date getManifestFileDate() { + return new Date(this.manifestFileDate); + } + + /** + * Sets the date the manifest was created. + * + * @param manifestFileDate The date the manifest was created. + */ + void setManifestFileDate(Date manifestFileDate) { + this.manifestFileDate = manifestFileDate.getTime(); + } + + /** + * Gets the manifest file path. + * + * @return The manifest file path. + */ + Path getManifestFilePath() { + return Paths.get(this.manifestFilePath); + } + + /** + * Sets the manifest file path. + * + * @param manifestFilePath The manifest file path. + */ + void setManifestFilePath(Path manifestFilePath) { + if (manifestFilePath != null) { + this.manifestFilePath = manifestFilePath.toString(); + } else { + this.manifestFilePath = ""; + } + } + + /** + * Gets the path of the data source for the job. + * + * @return The data source path. + */ + Path getDataSourcePath() { + return Paths.get(dataSourcePath); + } + + /** + * Get the file name portion of the path of the data source for the job. + * + * @return The data source file name. + */ + public String getDataSourceFileName() { + return Paths.get(dataSourcePath).getFileName().toString(); + } + + /** + * Sets the path of the data source for the job. + * + * @param dataSourcePath The data source path. + */ + void setDataSourcePath(Path dataSourcePath) { + if (dataSourcePath != null) { + this.dataSourcePath = dataSourcePath.toString(); + } else { + this.dataSourcePath = ""; + } + } + + /** + * Get the processing stage of the job. + * + * @return The processing stage. + */ + AutoIngestJob.Stage getProcessingStage() { + return AutoIngestJob.Stage.values()[this.processingStage]; + } + + /** + * Sets the processing stage job. + * + * @param processingStage The processing stage. + */ + void setProcessingStage(AutoIngestJob.Stage processingStage) { + this.processingStage = (byte) processingStage.ordinal(); + } + + /** + * Gets the processing stage start date. + * + * @return The processing stage start date. + */ + Date getProcessingStageStartDate() { + return new Date(this.processingStageStartDate); + } + + /** + * Sets the processing stage start date. + * + * @param processingStageStartDate The processing stage start date. + */ + void setProcessingStageStartDate(Date processingStageStartDate) { + this.processingStageStartDate = processingStageStartDate.getTime(); + } + + /** + * Get the processing stage details. + * + * @return A processing stage details object. + */ + AutoIngestJob.StageDetails getProcessingStageDetails() { + return new AutoIngestJob.StageDetails(this.processingStageDetailsDescription, new Date(this.processingStageDetailsStartDate)); + } + + /** + * Sets the details of the current processing stage. + * + * @param stageDetails A stage details object. + */ + void setProcessingStageDetails(AutoIngestJob.StageDetails stageDetails) { + this.processingStageDetailsDescription = stageDetails.getDescription(); + this.processingStageDetailsStartDate = stageDetails.getStartDate().getTime(); + } + + /** + * Gets the processing host name, may be the empty string. + * + * @return The processing host. The empty string if the job is not currently + * being processed. + */ + String getProcessingHostName() { + return this.processingHostName; + } + + /** + * Sets the processing host name. May be the empty string. + * + * @param processingHost The processing host name. The empty string if the + * job is not currently being processed. + */ + void setProcessingHostName(String processingHost) { + this.processingHostName = processingHost; + } + + /** + * Gets the node data as a byte array that can be sent to the coordination + * service. + * + * @return The node data as a byte array. + */ + byte[] toArray() { + ByteBuffer buffer = ByteBuffer.allocate(MAX_POSSIBLE_NODE_DATA_SIZE); + + // Write data (compatible with version 0) + buffer.putInt(this.processingStatus); + buffer.putInt(this.priority); + buffer.putInt(this.numberOfCrashes); + buffer.putLong(this.completedDate); + buffer.putInt(this.errorsOccurred ? 1 : 0); + + if (this.version > 0) { + // Write version + buffer.putInt(this.version); + + // Write data + putStringIntoBuffer(deviceId, buffer, TypeKind.BYTE); + putStringIntoBuffer(caseName, buffer, TypeKind.BYTE); + putStringIntoBuffer(caseDirectoryPath, buffer, TypeKind.SHORT); + buffer.putLong(this.manifestFileDate); + putStringIntoBuffer(manifestFilePath, buffer, TypeKind.SHORT); + putStringIntoBuffer(dataSourcePath, buffer, TypeKind.SHORT); + buffer.put(this.processingStage); + buffer.putLong(this.processingStageStartDate); + putStringIntoBuffer(this.processingStageDetailsDescription, buffer, TypeKind.BYTE); + buffer.putLong(this.processingStageDetailsStartDate); + putStringIntoBuffer(processingHostName, buffer, TypeKind.SHORT); + } + + // Prepare the array + byte[] array = new byte[buffer.position()]; + buffer.rewind(); + buffer.get(array, 0, array.length); + + return array; + } + + // DGL: Document what is going on here and how the max buffer sie constant is calculated. + private String getStringFromBuffer(ByteBuffer buffer, TypeKind lengthType) { + int length = 0; + String output = ""; + + switch (lengthType) { + case BYTE: + length = buffer.get(); + break; + case SHORT: + length = buffer.getShort(); + break; + } + + if (length > 0) { + byte[] array = new byte[length]; + buffer.get(array, 0, length); + output = new String(array); + } + + return output; + } + + // DGL: Document what is going on here and how the max buffer sie constant is calculated. + private void putStringIntoBuffer(String stringValue, ByteBuffer buffer, TypeKind lengthType) { + switch (lengthType) { + case BYTE: + buffer.put((byte) stringValue.length()); + break; + case SHORT: + buffer.putShort((short) stringValue.length()); + break; + } + + buffer.put(stringValue.getBytes()); + } + + final static class InvalidDataException extends Exception { + + private static final long serialVersionUID = 1L; + + private InvalidDataException(String message) { + super(message); + } + + private InvalidDataException(String message, Throwable cause) { + super(message, cause); + } + } + +} diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestManager.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestManager.java index bc182b5384..c0944e9aaa 100755 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestManager.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestManager.java @@ -61,7 +61,6 @@ import java.util.stream.Collectors; import javax.annotation.concurrent.GuardedBy; import javax.annotation.concurrent.Immutable; import javax.annotation.concurrent.ThreadSafe; -import org.openide.util.Exceptions; import org.openide.util.Lookup; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.Case.CaseType; @@ -85,11 +84,10 @@ import org.sleuthkit.autopsy.experimental.autoingest.AutoIngestAlertFile.AutoIng import org.sleuthkit.autopsy.experimental.autoingest.AutoIngestJobLogger.AutoIngestJobLoggerException; import org.sleuthkit.autopsy.experimental.autoingest.FileExporter.FileExportException; import org.sleuthkit.autopsy.experimental.autoingest.ManifestFileParser.ManifestFileParserException; -import org.sleuthkit.autopsy.experimental.autoingest.AutoIngestJobData.ProcessingStatus; -import static org.sleuthkit.autopsy.experimental.autoingest.AutoIngestJobData.ProcessingStatus.COMPLETED; -import static org.sleuthkit.autopsy.experimental.autoingest.AutoIngestJobData.ProcessingStatus.DELETED; -import static org.sleuthkit.autopsy.experimental.autoingest.AutoIngestJobData.ProcessingStatus.PENDING; -import static org.sleuthkit.autopsy.experimental.autoingest.AutoIngestJobData.ProcessingStatus.PROCESSING; +import static org.sleuthkit.autopsy.experimental.autoingest.AutoIngestJob.ProcessingStatus.COMPLETED; +import static org.sleuthkit.autopsy.experimental.autoingest.AutoIngestJob.ProcessingStatus.DELETED; +import static org.sleuthkit.autopsy.experimental.autoingest.AutoIngestJob.ProcessingStatus.PENDING; +import static org.sleuthkit.autopsy.experimental.autoingest.AutoIngestJob.ProcessingStatus.PROCESSING; import org.sleuthkit.autopsy.experimental.configuration.AutoIngestUserPreferences; import org.sleuthkit.autopsy.experimental.configuration.SharedConfiguration; import org.sleuthkit.autopsy.experimental.configuration.SharedConfiguration.SharedConfigurationException; @@ -125,7 +123,7 @@ public final class AutoIngestManager extends Observable implements PropertyChang private static int DEFAULT_JOB_PRIORITY = 0; private static final String AUTO_INGEST_THREAD_NAME = "AIM-job-processing-%d"; private static final String LOCAL_HOST_NAME = NetworkUtils.getLocalHostName(); - private static final String EVENT_CHANNEL_NAME = "Auto-Ingest-Events"; + private static final String EVENT_CHANNEL_NAME = "Auto-Ingest-Manager-Events"; private static final Set EVENT_LIST = new HashSet<>(Arrays.asList(new String[]{ Event.JOB_STATUS_UPDATED.toString(), Event.JOB_COMPLETED.toString(), @@ -206,22 +204,22 @@ public final class AutoIngestManager extends Observable implements PropertyChang /** * Starts up auto ingest. * - * @throws AutoIngestManagerStartupException if there is a problem starting - * auto ingest. + * @throws AutoIngestManagerException if there is a problem starting auto + * ingest. */ - void startUp() throws AutoIngestManagerStartupException { + void startUp() throws AutoIngestManagerException { SYS_LOGGER.log(Level.INFO, "Auto ingest starting"); try { coordinationService = CoordinationService.getInstance(); } catch (CoordinationServiceException ex) { - throw new AutoIngestManagerStartupException("Failed to get coordination service", ex); + throw new AutoIngestManagerException("Failed to get coordination service", ex); } try { eventPublisher.openRemoteEventChannel(EVENT_CHANNEL_NAME); SYS_LOGGER.log(Level.INFO, "Opened auto ingest event channel"); } catch (AutopsyEventException ex) { SYS_LOGGER.log(Level.SEVERE, "Failed to open auto ingest event channel", ex); - throw new AutoIngestManagerStartupException("Failed to open auto ingest event channel", ex); + throw new AutoIngestManagerException("Failed to open auto ingest event channel", ex); } rootInputDirectory = Paths.get(AutoIngestUserPreferences.getAutoModeImageFolder()); rootOutputDirectory = Paths.get(AutoIngestUserPreferences.getAutoModeResultsFolder()); @@ -287,7 +285,7 @@ public final class AutoIngestManager extends Observable implements PropertyChang * @param event A job started from another auto ingest node. */ private void handleRemoteJobStartedEvent(AutoIngestJobStartedEvent event) { - String hostName = event.getJob().getNodeName(); + String hostName = event.getJob().getProcessingHostName(); hostNamesToLastMsgTime.put(hostName, Instant.now()); synchronized (jobsLock) { Path manifestFilePath = event.getJob().getManifest().getFilePath(); @@ -314,9 +312,17 @@ public final class AutoIngestManager extends Observable implements PropertyChang * @param event An job status event from another auto ingest node. */ private void handleRemoteJobStatusEvent(AutoIngestJobStatusEvent event) { - String hostName = event.getJob().getNodeName(); + AutoIngestJob job = event.getJob(); + for (Iterator iterator = pendingJobs.iterator(); iterator.hasNext();) { + AutoIngestJob pendingJob = iterator.next(); + if (job.equals(pendingJob)) { + iterator.remove(); + break; + } + } + String hostName = job.getProcessingHostName(); hostNamesToLastMsgTime.put(hostName, Instant.now()); - hostNamesToRunningJobs.put(hostName, event.getJob()); + hostNamesToRunningJobs.put(hostName, job); setChanged(); notifyObservers(Event.JOB_STATUS_UPDATED); } @@ -332,7 +338,7 @@ public final class AutoIngestManager extends Observable implements PropertyChang * @param event An job completed event from another auto ingest node. */ private void handleRemoteJobCompletedEvent(AutoIngestJobCompletedEvent event) { - String hostName = event.getJob().getNodeName(); + String hostName = event.getJob().getProcessingHostName(); hostNamesToLastMsgTime.put(hostName, Instant.now()); hostNamesToRunningJobs.remove(hostName); if (event.shouldRetry() == false) { @@ -340,7 +346,6 @@ public final class AutoIngestManager extends Observable implements PropertyChang completedJobs.add(event.getJob()); } } - //scanInputDirsNow(); setChanged(); notifyObservers(Event.JOB_COMPLETED); } @@ -463,7 +468,7 @@ public final class AutoIngestManager extends Observable implements PropertyChang } for (AutoIngestJob job : hostNamesToRunningJobs.values()) { runningJobs.add(job); - runningJobs.sort(new AutoIngestJob.AlphabeticalComparator()); + runningJobs.sort(new AutoIngestJob.CaseNameAndProcessingHostComparator()); } } if (null != completedJobs) { @@ -517,12 +522,17 @@ public final class AutoIngestManager extends Observable implements PropertyChang jobProcessingTask.requestResume(); } + /** + */ /** * Bumps the priority of all pending ingest jobs for a specified case. * * @param caseName The name of the case to be prioritized. + * + * @throws AutoIngestManagerException If there is an error bumping the + * priority of the jobs for the case. */ - void prioritizeCase(final String caseName) { + void prioritizeCase(final String caseName) throws AutoIngestManagerException { if (state != State.RUNNING) { return; @@ -542,19 +552,12 @@ public final class AutoIngestManager extends Observable implements PropertyChang if (!prioritizedJobs.isEmpty()) { ++maxPriority; for (AutoIngestJob job : prioritizedJobs) { - String manifestNodePath = job.getManifest().getFilePath().toString(); try { - AutoIngestJobData nodeData = new AutoIngestJobData(coordinationService.getNodeData(CoordinationService.CategoryNode.MANIFESTS, manifestNodePath)); - nodeData.setPriority(maxPriority); - coordinationService.setNodeData(CoordinationService.CategoryNode.MANIFESTS, manifestNodePath, nodeData.toArray()); - } catch (AutoIngestJobDataException ex) { - SYS_LOGGER.log(Level.WARNING, String.format("Unable to use node data for %s", manifestNodePath), ex); - } catch (CoordinationServiceException ex) { - SYS_LOGGER.log(Level.SEVERE, String.format("Coordination service error while prioritizing %s", manifestNodePath), ex); - } catch (InterruptedException ex) { - SYS_LOGGER.log(Level.SEVERE, "Unexpected interrupt while updating coordination service node data for {0}", manifestNodePath); + this.updateCoordinationServiceNode(job); + job.setPriority(maxPriority); + } catch (CoordinationServiceException | InterruptedException ex) { + throw new AutoIngestManagerException("Error updating case priority", ex); } - job.setPriority(maxPriority); } } @@ -572,8 +575,11 @@ public final class AutoIngestManager extends Observable implements PropertyChang * Bumps the priority of an auto ingest job. * * @param manifestPath The manifest file path for the job to be prioritized. + * + * @throws AutoIngestManagerException If there is an error bumping the + * priority of the job. */ - void prioritizeJob(Path manifestPath) { + void prioritizeJob(Path manifestPath) throws AutoIngestManagerException { if (state != State.RUNNING) { return; } @@ -581,6 +587,10 @@ public final class AutoIngestManager extends Observable implements PropertyChang int maxPriority = 0; AutoIngestJob prioritizedJob = null; synchronized (jobsLock) { + /* + * Find the job in the pending jobs list and record the highest + * existing priority. + */ for (AutoIngestJob job : pendingJobs) { if (job.getPriority() > maxPriority) { maxPriority = job.getPriority(); @@ -589,19 +599,17 @@ public final class AutoIngestManager extends Observable implements PropertyChang prioritizedJob = job; } } + + /* + * Bump the priority by one and update the coordination service node + * data for the job. + */ if (null != prioritizedJob) { ++maxPriority; - String manifestNodePath = prioritizedJob.getManifest().getFilePath().toString(); try { - AutoIngestJobData nodeData = new AutoIngestJobData(coordinationService.getNodeData(CoordinationService.CategoryNode.MANIFESTS, manifestNodePath)); - nodeData.setPriority(maxPriority); - coordinationService.setNodeData(CoordinationService.CategoryNode.MANIFESTS, manifestNodePath, nodeData.toArray()); - } catch (AutoIngestJobDataException ex) { - SYS_LOGGER.log(Level.WARNING, String.format("Unable to use node data for %s", manifestPath), ex); - } catch (CoordinationServiceException ex) { - SYS_LOGGER.log(Level.SEVERE, String.format("Coordination service error while prioritizing %s", manifestNodePath), ex); - } catch (InterruptedException ex) { - SYS_LOGGER.log(Level.SEVERE, "Unexpected interrupt while updating coordination service node data for {0}", manifestNodePath); + this.updateCoordinationServiceNode(prioritizedJob); + } catch (CoordinationServiceException | InterruptedException ex) { + throw new AutoIngestManagerException("Error updating job priority", ex); } prioritizedJob.setPriority(maxPriority); } @@ -626,6 +634,9 @@ public final class AutoIngestManager extends Observable implements PropertyChang void reprocessJob(Path manifestPath) { AutoIngestJob completedJob = null; synchronized (jobsLock) { + /* + * Find the job in the completed jobs list. + */ for (Iterator iterator = completedJobs.iterator(); iterator.hasNext();) { AutoIngestJob job = iterator.next(); if (job.getManifest().getFilePath().equals(manifestPath)) { @@ -635,11 +646,17 @@ public final class AutoIngestManager extends Observable implements PropertyChang } } + /* + * Add the job to the pending jobs queue and update the coordinatino + * service node data for the job. + */ if (null != completedJob && null != completedJob.getCaseDirectoryPath()) { try { - AutoIngestJobData nodeData = new AutoIngestJobData(completedJob.getManifest(), PENDING, DEFAULT_JOB_PRIORITY, 0, new Date(0), true); - coordinationService.setNodeData(CoordinationService.CategoryNode.MANIFESTS, manifestPath.toString(), nodeData.toArray()); - pendingJobs.add(new AutoIngestJob(nodeData)); + completedJob.setErrorsOccurred(false); + completedJob.setCompletedDate(new Date(0)); + completedJob.setPriority(DEFAULT_JOB_PRIORITY); + updateCoordinationServiceNode(completedJob); + pendingJobs.add(completedJob); } catch (CoordinationServiceException ex) { SYS_LOGGER.log(Level.SEVERE, String.format("Coordination service error while reprocessing %s", manifestPath), ex); completedJobs.add(completedJob); @@ -729,11 +746,13 @@ public final class AutoIngestManager extends Observable implements PropertyChang */ for (Path manifestPath : manifestPaths) { try { - AutoIngestJobData nodeData = new AutoIngestJobData(coordinationService.getNodeData(CoordinationService.CategoryNode.MANIFESTS, manifestPath.toString())); - nodeData.setProcessingStatus(AutoIngestJobData.ProcessingStatus.DELETED); - coordinationService.setNodeData(CoordinationService.CategoryNode.MANIFESTS, manifestPath.toString(), nodeData.toArray()); - } catch (AutoIngestJobDataException ex) { - SYS_LOGGER.log(Level.WARNING, String.format("Unable to use node data for %s", manifestPath), ex); + AutoIngestJobNodeData nodeData = new AutoIngestJobNodeData(coordinationService.getNodeData(CoordinationService.CategoryNode.MANIFESTS, manifestPath.toString())); + AutoIngestJob deletedJob = new AutoIngestJob(nodeData); + deletedJob.setProcessingStatus(AutoIngestJob.ProcessingStatus.DELETED); + this.updateCoordinationServiceNode(deletedJob); + } catch (AutoIngestJobNodeData.InvalidDataException ex) { + SYS_LOGGER.log(Level.WARNING, String.format("Invalid auto ingest job node data for %s", manifestPath), ex); + return CaseDeletionResult.PARTIALLY_DELETED; } catch (InterruptedException | CoordinationServiceException ex) { SYS_LOGGER.log(Level.SEVERE, String.format("Error attempting to set delete flag on manifest data for %s for case %s at %s", manifestPath, caseName, caseDirectoryPath), ex); return CaseDeletionResult.PARTIALLY_DELETED; @@ -839,6 +858,22 @@ public final class AutoIngestManager extends Observable implements PropertyChang } } + /** + * Sets the coordination service node data for an auto ingest job. + * + * Note that a new auto ingest node data object will be created from the job + * passed in. Thus, if the data version of the node has changed, the node + * will be "upgraded" as well as updated. + * + * @param job The auto ingest job. + */ + void updateCoordinationServiceNode(AutoIngestJob job) throws CoordinationServiceException, InterruptedException { + AutoIngestJobNodeData nodeData = new AutoIngestJobNodeData(job); + String manifestNodePath = job.getManifest().getFilePath().toString(); + byte[] rawData = nodeData.toArray(); + coordinationService.setNodeData(CoordinationService.CategoryNode.MANIFESTS, manifestNodePath, rawData); + } + /** * A task that submits an input directory scan task to the input directory * scan task executor. @@ -1021,33 +1056,31 @@ public final class AutoIngestManager extends Observable implements PropertyChang */ try { byte[] rawData = coordinationService.getNodeData(CoordinationService.CategoryNode.MANIFESTS, manifestPath.toString()); - if (null != rawData) { + if (null != rawData && rawData.length > 0) { try { - AutoIngestJobData nodeData = new AutoIngestJobData(rawData); - if (nodeData.coordSvcNodeDataWasSet()) { - ProcessingStatus processingStatus = nodeData.getProcessingStatus(); - switch (processingStatus) { - case PENDING: - addPendingJob(nodeData, manifest); - break; - case PROCESSING: - doRecoveryIfCrashed(nodeData, manifest); - break; - case COMPLETED: - addCompletedJob(nodeData, manifest); - break; - case DELETED: - // Do nothing - we dont'want to add it to any job list or do recovery - break; - default: - SYS_LOGGER.log(Level.SEVERE, "Unknown ManifestNodeData.ProcessingStatus"); - break; - } - } else { - addNewPendingJob(manifest); + AutoIngestJobNodeData nodeData = new AutoIngestJobNodeData(rawData); + AutoIngestJob.ProcessingStatus processingStatus = nodeData.getProcessingStatus(); + switch (processingStatus) { + case PENDING: + addPendingJob(manifest, nodeData); + break; + case PROCESSING: + doRecoveryIfCrashed(manifest, nodeData); + break; + case COMPLETED: + addCompletedJob(manifest, nodeData); + break; + case DELETED: + /* + * Ignore jobs marked as "deleted." + */ + break; + default: + SYS_LOGGER.log(Level.SEVERE, "Unknown ManifestNodeData.ProcessingStatus"); + break; } - } catch (AutoIngestJobDataException ex) { - SYS_LOGGER.log(Level.WARNING, String.format("Unable to use node data for %s", manifestPath), ex); + } catch (AutoIngestJobNodeData.InvalidDataException ex) { + SYS_LOGGER.log(Level.WARNING, String.format("Invalid auto ingest job node data for %s", manifestPath), ex); } } else { addNewPendingJob(manifest); @@ -1069,51 +1102,49 @@ public final class AutoIngestManager extends Observable implements PropertyChang } /** - * This method will push a node to the coordination service if it does - * not exist. If the node is an older version, it will be upgraded prior - * to being pushed. - * - * @param nodeData The node data to upgrade. - * @param manifest The manifest. - * @param caseDirectoryPath The case directory path. - * @param processingStage The processing stage. - * - * @throws CoordinationServiceException - * @throws InterruptedException - */ - private void pushNodeToCoordinationService(AutoIngestJobData nodeData, Manifest manifest, Path caseDirectoryPath, AutoIngestJob.Stage processingStage) - throws CoordinationServiceException, InterruptedException { - if (nodeData.getVersion() < 1) { - nodeData.upgradeNode(manifest, caseDirectoryPath, LOCAL_HOST_NAME, processingStage); - } - if (!nodeData.coordSvcNodeDataWasSet()) { - byte[] rawData = nodeData.toArray(); - coordinationService.setNodeData(CoordinationService.CategoryNode.MANIFESTS, manifest.getFilePath().toString(), rawData); - } - } - - /** - * Adds a job to process a manifest to the pending jobs queue. + * Adds an existing job to the pending jobs queue. * + * @param manifest The manifest for the job. * @param nodeData The data stored in the coordination service node for - * the manifest. - * @param manifest The manifest for upgrading the node. + * the job. * - * @throws CoordinationServiceException - * @throws InterruptedException + * @throws InterruptedException if the thread running the input + * directory scan task is interrupted while + * blocked, i.e., if auto ingest is + * shutting down. */ - private void addPendingJob(AutoIngestJobData nodeData, Manifest manifest) throws CoordinationServiceException, InterruptedException { - Path caseDirectoryPath = PathUtils.findCaseDirectory(rootOutputDirectory, manifest.getCaseName()); - nodeData.setCompletedDate(new Date(0)); - nodeData.setErrorsOccurred(false); - pushNodeToCoordinationService(nodeData, manifest, caseDirectoryPath, AutoIngestJob.Stage.PENDING); - newPendingJobsList.add(new AutoIngestJob(nodeData)); + private void addPendingJob(Manifest manifest, AutoIngestJobNodeData nodeData) throws InterruptedException { + AutoIngestJob job = new AutoIngestJob(manifest); + job.setPriority(nodeData.getPriority()); + Path caseDirectory = PathUtils.findCaseDirectory(rootOutputDirectory, manifest.getCaseName()); + job.setCaseDirectoryPath(caseDirectory); + newPendingJobsList.add(job); + + /* + * Try to upgrade/update the coordination service node data for the + * job. + * + * An exclusive lock is obtained before doing so because another + * host may have already found the job, obtained an exclusive lock, + * and started processing it. However, this locking does make it + * possible that two hosts will both try to obtain the lock to do + * the upgrade/update operation at the same time. If this happens, + * the host that is holding the lock will complete the + * update/upgrade operation, so there is nothing more to do. + */ + try (Lock manifestLock = coordinationService.tryGetExclusiveLock(CoordinationService.CategoryNode.MANIFESTS, manifest.getFilePath().toString())) { + if (null != manifestLock) { + AutoIngestManager.this.updateCoordinationServiceNode(job); + } + } catch (CoordinationServiceException ex) { + SYS_LOGGER.log(Level.SEVERE, String.format("Error attempting to set node data for %s", manifest.getFilePath()), ex); + } } /** - * Adds a job to process a manifest to the pending jobs queue. + * Adds a new job to the pending jobs queue. * - * @param manifest The manifest. + * @param manifest The manifest for the job. * * @throws InterruptedException if the thread running the input * directory scan task is interrupted while @@ -1121,13 +1152,25 @@ public final class AutoIngestManager extends Observable implements PropertyChang * shutting down. */ private void addNewPendingJob(Manifest manifest) throws InterruptedException { - // TODO (JIRA-1960): This is something of a hack, grabbing the lock to create the node. - // Is use of Curator.create().forPath() possible instead? + /* + * Create the coordination service node data for the job. Note that + * getting the lock will create the node for the job (with no data) + * if it does not already exist. + * + * An exclusive lock is obtained before creating the node data + * because another host may have already found the job, obtained an + * exclusive lock, and started processing it. However, this locking + * does make it possible that two hosts will both try to obtain the + * lock to do the create operation at the same time. If this + * happens, the host that is locked out will not add the job to its + * pending queue for this scan of the input directory, but it will + * be picked up on the next scan. + */ try (Lock manifestLock = coordinationService.tryGetExclusiveLock(CoordinationService.CategoryNode.MANIFESTS, manifest.getFilePath().toString())) { if (null != manifestLock) { - AutoIngestJobData newNodeData = new AutoIngestJobData(manifest, PENDING, DEFAULT_JOB_PRIORITY, 0, new Date(0), false); - pushNodeToCoordinationService(newNodeData, manifest, null, AutoIngestJob.Stage.PENDING); - newPendingJobsList.add(new AutoIngestJob(newNodeData)); + AutoIngestJob job = new AutoIngestJob(manifest); + AutoIngestManager.this.updateCoordinationServiceNode(job); + newPendingJobsList.add(job); } } catch (CoordinationServiceException ex) { SYS_LOGGER.log(Level.SEVERE, String.format("Error attempting to set node data for %s", manifest.getFilePath()), ex); @@ -1142,67 +1185,87 @@ public final class AutoIngestManager extends Observable implements PropertyChang * the node that was processing the job crashed and the processing * status was not updated. * - * @param nodeData The node data. * @param manifest The manifest for upgrading the node. + * @param nodeData The node data. * * @throws InterruptedException if the thread running the input * directory scan task is interrupted while * blocked, i.e., if auto ingest is * shutting down. */ - private void doRecoveryIfCrashed(AutoIngestJobData nodeData, Manifest manifest) throws InterruptedException { - String manifestPath = nodeData.getManifestFilePath().toString(); - try { - Lock manifestLock = coordinationService.tryGetExclusiveLock(CoordinationService.CategoryNode.MANIFESTS, manifestPath); + private void doRecoveryIfCrashed(Manifest manifest, AutoIngestJobNodeData nodeData) throws InterruptedException { + /* + * Try to get an exclusive lock on the coordination service node for + * the job. If the lock cannot be obtained, another host in the auto + * ingest cluster is already doing the recovery. + */ + String manifestPath = manifest.getFilePath().toString(); + try (Lock manifestLock = coordinationService.tryGetExclusiveLock(CoordinationService.CategoryNode.MANIFESTS, manifestPath)) { if (null != manifestLock) { + SYS_LOGGER.log(Level.SEVERE, "Attempting crash recovery for {0}", manifestPath); try { - if (nodeData.coordSvcNodeDataWasSet() && ProcessingStatus.PROCESSING == nodeData.getProcessingStatus()) { - SYS_LOGGER.log(Level.SEVERE, "Attempting crash recovery for {0}", manifestPath); - int numberOfCrashes = nodeData.getNumberOfCrashes(); - ++numberOfCrashes; - Path caseDirectoryPath = PathUtils.findCaseDirectory(rootOutputDirectory, manifest.getCaseName()); - nodeData.setNumberOfCrashes(numberOfCrashes); - nodeData.setCompletedDate(new Date(0)); - nodeData.setErrorsOccurred(true); + /* + * Create the recovery job. + */ + AutoIngestJob job = new AutoIngestJob(nodeData); + int numberOfCrashes = job.getNumberOfCrashes(); + ++numberOfCrashes; + job.setNumberOfCrashes(numberOfCrashes); + job.setCompletedDate(new Date(0)); + Path caseDirectoryPath = PathUtils.findCaseDirectory(rootOutputDirectory, manifest.getCaseName()); + if (null != caseDirectoryPath) { + job.setCaseDirectoryPath(caseDirectoryPath); + job.setErrorsOccurred(true); + } else { + job.setErrorsOccurred(false); + } + + /* + * Update the coordination service node for the job. If + * this fails, leave the recovery to anoterh host. + */ + try { + updateCoordinationServiceNode(job); if (numberOfCrashes <= AutoIngestUserPreferences.getMaxNumTimesToProcessImage()) { - nodeData.setProcessingStatus(PENDING); - pushNodeToCoordinationService(nodeData, manifest, caseDirectoryPath, AutoIngestJob.Stage.PENDING); - newPendingJobsList.add(new AutoIngestJob(nodeData)); - if (null != nodeData.getCaseDirectoryPath()) { - try { - AutoIngestAlertFile.create(nodeData.getCaseDirectoryPath()); - } catch (AutoIngestAlertFileException ex) { - SYS_LOGGER.log(Level.SEVERE, String.format("Error creating alert file for crashed job for %s", manifestPath), ex); - } - try { - new AutoIngestJobLogger(nodeData.getManifestFilePath(), nodeData.getDataSourceFileName(), caseDirectoryPath).logCrashRecoveryWithRetry(); - } catch (AutoIngestJobLoggerException ex) { - SYS_LOGGER.log(Level.SEVERE, String.format("Error creating case auto ingest log entry for crashed job for %s", manifestPath), ex); - } - } + newPendingJobsList.add(job); } else { - nodeData.setProcessingStatus(COMPLETED); - pushNodeToCoordinationService(nodeData, manifest, caseDirectoryPath, AutoIngestJob.Stage.COMPLETED); newCompletedJobsList.add(new AutoIngestJob(nodeData)); - if (null != nodeData.getCaseDirectoryPath()) { - try { - AutoIngestAlertFile.create(nodeData.getCaseDirectoryPath()); - } catch (AutoIngestAlertFileException ex) { - SYS_LOGGER.log(Level.SEVERE, String.format("Error creating alert file for crashed job for %s", manifestPath), ex); - } - try { - new AutoIngestJobLogger(nodeData.getManifestFilePath(), nodeData.getDataSourceFileName(), nodeData.getCaseDirectoryPath()).logCrashRecoveryNoRetry(); - } catch (AutoIngestJobLoggerException ex) { - SYS_LOGGER.log(Level.SEVERE, String.format("Error creating case auto ingest log entry for crashed job for %s", manifestPath), ex); - } - } } + } catch (CoordinationServiceException ex) { + SYS_LOGGER.log(Level.SEVERE, String.format("Error attempting to set node data for %s", manifestPath), ex); + return; + } + + /* + * Write the alert file and do the logging. + */ + if (null != caseDirectoryPath) { try { - coordinationService.setNodeData(CoordinationService.CategoryNode.MANIFESTS, manifestPath, nodeData.toArray()); - } catch (CoordinationServiceException ex) { - SYS_LOGGER.log(Level.SEVERE, String.format("Error attempting to set node data for %s", manifestPath), ex); + AutoIngestAlertFile.create(nodeData.getCaseDirectoryPath()); + } catch (AutoIngestAlertFileException ex) { + SYS_LOGGER.log(Level.SEVERE, String.format("Error creating alert file for crashed job for %s", manifestPath), ex); } } + if (numberOfCrashes <= AutoIngestUserPreferences.getMaxNumTimesToProcessImage()) { + job.setProcessingStatus(AutoIngestJob.ProcessingStatus.PENDING); + if (null != caseDirectoryPath) { + try { + new AutoIngestJobLogger(manifest.getFilePath(), manifest.getDataSourceFileName(), caseDirectoryPath).logCrashRecoveryWithRetry(); + } catch (AutoIngestJobLoggerException ex) { + SYS_LOGGER.log(Level.SEVERE, String.format("Error creating case auto ingest log entry for crashed job for %s", manifestPath), ex); + } + } + } else { + job.setProcessingStatus(AutoIngestJob.ProcessingStatus.COMPLETED); + if (null != caseDirectoryPath) { + try { + new AutoIngestJobLogger(manifest.getFilePath(), manifest.getDataSourceFileName(), nodeData.getCaseDirectoryPath()).logCrashRecoveryNoRetry(); + } catch (AutoIngestJobLoggerException ex) { + SYS_LOGGER.log(Level.SEVERE, String.format("Error creating case auto ingest log entry for crashed job for %s", manifestPath), ex); + } + } + } + } finally { try { manifestLock.release(); @@ -1226,11 +1289,37 @@ public final class AutoIngestManager extends Observable implements PropertyChang * @throws CoordinationServiceException * @throws InterruptedException */ - private void addCompletedJob(AutoIngestJobData nodeData, Manifest manifest) throws CoordinationServiceException, InterruptedException { + private void addCompletedJob(Manifest manifest, AutoIngestJobNodeData nodeData) throws CoordinationServiceException, InterruptedException { Path caseDirectoryPath = PathUtils.findCaseDirectory(rootOutputDirectory, manifest.getCaseName()); if (null != caseDirectoryPath) { - pushNodeToCoordinationService(nodeData, manifest, caseDirectoryPath, AutoIngestJob.Stage.COMPLETED); + AutoIngestJob job = new AutoIngestJob(manifest); + job.setCaseDirectoryPath(caseDirectoryPath); + job.setProcessingStatus(AutoIngestJob.ProcessingStatus.COMPLETED); + job.setStage(AutoIngestJob.Stage.COMPLETED); + job.setCompletedDate(nodeData.getCompletedDate()); + job.setErrorsOccurred(true); newCompletedJobsList.add(new AutoIngestJob(nodeData)); + + /* + * Try to upgrade/update the coordination service node data for + * the job. + * + * An exclusive lock is obtained before doing so because another + * host may have already found the job, obtained an exclusive + * lock, and started processing it. However, this locking does + * make it possible that two hosts will both try to obtain the + * lock to do the upgrade/update operation at the same time. If + * this happens, the host that is holding the lock will complete + * the update/upgrade operation, so there is nothing more to do. + */ + try (Lock manifestLock = coordinationService.tryGetExclusiveLock(CoordinationService.CategoryNode.MANIFESTS, manifest.getFilePath().toString())) { + if (null != manifestLock) { + updateCoordinationServiceNode(job); + } + } catch (CoordinationServiceException ex) { + SYS_LOGGER.log(Level.SEVERE, String.format("Error attempting to set node data for %s", manifest.getFilePath()), ex); + } + } else { SYS_LOGGER.log(Level.WARNING, String.format("Job completed for %s, but cannot find case directory, ignoring job", nodeData.getManifestFilePath())); } @@ -1355,7 +1444,7 @@ public final class AutoIngestManager extends Observable implements PropertyChang if (jobProcessingTaskFuture.isCancelled()) { break; } - if (ex instanceof CoordinationServiceException) { + if (ex instanceof CoordinationServiceException || ex instanceof AutoIngestJobNodeData.InvalidDataException) { errorState = ErrorState.COORDINATION_SERVICE_ERROR; } else if (ex instanceof SharedConfigurationException) { errorState = ErrorState.SHARED_CONFIGURATION_DOWNLOAD_ERROR; @@ -1521,42 +1610,60 @@ public final class AutoIngestManager extends Observable implements PropertyChang /** * Processes jobs until the pending jobs queue is empty. * - * @throws CoordinationServiceException if there is an error - * acquiring or releasing - * coordination service locks - * or setting coordination - * service node data. - * @throws SharedConfigurationException if there is an error while - * downloading shared - * configuration. - * @throws ServicesMonitorException if there is an error - * querying the services - * monitor. - * @throws DatabaseServerDownException if the database server is - * down. - * @throws KeywordSearchServerDownException if the Solr server is down. - * @throws CaseManagementException if there is an error - * creating, opening or closing - * the case for the job. - * @throws AnalysisStartupException if there is an error - * starting analysis of the - * data source by the data - * source level and file level - * ingest modules. - * @throws FileExportException if there is an error - * exporting files. - * @throws AutoIngestAlertFileException if there is an error - * creating an alert file. - * @throws AutoIngestJobLoggerException if there is an error writing - * to the auto ingest log for - * the case. - * @throws InterruptedException if the thread running the - * job processing task is - * interrupted while blocked, - * i.e., if auto ingest is - * shutting down. + * @throws CoordinationServiceException if there is an + * error acquiring or + * releasing + * coordination + * service locks or + * setting + * coordination + * service node data. + * @throws SharedConfigurationException if there is an + * error while + * downloading shared + * configuration. + * @throws ServicesMonitorException if there is an + * error querying the + * services monitor. + * @throws DatabaseServerDownException if the database + * server is down. + * @throws KeywordSearchServerDownException if the Solr server + * is down. + * @throws CaseManagementException if there is an + * error creating, + * opening or closing + * the case for the + * job. + * @throws AnalysisStartupException if there is an + * error starting + * analysis of the + * data source by the + * data source level + * and file level + * ingest modules. + * @throws FileExportException if there is an + * error exporting + * files. + * @throws AutoIngestAlertFileException if there is an + * error creating an + * alert file. + * @throws AutoIngestJobLoggerException if there is an + * error writing to + * the auto ingest + * log for the case. + * @throws InterruptedException if the thread + * running the job + * processing task is + * interrupted while + * blocked, i.e., if + * auto ingest is + * shutting down. + * @throws AutoIngestJobNodeData.InvalidDataException if there is an + * error constructing + * auto ingest node + * data objects. */ - private void processJobs() throws CoordinationServiceException, SharedConfigurationException, ServicesMonitorException, DatabaseServerDownException, KeywordSearchServerDownException, CaseManagementException, AnalysisStartupException, FileExportException, AutoIngestAlertFileException, AutoIngestJobLoggerException, InterruptedException, AutoIngestDataSourceProcessor.AutoIngestDataSourceProcessorException { + private void processJobs() throws CoordinationServiceException, SharedConfigurationException, ServicesMonitorException, DatabaseServerDownException, KeywordSearchServerDownException, CaseManagementException, AnalysisStartupException, FileExportException, AutoIngestAlertFileException, AutoIngestJobLoggerException, InterruptedException, AutoIngestDataSourceProcessor.AutoIngestDataSourceProcessorException, AutoIngestJobNodeData.InvalidDataException { SYS_LOGGER.log(Level.INFO, "Started processing pending jobs queue"); Lock manifestLock = JobProcessingTask.this.dequeueAndLockNextJob(); while (null != manifestLock) { @@ -1565,8 +1672,6 @@ public final class AutoIngestManager extends Observable implements PropertyChang return; } processJob(); - } catch (AutoIngestJobDataException ex) { - SYS_LOGGER.log(Level.WARNING, String.format("Unable to use node data"), ex); } finally { manifestLock.release(); } @@ -1666,7 +1771,7 @@ public final class AutoIngestManager extends Observable implements PropertyChang } try { - AutoIngestJobData nodeData = new AutoIngestJobData(coordinationService.getNodeData(CoordinationService.CategoryNode.MANIFESTS, manifestPath.toString())); + AutoIngestJobNodeData nodeData = new AutoIngestJobNodeData(coordinationService.getNodeData(CoordinationService.CategoryNode.MANIFESTS, manifestPath.toString())); if (!nodeData.getProcessingStatus().equals(PENDING)) { /* * Due to a timing issue or a missed event, a @@ -1693,8 +1798,8 @@ public final class AutoIngestManager extends Observable implements PropertyChang iterator.remove(); currentJob = job; break; - } catch (AutoIngestJobDataException ex) { - SYS_LOGGER.log(Level.WARNING, String.format("Unable to use node data for %s", manifestPath), ex); + } catch (AutoIngestJobNodeData.InvalidDataException ex) { + SYS_LOGGER.log(Level.WARNING, String.format("Unable to use node data for %s", manifestPath), ex); // JCTODO: Is this right? } } } @@ -1704,46 +1809,63 @@ public final class AutoIngestManager extends Observable implements PropertyChang /** * Processes and auto ingest job. * - * @throws CoordinationServiceException if there is an error - * acquiring or releasing - * coordination service locks - * or setting coordination - * service node data. - * @throws SharedConfigurationException if there is an error while - * downloading shared - * configuration. - * @throws ServicesMonitorException if there is an error - * querying the services - * monitor. - * @throws DatabaseServerDownException if the database server is - * down. - * @throws KeywordSearchServerDownException if the Solr server is down. - * @throws CaseManagementException if there is an error - * creating, opening or closing - * the case for the job. - * @throws AnalysisStartupException if there is an error - * starting analysis of the - * data source by the data - * source level and file level - * ingest modules. - * @throws FileExportException if there is an error - * exporting files. - * @throws AutoIngestAlertFileException if there is an error - * creating an alert file. - * @throws AutoIngestJobLoggerException if there is an error writing - * to the auto ingest log for - * the case. - * @throws InterruptedException if the thread running the - * job processing task is - * interrupted while blocked, - * i.e., if auto ingest is - * shutting down. + * @throws CoordinationServiceException if there is an + * error acquiring or + * releasing + * coordination + * service locks or + * setting + * coordination + * service node data. + * @throws SharedConfigurationException if there is an + * error while + * downloading shared + * configuration. + * @throws ServicesMonitorException if there is an + * error querying the + * services monitor. + * @throws DatabaseServerDownException if the database + * server is down. + * @throws KeywordSearchServerDownException if the Solr server + * is down. + * @throws CaseManagementException if there is an + * error creating, + * opening or closing + * the case for the + * job. + * @throws AnalysisStartupException if there is an + * error starting + * analysis of the + * data source by the + * data source level + * and file level + * ingest modules. + * @throws FileExportException if there is an + * error exporting + * files. + * @throws AutoIngestAlertFileException if there is an + * error creating an + * alert file. + * @throws AutoIngestJobLoggerException if there is an + * error writing to + * the auto ingest + * log for the case. + * @throws InterruptedException if the thread + * running the job + * processing task is + * interrupted while + * blocked, i.e., if + * auto ingest is + * shutting down. + * @throws AutoIngestJobNodeData.InvalidDataException if there is an + * error constructing + * auto ingest node + * data objects. */ - private void processJob() throws CoordinationServiceException, SharedConfigurationException, ServicesMonitorException, DatabaseServerDownException, KeywordSearchServerDownException, CaseManagementException, AnalysisStartupException, FileExportException, AutoIngestAlertFileException, AutoIngestJobLoggerException, InterruptedException, AutoIngestDataSourceProcessor.AutoIngestDataSourceProcessorException, AutoIngestJobDataException { + private void processJob() throws CoordinationServiceException, SharedConfigurationException, ServicesMonitorException, DatabaseServerDownException, KeywordSearchServerDownException, CaseManagementException, AnalysisStartupException, FileExportException, AutoIngestAlertFileException, AutoIngestJobLoggerException, InterruptedException, AutoIngestDataSourceProcessor.AutoIngestDataSourceProcessorException, AutoIngestJobNodeData.InvalidDataException { Path manifestPath = currentJob.getManifest().getFilePath(); - AutoIngestJobData nodeData = new AutoIngestJobData(coordinationService.getNodeData(CoordinationService.CategoryNode.MANIFESTS, manifestPath.toString())); - nodeData.setProcessingStatus(PROCESSING); - coordinationService.setNodeData(CoordinationService.CategoryNode.MANIFESTS, manifestPath.toString(), nodeData.toArray()); + currentJob.setProcessingStatus(AutoIngestJob.ProcessingStatus.PROCESSING); + updateCoordinationServiceNode(currentJob); SYS_LOGGER.log(Level.INFO, "Started processing of {0}", manifestPath); currentJob.setStage(AutoIngestJob.Stage.STARTING); setChanged(); @@ -1760,18 +1882,15 @@ public final class AutoIngestManager extends Observable implements PropertyChang currentJob.cancel(); } - nodeData = new AutoIngestJobData(coordinationService.getNodeData(CoordinationService.CategoryNode.MANIFESTS, manifestPath.toString())); if (currentJob.isCompleted() || currentJob.isCanceled()) { - nodeData.setProcessingStatus(COMPLETED); + currentJob.setProcessingStatus(AutoIngestJob.ProcessingStatus.COMPLETED); Date completedDate = new Date(); currentJob.setCompletedDate(completedDate); - nodeData.setCompletedDate(currentJob.getCompletedDate()); - nodeData.setErrorsOccurred(currentJob.hasErrors()); } else { // The job may get retried - nodeData.setProcessingStatus(PENDING); + currentJob.setProcessingStatus(AutoIngestJob.ProcessingStatus.PENDING); } - coordinationService.setNodeData(CoordinationService.CategoryNode.MANIFESTS, manifestPath.toString(), nodeData.toArray()); + updateCoordinationServiceNode(currentJob); boolean retry = (!currentJob.isCanceled() && !currentJob.isCompleted()); SYS_LOGGER.log(Level.INFO, "Completed processing of {0}, retry = {1}", new Object[]{manifestPath, retry}); @@ -1779,7 +1898,7 @@ public final class AutoIngestManager extends Observable implements PropertyChang Path caseDirectoryPath = currentJob.getCaseDirectoryPath(); if (null != caseDirectoryPath) { AutoIngestAlertFile.create(caseDirectoryPath); // Do this first, it is more important than the case log - AutoIngestJobLogger jobLogger = new AutoIngestJobLogger(nodeData.getManifestFilePath(), nodeData.getDataSourceFileName(), caseDirectoryPath); + AutoIngestJobLogger jobLogger = new AutoIngestJobLogger(manifestPath, currentJob.getManifest().getDataSourceFileName(), caseDirectoryPath); jobLogger.logJobCancelled(); } } @@ -1974,7 +2093,7 @@ public final class AutoIngestManager extends Observable implements PropertyChang } } } - + /** * Runs the ingest process for the current job. * @@ -2651,6 +2770,7 @@ public final class AutoIngestManager extends Observable implements PropertyChang try { synchronized (jobsLock) { if (currentJob != null) { + currentJob.getStageDetails(); setChanged(); notifyObservers(Event.JOB_STATUS_UPDATED); eventPublisher.publishRemotely(new AutoIngestJobStatusEvent(currentJob)); @@ -2680,7 +2800,7 @@ public final class AutoIngestManager extends Observable implements PropertyChang // check whether any remote nodes have timed out for (AutoIngestJob job : hostNamesToRunningJobs.values()) { - if (isStale(hostNamesToLastMsgTime.get(job.getNodeName()))) { + if (isStale(hostNamesToLastMsgTime.get(job.getProcessingHostName()))) { // remove the job from remote job running map. /* * NOTE: there is theoretically a check-then-act race @@ -2692,7 +2812,7 @@ public final class AutoIngestManager extends Observable implements PropertyChang * back into hostNamesToRunningJobs as a result of * processing the job status update. */ - hostNamesToRunningJobs.remove(job.getNodeName()); + hostNamesToRunningJobs.remove(job.getProcessingHostName()); setChanged(); notifyObservers(Event.JOB_COMPLETED); } @@ -2873,15 +2993,15 @@ public final class AutoIngestManager extends Observable implements PropertyChang } - static final class AutoIngestManagerStartupException extends Exception { + static final class AutoIngestManagerException extends Exception { private static final long serialVersionUID = 1L; - private AutoIngestManagerStartupException(String message) { + private AutoIngestManagerException(String message) { super(message); } - private AutoIngestManagerStartupException(String message, Throwable cause) { + private AutoIngestManagerException(String message, Throwable cause) { super(message, cause); } diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestMonitor.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestMonitor.java index 5abc0163d2..2000bba6f0 100755 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestMonitor.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestMonitor.java @@ -41,7 +41,7 @@ import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.NetworkUtils; import org.sleuthkit.autopsy.events.AutopsyEventException; import org.sleuthkit.autopsy.events.AutopsyEventPublisher; -import org.sleuthkit.autopsy.experimental.autoingest.AutoIngestJobData.ProcessingStatus; +import org.sleuthkit.autopsy.experimental.autoingest.AutoIngestJob.ProcessingStatus; /** * An auto ingest monitor responsible for monitoring and reporting the @@ -160,8 +160,7 @@ public final class AutoIngestMonitor extends Observable implements PropertyChang private void handleJobStatusEvent(AutoIngestJobStatusEvent event) { synchronized (jobsLock) { // DLG: TEST! Replace job in running list with job from event - jobsSnapshot.getRunningJobs().remove((AutoIngestJob)event.getOldValue()); - jobsSnapshot.getRunningJobs().add(event.getJob()); + jobsSnapshot.addOrReplaceRunningJob(event.getJob()); setChanged(); notifyObservers(jobsSnapshot); } @@ -189,26 +188,7 @@ public final class AutoIngestMonitor extends Observable implements PropertyChang * @param event A job/case prioritization event. */ private void handleCasePrioritizationEvent(AutoIngestCasePrioritizedEvent event) { - synchronized (jobsLock) { - // DLG: TEST! Replace job in pending queue with job from event - - jobsSnapshot.getPendingJobs().remove((AutoIngestJob)event.getOldValue()); - jobsSnapshot.getPendingJobs().add((AutoIngestJob)event.getNewValue()); - - /* DLG: List pendingJobsList = jobsSnapshot.getPendingJobs(); - for(int i=0; i < pendingJobsList.size(); i++) { - AutoIngestJob job = pendingJobsList.get(i); - if(job.getNodeName().equalsIgnoreCase(event.getNodeName())) { - if(job.getNodeData().getCaseName().equalsIgnoreCase(event.getCaseName())) { - int newPriority = ((AutoIngestJob)event.getNewValue()).getNodeData().getPriority(); - jobsSnapshot.getPendingJobs().get(i).getNodeData().setPriority(newPriority); - } - } - }*/ - - setChanged(); - notifyObservers(jobsSnapshot); - } + coordSvcQueryExecutor.submit(new CoordinationServiceQueryTask()); } /** @@ -260,12 +240,7 @@ public final class AutoIngestMonitor extends Observable implements PropertyChang List nodeList = coordinationService.getNodeList(CoordinationService.CategoryNode.MANIFESTS); for (String node : nodeList) { try { - // DLG: DONE! Do not need a lock here - - // DLG: DONE! Get the node data and construct an AutoIngestJobData object (DONE! rename AutoIngestJobData => AutoIngestJobData) - AutoIngestJobData nodeData = new AutoIngestJobData(coordinationService.getNodeData(CoordinationService.CategoryNode.MANIFESTS, node)); - - // DLG: DONE! Construct an AutoIngestJob object from the AutoIngestJobData object, need new AutoIngestJob constructor + AutoIngestJobNodeData nodeData = new AutoIngestJobNodeData(coordinationService.getNodeData(CoordinationService.CategoryNode.MANIFESTS, node)); AutoIngestJob job = new AutoIngestJob(nodeData); ProcessingStatus processingStatus = nodeData.getProcessingStatus(); switch (processingStatus) { @@ -279,16 +254,15 @@ public final class AutoIngestMonitor extends Observable implements PropertyChang newJobsSnapshot.addOrReplaceCompletedJob(job); break; case DELETED: - // Do nothing - we dont'want to add it to any job list or do recovery break; default: LOGGER.log(Level.SEVERE, "Unknown AutoIngestJobData.ProcessingStatus"); break; } } catch (InterruptedException ex) { - LOGGER.log(Level.SEVERE, "Unexpected interrupt while retrieving coordination service node data for '{0}'", node); - } catch (AutoIngestJobDataException ex) { - LOGGER.log(Level.WARNING, String.format("Unable to use node data for '%s'", node), ex); + LOGGER.log(Level.SEVERE, String.format("Unexpected interrupt while retrieving coordination service node data for '%s'", node), ex); + } catch (AutoIngestJobNodeData.InvalidDataException ex) { + LOGGER.log(Level.SEVERE, String.format("Unable to use node data for '%s'", node), ex); } } return newJobsSnapshot; @@ -328,10 +302,10 @@ public final class AutoIngestMonitor extends Observable implements PropertyChang ++highestPriority; String manifestNodePath = prioritizedJob.getManifest().getFilePath().toString(); try { - AutoIngestJobData nodeData = new AutoIngestJobData(coordinationService.getNodeData(CoordinationService.CategoryNode.MANIFESTS, manifestNodePath)); + AutoIngestJobNodeData nodeData = new AutoIngestJobNodeData(coordinationService.getNodeData(CoordinationService.CategoryNode.MANIFESTS, manifestNodePath)); nodeData.setPriority(highestPriority); coordinationService.setNodeData(CoordinationService.CategoryNode.MANIFESTS, manifestNodePath, nodeData.toArray()); - } catch (AutoIngestJobDataException | CoordinationServiceException | InterruptedException ex) { + } catch (AutoIngestJobNodeData.InvalidDataException | CoordinationServiceException | InterruptedException ex) { throw new AutoIngestMonitorException("Error bumping priority for job " + prioritizedJob.toString(), ex); } prioritizedJob.setPriority(highestPriority); diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutopsyManifestFileParser.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutopsyManifestFileParser.java index 4a5a3c86c5..2edc31ab71 100755 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutopsyManifestFileParser.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutopsyManifestFileParser.java @@ -18,11 +18,11 @@ */ package org.sleuthkit.autopsy.experimental.autoingest; -import java.io.File; import java.io.IOException; +import java.nio.file.Files; import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.Arrays; +import java.nio.file.attribute.BasicFileAttributes; +import java.util.Date; import java.util.HashMap; import javax.annotation.concurrent.Immutable; import javax.xml.parsers.DocumentBuilder; @@ -42,24 +42,11 @@ import org.xml.sax.SAXException; public final class AutopsyManifestFileParser implements ManifestFileParser { private static final String MANIFEST_FILE_NAME_SIGNATURE = "_MANIFEST.XML"; - private static final String NMEC_MANIFEST_ELEM_TAG_NAME = "NMEC_Manifest"; - private static final String MANIFEST_ELEM_TAG_NAME = "Manifest"; - private static final String CASE_NAME_XPATH = "/Collection/Name/text()"; - private static final String DEVICE_ID_XPATH = "/Collection/Image/ID/text()"; - private static final String IMAGE_NAME_XPATH = "/Collection/Image/Name/text()"; - private static final String IMAGE_FULL_NAME_XPATH = "/Collection/Image/FullName/text()"; - private static final String IMAGE_RELATIVE_PATH_XPATH = "/Collection/Image/RelativePath/text()"; - - private String actualRootElementTag = ""; - + private static final String ROOT_ELEM_TAG_NAME = "AutopsyManifest"; + private static final String CASE_NAME_XPATH = "/AutopsyManifest/CaseName/text()"; + private static final String DEVICE_ID_XPATH = "/AutopsyManifest/DeviceId/text()"; + private static final String DATA_SOURCE_NAME_XPATH = "/AutopsyManifest/DataSource/text()"; - /** - * Determine whether the given file is a supported manifest file. - * - * @param filePath - * - * @return true if this is a supported manifest file, otherwise false - */ @Override public boolean fileIsManifest(Path filePath) { boolean fileIsManifest = false; @@ -68,9 +55,7 @@ public final class AutopsyManifestFileParser implements ManifestFileParser { if (fileName.toString().toUpperCase().endsWith(MANIFEST_FILE_NAME_SIGNATURE)) { Document doc = this.createManifestDOM(filePath); Element docElement = doc.getDocumentElement(); - actualRootElementTag = docElement.getTagName(); - fileIsManifest = actualRootElementTag.equals(MANIFEST_ELEM_TAG_NAME) || - actualRootElementTag.equals(NMEC_MANIFEST_ELEM_TAG_NAME); + fileIsManifest = docElement.getTagName().equals(ROOT_ELEM_TAG_NAME); } } catch (Exception unused) { fileIsManifest = false; @@ -78,95 +63,30 @@ public final class AutopsyManifestFileParser implements ManifestFileParser { return fileIsManifest; } - /** - * Parse the given manifest file and create a Manifest object. - * - * @param filePath Fully qualified path to manifest file - * - * @return A Manifest object representing the parsed manifest file. - * - * @throws ManifestFileParserException - */ @Override public Manifest parse(Path filePath) throws ManifestFileParserException { try { + BasicFileAttributes attrs = Files.readAttributes(filePath, BasicFileAttributes.class); + Date dateFileCreated = new Date(attrs.creationTime().toMillis()); Document doc = this.createManifestDOM(filePath); XPath xpath = XPathFactory.newInstance().newXPath(); - XPathExpression expr = xpath.compile(constructXPathExpression(CASE_NAME_XPATH)); - String caseName = (String) expr.evaluate(doc, XPathConstants.STRING); - expr = xpath.compile(constructXPathExpression(DEVICE_ID_XPATH)); + XPathExpression expr = xpath.compile(CASE_NAME_XPATH); + String caseName = (String) expr.evaluate(doc, XPathConstants.STRING); + expr = xpath.compile(DEVICE_ID_XPATH); String deviceId = (String) expr.evaluate(doc, XPathConstants.STRING); - Path dataSourcePath = determineDataSourcePath(filePath, doc); - return new Manifest(filePath, caseName, deviceId, dataSourcePath, new HashMap<>()); + expr = xpath.compile(DATA_SOURCE_NAME_XPATH); + String dataSourceName = (String) expr.evaluate(doc, XPathConstants.STRING); + Path dataSourcePath = filePath.getParent().resolve(dataSourceName); + return new Manifest(filePath, dateFileCreated, caseName, deviceId, dataSourcePath, new HashMap<>()); } catch (Exception ex) { throw new ManifestFileParserException(String.format("Error parsing manifest %s", filePath), ex); } } - /** - * Create a new DOM document object for the given manifest file. - * - * @param manifestFilePath Fully qualified path to manifest file. - * - * @return DOM document object - * - * @throws ParserConfigurationException - * @throws SAXException - * @throws IOException - */ private Document createManifestDOM(Path manifestFilePath) throws ParserConfigurationException, SAXException, IOException { DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory.newInstance(); DocumentBuilder docBuilder = docBuilderFactory.newDocumentBuilder(); return docBuilder.parse(manifestFilePath.toFile()); } - /** - * Creates an XPath expression string relative to the actual root - * element of the manifest for the given path. - * - * @param path - * @return XPath expression string. - */ - private String constructXPathExpression(String path) { - return "/" + actualRootElementTag + path; - } - - /** - * Attempt to find a valid (existing) data source for the manifest file. - * - * @param manifestFilePath Fully qualified path to manifest file. - * @param doc DOM document object for the manifest file. - * @return Path to an existing data source. - * @throws ManifestFileParserException if an error occurred while parsing manifest file. - */ - private Path determineDataSourcePath(Path manifestFilePath, Document doc) throws ManifestFileParserException { - String dataSourcePath = ""; - try { - for (String element : Arrays.asList(IMAGE_NAME_XPATH, IMAGE_FULL_NAME_XPATH, IMAGE_RELATIVE_PATH_XPATH)) { - XPath xpath = XPathFactory.newInstance().newXPath(); - XPathExpression expr = xpath.compile(constructXPathExpression(element)); - String fileName = (String) expr.evaluate(doc, XPathConstants.STRING); - if (fileName.contains("\\")) { - fileName = fileName.substring(fileName.lastIndexOf("\\") + 1); - } - try { - dataSourcePath = manifestFilePath.getParent().resolve(fileName).toString(); - } catch (Exception ignore) { - // NOTE: exceptions can be thrown by resolve() method based on contents of the manifest file. - // For example if file name is "test .txt" and in one of the path fields they only enter "test " - // i.e. the file name without extension. - // We should continue on to the next XML path field - continue; - } - if (new File(dataSourcePath).exists()) { - // found the data source - return Paths.get(dataSourcePath); - } - // keep trying other XML fields - } - return Paths.get(dataSourcePath); - } catch (Exception ex) { - throw new ManifestFileParserException(String.format("Error parsing manifest %s", manifestFilePath), ex); - } - } } diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/Manifest.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/Manifest.java index 0be566d47c..e3c4cb6bb1 100755 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/Manifest.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/Manifest.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2015 Basis Technology Corp. + * Copyright 2011-2017 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,12 +18,9 @@ */ package org.sleuthkit.autopsy.experimental.autoingest; -import java.io.IOException; import java.io.Serializable; -import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; -import java.nio.file.attribute.BasicFileAttributes; import java.util.Date; import java.util.HashMap; import java.util.Map; @@ -40,22 +37,6 @@ public final class Manifest implements Serializable { private final String dataSourcePath; private final Map manifestProperties; - // RJCTODO - public Manifest(Path manifestFilePath, String caseName, String deviceId, Path dataSourcePath, Map manifestProperties) throws IOException { - this.filePath = manifestFilePath.toString(); - BasicFileAttributes attrs = Files.readAttributes(manifestFilePath, BasicFileAttributes.class); - this.dateFileCreated = new Date(attrs.creationTime().toMillis()); - this.caseName = caseName; - this.deviceId = deviceId; - if (dataSourcePath != null) { - this.dataSourcePath = dataSourcePath.toString(); - } else { - this.dataSourcePath = ""; - } - this.manifestProperties = new HashMap<>(manifestProperties); - } - - // RJCTODO public Manifest(Path manifestFilePath, Date dateFileCreated, String caseName, String deviceId, Path dataSourcePath, Map manifestProperties) { this.filePath = manifestFilePath.toString(); this.dateFileCreated = dateFileCreated; From 38123b0f7bd4054c1700a789d77df2af60646787 Mon Sep 17 00:00:00 2001 From: Richard Cordovano Date: Fri, 15 Sep 2017 15:44:18 -0400 Subject: [PATCH 14/14] Remove obsolete CentralRepository directory --- .../Central Repository User Guide.pdf | Bin 246214 -> 0 bytes CentralRepository/ivy.xml | 15 --------------- CentralRepository/ivysettings.xml | 10 ---------- 3 files changed, 25 deletions(-) delete mode 100755 CentralRepository/Central Repository User Guide.pdf delete mode 100755 CentralRepository/ivy.xml delete mode 100755 CentralRepository/ivysettings.xml diff --git a/CentralRepository/Central Repository User Guide.pdf b/CentralRepository/Central Repository User Guide.pdf deleted file mode 100755 index 0b597fbfbe1eb1f8b918073fa2aa72ed19f949a9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 246214 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

cW)vb+2 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

2M2Y@aj*tIheoC%l4J>?>w%GXp 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 kM>sGTa_D3U53wK!;0}1o2w<>40t - - - - - - - - - - - - - - diff --git a/CentralRepository/ivysettings.xml b/CentralRepository/ivysettings.xml deleted file mode 100755 index e3e086637b..0000000000 --- a/CentralRepository/ivysettings.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - -