diff --git a/Core/ivy.xml b/Core/ivy.xml
index eea67feb19..35cb8de904 100755
--- a/Core/ivy.xml
+++ b/Core/ivy.xml
@@ -22,5 +22,6 @@
+
diff --git a/Core/nbproject/project.properties b/Core/nbproject/project.properties
index 7c36c61db7..b6931ff315 100755
--- a/Core/nbproject/project.properties
+++ b/Core/nbproject/project.properties
@@ -5,6 +5,7 @@ file.reference.commons-dbcp2-2.1.1.jar=release\\modules\\ext\\commons-dbcp2-2.1.
file.reference.commons-pool2-2.4.2.jar=release\\modules\\ext\\commons-pool2-2.4.2.jar
file.reference.jdom-2.0.5-contrib.jar=release/modules/ext/jdom-2.0.5-contrib.jar
file.reference.jdom-2.0.5.jar=release/modules/ext/jdom-2.0.5.jar
+file.reference.jsoup-1.10.3.jar=release/modules/ext/jsoup-1.10.3.jar
file.reference.jython-standalone-2.7.0.jar=release/modules/ext/jython-standalone-2.7.0.jar
file.reference.mchange-commons-java-0.2.9.jar=release/modules/ext/mchange-commons-java-0.2.9.jar
file.reference.metadata-extractor-2.9.1.jar=release/modules/ext/metadata-extractor-2.9.1.jar
diff --git a/Core/nbproject/project.xml b/Core/nbproject/project.xml
index 525e6da6a7..c4fea0885c 100755
--- a/Core/nbproject/project.xml
+++ b/Core/nbproject/project.xml
@@ -320,6 +320,10 @@
org.sleuthkit.autopsy.reportorg.sleuthkit.datamodel
+
+ ext/xmpcore-5.1.2.jar
+ release/modules/ext/xmpcore-5.1.2.jar
+ ext/zookeeper-3.4.6.jarrelease/modules/ext/zookeeper-3.4.6.jar
@@ -335,6 +339,10 @@
ext/Tsk_DataModel_PostgreSQL.jarrelease/modules/ext/Tsk_DataModel_PostgreSQL.jar
+
+
+ ext/StixLib.jar
+ release/modules/ext/StixLib.jarext/opencv-248.jar
@@ -344,6 +352,10 @@
ext/curator-framework-2.8.0.jarrelease/modules/ext/curator-framework-2.8.0.jar
+
+ ext/curator-client-2.8.0.jar
+ release/modules/ext/curator-client-2.8.0.jar
+ ext/commons-dbcp2-2.1.1.jarrelease\modules\ext\commons-dbcp2-2.1.1.jar
@@ -356,6 +368,14 @@
ext/jython-standalone-2.7.0.jarrelease/modules/ext/jython-standalone-2.7.0.jar
+
+ ext/activemq-all-5.11.1.jar
+ release/modules/ext/activemq-all-5.11.1.jar
+
+
+ ext/opencv-248.jar
+ release/modules/ext/opencv-248.jar
+ ext/sevenzipjbinding.jarrelease/modules/ext/sevenzipjbinding.jar
@@ -377,12 +397,12 @@
release/modules/ext/xmpcore-5.1.2.jar
- ext/StixLib.jar
- release/modules/ext/StixLib.jar
+ ext/sevenzipjbinding-AllPlatforms.jar
+ release/modules/ext/sevenzipjbinding-AllPlatforms.jar
- ext/curator-client-2.8.0.jar
- release/modules/ext/curator-client-2.8.0.jar
+ ext/commons-pool2-2.4.2.jar
+ release\modules\ext\commons-pool2-2.4.2.jarext/sqlite-jdbc-3.8.11.jar
@@ -392,18 +412,14 @@
ext/activemq-all-5.11.1.jarrelease/modules/ext/activemq-all-5.11.1.jar
+
+ ext/jsoup-1.10.3.jar
+ release/modules/ext/jsoup-1.10.3.jar
+ ext/Rejistry-1.0-SNAPSHOT.jarrelease/modules/ext/Rejistry-1.0-SNAPSHOT.jar
-
- ext/sevenzipjbinding-AllPlatforms.jar
- release/modules/ext/sevenzipjbinding-AllPlatforms.jar
-
-
- ext/commons-pool2-2.4.2.jar
- release\modules\ext\commons-pool2-2.4.2.jar
- ext/metadata-extractor-2.9.1.jarrelease/modules/ext/metadata-extractor-2.9.1.jar
@@ -420,6 +436,10 @@
ext/c3p0-0.9.5.jarrelease/modules/ext/c3p0-0.9.5.jar
+
+ ext/c3p0-0.9.5.jar
+ release/modules/ext/c3p0-0.9.5.jar
+
diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java
index 52a2b7fa97..e2e58c5b5a 100755
--- a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java
+++ b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java
@@ -76,6 +76,7 @@ import org.sleuthkit.autopsy.casemodule.events.ContentTagDeletedEvent;
import org.sleuthkit.autopsy.casemodule.events.DataSourceAddedEvent;
import org.sleuthkit.autopsy.casemodule.events.ReportAddedEvent;
import org.sleuthkit.autopsy.casemodule.services.Services;
+import org.sleuthkit.autopsy.communications.OpenCommVisualizationToolAction;
import org.sleuthkit.autopsy.coordinationservice.CoordinationService;
import org.sleuthkit.autopsy.coordinationservice.CoordinationService.CategoryNode;
import org.sleuthkit.autopsy.coordinationservice.CoordinationService.CoordinationServiceException;
@@ -1050,6 +1051,7 @@ public class Case {
CallableSystemAction.get(CasePropertiesAction.class).setEnabled(true);
CallableSystemAction.get(CaseDeleteAction.class).setEnabled(true);
CallableSystemAction.get(OpenTimelineAction.class).setEnabled(true);
+ CallableSystemAction.get(OpenCommVisualizationToolAction.class).setEnabled(true);
CallableSystemAction.get(OpenOutputFolderAction.class).setEnabled(false);
/*
@@ -1092,24 +1094,13 @@ public class Case {
/*
* Disable the case-specific menu items.
*/
- CallableSystemAction
- .get(AddImageAction.class
- ).setEnabled(false);
- CallableSystemAction
- .get(CaseCloseAction.class
- ).setEnabled(false);
- CallableSystemAction
- .get(CasePropertiesAction.class
- ).setEnabled(false);
- CallableSystemAction
- .get(CaseDeleteAction.class
- ).setEnabled(false);
- CallableSystemAction
- .get(OpenTimelineAction.class
- ).setEnabled(false);
- CallableSystemAction
- .get(OpenOutputFolderAction.class
- ).setEnabled(false);
+ CallableSystemAction.get(AddImageAction.class).setEnabled(false);
+ CallableSystemAction.get(CaseCloseAction.class).setEnabled(false);
+ CallableSystemAction.get(CasePropertiesAction.class).setEnabled(false);
+ CallableSystemAction.get(CaseDeleteAction.class).setEnabled(false);
+ CallableSystemAction.get(OpenTimelineAction.class).setEnabled(false);
+ CallableSystemAction.get(OpenCommVisualizationToolAction.class).setEnabled(false);
+ CallableSystemAction.get(OpenOutputFolderAction.class).setEnabled(false);
/*
* Clear the notifications in the notfier component in the lower
diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/AbstractSqlEamDb.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/AbstractSqlEamDb.java
index bf0541009a..ce95196f08 100644
--- a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/AbstractSqlEamDb.java
+++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/AbstractSqlEamDb.java
@@ -34,9 +34,12 @@ import java.time.LocalDate;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
+import java.util.logging.Level;
import org.openide.util.NbBundle.Messages;
import org.sleuthkit.autopsy.casemodule.Case;
+import static org.sleuthkit.autopsy.centralrepository.datamodel.EamDbUtil.updateSchemaVersion;
import org.sleuthkit.autopsy.coreutils.Logger;
+import org.sleuthkit.datamodel.CaseDbSchemaVersionNumber;
import org.sleuthkit.datamodel.TskData;
/**
@@ -77,7 +80,7 @@ public abstract class AbstractSqlEamDb implements EamDb {
/**
* Add a new name/value pair in the db_info table.
*
- * @param name Key to set
+ * @param name Key to set
* @param value Value to set
*
* @throws EamDbException
@@ -140,7 +143,7 @@ public abstract class AbstractSqlEamDb implements EamDb {
/**
* Update the value for a name in the name/value db_info table.
*
- * @param name Name to find
+ * @param name Name to find
* @param value Value to assign to name.
*
* @throws EamDbException
@@ -226,7 +229,7 @@ public abstract class AbstractSqlEamDb implements EamDb {
EamDbUtil.closePreparedStatement(preparedStatement);
EamDbUtil.closeConnection(conn);
}
-
+
// get a new version with the updated ID
return getCaseByUUID(eamCase.getCaseUUID());
}
@@ -255,12 +258,11 @@ public abstract class AbstractSqlEamDb implements EamDb {
autopsyCase.getCaseNotes());
return newCase(curCeCase);
}
-
+
@Override
public CorrelationCase getCase(Case autopsyCase) throws EamDbException {
return getCaseByUUID(autopsyCase.getName());
}
-
/**
* Updates an existing Case in the database
@@ -434,8 +436,8 @@ public abstract class AbstractSqlEamDb implements EamDb {
/**
* Retrieves Data Source details based on data source device ID
*
- * @param correlationCase the current CorrelationCase used for ensuring
- * uniqueness of DataSource
+ * @param correlationCase the current CorrelationCase used for ensuring
+ * uniqueness of DataSource
* @param dataSourceDeviceId the data source device ID number
*
* @return The data source
@@ -608,7 +610,7 @@ public abstract class AbstractSqlEamDb implements EamDb {
* Retrieves eamArtifact instances from the database that are associated
* with the aType and filePath
*
- * @param aType EamArtifact.Type to search for
+ * @param aType EamArtifact.Type to search for
* @param filePath File path to search for
*
* @return List of 0 or more EamArtifactInstances
@@ -664,7 +666,7 @@ public abstract class AbstractSqlEamDb implements EamDb {
* @param value The correlation value
*
* @return Number of artifact instances having ArtifactType and
- * ArtifactValue.
+ * ArtifactValue.
*/
@Override
public Long getCountArtifactInstancesByTypeValue(CorrelationAttribute.Type aType, String value) throws EamDbException {
@@ -779,11 +781,11 @@ public abstract class AbstractSqlEamDb implements EamDb {
* associated with the caseDisplayName and dataSource of the given
* eamArtifact instance.
*
- * @param caseUUID Case ID to search for
+ * @param caseUUID Case ID to search for
* @param dataSourceID Data source ID to search for
*
* @return Number of artifact instances having caseDisplayName and
- * dataSource
+ * dataSource
*/
@Override
public Long getCountArtifactInstancesByCaseDataSource(String caseUUID, String dataSourceID) throws EamDbException {
@@ -1070,7 +1072,7 @@ public abstract class AbstractSqlEamDb implements EamDb {
if (null == correlationCaseWithId) {
correlationCaseWithId = newCase(eamInstance.getCorrelationCase());
}
-
+
if (null == getDataSource(correlationCaseWithId, eamInstance.getCorrelationDataSource().getDeviceID())) {
newDataSource(eamInstance.getCorrelationDataSource());
}
@@ -1187,7 +1189,7 @@ public abstract class AbstractSqlEamDb implements EamDb {
* @param value Value to search for
*
* @return List of cases containing this artifact with instances marked as
- * bad
+ * bad
*
* @throws EamDbException
*/
@@ -1230,24 +1232,26 @@ public abstract class AbstractSqlEamDb implements EamDb {
return caseNames.stream().collect(Collectors.toList());
}
-
+
/**
* Remove a reference set and all entries contained in it.
+ *
* @param referenceSetID
- * @throws EamDbException
+ * @throws EamDbException
*/
@Override
- public void deleteReferenceSet(int referenceSetID) throws EamDbException{
+ public void deleteReferenceSet(int referenceSetID) throws EamDbException {
deleteReferenceSetEntries(referenceSetID);
deleteReferenceSetEntry(referenceSetID);
- }
-
+ }
+
/**
* Remove the entry for this set from the reference_sets table
+ *
* @param referenceSetID
- * @throws EamDbException
+ * @throws EamDbException
*/
- private void deleteReferenceSetEntry(int referenceSetID) throws EamDbException{
+ private void deleteReferenceSetEntry(int referenceSetID) throws EamDbException {
Connection conn = connect();
PreparedStatement preparedStatement = null;
@@ -1262,21 +1266,22 @@ public abstract class AbstractSqlEamDb implements EamDb {
} finally {
EamDbUtil.closePreparedStatement(preparedStatement);
EamDbUtil.closeConnection(conn);
- }
+ }
}
-
+
/**
* Remove all entries for this reference set from the reference tables
* (Currently only removes entries from the reference_file table)
+ *
* @param referenceSetID
- * @throws EamDbException
+ * @throws EamDbException
*/
- private void deleteReferenceSetEntries(int referenceSetID) throws EamDbException{
+ private void deleteReferenceSetEntries(int referenceSetID) throws EamDbException {
Connection conn = connect();
PreparedStatement preparedStatement = null;
String sql = "DELETE FROM %s WHERE reference_set_id=?";
-
+
// When other reference types are added, this will need to loop over all the tables
String fileTableName = EamDbUtil.correlationTypeToReferenceTableName(getCorrelationTypeById(CorrelationAttribute.FILES_TYPE_ID));
@@ -1289,12 +1294,14 @@ public abstract class AbstractSqlEamDb implements EamDb {
} finally {
EamDbUtil.closePreparedStatement(preparedStatement);
EamDbUtil.closeConnection(conn);
- }
+ }
}
-
+
/**
- * Check whether a reference set with the given parameters exists in the central repository.
- * Used to check whether reference sets saved in the settings are still present.
+ * Check whether a reference set with the given parameters exists in the
+ * central repository. Used to check whether reference sets saved in the
+ * settings are still present.
+ *
* @param referenceSetID
* @param setName
* @param version
@@ -1302,37 +1309,39 @@ public abstract class AbstractSqlEamDb implements EamDb {
* @throws EamDbException
*/
@Override
- public boolean referenceSetIsValid(int referenceSetID, String setName, String version) throws EamDbException{
+ public boolean referenceSetIsValid(int referenceSetID, String setName, String version) throws EamDbException {
EamGlobalSet refSet = this.getReferenceSetByID(referenceSetID);
- if(refSet == null){
+ if (refSet == null) {
return false;
}
-
- return(refSet.getSetName().equals(setName) && refSet.getVersion().equals(version));
+
+ return (refSet.getSetName().equals(setName) && refSet.getVersion().equals(version));
}
-
+
/**
- * Check if the given file hash is in this reference set.
- * Only searches the reference_files table.
+ * Check if the given file hash is in this reference set. Only searches the
+ * reference_files table.
+ *
* @param hash
* @param referenceSetID
* @return true if the hash is found in the reference set
- * @throws EamDbException
+ * @throws EamDbException
*/
@Override
- public boolean isFileHashInReferenceSet(String hash, int referenceSetID) throws EamDbException{
+ public boolean isFileHashInReferenceSet(String hash, int referenceSetID) throws EamDbException {
return isValueInReferenceSet(hash, referenceSetID, CorrelationAttribute.FILES_TYPE_ID);
- }
-
+ }
+
/**
* Check if the given value is in a specific reference set
+ *
* @param value
* @param referenceSetID
- * @param correlationTypeID
+ * @param correlationTypeID
* @return true if the value is found in the reference set
*/
@Override
- public boolean isValueInReferenceSet(String value, int referenceSetID, int correlationTypeID) throws EamDbException{
+ public boolean isValueInReferenceSet(String value, int referenceSetID, int correlationTypeID) throws EamDbException {
Connection conn = connect();
@@ -1340,7 +1349,7 @@ public abstract class AbstractSqlEamDb implements EamDb {
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;
String sql = "SELECT count(*) FROM %s WHERE value=? AND reference_set_id=?";
-
+
String fileTableName = EamDbUtil.correlationTypeToReferenceTableName(getCorrelationTypeById(correlationTypeID));
try {
@@ -1506,15 +1515,16 @@ public abstract class AbstractSqlEamDb implements EamDb {
EamDbUtil.closeConnection(conn);
}
}
-
+
/**
* Get the organization associated with the given reference set.
+ *
* @param referenceSetID ID of the reference set
* @return The organization object
- * @throws EamDbException
+ * @throws EamDbException
*/
@Override
- public EamOrganization getReferenceSetOrganization(int referenceSetID) throws EamDbException{
+ public EamOrganization getReferenceSetOrganization(int referenceSetID) throws EamDbException {
EamGlobalSet globalSet = getReferenceSetByID(referenceSetID);
return (getOrganizationByID(globalSet.getOrgID()));
@@ -1524,7 +1534,7 @@ public abstract class AbstractSqlEamDb implements EamDb {
* Update an existing organization.
*
* @param updatedOrganization the values the Organization with the same ID
- * will be updated to in the database.
+ * will be updated to in the database.
*
* @throws EamDbException
*/
@@ -1550,8 +1560,8 @@ public abstract class AbstractSqlEamDb implements EamDb {
}
@Messages({"AbstractSqlEamDb.deleteOrganization.inUseException.message=Can not delete organization "
- + "which is currently in use by a case or reference set in the central repository.",
- "AbstractSqlEamDb.deleteOrganization.errorDeleting.message=Error executing query when attempting to delete organization by id."})
+ + "which is currently in use by a case or reference set in the central repository.",
+ "AbstractSqlEamDb.deleteOrganization.errorDeleting.message=Error executing query when attempting to delete organization by id."})
@Override
public void deleteOrganization(EamOrganization organizationToDelete) throws EamDbException {
Connection conn = connect();
@@ -1664,7 +1674,7 @@ public abstract class AbstractSqlEamDb implements EamDb {
EamDbUtil.closeConnection(conn);
}
}
-
+
/**
* Get all reference sets
*
@@ -1673,7 +1683,7 @@ public abstract class AbstractSqlEamDb implements EamDb {
* @throws EamDbException
*/
@Override
- public List getAllReferenceSets() throws EamDbException{
+ public List getAllReferenceSets() throws EamDbException {
List results = new ArrayList<>();
Connection conn = connect();
@@ -1702,8 +1712,7 @@ public abstract class AbstractSqlEamDb implements EamDb {
* Add a new reference instance
*
* @param eamGlobalFileInstance The reference instance to add
- * @param correlationType Correlation Type that this Reference
- * Instance is
+ * @param correlationType Correlation Type that this Reference Instance is
*
* @throws EamDbException
*/
@@ -1729,17 +1738,19 @@ public abstract class AbstractSqlEamDb implements EamDb {
EamDbUtil.closeConnection(conn);
}
}
-
+
/**
- * Check whether a reference set with the given name/version is in the central repo.
- * Used to check for name collisions when creating reference sets.
+ * Check whether a reference set with the given name/version is in the
+ * central repo. Used to check for name collisions when creating reference
+ * sets.
+ *
* @param referenceSetName
* @param version
* @return true if a matching set is found
- * @throws EamDbException
+ * @throws EamDbException
*/
@Override
- public boolean referenceSetExists(String referenceSetName, String version) throws EamDbException{
+ public boolean referenceSetExists(String referenceSetName, String version) throws EamDbException {
Connection conn = connect();
PreparedStatement preparedStatement1 = null;
@@ -1754,13 +1765,13 @@ public abstract class AbstractSqlEamDb implements EamDb {
return (resultSet.next());
} catch (SQLException ex) {
- throw new EamDbException("Error testing whether reference set exists (name: " + referenceSetName
+ throw new EamDbException("Error testing whether reference set exists (name: " + referenceSetName
+ " version: " + version, ex); // NON-NLS
} finally {
EamDbUtil.closePreparedStatement(preparedStatement1);
EamDbUtil.closeResultSet(resultSet);
EamDbUtil.closeConnection(conn);
- }
+ }
}
/**
@@ -1808,7 +1819,7 @@ public abstract class AbstractSqlEamDb implements EamDb {
/**
* Get all reference entries having a given correlation type and value
*
- * @param aType Type to use for matching
+ * @param aType Type to use for matching
* @param aValue Value to use for matching
*
* @return List of all global file instances with a type and value
@@ -1938,7 +1949,7 @@ public abstract class AbstractSqlEamDb implements EamDb {
* artifacts.
*
* @return List of enabled EamArtifact.Type's. If none are defined in the
- * database, the default list will be returned.
+ * database, the default list will be returned.
*
* @throws EamDbException
*/
@@ -1973,7 +1984,7 @@ public abstract class AbstractSqlEamDb implements EamDb {
* correlate artifacts.
*
* @return List of supported EamArtifact.Type's. If none are defined in the
- * database, the default list will be returned.
+ * database, the default list will be returned.
*
* @throws EamDbException
*/
@@ -2074,7 +2085,7 @@ public abstract class AbstractSqlEamDb implements EamDb {
* Convert a ResultSet to a EamCase object
*
* @param resultSet A resultSet with a set of values to create a EamCase
- * object.
+ * object.
*
* @return fully populated EamCase object, or null
*
@@ -2144,7 +2155,7 @@ public abstract class AbstractSqlEamDb implements EamDb {
* Convert a ResultSet to a EamArtifactInstance object
*
* @param resultSet A resultSet with a set of values to create a
- * EamArtifactInstance object.
+ * EamArtifactInstance object.
*
* @return fully populated EamArtifactInstance, or null
*
@@ -2215,4 +2226,82 @@ public abstract class AbstractSqlEamDb implements EamDb {
return eamGlobalFileInstance;
}
+ /**
+ * Upgrade the schema of the database (if needed)
+ *
+ * @throws EamDbException
+ */
+ @Override
+ public void upgradeSchema() throws EamDbException, SQLException {
+
+ ResultSet resultSet = null;
+ Statement statement;
+ Connection conn = null;
+ try {
+
+ conn = connect();
+ conn.setAutoCommit(false);
+ statement = conn.createStatement();
+
+ int minorVersion = 0;
+ int majorVersion = 0;
+ resultSet = statement.executeQuery("SELECT value FROM db_info WHERE name='SCHEMA_MINOR_VERSION'");
+ if (resultSet.next()) {
+ String minorVersionStr = resultSet.getString("value");
+ try {
+ minorVersion = Integer.parseInt(minorVersionStr);
+ } catch (NumberFormatException ex) {
+ throw new EamDbException("Bad value for schema minor version (" + minorVersionStr + ") - database is corrupt");
+ }
+ }
+
+ resultSet = statement.executeQuery("SELECT value FROM db_info WHERE name='SCHEMA_VERSION'");
+ if (resultSet.next()) {
+ String majorVersionStr = resultSet.getString("value");
+ try {
+ majorVersion = Integer.parseInt(majorVersionStr);
+ } catch (NumberFormatException ex) {
+ throw new EamDbException("Bad value for schema version (" + majorVersionStr + ") - database is corrupt");
+ }
+ }
+
+ CaseDbSchemaVersionNumber dbSchemaVersion = new CaseDbSchemaVersionNumber(majorVersion, minorVersion);
+ if (dbSchemaVersion.equals(CURRENT_DB_SCHEMA_VERSION)) {
+ LOGGER.log(Level.INFO, "Central Repository is up to date");
+ return;
+ }
+
+ // Update from 1.0 to 1.1
+ if (dbSchemaVersion.compareTo(new CaseDbSchemaVersionNumber(1, 1)) < 0) {
+ statement.execute("ALTER TABLE reference_sets ADD COLUMN known_status INTEGER;"); //NON-NLS
+ statement.execute("ALTER TABLE reference_sets ADD COLUMN read_only BOOLEAN;"); //NON-NLS
+ statement.execute("ALTER TABLE reference_sets ADD COLUMN type INTEGER;"); //NON-NLS
+
+ // There's an outide chance that the user has already made an organization with the default name,
+ // and the default org being missing will not impact any database operations, so continue on
+ // regardless of whether this succeeds.
+ EamDbUtil.insertDefaultOrganization(conn);
+ }
+
+ if (!updateSchemaVersion(conn)) {
+ throw new EamDbException("Error updating schema version");
+ }
+
+ conn.commit();
+ LOGGER.log(Level.INFO, "Central Repository upgraded to version " + CURRENT_DB_SCHEMA_VERSION);
+ } catch (SQLException | EamDbException ex) {
+ try {
+ if (conn != null) {
+ conn.rollback();
+ }
+ } catch (SQLException ex2) {
+ LOGGER.log(Level.SEVERE, "Database rollback failed", ex2);
+ }
+ throw ex;
+ } finally {
+ EamDbUtil.closeResultSet(resultSet);
+ EamDbUtil.closeConnection(conn);
+ }
+ }
+
}
diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/EamDb.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/EamDb.java
index b9ce4320de..f8ab7bfc60 100644
--- a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/EamDb.java
+++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/EamDb.java
@@ -18,10 +18,13 @@
*/
package org.sleuthkit.autopsy.centralrepository.datamodel;
+import java.sql.SQLException;
import java.util.List;
import java.util.Set;
import org.sleuthkit.datamodel.TskData;
import org.sleuthkit.autopsy.casemodule.Case;
+import org.sleuthkit.autopsy.coordinationservice.CoordinationService;
+import org.sleuthkit.datamodel.CaseDbSchemaVersionNumber;
/**
* Main interface for interacting with the database
@@ -29,6 +32,8 @@ import org.sleuthkit.autopsy.casemodule.Case;
public interface EamDb {
public static final int SCHEMA_VERSION = 1;
+ public static final CaseDbSchemaVersionNumber CURRENT_DB_SCHEMA_VERSION
+ = new CaseDbSchemaVersionNumber(1, 1);
/**
* Get the instance
@@ -593,4 +598,20 @@ public interface EamDb {
* @throws EamDbException
*/
public CorrelationAttribute.Type getCorrelationTypeById(int typeId) throws EamDbException;
+
+ /**
+ * Upgrade the schema of the database (if needed)
+ * @throws EamDbException
+ */
+ public void upgradeSchema() throws EamDbException, SQLException;
+
+ /**
+ * Gets an exclusive lock (if applicable).
+ * Will return the lock if successful, null if unsuccessful because locking
+ * isn't supported, and throw an exception if we should have been able to get the
+ * lock but failed (meaning the database is in use).
+ * @return the lock, or null if locking is not supported
+ * @throws EamDbException if the coordination service is running but we fail to get the lock
+ */
+ public CoordinationService.Lock getExclusiveMultiUserDbLock() throws EamDbException;
}
diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/EamDbUtil.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/EamDbUtil.java
old mode 100755
new mode 100644
index 49f49f9d38..7c91f24648
--- a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/EamDbUtil.java
+++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/EamDbUtil.java
@@ -25,7 +25,9 @@ import java.sql.SQLException;
import java.sql.Statement;
import java.util.List;
import java.util.logging.Level;
-import static org.sleuthkit.autopsy.centralrepository.datamodel.EamDb.SCHEMA_VERSION;
+import static org.sleuthkit.autopsy.centralrepository.datamodel.EamDb.CURRENT_DB_SCHEMA_VERSION;
+import org.sleuthkit.autopsy.coordinationservice.CoordinationService;
+import org.sleuthkit.autopsy.coordinationservice.CoordinationService.CoordinationServiceException;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.coreutils.ModuleSettings;
@@ -133,20 +135,33 @@ public class EamDbUtil {
*
* @return true on success, else false
*/
- public static boolean insertSchemaVersion(Connection conn) {
- PreparedStatement preparedStatement = null;
+ static boolean updateSchemaVersion(Connection conn) {
+
+ Statement statement;
+ ResultSet resultSet;
String sql = "INSERT INTO db_info (name, value) VALUES (?, ?)";
try {
- preparedStatement = conn.prepareStatement(sql);
- preparedStatement.setString(1, "SCHEMA_VERSION");
- preparedStatement.setString(2, String.valueOf(SCHEMA_VERSION));
- preparedStatement.executeUpdate();
+ statement = conn.createStatement();
+ resultSet = statement.executeQuery("SELECT id FROM db_info WHERE name='SCHEMA_VERSION'");
+ if (resultSet.next()) {
+ int id = resultSet.getInt("id");
+ statement.execute("UPDATE db_info SET value=" + CURRENT_DB_SCHEMA_VERSION.getMajor() + " WHERE id=" + id);
+ } else {
+ statement.execute("INSERT INTO db_info (name, value) VALUES ('SCHEMA_VERSION', '" + CURRENT_DB_SCHEMA_VERSION.getMajor() + "')");
+ }
+
+ resultSet = statement.executeQuery("SELECT id FROM db_info WHERE name='SCHEMA_MINOR_VERSION'");
+ if (resultSet.next()) {
+ int id = resultSet.getInt("id");
+ statement.execute("UPDATE db_info SET value=" + CURRENT_DB_SCHEMA_VERSION.getMinor() + " WHERE id=" + id);
+ } else {
+ statement.execute("INSERT INTO db_info (name, value) VALUES ('SCHEMA_MINOR_VERSION', '" + CURRENT_DB_SCHEMA_VERSION.getMinor() + "')");
+ }
} catch (SQLException ex) {
LOGGER.log(Level.SEVERE, "Error adding schema version to db_info.", ex);
return false;
- } finally {
- EamDbUtil.closePreparedStatement(preparedStatement);
}
+
return true;
}
@@ -176,6 +191,66 @@ public class EamDbUtil {
return true;
}
+ /**
+ * Upgrade the current central reposity to the newest version. If the upgrade
+ * fails, the central repository will be disabled and the current settings
+ * will be cleared.
+ *
+ * @return true if the upgrade succeeds, false otherwise.
+ */
+ public static boolean upgradeDatabase() {
+ if (!EamDb.isEnabled()) {
+ return true;
+ }
+
+ CoordinationService.Lock lock = null;
+ try {
+ EamDb db = EamDb.getInstance();
+
+ // This may return null if locking isn't supported, which is fine. It will
+ // throw an exception if locking is supported but we can't get the lock
+ // (meaning the database is in use by another user)
+ lock = db.getExclusiveMultiUserDbLock();
+
+ db.upgradeSchema();
+
+ } catch (EamDbException | SQLException ex) {
+ LOGGER.log(Level.SEVERE, "Error updating central repository", ex);
+
+ // Disable the central repo and clear the current settings.
+ try{
+ if (null != EamDb.getInstance()) {
+ EamDb.getInstance().shutdownConnections();
+ }
+ } catch (EamDbException ex2){
+ LOGGER.log(Level.SEVERE, "Error shutting down central repo connection pool", ex);
+ }
+ setUseCentralRepo(false);
+ EamDbPlatformEnum.setSelectedPlatform(EamDbPlatformEnum.DISABLED.name());
+ EamDbPlatformEnum.saveSelectedPlatform();
+
+ return false;
+ } finally {
+ if(lock != null){
+ try{
+ lock.release();
+ } catch (CoordinationServiceException ex){
+ LOGGER.log(Level.SEVERE, "Error releasing database lock", ex);
+ }
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Get the default organization name
+ *
+ * @return the default org name
+ */
+ static String getDefaultOrgName() {
+ return DEFAULT_ORG_NAME;
+ }
+
/**
* Check whether the given org is the default organization.
*
diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/PostgresEamDb.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/PostgresEamDb.java
index c139554c9c..46d579ad87 100755
--- a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/PostgresEamDb.java
+++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/PostgresEamDb.java
@@ -21,8 +21,13 @@ package org.sleuthkit.autopsy.centralrepository.datamodel;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
+import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import org.apache.commons.dbcp2.BasicDataSource;
+import org.sleuthkit.autopsy.casemodule.CaseActionCancelledException;
+import org.sleuthkit.autopsy.casemodule.CaseActionException;
+import org.sleuthkit.autopsy.coordinationservice.CoordinationService;
+import org.sleuthkit.autopsy.core.UserPreferences;
import org.sleuthkit.autopsy.coreutils.Logger;
/**
@@ -185,5 +190,36 @@ public class PostgresEamDb extends AbstractSqlEamDb {
protected String getConflictClause() {
return CONFLICT_CLAUSE;
}
+
+ /**
+ * Gets an exclusive lock (if applicable).
+ * Will return the lock if successful, null if unsuccessful because locking
+ * isn't supported, and throw an exception if we should have been able to get the
+ * lock but failed (meaning the database is in use).
+ * @return the lock, or null if locking is not supported
+ * @throws EamDbException if the coordination service is running but we fail to get the lock
+ */
+ @Override
+ public CoordinationService.Lock getExclusiveMultiUserDbLock() throws EamDbException{
+ try {
+ // First check if multi user mode is enabled - if not there's no point trying to get a lock
+ if( ! UserPreferences.getIsMultiUserModeEnabled()){
+ return null;
+ }
+
+ String databaseNodeName = dbSettings.getHost() + "_" + dbSettings.getDbName();
+ CoordinationService.Lock lock = CoordinationService.getInstance().tryGetExclusiveLock(CoordinationService.CategoryNode.CENTRAL_REPO, databaseNodeName, 5, TimeUnit.MINUTES);
+
+ if(lock != null){
+ return lock;
+ }
+ throw new EamDbException("Error acquiring database lock");
+ } catch (InterruptedException ex){
+ throw new EamDbException("Error acquiring database lock");
+ } catch (CoordinationService.CoordinationServiceException ex) {
+ // This likely just means the coordination service isn't running, which is ok
+ return null;
+ }
+ }
}
diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/PostgresEamDbSettings.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/PostgresEamDbSettings.java
old mode 100755
new mode 100644
index fe85685de8..bafd76af66
--- a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/PostgresEamDbSettings.java
+++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/PostgresEamDbSettings.java
@@ -485,7 +485,7 @@ public final class PostgresEamDbSettings {
}
boolean result = EamDbUtil.insertDefaultCorrelationTypes(conn)
- && EamDbUtil.insertSchemaVersion(conn)
+ && EamDbUtil.updateSchemaVersion(conn)
&& EamDbUtil.insertDefaultOrganization(conn);
EamDbUtil.closeConnection(conn);
diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/SqliteEamDb.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/SqliteEamDb.java
index 85fda8d4a1..55e60dd380 100644
--- a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/SqliteEamDb.java
+++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/SqliteEamDb.java
@@ -30,6 +30,7 @@ import org.apache.commons.dbcp2.BasicDataSource;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.datamodel.TskData;
import org.sleuthkit.autopsy.casemodule.Case;
+import org.sleuthkit.autopsy.coordinationservice.CoordinationService;
/**
* Sqlite implementation of the Central Repository database.
@@ -986,6 +987,34 @@ public class SqliteEamDb extends AbstractSqlEamDb {
}
}
+ /**
+ * Upgrade the schema of the database (if needed)
+ * @throws EamDbException
+ */
+ @Override
+ public void upgradeSchema() throws EamDbException, SQLException {
+ try{
+ acquireExclusiveLock();
+ super.upgradeSchema();
+ } finally {
+ releaseExclusiveLock();
+ }
+ }
+
+ /**
+ * Gets an exclusive lock (if applicable).
+ * Will return the lock if successful, null if unsuccessful because locking
+ * isn't supported, and throw an exception if we should have been able to get the
+ * lock but failed (meaning the database is in use).
+ * @return the lock, or null if locking is not supported
+ * @throws EamDbException if the coordination service is running but we fail to get the lock
+ */
+ @Override
+ public CoordinationService.Lock getExclusiveMultiUserDbLock() throws EamDbException{
+ // Multiple users are not supported for SQLite
+ return null;
+ }
+
/**
* Acquire the lock that provides exclusive access to the case database.
* Call this method in a try block with a call to
diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/SqliteEamDbSettings.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/SqliteEamDbSettings.java
old mode 100755
new mode 100644
index c7a3730e46..9355dbacde
--- a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/SqliteEamDbSettings.java
+++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/SqliteEamDbSettings.java
@@ -85,8 +85,6 @@ public final class SqliteEamDbSettings {
} catch (NumberFormatException ex) {
this.bulkThreshold = DEFAULT_BULK_THRESHHOLD;
}
-
-
}
public void saveSettings() {
@@ -434,7 +432,7 @@ public final class SqliteEamDbSettings {
}
boolean result = EamDbUtil.insertDefaultCorrelationTypes(conn)
- && EamDbUtil.insertSchemaVersion(conn)
+ && EamDbUtil.updateSchemaVersion(conn)
&& EamDbUtil.insertDefaultOrganization(conn);
EamDbUtil.closeConnection(conn);
return result;
diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/eventlisteners/Installer.java b/Core/src/org/sleuthkit/autopsy/centralrepository/eventlisteners/Installer.java
index 8af34bc718..7ca97535d9 100755
--- a/Core/src/org/sleuthkit/autopsy/centralrepository/eventlisteners/Installer.java
+++ b/Core/src/org/sleuthkit/autopsy/centralrepository/eventlisteners/Installer.java
@@ -18,8 +18,13 @@
*/
package org.sleuthkit.autopsy.centralrepository.eventlisteners;
+import javax.swing.JOptionPane;
import org.openide.modules.ModuleInstall;
+import org.openide.util.NbBundle;
+import org.openide.windows.WindowManager;
import org.sleuthkit.autopsy.casemodule.Case;
+import org.sleuthkit.autopsy.centralrepository.datamodel.EamDbUtil;
+import org.sleuthkit.autopsy.core.RuntimeProperties;
import org.sleuthkit.autopsy.coreutils.Logger;
/**
@@ -40,18 +45,32 @@ public class Installer extends ModuleInstall {
}
return instance;
}
-
+
private Installer() {
super();
}
-
-
+
+ @NbBundle.Messages({"Installer.centralRepoUpgradeFailed.title=Central repository upgrade failed",
+ "Installer.centralRepoUpgradeFailed.message=Failed to upgrade central repository. It has been disabled."
+ })
@Override
public void restored() {
Case.addPropertyChangeListener(pcl);
ieListener.installListeners();
- // TODO: create a thread pool to process Runners.
+ // Perform the database upgrade and inform the user if it fails
+ if (!EamDbUtil.upgradeDatabase()) {
+ if (RuntimeProperties.runningWithGUI()) {
+ WindowManager.getDefault().invokeWhenUIReady(() -> {
+ JOptionPane.showMessageDialog(null,
+ NbBundle.getMessage(this.getClass(),
+ "Installer.centralRepoUpgradeFailed.message"),
+ NbBundle.getMessage(this.getClass(),
+ "Installer.centralRepoUpgradeFailed.title"),
+ JOptionPane.ERROR_MESSAGE);
+ });
+ }
+ }
}
@Override
diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/GlobalSettingsPanel.java b/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/GlobalSettingsPanel.java
index 8b6ce90e96..914d4c4d34 100755
--- a/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/GlobalSettingsPanel.java
+++ b/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/GlobalSettingsPanel.java
@@ -18,11 +18,14 @@
*/
package org.sleuthkit.autopsy.centralrepository.optionspanel;
+import java.awt.Cursor;
import org.sleuthkit.autopsy.coreutils.Logger;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
+import javax.swing.JOptionPane;
import javax.swing.SwingUtilities;
import org.netbeans.spi.options.OptionsPanelController;
+import org.openide.util.NbBundle;
import org.openide.util.NbBundle.Messages;
import org.sleuthkit.autopsy.corecomponents.OptionsPanel;
import org.sleuthkit.autopsy.events.AutopsyEvent;
@@ -77,6 +80,32 @@ public final class GlobalSettingsPanel extends IngestModuleGlobalSettingsPanel i
IngestManager.getInstance().addIngestJobEventListener(ingestJobEventListener);
ingestStateUpdated();
}
+
+ @Messages({"GlobalSettingsPanel.updateFailed.title=Update failed",
+ "GlobalSettingsPanel.updateFailed.message=Failed to update database. Central repository has been disabled."
+ })
+ private void updateDatabase(){
+
+ if(EamDbPlatformEnum.getSelectedPlatform().equals(DISABLED)){
+ return;
+ }
+ setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
+
+ try {
+ boolean result = EamDbUtil.upgradeDatabase();
+ setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
+ if(! result){
+ JOptionPane.showMessageDialog(null,
+ NbBundle.getMessage(this.getClass(),
+ "GlobalSettingsPanel.updateFailed.message"),
+ NbBundle.getMessage(this.getClass(),
+ "GlobalSettingsPanel.updateFailed.title"),
+ JOptionPane.WARNING_MESSAGE);
+ }
+ } finally {
+ setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
+ }
+ }
/**
* This method is called from within the constructor to initialize the form.
@@ -315,6 +344,7 @@ public final class GlobalSettingsPanel extends IngestModuleGlobalSettingsPanel i
private void bnDbConfigureActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_bnDbConfigureActionPerformed
store();
EamDbSettingsDialog dialog = new EamDbSettingsDialog();
+ updateDatabase();
load(); // reload db settings content and update buttons
if (dialog.wasConfigurationChanged()) {
firePropertyChange(OptionsPanelController.PROP_CHANGED, null, null);
@@ -323,8 +353,9 @@ public final class GlobalSettingsPanel extends IngestModuleGlobalSettingsPanel i
private void cbUseCentralRepoActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_cbUseCentralRepoActionPerformed
//if saved setting is disabled checkbox should be disabled already
- enableDatabaseConfigureButton(cbUseCentralRepo.isSelected());
- enableButtonSubComponents(cbUseCentralRepo.isSelected() && !EamDbPlatformEnum.getSelectedPlatform().equals(DISABLED));
+ store();
+ updateDatabase();
+ load();
this.ingestStateUpdated();
firePropertyChange(OptionsPanelController.PROP_CHANGED, null, null);
}//GEN-LAST:event_cbUseCentralRepoActionPerformed
diff --git a/Core/src/org/sleuthkit/autopsy/communications/AccountDetailsNode.java b/Core/src/org/sleuthkit/autopsy/communications/AccountDetailsNode.java
new file mode 100644
index 0000000000..69761aa12f
--- /dev/null
+++ b/Core/src/org/sleuthkit/autopsy/communications/AccountDetailsNode.java
@@ -0,0 +1,78 @@
+/*
+ * 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.communications;
+
+import java.util.List;
+import java.util.Set;
+import java.util.logging.Level;
+import org.openide.nodes.AbstractNode;
+import org.openide.nodes.ChildFactory;
+import org.openide.nodes.Children;
+import org.openide.nodes.Node;
+import org.sleuthkit.autopsy.coreutils.Logger;
+import org.sleuthkit.datamodel.AccountDeviceInstance;
+import org.sleuthkit.datamodel.BlackboardArtifact;
+import org.sleuthkit.datamodel.CommunicationsFilter;
+import org.sleuthkit.datamodel.CommunicationsManager;
+import org.sleuthkit.datamodel.TskCoreException;
+
+/**
+ * 'Root' Node for the Account/Messages area. Has children which are all the
+ * relationships of all the accounts in this node.
+ *
+ */
+class AccountDetailsNode extends AbstractNode {
+
+ private final static Logger logger = Logger.getLogger(AccountDetailsNode.class.getName());
+
+ AccountDetailsNode(Set accountDeviceInstances, CommunicationsFilter filter, CommunicationsManager commsManager) {
+ super(Children.create(new AccountRelationshipChildren(accountDeviceInstances, commsManager, filter), true));
+ }
+
+ /**
+ * Children object for the relationships that the accounts are part of.
+ */
+ private static class AccountRelationshipChildren extends ChildFactory {
+
+ private final Set accountDeviceInstances;
+ private final CommunicationsManager commsManager;
+ private final CommunicationsFilter filter;
+
+ private AccountRelationshipChildren(Set accountDeviceInstances, CommunicationsManager commsManager, CommunicationsFilter filter) {
+ this.accountDeviceInstances = accountDeviceInstances;
+ this.commsManager = commsManager;
+ this.filter = filter;
+ }
+
+ @Override
+ protected boolean createKeys(List list) {
+ try {
+ list.addAll(commsManager.getRelationshipSources(accountDeviceInstances, filter));
+ } catch (TskCoreException ex) {
+ logger.log(Level.SEVERE, "Error getting communications", ex);
+ }
+ return true;
+ }
+
+ @Override
+ protected Node createNodeForKey(BlackboardArtifact t) {
+ return new RelationShipNode(t);
+ }
+ }
+}
diff --git a/Core/src/org/sleuthkit/autopsy/communications/AccountDeviceInstanceKey.java b/Core/src/org/sleuthkit/autopsy/communications/AccountDeviceInstanceKey.java
new file mode 100644
index 0000000000..92b6938ac0
--- /dev/null
+++ b/Core/src/org/sleuthkit/autopsy/communications/AccountDeviceInstanceKey.java
@@ -0,0 +1,54 @@
+/*
+ * 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.communications;
+
+import org.sleuthkit.datamodel.AccountDeviceInstance;
+import org.sleuthkit.datamodel.CommunicationsFilter;
+
+/**
+ * Key for AccountDeviceInstance node.
+ *
+ * Encapsulates a AccountDeviceInstance, and CommunicationsFilter.
+ */
+class AccountDeviceInstanceKey {
+
+ private final AccountDeviceInstance accountDeviceInstance;
+ private final CommunicationsFilter filter;
+ private final long messageCount;
+
+
+
+ AccountDeviceInstanceKey(AccountDeviceInstance accountDeviceInstance, CommunicationsFilter filter, long msgCount) {
+ this.accountDeviceInstance = accountDeviceInstance;
+ this.filter = filter;
+ this.messageCount = msgCount;
+ }
+
+ AccountDeviceInstance getAccountDeviceInstance() {
+ return accountDeviceInstance;
+ }
+
+ CommunicationsFilter getCommunicationsFilter() {
+ return filter;
+ }
+
+ long getMessageCount() {
+ return messageCount;
+ }
+}
diff --git a/Core/src/org/sleuthkit/autopsy/communications/AccountsBrowser.form b/Core/src/org/sleuthkit/autopsy/communications/AccountsBrowser.form
new file mode 100644
index 0000000000..83a3e75e9a
--- /dev/null
+++ b/Core/src/org/sleuthkit/autopsy/communications/AccountsBrowser.form
@@ -0,0 +1,40 @@
+
+
+
diff --git a/Core/src/org/sleuthkit/autopsy/communications/AccountsBrowser.java b/Core/src/org/sleuthkit/autopsy/communications/AccountsBrowser.java
new file mode 100644
index 0000000000..8e418d65fb
--- /dev/null
+++ b/Core/src/org/sleuthkit/autopsy/communications/AccountsBrowser.java
@@ -0,0 +1,129 @@
+/*
+ * 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.communications;
+
+import java.awt.Component;
+import javax.swing.JPanel;
+import javax.swing.ListSelectionModel;
+import javax.swing.SwingUtilities;
+import javax.swing.table.TableCellRenderer;
+import org.netbeans.swing.outline.DefaultOutlineModel;
+import org.netbeans.swing.outline.Outline;
+import org.openide.explorer.ExplorerManager;
+
+/**
+ * A panel that goes in the Browse tab of the Communications Visualization Tool.
+ * Hosts an OutlineView that shows information about Accounts.
+ */
+public class AccountsBrowser extends JPanel {
+
+ private static final long serialVersionUID = 1L;
+
+ private final Outline outline;
+ private ExplorerManager em;
+
+ /**
+ * Creates new form AccountsBrowser
+ */
+ public AccountsBrowser() {
+ initComponents();
+ outline = outlineView.getOutline();
+ outlineView.setPropertyColumns(
+ "device", Bundle.AccountNode_device(),
+ "type", Bundle.AccountNode_accountType(),
+ "count", Bundle.AccountNode_messageCount()
+ );
+
+ outline.setRootVisible(false);
+ ((DefaultOutlineModel) outline.getOutlineModel()).setNodesColumnLabel(Bundle.AccountNode_accountName());
+ outline.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
+ outline.setColumnSorted(3, false, 1); //it would be nice if the column index wasn't hardcoded
+ }
+
+ @Override
+ public void addNotify() {
+ super.addNotify();
+ em = ExplorerManager.find(this);
+ em.addPropertyChangeListener(evt -> {
+ if (ExplorerManager.PROP_ROOT_CONTEXT.equals(evt.getPropertyName())) {
+ SwingUtilities.invokeLater(this::setColumnWidths);
+ } else if (ExplorerManager.PROP_EXPLORED_CONTEXT.equals(evt.getPropertyName())) {
+ SwingUtilities.invokeLater(this::setColumnWidths);
+ }
+ });
+ }
+
+ private void setColumnWidths() {
+ int margin = 4;
+ int padding = 8;
+
+ final int rows = Math.min(100, outline.getRowCount());
+
+ for (int column = 0; column < outline.getModel().getColumnCount(); column++) {
+ int columnWidthLimit = 500;
+ int columnWidth = 0;
+
+ // find the maximum width needed to fit the values for the first 100 rows, at most
+ for (int row = 0; row < rows; row++) {
+ TableCellRenderer renderer = outline.getCellRenderer(row, column);
+ Component comp = outline.prepareRenderer(renderer, row, column);
+ columnWidth = Math.max(comp.getPreferredSize().width, columnWidth);
+ }
+
+ columnWidth += 2 * margin + padding; // add margin and regular padding
+ columnWidth = Math.min(columnWidth, columnWidthLimit);
+
+ outline.getColumnModel().getColumn(column).setPreferredWidth(columnWidth);
+ }
+ }
+
+ /**
+ * This method is called from within the constructor to initialize the form.
+ * WARNING: Do NOT modify this code. The content of this method is always
+ * regenerated by the Form Editor.
+ */
+ @SuppressWarnings("unchecked")
+ // //GEN-BEGIN:initComponents
+ private void initComponents() {
+
+ outlineView = new org.openide.explorer.view.OutlineView();
+
+ javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
+ this.setLayout(layout);
+ layout.setHorizontalGroup(
+ layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
+ .addGap(0, 0, 0)
+ .addComponent(outlineView, javax.swing.GroupLayout.DEFAULT_SIZE, 400, Short.MAX_VALUE)
+ .addGap(0, 0, 0))
+ );
+ layout.setVerticalGroup(
+ layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
+ .addGap(0, 0, 0)
+ .addComponent(outlineView, javax.swing.GroupLayout.DEFAULT_SIZE, 300, Short.MAX_VALUE)
+ .addGap(0, 0, 0))
+ );
+ }// //GEN-END:initComponents
+
+
+ // Variables declaration - do not modify//GEN-BEGIN:variables
+ private org.openide.explorer.view.OutlineView outlineView;
+ // End of variables declaration//GEN-END:variables
+}
diff --git a/Core/src/org/sleuthkit/autopsy/communications/AccountsRootChildren.java b/Core/src/org/sleuthkit/autopsy/communications/AccountsRootChildren.java
new file mode 100644
index 0000000000..9b0b5081d2
--- /dev/null
+++ b/Core/src/org/sleuthkit/autopsy/communications/AccountsRootChildren.java
@@ -0,0 +1,142 @@
+/*
+ * 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.communications;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import org.openide.nodes.AbstractNode;
+import org.openide.nodes.ChildFactory;
+import org.openide.nodes.Children;
+import org.openide.nodes.Node;
+import org.openide.nodes.Sheet;
+import org.openide.util.NbBundle;
+import org.openide.util.lookup.Lookups;
+import org.sleuthkit.autopsy.casemodule.Case;
+import org.sleuthkit.autopsy.datamodel.NodeProperty;
+import org.sleuthkit.datamodel.Account;
+import org.sleuthkit.datamodel.AccountDeviceInstance;
+import org.sleuthkit.datamodel.CommunicationsFilter;
+import org.sleuthkit.datamodel.CommunicationsManager;
+import org.sleuthkit.datamodel.DataSource;
+import org.sleuthkit.datamodel.SleuthkitCase;
+import org.sleuthkit.datamodel.TskCoreException;
+
+class AccountsRootChildren extends ChildFactory {
+
+ private static final Logger logger = Logger.getLogger(AccountsRootChildren.class.getName());
+
+ private final CommunicationsManager commsManager;
+ private final CommunicationsFilter commsFilter;
+
+ AccountsRootChildren(CommunicationsManager commsManager, CommunicationsFilter commsFilter) {
+ super();
+ this.commsManager = commsManager;
+ this.commsFilter = commsFilter;
+ }
+
+ @Override
+ protected boolean createKeys(List list) {
+ List accountDeviceInstanceKeys = new ArrayList<>();
+ try {
+ for (AccountDeviceInstance adi : commsManager.getAccountDeviceInstancesWithRelationships(commsFilter)) {
+ long communicationsCount = commsManager.getRelationshipSourcesCount(adi, commsFilter);
+ accountDeviceInstanceKeys.add(new AccountDeviceInstanceKey(adi, commsFilter, communicationsCount));
+ };
+ } catch (TskCoreException tskCoreException) {
+ logger.log(Level.SEVERE, "Error getting filtered account device instances", tskCoreException);
+ }
+ list.addAll(accountDeviceInstanceKeys);
+
+ return true;
+ }
+
+ @Override
+ protected Node createNodeForKey(AccountDeviceInstanceKey key) {
+ return new AccountDeviceInstanceNode(key, commsManager);
+ }
+
+ /**
+ * Node to represent an Account in the AccountsBrowser
+ */
+ static class AccountDeviceInstanceNode extends AbstractNode {
+
+ private final AccountDeviceInstanceKey accountDeviceInstanceKey;
+ private final CommunicationsManager commsManager;
+ private final Account account;
+
+ private AccountDeviceInstanceNode(AccountDeviceInstanceKey accountDeviceInstanceKey, CommunicationsManager commsManager) {
+ super(Children.LEAF, Lookups.fixed(accountDeviceInstanceKey, commsManager));
+ this.accountDeviceInstanceKey = accountDeviceInstanceKey;
+ this.commsManager = commsManager;
+ this.account = accountDeviceInstanceKey.getAccountDeviceInstance().getAccount();
+ setName(account.getTypeSpecificID());
+ setIconBaseWithExtension("org/sleuthkit/autopsy/communications/images/" + Utils.getIconFileName(account.getAccountType()));
+ }
+
+ public AccountDeviceInstance getAccountDeviceInstance() {
+ return accountDeviceInstanceKey.getAccountDeviceInstance();
+ }
+
+ public CommunicationsManager getCommsManager() {
+ return commsManager;
+ }
+
+ public CommunicationsFilter getFilter() {
+ return accountDeviceInstanceKey.getCommunicationsFilter();
+ }
+
+ @Override
+ @NbBundle.Messages(value = {"AccountNode.device=Device",
+ "AccountNode.accountName=Account",
+ "AccountNode.accountType=Type",
+ "AccountNode.messageCount=Msgs"})
+ protected Sheet createSheet() {
+ Sheet s = super.createSheet();
+ Sheet.Set properties = s.get(Sheet.PROPERTIES);
+ if (properties == null) {
+ properties = Sheet.createPropertiesSet();
+ s.put(properties);
+ }
+
+ properties.put(new NodeProperty<>("type", Bundle.AccountNode_accountType(), "type",
+ account.getAccountType().getDisplayName())); // NON-NLS
+ properties.put(new NodeProperty<>("count", Bundle.AccountNode_messageCount(), "count",
+ accountDeviceInstanceKey.getMessageCount())); // NON-NLS
+ properties.put(new NodeProperty<>("device", Bundle.AccountNode_device(), "device",
+ getDataSourceName())); // NON-NLS
+ return s;
+ }
+
+ private String getDataSourceName() {
+ try {
+ final SleuthkitCase sleuthkitCase = Case.getCurrentCase().getSleuthkitCase();
+ for (DataSource dataSource : sleuthkitCase.getDataSources()) {
+ if (dataSource.getDeviceId().equals(getAccountDeviceInstance().getDeviceId())) {
+ return sleuthkitCase.getContentById(dataSource.getId()).getName();
+ }
+ }
+ } catch (TskCoreException ex) {
+ logger.log(Level.SEVERE, "Error getting datasource name, falling back on device ID.", ex);
+ }
+ return getAccountDeviceInstance().getDeviceId();
+ }
+ }
+}
diff --git a/Core/src/org/sleuthkit/autopsy/communications/Bundle.properties b/Core/src/org/sleuthkit/autopsy/communications/Bundle.properties
new file mode 100644
index 0000000000..af6d79d8ab
--- /dev/null
+++ b/Core/src/org/sleuthkit/autopsy/communications/Bundle.properties
@@ -0,0 +1,14 @@
+CVTTopComponent.TabConstraints.tabTitle=Visualize
+CVTTopComponent.accountsBrowser.TabConstraints.tabTitle=Browse
+FiltersPanel.applyFiltersButton.text=Apply
+FiltersPanel.devicesLabel.text=Devices:
+FiltersPanel.accountTypesLabel.text=Account Types:
+FiltersPanel.filtersTitleLabel.text=Filters
+FiltersPanel.unCheckAllAccountTypesButton.text=Uncheck All
+FiltersPanel.checkAllAccountTypesButton.text=Check All
+FiltersPanel.unCheckAllDevicesButton.text=Uncheck All
+FiltersPanel.checkAllDevicesButton.text=Check All
+FiltersPanel.dateRangeLabel.text=Date Range:
+FiltersPanel.startCheckBox.text=Start:
+FiltersPanel.endCheckBox.text=End:
+FiltersPanel.refreshButton.text=Refresh
diff --git a/Core/src/org/sleuthkit/autopsy/communications/CVTTopComponent.form b/Core/src/org/sleuthkit/autopsy/communications/CVTTopComponent.form
new file mode 100644
index 0000000000..e8d83b24f1
--- /dev/null
+++ b/Core/src/org/sleuthkit/autopsy/communications/CVTTopComponent.form
@@ -0,0 +1,120 @@
+
+
+
diff --git a/Core/src/org/sleuthkit/autopsy/communications/CVTTopComponent.java b/Core/src/org/sleuthkit/autopsy/communications/CVTTopComponent.java
new file mode 100644
index 0000000000..4099af0bfa
--- /dev/null
+++ b/Core/src/org/sleuthkit/autopsy/communications/CVTTopComponent.java
@@ -0,0 +1,158 @@
+/*
+ * 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.communications;
+
+import java.util.List;
+import java.util.stream.Collectors;
+import org.openide.explorer.ExplorerManager;
+import org.openide.util.NbBundle;
+import org.openide.windows.Mode;
+import org.openide.windows.RetainLocation;
+import org.openide.windows.TopComponent;
+import org.openide.windows.WindowManager;
+import org.sleuthkit.autopsy.coreutils.ThreadConfined;
+
+/**
+ * Top component which displays the Communications Visualization Tool.
+ */
+@TopComponent.Description(
+ preferredID = "CVTTopComponent",
+ //iconBase="SET/PATH/TO/ICON/HERE", //use this to put icon in window title area,
+ persistenceType = TopComponent.PERSISTENCE_NEVER)
+@TopComponent.Registration(mode = "cvt", openAtStartup = false)
+@RetainLocation("cvt")
+@NbBundle.Messages("CVTTopComponent.name= Communications Visualization")
+public final class CVTTopComponent extends TopComponent implements ExplorerManager.Provider {
+
+ private static final long serialVersionUID = 1L;
+
+ @ThreadConfined(type = ThreadConfined.ThreadType.AWT)
+ private final ExplorerManager em = new ExplorerManager();
+
+ public CVTTopComponent() {
+ initComponents();
+ browseVisualizeTabPane.setEnabledAt(1, false);
+ setName(Bundle.CVTTopComponent_name());
+ splitPane.setRightComponent(new MessageBrowser());
+ }
+
+ /**
+ * This method is called from within the constructor to initialize the form.
+ * WARNING: Do NOT modify this code. The content of this method is always
+ * regenerated by the Form Editor.
+ */
+ // //GEN-BEGIN:initComponents
+ private void initComponents() {
+
+ splitPane = new javax.swing.JSplitPane();
+ browseVisualizeTabPane = new javax.swing.JTabbedPane();
+ accountsBrowser = new org.sleuthkit.autopsy.communications.AccountsBrowser();
+ jPanel1 = new javax.swing.JPanel();
+ filtersPane = new org.sleuthkit.autopsy.communications.FiltersPanel();
+
+ splitPane.setDividerLocation(400);
+ splitPane.setResizeWeight(0.7);
+
+ browseVisualizeTabPane.setFont(new java.awt.Font("Tahoma", 0, 18)); // NOI18N
+ browseVisualizeTabPane.addTab(org.openide.util.NbBundle.getMessage(CVTTopComponent.class, "CVTTopComponent.accountsBrowser.TabConstraints.tabTitle"), new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/communications/images/table.png")), accountsBrowser); // NOI18N
+
+ jPanel1.setName(""); // NOI18N
+
+ javax.swing.GroupLayout jPanel1Layout = new javax.swing.GroupLayout(jPanel1);
+ jPanel1.setLayout(jPanel1Layout);
+ jPanel1Layout.setHorizontalGroup(
+ jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addGap(0, 397, Short.MAX_VALUE)
+ );
+ jPanel1Layout.setVerticalGroup(
+ jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addGap(0, 725, Short.MAX_VALUE)
+ );
+
+ browseVisualizeTabPane.addTab(org.openide.util.NbBundle.getMessage(CVTTopComponent.class, "CVTTopComponent.TabConstraints.tabTitle"), new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/communications/images/emblem-web.png")), jPanel1); // NOI18N
+
+ splitPane.setLeftComponent(browseVisualizeTabPane);
+
+ filtersPane.setMinimumSize(new java.awt.Dimension(256, 495));
+
+ javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
+ this.setLayout(layout);
+ layout.setHorizontalGroup(
+ layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addGroup(layout.createSequentialGroup()
+ .addGap(6, 6, 6)
+ .addComponent(filtersPane, javax.swing.GroupLayout.PREFERRED_SIZE, 250, javax.swing.GroupLayout.PREFERRED_SIZE)
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+ .addComponent(splitPane, javax.swing.GroupLayout.DEFAULT_SIZE, 1339, Short.MAX_VALUE)
+ .addGap(0, 0, 0))
+ );
+ layout.setVerticalGroup(
+ layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addGroup(layout.createSequentialGroup()
+ .addGap(6, 6, 6)
+ .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addComponent(filtersPane, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
+ .addComponent(splitPane))
+ .addGap(5, 5, 5))
+ );
+ }// //GEN-END:initComponents
+
+
+ // Variables declaration - do not modify//GEN-BEGIN:variables
+ private org.sleuthkit.autopsy.communications.AccountsBrowser accountsBrowser;
+ private javax.swing.JTabbedPane browseVisualizeTabPane;
+ private org.sleuthkit.autopsy.communications.FiltersPanel filtersPane;
+ private javax.swing.JPanel jPanel1;
+ private javax.swing.JSplitPane splitPane;
+ // End of variables declaration//GEN-END:variables
+
+ @Override
+ public void componentOpened() {
+ super.componentOpened();
+ WindowManager.getDefault().setTopComponentFloating(this, true);
+ }
+
+ @Override
+ public ExplorerManager getExplorerManager() {
+ return em;
+ }
+
+ @Override
+ public void open() {
+ super.open();
+ /*
+ * when the window is (re)opened make sure the filters and accounts are
+ * in an up to date and consistent state.
+ *
+ * Re-applying the filters means we will lose the selection...
+ */
+ filtersPane.updateAndApplyFilters();
+ }
+
+ @Override
+ public List availableModes(List modes) {
+ /*
+ * This looks like the right thing to do, but online discussions seems
+ * to indicate this method is effectively deprecated. A break point
+ * placed here was never hit.
+ */
+ return modes.stream().filter(mode -> mode.getName().equals("cvt"))
+ .collect(Collectors.toList());
+ }
+}
diff --git a/Core/src/org/sleuthkit/autopsy/communications/FiltersPanel.form b/Core/src/org/sleuthkit/autopsy/communications/FiltersPanel.form
new file mode 100644
index 0000000000..da402cb4e6
--- /dev/null
+++ b/Core/src/org/sleuthkit/autopsy/communications/FiltersPanel.form
@@ -0,0 +1,364 @@
+
+
+
diff --git a/Core/src/org/sleuthkit/autopsy/communications/FiltersPanel.java b/Core/src/org/sleuthkit/autopsy/communications/FiltersPanel.java
new file mode 100644
index 0000000000..93e3230576
--- /dev/null
+++ b/Core/src/org/sleuthkit/autopsy/communications/FiltersPanel.java
@@ -0,0 +1,556 @@
+/*
+ * 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.communications;
+
+import com.google.common.collect.ImmutableSet;
+import java.beans.PropertyChangeListener;
+import java.time.LocalDate;
+import java.time.ZoneId;
+import java.util.EnumSet;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.logging.Level;
+import java.util.stream.Collectors;
+import javax.swing.JCheckBox;
+import org.openide.explorer.ExplorerManager;
+import org.openide.nodes.AbstractNode;
+import org.openide.nodes.Children;
+import org.openide.util.NbBundle;
+import org.sleuthkit.autopsy.casemodule.Case;
+import static org.sleuthkit.autopsy.casemodule.Case.Events.CURRENT_CASE;
+import org.sleuthkit.autopsy.core.UserPreferences;
+import org.sleuthkit.autopsy.coreutils.Logger;
+import org.sleuthkit.autopsy.coreutils.ThreadConfined;
+import org.sleuthkit.autopsy.ingest.IngestManager;
+import static org.sleuthkit.autopsy.ingest.IngestManager.IngestModuleEvent.DATA_ADDED;
+import org.sleuthkit.datamodel.Account;
+import org.sleuthkit.datamodel.CommunicationsFilter;
+import org.sleuthkit.datamodel.CommunicationsFilter.AccountTypeFilter;
+import org.sleuthkit.datamodel.CommunicationsFilter.DateRangeFilter;
+import org.sleuthkit.datamodel.CommunicationsFilter.DeviceFilter;
+import org.sleuthkit.datamodel.CommunicationsManager;
+import org.sleuthkit.datamodel.DataSource;
+import static org.sleuthkit.datamodel.Relationship.Type.CALL_LOG;
+import static org.sleuthkit.datamodel.Relationship.Type.MESSAGE;
+import org.sleuthkit.datamodel.SleuthkitCase;
+import org.sleuthkit.datamodel.TskCoreException;
+
+/**
+ * Panel that holds the Filter control widgets and translates user filtering
+ * changes into queries against the CommunicationsManager.
+ */
+final public class FiltersPanel extends javax.swing.JPanel {
+
+ private static final Logger logger = Logger.getLogger(FiltersPanel.class.getName());
+ private static final long serialVersionUID = 1L;
+
+ private ExplorerManager em;
+
+ @ThreadConfined(type = ThreadConfined.ThreadType.AWT)
+ private final Map accountTypeMap = new HashMap<>();
+ @ThreadConfined(type = ThreadConfined.ThreadType.AWT)
+ private final Map devicesMap = new HashMap<>();
+
+ private final PropertyChangeListener ingestListener;
+
+ @NbBundle.Messages({"refreshText=Refresh Results",
+ "applyText=Apply"})
+ public FiltersPanel() {
+ initComponents();
+ startDatePicker.setDate(LocalDate.now().minusWeeks(3));
+ endDatePicker.setDateToToday();
+ startDatePicker.getSettings().setVetoPolicy(
+ //no end date, or start is before end
+ startDate -> endCheckBox.isSelected() == false
+ || startDate.compareTo(endDatePicker.getDate()) <= 0
+ );
+ endDatePicker.getSettings().setVetoPolicy(
+ //no start date, or end is after start
+ endDate -> startCheckBox.isSelected() == false
+ || endDate.compareTo(startDatePicker.getDate()) >= 0
+ );
+
+ updateTimeZone();
+ updateFilters();
+ UserPreferences.addChangeListener(preferenceChangeEvent -> {
+ if (preferenceChangeEvent.getKey().equals(UserPreferences.DISPLAY_TIMES_IN_LOCAL_TIME)) {
+ updateTimeZone();
+ }
+ });
+
+ this.ingestListener = pce -> {
+ String eventType = pce.getPropertyName();
+ if (eventType.equals(DATA_ADDED.toString())) {
+ updateFilters();
+ refreshButton.setEnabled(true);
+ }
+ };
+
+ applyFiltersButton.addActionListener(e -> applyFilters());
+ refreshButton.addActionListener(e -> applyFilters());
+ }
+
+ /**
+ * Update the filter widgets, and apply them.
+ */
+ void updateAndApplyFilters() {
+ updateFilters();
+ if (em != null) {
+ applyFilters();
+ }
+ }
+
+ private void updateTimeZone() {
+ dateRangeLabel.setText("Date Range ( " + Utils.getUserPreferredZoneId().toString() + "):");
+ }
+
+ private void updateFilters() {
+ updateAccountTypeFilter();
+ updateDeviceFilter();
+ }
+
+ @Override
+ public void addNotify() {
+ super.addNotify();
+ /*
+ * Since we get the exploreremanager from the parent JComponenet, wait
+ * till this FiltersPanel is actaully added to a parent.
+ */
+ em = ExplorerManager.find(this);
+ IngestManager.getInstance().addIngestModuleEventListener(ingestListener);
+ Case.addEventTypeSubscriber(EnumSet.of(CURRENT_CASE), evt -> {
+ devicesMap.clear();
+ devicesPane.removeAll();
+ });
+ }
+
+ @Override
+ public void removeNotify() {
+ super.removeNotify();
+ IngestManager.getInstance().removeIngestModuleEventListener(ingestListener);
+ }
+
+ /**
+ * Populate the Account Types filter widgets
+ */
+ private void updateAccountTypeFilter() {
+
+ //TODO: something like this commented code could be used to show only
+ //the account types that are found:
+ //final CommunicationsManager communicationsManager = Case.getCurrentCase().getSleuthkitCase().getCommunicationsManager();
+ //List accountTypesInUse = communicationsManager.getAccountTypesInUse();
+ //accountTypesInUSe.forEach(...)
+ Account.Type.PREDEFINED_ACCOUNT_TYPES.forEach(type -> {
+ if (type.equals(Account.Type.CREDIT_CARD)) {
+ //don't show a check box for credit cards
+ } else if (type.equals(Account.Type.DEVICE)) {
+ //don't show a check box fro device
+ } else {
+ accountTypeMap.computeIfAbsent(type, t -> {
+ final JCheckBox jCheckBox = new JCheckBox(
+ "