diff --git a/Core/build.xml b/Core/build.xml
index 2efe03ecea..d246edaa54 100644
--- a/Core/build.xml
+++ b/Core/build.xml
@@ -97,6 +97,12 @@
+
+
+
+
+
+
diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/AbstractSqlEamDb.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/AbstractSqlEamDb.java
index f535d58ccd..991821bebf 100644
--- a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/AbstractSqlEamDb.java
+++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/AbstractSqlEamDb.java
@@ -387,6 +387,46 @@ abstract class AbstractSqlEamDb implements EamDb {
return eamCaseResult;
}
+ /**
+ * Retrieves Case details based on Case ID
+ *
+ * @param caseID unique identifier for a case
+ *
+ * @return The retrieved case
+ */
+ @Override
+ public CorrelationCase getCaseById(int caseId) throws EamDbException {
+ // @@@ We should have a cache here...
+
+ Connection conn = connect();
+
+ CorrelationCase eamCaseResult = null;
+ PreparedStatement preparedStatement = null;
+ ResultSet resultSet = null;
+
+ String sql = "SELECT cases.id as case_id, case_uid, case_name, creation_date, case_number, examiner_name, "
+ + "examiner_email, examiner_phone, notes, organizations.id as org_id, org_name, poc_name, poc_email, poc_phone "
+ + "FROM cases "
+ + "LEFT JOIN organizations ON cases.org_id=organizations.id "
+ + "WHERE cases.id=?";
+
+ try {
+ preparedStatement = conn.prepareStatement(sql);
+ preparedStatement.setInt(1, caseId);
+ resultSet = preparedStatement.executeQuery();
+ if (resultSet.next()) {
+ eamCaseResult = getEamCaseFromResultSet(resultSet);
+ }
+ } catch (SQLException ex) {
+ throw new EamDbException("Error getting case details.", ex); // NON-NLS
+ } finally {
+ EamDbUtil.closeStatement(preparedStatement);
+ EamDbUtil.closeResultSet(resultSet);
+ EamDbUtil.closeConnection(conn);
+ }
+
+ return eamCaseResult;
+ }
/**
* Retrieves cases that are in DB.
@@ -502,6 +542,48 @@ abstract class AbstractSqlEamDb implements EamDb {
return eamDataSourceResult;
}
+
+ /**
+ * Retrieves Data Source details based on data source ID
+ *
+ * @param correlationCase the current CorrelationCase used for ensuring
+ * uniqueness of DataSource
+ * @param dataSourceId the data source ID number
+ *
+ * @return The data source
+ */
+ @Override
+ public CorrelationDataSource getDataSourceById(CorrelationCase correlationCase, int dataSourceId) throws EamDbException {
+ if (correlationCase == null) {
+ throw new EamDbException("Correlation case is null");
+ }
+
+ Connection conn = connect();
+
+ CorrelationDataSource eamDataSourceResult = null;
+ PreparedStatement preparedStatement = null;
+ ResultSet resultSet = null;
+
+ String sql = "SELECT * FROM data_sources WHERE id=? AND case_id=?"; // NON-NLS
+
+ try {
+ preparedStatement = conn.prepareStatement(sql);
+ preparedStatement.setInt(1, dataSourceId);
+ preparedStatement.setInt(2, correlationCase.getID());
+ resultSet = preparedStatement.executeQuery();
+ if (resultSet.next()) {
+ eamDataSourceResult = getEamDataSourceFromResultSet(resultSet);
+ }
+ } catch (SQLException ex) {
+ throw new EamDbException("Error getting data source.", ex); // NON-NLS
+ } finally {
+ EamDbUtil.closeStatement(preparedStatement);
+ EamDbUtil.closeResultSet(resultSet);
+ EamDbUtil.closeConnection(conn);
+ }
+
+ return eamDataSourceResult;
+ }
/**
* Return a list of data sources in the DB
@@ -670,7 +752,7 @@ abstract class AbstractSqlEamDb implements EamDb {
return artifactInstances;
}
-
+
/**
* Retrieves eamArtifact instances from the database that are associated
* with the aType and filePath
@@ -1814,6 +1896,52 @@ abstract class AbstractSqlEamDb implements EamDb {
}
}
+ /**
+ * Process the Artifact instance in the EamDb give a where clause
+ *
+ * @param type EamArtifact.Type to search for
+ * @param instanceTableCallback callback to process the instance
+ * @param whereClause query string to execute
+ * @throws EamDbException
+ */
+ @Override
+ public void processInstanceTableWhere(CorrelationAttribute.Type type, String whereClause, InstanceTableCallback instanceTableCallback) throws EamDbException {
+ if (type == null) {
+ throw new EamDbException("Correlation type is null");
+ }
+
+ if (instanceTableCallback == null) {
+ throw new EamDbException("Callback interface is null");
+ }
+
+ if(whereClause == null) {
+ throw new EamDbException("Where clause is null");
+ }
+
+ Connection conn = connect();
+ PreparedStatement preparedStatement = null;
+ ResultSet resultSet = null;
+ String tableName = EamDbUtil.correlationTypeToInstanceTableName(type);
+ StringBuilder sql = new StringBuilder(300);
+ sql.append("select * from ")
+ .append(tableName)
+ .append(" WHERE ")
+ .append(whereClause);
+
+ try {
+ preparedStatement = conn.prepareStatement(sql.toString());
+ resultSet = preparedStatement.executeQuery();
+ instanceTableCallback.process(resultSet);
+ } catch (SQLException ex) {
+ throw new EamDbException("Error getting all artifact instances from instances table", ex);
+ } finally {
+ EamDbUtil.closeStatement(preparedStatement);
+ EamDbUtil.closeResultSet(resultSet);
+ EamDbUtil.closeConnection(conn);
+ }
+ }
+
+
@Override
public EamOrganization newOrganization(EamOrganization eamOrg) throws EamDbException {
if (eamOrg == null) {
diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/EamDb.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/EamDb.java
index 7ced4a1d7d..e4fb30583e 100644
--- a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/EamDb.java
+++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/EamDb.java
@@ -175,13 +175,21 @@ public interface EamDb {
*/
CorrelationCase getCaseByUUID(String caseUUID) throws EamDbException;
+ /**
+ * Retrieves Case details based on Case ID
+ *
+ * @param caseID unique identifier for a case
+ *
+ * @return The retrieved case
+ */
+ CorrelationCase getCaseById(int caseId) throws EamDbException;
/**
* Retrieves cases that are in DB.
*
* @return List of cases
*/
List getCases() throws EamDbException;
-
+
/**
* Creates new Data Source in the database
*
@@ -200,6 +208,18 @@ public interface EamDb {
*/
CorrelationDataSource getDataSource(CorrelationCase correlationCase, String dataSourceDeviceId) throws EamDbException;
+
+ /**
+ * Retrieves Data Source details based on data source ID
+ *
+ * @param correlationCase the current CorrelationCase used for ensuring
+ * uniqueness of DataSource
+ * @param dataSourceId the data source ID number
+ *
+ * @return The data source
+ */
+ CorrelationDataSource getDataSourceById(CorrelationCase correlationCase, int dataSourceId) throws EamDbException;
+
/**
* Retrieves data sources that are in DB
*
@@ -225,7 +245,7 @@ public interface EamDb {
* @return List of artifact instances for a given type/value
*/
List getArtifactInstancesByTypeValue(CorrelationAttribute.Type aType, String value) throws EamDbException;
-
+
/**
* Retrieves eamArtifact instances from the database that are associated
* with the aType and filePath
@@ -685,4 +705,15 @@ public interface EamDb {
* @throws EamDbException
*/
void processInstanceTable(CorrelationAttribute.Type type, InstanceTableCallback instanceTableCallback) throws EamDbException;
+
+ /**
+ * Process the Artifact instance in the EamDb
+ *
+ * @param type EamArtifact.Type to search for
+ * @param instanceTableCallback callback to process the instance
+ * @param whereClause query string to execute
+ * @throws EamDbException
+ */
+ void processInstanceTableWhere(CorrelationAttribute.Type type, String whereClause, InstanceTableCallback instanceTableCallback) throws EamDbException;
+
}
diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/SqliteEamDb.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/SqliteEamDb.java
index 4a3ef36530..74d0c36a66 100644
--- a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/SqliteEamDb.java
+++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/SqliteEamDb.java
@@ -33,9 +33,9 @@ import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.coordinationservice.CoordinationService;
/**
- * Sqlite implementation of the Central Repository database.
- * All methods in AbstractSqlEamDb that read or write to the database should
- * be overriden here and use appropriate locking.
+ * Sqlite implementation of the Central Repository database. All methods in
+ * AbstractSqlEamDb that read or write to the database should be overriden here
+ * and use appropriate locking.
*/
final class SqliteEamDb extends AbstractSqlEamDb {
@@ -46,17 +46,18 @@ final class SqliteEamDb extends AbstractSqlEamDb {
private BasicDataSource connectionPool = null;
private final SqliteEamDbSettings dbSettings;
-
+
// While the Sqlite database should only be used for single users, it is still
// possible for multiple threads to attempt to write to the database simultaneously.
private final ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock(true);
/**
* Get the singleton instance of SqliteEamDb
- *
+ *
* @return the singleton instance of SqliteEamDb
- *
- * @throws EamDbException if one or more default correlation type(s) have an invalid db table name.
+ *
+ * @throws EamDbException if one or more default correlation type(s) have an
+ * invalid db table name.
*/
public synchronized static SqliteEamDb getInstance() throws EamDbException {
if (instance == null) {
@@ -67,9 +68,9 @@ final class SqliteEamDb extends AbstractSqlEamDb {
}
/**
- *
- * @throws EamDbException if the AbstractSqlEamDb class has one or more default
- * correlation type(s) having an invalid db table name.
+ *
+ * @throws EamDbException if the AbstractSqlEamDb class has one or more
+ * default correlation type(s) having an invalid db table name.
*/
private SqliteEamDb() throws EamDbException {
dbSettings = new SqliteEamDbSettings();
@@ -79,7 +80,7 @@ final class SqliteEamDb extends AbstractSqlEamDb {
@Override
public void shutdownConnections() throws EamDbException {
try {
- synchronized(this) {
+ synchronized (this) {
if (null != connectionPool) {
connectionPool.close();
connectionPool = null; // force it to be re-created on next connect()
@@ -89,7 +90,7 @@ final class SqliteEamDb extends AbstractSqlEamDb {
throw new EamDbException("Failed to close existing database connections.", ex); // NON-NLS
}
}
-
+
@Override
public void updateSettings() {
synchronized (this) {
@@ -107,9 +108,9 @@ final class SqliteEamDb extends AbstractSqlEamDb {
@Override
public void reset() throws EamDbException {
- try{
+ try {
acquireExclusiveLock();
-
+
Connection conn = connect();
try {
@@ -150,11 +151,11 @@ final class SqliteEamDb extends AbstractSqlEamDb {
*
*/
private void setupConnectionPool() throws EamDbException {
-
+
if (dbSettings.dbFileExists() == false) {
throw new EamDbException("Central repository database missing");
}
-
+
connectionPool = new BasicDataSource();
connectionPool.setDriverClassName(dbSettings.getDriver());
connectionPool.setUrl(dbSettings.getConnectionURL());
@@ -200,25 +201,24 @@ final class SqliteEamDb extends AbstractSqlEamDb {
return "";
}
-
/**
* 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
*/
@Override
public void newDbInfo(String name, String value) throws EamDbException {
- try{
+ try {
acquireExclusiveLock();
super.newDbInfo(name, value);
} finally {
releaseExclusiveLock();
}
}
-
+
/**
* Get the value for the given name from the name/value db_info table.
*
@@ -230,47 +230,47 @@ final class SqliteEamDb extends AbstractSqlEamDb {
*/
@Override
public String getDbInfo(String name) throws EamDbException {
- try{
+ try {
acquireSharedLock();
return super.getDbInfo(name);
} finally {
releaseSharedLock();
- }
+ }
}
-
+
/**
* 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
*/
@Override
public void updateDbInfo(String name, String value) throws EamDbException {
- try{
+ try {
acquireExclusiveLock();
super.updateDbInfo(name, value);
} finally {
releaseExclusiveLock();
- }
+ }
}
-
- /**
+
+ /**
* Creates new Case in the database from the given case
- *
+ *
* @param autopsyCase The case to add
*/
@Override
public CorrelationCase newCase(Case autopsyCase) throws EamDbException {
- try{
+ try {
acquireExclusiveLock();
return super.newCase(autopsyCase);
} finally {
releaseExclusiveLock();
- }
- }
-
+ }
+ }
+
/**
* Creates new Case in the database
*
@@ -280,14 +280,14 @@ final class SqliteEamDb extends AbstractSqlEamDb {
*/
@Override
public CorrelationCase newCase(CorrelationCase eamCase) throws EamDbException {
- try{
+ try {
acquireExclusiveLock();
return super.newCase(eamCase);
} finally {
releaseExclusiveLock();
- }
+ }
}
-
+
/**
* Updates an existing Case in the database
*
@@ -295,14 +295,14 @@ final class SqliteEamDb extends AbstractSqlEamDb {
*/
@Override
public void updateCase(CorrelationCase eamCase) throws EamDbException {
- try{
+ try {
acquireExclusiveLock();
super.updateCase(eamCase);
} finally {
releaseExclusiveLock();
- }
- }
-
+ }
+ }
+
/**
* Retrieves Case details based on Case UUID
*
@@ -312,14 +312,32 @@ final class SqliteEamDb extends AbstractSqlEamDb {
*/
@Override
public CorrelationCase getCaseByUUID(String caseUUID) throws EamDbException {
- try{
+ try {
acquireSharedLock();
return super.getCaseByUUID(caseUUID);
} finally {
releaseSharedLock();
- }
- }
-
+ }
+ }
+
+ /**
+ * Retrieves Case details based on Case ID
+ *
+ * @param caseID unique identifier for a case
+ *
+ * @return The retrieved case
+ */
+ @Override
+ public CorrelationCase getCaseById(int caseId) throws EamDbException {
+ try {
+ acquireSharedLock();
+ return super.getCaseById(caseId);
+ } finally {
+ releaseSharedLock();
+ }
+
+ }
+
/**
* Retrieves cases that are in DB.
*
@@ -327,14 +345,14 @@ final class SqliteEamDb extends AbstractSqlEamDb {
*/
@Override
public List getCases() throws EamDbException {
- try{
+ try {
acquireSharedLock();
return super.getCases();
} finally {
releaseSharedLock();
- }
- }
-
+ }
+ }
+
/**
* Creates new Data Source in the database
*
@@ -342,32 +360,52 @@ final class SqliteEamDb extends AbstractSqlEamDb {
*/
@Override
public void newDataSource(CorrelationDataSource eamDataSource) throws EamDbException {
- try{
+ try {
acquireExclusiveLock();
super.newDataSource(eamDataSource);
} finally {
releaseExclusiveLock();
- }
- }
-
+ }
+ }
+
/**
* 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
*/
@Override
public CorrelationDataSource getDataSource(CorrelationCase correlationCase, String dataSourceDeviceId) throws EamDbException {
- try{
+ try {
acquireSharedLock();
return super.getDataSource(correlationCase, dataSourceDeviceId);
} finally {
releaseSharedLock();
- }
- }
+ }
+ }
+ /**
+ * Retrieves Data Source details based on data source ID
+ *
+ * @param correlationCase the current CorrelationCase used for ensuring
+ * uniqueness of DataSource
+ * @param dataSourceId the data source ID number
+ *
+ * @return The data source
+ */
+ @Override
+ public CorrelationDataSource getDataSourceById(CorrelationCase correlationCase, int dataSourceId) throws EamDbException {
+ try {
+ acquireSharedLock();
+ return super.getDataSourceById(correlationCase, dataSourceId);
+ } finally {
+ releaseSharedLock();
+ }
+ }
+
/**
* Return a list of data sources in the DB
*
@@ -375,14 +413,14 @@ final class SqliteEamDb extends AbstractSqlEamDb {
*/
@Override
public List getDataSources() throws EamDbException {
- try{
+ try {
acquireSharedLock();
return super.getDataSources();
} finally {
releaseSharedLock();
- }
- }
-
+ }
+ }
+
/**
* Inserts new Artifact(s) into the database. Should add associated Case and
* Data Source first.
@@ -391,38 +429,38 @@ final class SqliteEamDb extends AbstractSqlEamDb {
*/
@Override
public void addArtifact(CorrelationAttribute eamArtifact) throws EamDbException {
- try{
+ try {
acquireExclusiveLock();
super.addArtifact(eamArtifact);
} finally {
releaseExclusiveLock();
- }
- }
-
+ }
+ }
+
/**
* Retrieves eamArtifact instances from the database that are associated
* with the eamArtifactType and eamArtifactValue of the given eamArtifact.
*
- * @param aType The type of the artifact
- * @param value The correlation value
+ * @param aType The type of the artifact
+ * @param value The correlation value
*
* @return List of artifact instances for a given type/value
*/
@Override
public List getArtifactInstancesByTypeValue(CorrelationAttribute.Type aType, String value) throws EamDbException {
- try{
+ try {
acquireSharedLock();
return super.getArtifactInstancesByTypeValue(aType, value);
} finally {
releaseSharedLock();
- }
+ }
}
/**
* 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
@@ -431,14 +469,14 @@ final class SqliteEamDb extends AbstractSqlEamDb {
*/
@Override
public List getArtifactInstancesByPath(CorrelationAttribute.Type aType, String filePath) throws EamDbException {
- try{
+ try {
acquireSharedLock();
return super.getArtifactInstancesByPath(aType, filePath);
} finally {
releaseSharedLock();
- }
- }
-
+ }
+ }
+
/**
* Retrieves number of artifact instances in the database that are
* associated with the ArtifactType and artifactValue of the given artifact.
@@ -447,29 +485,29 @@ final class SqliteEamDb extends AbstractSqlEamDb {
* @param value The value to search for
*
* @return Number of artifact instances having ArtifactType and
- * ArtifactValue.
- * @throws EamDbException
+ * ArtifactValue.
+ * @throws EamDbException
*/
@Override
public Long getCountArtifactInstancesByTypeValue(CorrelationAttribute.Type aType, String value) throws EamDbException {
- try{
+ try {
acquireSharedLock();
return super.getCountArtifactInstancesByTypeValue(aType, value);
} finally {
releaseSharedLock();
- }
+ }
}
-
+
@Override
public int getFrequencyPercentage(CorrelationAttribute corAttr) throws EamDbException {
- try{
+ try {
acquireSharedLock();
return super.getFrequencyPercentage(corAttr);
} finally {
releaseSharedLock();
- }
- }
-
+ }
+ }
+
/**
* Retrieves number of unique caseDisplayName / dataSource tuples in the
* database that are associated with the artifactType and artifactValue of
@@ -483,130 +521,130 @@ final class SqliteEamDb extends AbstractSqlEamDb {
*/
@Override
public Long getCountUniqueCaseDataSourceTuplesHavingTypeValue(CorrelationAttribute.Type aType, String value) throws EamDbException {
- try{
+ try {
acquireSharedLock();
return super.getCountUniqueCaseDataSourceTuplesHavingTypeValue(aType, value);
} finally {
releaseSharedLock();
- }
- }
-
+ }
+ }
@Override
public Long getCountUniqueDataSources() throws EamDbException {
- try{
+ try {
acquireSharedLock();
return super.getCountUniqueDataSources();
} finally {
releaseSharedLock();
- }
- }
-
+ }
+ }
+
/**
* Retrieves number of eamArtifact instances in the database that are
* 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 {
- try{
+ try {
acquireSharedLock();
return super.getCountArtifactInstancesByCaseDataSource(caseUUID, dataSourceID);
} finally {
releaseSharedLock();
- }
- }
-
+ }
+ }
+
/**
* Executes a bulk insert of the eamArtifacts added from the
* prepareBulkArtifact() method
*/
@Override
public void bulkInsertArtifacts() throws EamDbException {
- try{
+ try {
acquireExclusiveLock();
super.bulkInsertArtifacts();
} finally {
releaseExclusiveLock();
- }
- }
-
+ }
+ }
+
/**
* Executes a bulk insert of the cases
*/
@Override
public void bulkInsertCases(List cases) throws EamDbException {
- try{
+ try {
acquireExclusiveLock();
super.bulkInsertCases(cases);
} finally {
releaseExclusiveLock();
- }
- }
-
+ }
+ }
+
/**
- * Sets an eamArtifact instance to the given knownStatus.
- * knownStatus should be BAD if the file has been tagged with a notable tag and
- * UNKNOWN otherwise. If eamArtifact
- * exists, it is updated. If eamArtifact does not exist it is added with the
- * given status.
+ * Sets an eamArtifact instance to the given knownStatus. knownStatus should
+ * be BAD if the file has been tagged with a notable tag and UNKNOWN
+ * otherwise. If eamArtifact exists, it is updated. If eamArtifact does not
+ * exist it is added with the given status.
*
* @param eamArtifact Artifact containing exactly one (1) ArtifactInstance.
- * @param knownStatus The status to change the artifact to. Should never be KNOWN
+ * @param knownStatus The status to change the artifact to. Should never be
+ * KNOWN
*/
@Override
public void setArtifactInstanceKnownStatus(CorrelationAttribute eamArtifact, TskData.FileKnown knownStatus) throws EamDbException {
- try{
+ try {
acquireExclusiveLock();
super.setArtifactInstanceKnownStatus(eamArtifact, knownStatus);
} finally {
releaseExclusiveLock();
- }
- }
-
+ }
+ }
+
/**
* Gets list of matching eamArtifact instances that have knownStatus =
* "Bad".
*
* @param aType EamArtifact.Type to search for
* @param value Value to search for
- *
+ *
* @return List with 0 or more matching eamArtifact instances.
*/
@Override
public List getArtifactInstancesKnownBad(CorrelationAttribute.Type aType, String value) throws EamDbException {
- try{
+ try {
acquireSharedLock();
return super.getArtifactInstancesKnownBad(aType, value);
} finally {
releaseSharedLock();
- }
- }
-
+ }
+ }
+
/**
*
* Gets list of matching eamArtifact instances that have knownStatus =
* "Bad".
+ *
* @param aType EamArtifact.Type to search for
* @return List with 0 or more matching eamArtifact instances.
* @throws EamDbException
*/
@Override
public List getArtifactInstancesKnownBad(CorrelationAttribute.Type aType) throws EamDbException {
- try{
+ try {
acquireSharedLock();
return super.getArtifactInstancesKnownBad(aType);
} finally {
releaseSharedLock();
- }
+ }
}
-
+
/**
* Count matching eamArtifacts instances that have knownStatus = "Bad".
*
@@ -617,14 +655,14 @@ final class SqliteEamDb extends AbstractSqlEamDb {
*/
@Override
public Long getCountArtifactInstancesKnownBad(CorrelationAttribute.Type aType, String value) throws EamDbException {
- try{
+ try {
acquireSharedLock();
return super.getCountArtifactInstancesKnownBad(aType, value);
} finally {
releaseSharedLock();
- }
- }
-
+ }
+ }
+
/**
* Gets list of distinct case display names, where each case has 1+ Artifact
* Instance matching eamArtifact with knownStatus = "Bad".
@@ -633,37 +671,39 @@ final class SqliteEamDb extends AbstractSqlEamDb {
* @param value Value to search for
*
* @return List of cases containing this artifact with instances marked as
- * bad
+ * bad
*
* @throws EamDbException
*/
@Override
public List getListCasesHavingArtifactInstancesKnownBad(CorrelationAttribute.Type aType, String value) throws EamDbException {
- try{
+ try {
acquireSharedLock();
return super.getListCasesHavingArtifactInstancesKnownBad(aType, value);
} finally {
releaseSharedLock();
- }
- }
-
+ }
+ }
+
/**
* Remove a reference set and all values contained in it.
+ *
* @param referenceSetID
- * @throws EamDbException
+ * @throws EamDbException
*/
@Override
- public void deleteReferenceSet(int referenceSetID) throws EamDbException{
- try{
+ public void deleteReferenceSet(int referenceSetID) throws EamDbException {
+ try {
acquireExclusiveLock();
super.deleteReferenceSet(referenceSetID);
} finally {
releaseExclusiveLock();
- }
- }
-
+ }
+ }
+
/**
* Check if the given hash is in a specific reference set
+ *
* @param value
* @param referenceSetID
* @param correlationTypeID
@@ -671,14 +711,14 @@ final class SqliteEamDb extends AbstractSqlEamDb {
*/
@Override
public boolean isValueInReferenceSet(String value, int referenceSetID, int correlationTypeID) throws EamDbException {
- try{
+ try {
acquireSharedLock();
return super.isValueInReferenceSet(value, referenceSetID, correlationTypeID);
} finally {
releaseSharedLock();
- }
+ }
}
-
+
/**
* Process the Artifact instance in the EamDb
*
@@ -695,24 +735,44 @@ final class SqliteEamDb extends AbstractSqlEamDb {
releaseSharedLock();
}
}
+
/**
- * 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.
+ * Process the Artifact instance in the EamDb
+ *
+ * @param type EamArtifact.Type to search for
+ * @param instanceTableCallback callback to process the instance
+ * @throws EamDbException
+ */
+ @Override
+ public void processInstanceTableWhere(CorrelationAttribute.Type type, String whereClause, InstanceTableCallback instanceTableCallback) throws EamDbException {
+ try {
+ acquireSharedLock();
+ super.processInstanceTableWhere(type, whereClause, instanceTableCallback);
+ } finally {
+ releaseSharedLock();
+ }
+ }
+
+ /**
+ * 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 {
- try{
+ try {
acquireSharedLock();
return super.referenceSetExists(referenceSetName, version);
} finally {
releaseSharedLock();
- }
+ }
}
-
+
/**
* Is the artifact known as bad according to the reference entries?
*
@@ -722,34 +782,34 @@ final class SqliteEamDb extends AbstractSqlEamDb {
* @return Global known status of the artifact
*/
@Override
- public boolean isArtifactKnownBadByReference(CorrelationAttribute.Type aType, String value) throws EamDbException {
- try{
+ public boolean isArtifactKnownBadByReference(CorrelationAttribute.Type aType, String value) throws EamDbException {
+ try {
acquireSharedLock();
return super.isArtifactKnownBadByReference(aType, value);
} finally {
releaseSharedLock();
- }
+ }
}
-
+
/**
* Add a new organization
*
* @return the Organization ID of the newly created organization.
- *
+ *
* @param eamOrg The organization to add
*
* @throws EamDbException
*/
@Override
public EamOrganization newOrganization(EamOrganization eamOrg) throws EamDbException {
- try{
+ try {
acquireExclusiveLock();
return super.newOrganization(eamOrg);
} finally {
releaseExclusiveLock();
- }
- }
-
+ }
+ }
+
/**
* Get all organizations
*
@@ -759,14 +819,14 @@ final class SqliteEamDb extends AbstractSqlEamDb {
*/
@Override
public List getOrganizations() throws EamDbException {
- try{
+ try {
acquireSharedLock();
return super.getOrganizations();
} finally {
releaseSharedLock();
- }
- }
-
+ }
+ }
+
/**
* Get an organization having the given ID
*
@@ -778,33 +838,34 @@ final class SqliteEamDb extends AbstractSqlEamDb {
*/
@Override
public EamOrganization getOrganizationByID(int orgID) throws EamDbException {
- try{
+ try {
acquireSharedLock();
return super.getOrganizationByID(orgID);
} finally {
releaseSharedLock();
- }
- }
-
+ }
+ }
+
@Override
public void updateOrganization(EamOrganization updatedOrganization) throws EamDbException {
- try{
+ try {
acquireExclusiveLock();
super.updateOrganization(updatedOrganization);
} finally {
releaseExclusiveLock();
- }
+ }
}
-
+
@Override
public void deleteOrganization(EamOrganization organizationToDelete) throws EamDbException {
- try{
+ try {
acquireExclusiveLock();
super.deleteOrganization(organizationToDelete);
} finally {
releaseExclusiveLock();
- }
+ }
}
+
/**
* Add a new Global Set
*
@@ -816,14 +877,14 @@ final class SqliteEamDb extends AbstractSqlEamDb {
*/
@Override
public int newReferenceSet(EamGlobalSet eamGlobalSet) throws EamDbException {
- try{
+ try {
acquireExclusiveLock();
return super.newReferenceSet(eamGlobalSet);
} finally {
releaseExclusiveLock();
- }
- }
-
+ }
+ }
+
/**
* Get a reference set by ID
*
@@ -835,52 +896,51 @@ final class SqliteEamDb extends AbstractSqlEamDb {
*/
@Override
public EamGlobalSet getReferenceSetByID(int referenceSetID) throws EamDbException {
- try{
+ try {
acquireSharedLock();
return super.getReferenceSetByID(referenceSetID);
} finally {
releaseSharedLock();
- }
- }
-
+ }
+ }
+
/**
* Get all reference sets
*
* @param correlationType Type of sets to return
- *
+ *
* @return List of all reference sets in the central repository
*
* @throws EamDbException
*/
@Override
public List getAllReferenceSets(CorrelationAttribute.Type correlationType) throws EamDbException {
- try{
+ try {
acquireSharedLock();
return super.getAllReferenceSets(correlationType);
} finally {
releaseSharedLock();
- }
+ }
}
-
+
/**
* 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
*/
@Override
public void addReferenceInstance(EamGlobalFileInstance eamGlobalFileInstance, CorrelationAttribute.Type correlationType) throws EamDbException {
- try{
+ try {
acquireExclusiveLock();
super.addReferenceInstance(eamGlobalFileInstance, correlationType);
} finally {
releaseExclusiveLock();
- }
- }
-
+ }
+ }
+
/**
* Insert the bulk collection of Reference Type Instances
*
@@ -888,18 +948,18 @@ final class SqliteEamDb extends AbstractSqlEamDb {
*/
@Override
public void bulkInsertReferenceTypeEntries(Set globalInstances, CorrelationAttribute.Type contentType) throws EamDbException {
- try{
+ try {
acquireExclusiveLock();
super.bulkInsertReferenceTypeEntries(globalInstances, contentType);
} finally {
releaseExclusiveLock();
- }
- }
-
+ }
+ }
+
/**
* 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
@@ -908,14 +968,14 @@ final class SqliteEamDb extends AbstractSqlEamDb {
*/
@Override
public List getReferenceInstancesByTypeValue(CorrelationAttribute.Type aType, String aValue) throws EamDbException {
- try{
+ try {
acquireSharedLock();
return super.getReferenceInstancesByTypeValue(aType, aValue);
} finally {
releaseSharedLock();
- }
- }
-
+ }
+ }
+
/**
* Add a new EamArtifact.Type to the db.
*
@@ -927,71 +987,71 @@ final class SqliteEamDb extends AbstractSqlEamDb {
*/
@Override
public int newCorrelationType(CorrelationAttribute.Type newType) throws EamDbException {
- try{
+ try {
acquireExclusiveLock();
return super.newCorrelationType(newType);
} finally {
releaseExclusiveLock();
- }
- }
-
+ }
+ }
+
/**
* Get the list of EamArtifact.Type's that will be used to correlate
* artifacts.
*
* @return List of EamArtifact.Type's. If none are defined in the database,
- * the default list will be returned.
+ * the default list will be returned.
*
* @throws EamDbException
*/
@Override
public List getDefinedCorrelationTypes() throws EamDbException {
- try{
+ try {
acquireSharedLock();
return super.getDefinedCorrelationTypes();
} finally {
releaseSharedLock();
- }
- }
-
+ }
+ }
+
/**
* Get the list of enabled EamArtifact.Type's that will be used to correlate
* 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
*/
@Override
public List getEnabledCorrelationTypes() throws EamDbException {
- try{
+ try {
acquireSharedLock();
return super.getEnabledCorrelationTypes();
} finally {
releaseSharedLock();
- }
- }
-
+ }
+ }
+
/**
* Get the list of supported EamArtifact.Type's that can be used to
* 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
*/
@Override
public List getSupportedCorrelationTypes() throws EamDbException {
- try{
+ try {
acquireSharedLock();
return super.getSupportedCorrelationTypes();
} finally {
releaseSharedLock();
- }
- }
-
+ }
+ }
+
/**
* Update a EamArtifact.Type.
*
@@ -1001,14 +1061,14 @@ final class SqliteEamDb extends AbstractSqlEamDb {
*/
@Override
public void updateCorrelationType(CorrelationAttribute.Type aType) throws EamDbException {
- try{
+ try {
acquireExclusiveLock();
super.updateCorrelationType(aType);
} finally {
releaseExclusiveLock();
- }
- }
-
+ }
+ }
+
/**
* Get the EamArtifact.Type that has the given Type.Id.
*
@@ -1020,73 +1080,76 @@ final class SqliteEamDb extends AbstractSqlEamDb {
*/
@Override
public CorrelationAttribute.Type getCorrelationTypeById(int typeId) throws EamDbException {
- try{
+ try {
acquireSharedLock();
return super.getCorrelationTypeById(typeId);
} finally {
releaseSharedLock();
- }
- }
-
+ }
+ }
+
/**
* Upgrade the schema of the database (if needed)
- * @throws EamDbException
+ *
+ * @throws EamDbException
*/
@Override
public void upgradeSchema() throws EamDbException, SQLException {
- try{
+ 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).
+ * 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
+ * @throws EamDbException if the coordination service is running but we fail
+ * to get the lock
*/
@Override
- public CoordinationService.Lock getExclusiveMultiUserDbLock() throws EamDbException{
+ 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
- * the lock release method in an associated finally block.
+ * Acquire the lock that provides exclusive access to the case database.
+ * Call this method in a try block with a call to the lock release method in
+ * an associated finally block.
*/
private void acquireExclusiveLock() {
rwLock.writeLock().lock();
}
/**
- * Release the lock that provides exclusive access to the database.
- * This method should always be called in the finally
- * block of a try block in which the lock was acquired.
+ * Release the lock that provides exclusive access to the database. This
+ * method should always be called in the finally block of a try block in
+ * which the lock was acquired.
*/
private void releaseExclusiveLock() {
rwLock.writeLock().unlock();
}
/**
- * Acquire the lock that provides shared access to the case database.
- * Call this method in a try block with a call to the
- * lock release method in an associated finally block.
+ * Acquire the lock that provides shared access to the case database. Call
+ * this method in a try block with a call to the lock release method in an
+ * associated finally block.
*/
private void acquireSharedLock() {
rwLock.readLock().lock();
}
/**
- * Release the lock that provides shared access to the database.
- * This method should always be called in the finally block
- * of a try block in which the lock was acquired.
+ * Release the lock that provides shared access to the database. This method
+ * should always be called in the finally block of a try block in which the
+ * lock was acquired.
*/
private void releaseSharedLock() {
rwLock.readLock().unlock();
diff --git a/Core/src/org/sleuthkit/autopsy/commonfilesearch/AbstractCommonAttributeInstance.java b/Core/src/org/sleuthkit/autopsy/commonfilesearch/AbstractCommonAttributeInstance.java
new file mode 100644
index 0000000000..7732c5d393
--- /dev/null
+++ b/Core/src/org/sleuthkit/autopsy/commonfilesearch/AbstractCommonAttributeInstance.java
@@ -0,0 +1,163 @@
+/*
+ *
+ * Autopsy Forensic Browser
+ *
+ * Copyright 2018 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.commonfilesearch;
+
+import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttribute;
+import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeInstance;
+import org.sleuthkit.autopsy.datamodel.DisplayableItemNode;
+import org.sleuthkit.datamodel.AbstractFile;
+import org.sleuthkit.datamodel.TskCoreException;
+
+/**
+ * Represents an instance in either the CaseDB or CR that had a common match.
+ * Different implementations know how to get the instance details from either
+ * CaseDB or CR.
+ *
+ * Defines leaf-type nodes used in the Common Files Search results tree. Leaf
+ * nodes, may describe common attributes which exist in the current case DB or
+ * in the Central Repo. When a reference to the AbstractFile is lacking (such as
+ * in the case that a common attribute is found in the Central Repo) not all
+ * features of the Content Viewer, and context menu can be supported. Thus,
+ * multiple types of leaf nodes are required to represent Common Attribute
+ * Instance nodes.
+ */
+public abstract class AbstractCommonAttributeInstance {
+
+ private final Long abstractFileObjectId;
+ private final String caseName;
+ private final String dataSource;
+
+ /**
+ * Create a leaf node for attributes found in files in the current case db.
+ *
+ * @param abstractFileReference file from which the common attribute was
+ * found
+ * @param cachedFiles storage for abstract files which have been used
+ * already so we can avoid extra roundtrips to the case db
+ * @param dataSource datasource where this attribute appears
+ * @param caseName case where this attribute appears
+ */
+ AbstractCommonAttributeInstance(Long abstractFileReference, String dataSource, String caseName) {
+ this.abstractFileObjectId = abstractFileReference;
+ this.caseName = caseName;
+ this.dataSource = dataSource;
+ }
+
+ /**
+ * Create a leaf node for attributes found in the central repo and not
+ * available in the current data case.
+ *
+ * @param cachedFiles storage for abstract files which have been used
+ * already so we can avoid extra roundtrips to the case db
+ */
+ AbstractCommonAttributeInstance() {
+ this.abstractFileObjectId = -1L;
+ this.caseName = "";
+ this.dataSource = "";
+ }
+
+ /**
+ * Get an AbstractFile for this instance if it can be retrieved from the
+ * CaseDB.
+ *
+ * @return AbstractFile corresponding to this common attribute or null if it
+ * cannot be found (for example, in the event that this is a central repo
+ * file)
+ */
+ abstract AbstractFile getAbstractFile();
+
+ /**
+ * Create a list of leaf nodes, to be used to display a row in the tree
+ * table
+ *
+ * @return leaf nodes for tree
+ */
+ abstract DisplayableItemNode[] generateNodes();
+
+ /**
+ * The name of the case where this common attribute is found.
+ *
+ * @return case name
+ */
+ String getCaseName() {
+ return this.caseName;
+ }
+
+ /**
+ * Get string name of the data source where this common attribute appears.
+ *
+ * @return data source name
+ */
+ public String getDataSource() {
+
+ /**
+ * Even though we could get this from the CR record or the AbstractFile,
+ * we want to avoid getting it from the AbstractFile because it would be
+ * an extra database roundtrip.
+ */
+ return this.dataSource;
+ }
+
+ /**
+ * ObjectId of the AbstractFile that is equivalent to the file from which
+ * this common attribute instance
+ *
+ * @return the abstractFileObjectId
+ */
+ public Long getAbstractFileObjectId() {
+ return abstractFileObjectId;
+ }
+
+ /**
+ * Use this to create an AbstractCommonAttributeInstanceNode of the
+ * appropriate type. In any case, we'll get something which extends
+ * DisplayableItemNode which can be used to populate the tree.
+ *
+ * If the common attribute in question could be derived from an AbstractFile
+ * in the present SleuthkitCase, we can use an
+ * IntraCaseCommonAttributeInstanceNode which enables extended functionality
+ * in the context menu and in the content viewer.
+ *
+ * Otherwise, we will get an InterCaseCommonAttributeInstanceNode which
+ * supports only baseline functionality.
+ *
+ * @param attributeInstance common file attribute instance form the central
+ * repo
+ * @param abstractFile an AbstractFile from which the attribute instance was
+ * found - applies to CaseDbCommonAttributeInstance only
+ * @param currentCaseName
+ * @return the appropriate leaf node for the results tree
+ * @throws TskCoreException
+ */
+ static DisplayableItemNode createNode(CorrelationAttribute attribute, AbstractFile abstractFile, String currentCaseName) throws TskCoreException {
+
+ DisplayableItemNode leafNode;
+ CorrelationAttributeInstance attributeInstance = attribute.getInstances().get(0);
+
+ if (abstractFile == null) {
+ leafNode = new CentralRepoCommonAttributeInstanceNode(attributeInstance);
+ } else {
+ final String abstractFileDataSourceName = abstractFile.getDataSource().getName();
+ leafNode = new CaseDBCommonAttributeInstanceNode(abstractFile, currentCaseName, abstractFileDataSourceName);
+ }
+
+ return leafNode;
+ }
+}
diff --git a/Core/src/org/sleuthkit/autopsy/commonfilesearch/AbstractCommonAttributeSearcher.java b/Core/src/org/sleuthkit/autopsy/commonfilesearch/AbstractCommonAttributeSearcher.java
new file mode 100644
index 0000000000..97456bda04
--- /dev/null
+++ b/Core/src/org/sleuthkit/autopsy/commonfilesearch/AbstractCommonAttributeSearcher.java
@@ -0,0 +1,212 @@
+/*
+ *
+ * Autopsy Forensic Browser
+ *
+ * Copyright 2018 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.commonfilesearch;
+
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import org.openide.util.NbBundle;
+import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
+import org.sleuthkit.autopsy.centralrepository.datamodel.EamDbException;
+import org.sleuthkit.datamodel.TskCoreException;
+
+/**
+ * Prototype for an object which finds files with common attributes.
+ * Subclass this and implement findFiles in order
+ */
+public abstract class AbstractCommonAttributeSearcher {
+
+ private final Map dataSourceIdToNameMap;
+ private boolean filterByMedia;
+ private boolean filterByDoc;
+
+ AbstractCommonAttributeSearcher(Map dataSourceIdMap, boolean filterByMedia, boolean filterByDoc){
+ this.filterByDoc = filterByDoc;
+ this.filterByMedia = filterByMedia;
+ this.dataSourceIdToNameMap = dataSourceIdMap;
+ }
+
+ Map getDataSourceIdToNameMap(){
+ return Collections.unmodifiableMap(this.dataSourceIdToNameMap);
+ }
+
+ /**
+ * Implement this to search for files with common attributes. Creates an
+ * object (CommonAttributeSearchResults) which contains all of the information
+ * required to display a tree view in the UI. The view will contain 3 layers:
+ * a top level node, indicating the number matches each of it's children possess,
+ * a mid level node indicating the matched attribute,
+ * @return
+ * @throws TskCoreException
+ * @throws NoCurrentCaseException
+ * @throws SQLException
+ * @throws EamDbException
+ */
+ public abstract CommonAttributeSearchResults findFiles() throws TskCoreException, NoCurrentCaseException, SQLException, EamDbException;
+
+ /**
+ * Implement this to create a descriptive string for the tab which will display
+ * this data.
+ * @return an informative string
+ */
+ @NbBundle.Messages({
+ "AbstractCommonFilesMetadataBuilder.buildTabTitle.titleIntraAll=Common Files (All Data Sources, %s)",
+ "AbstractCommonFilesMetadataBuilder.buildTabTitle.titleIntraSingle=Common Files (Data Source: %s, %s)",
+ "AbstractCommonFilesMetadataBuilder.buildTabTitle.titleInterAll=Common Files (All Central Repository Cases, %s)",
+ "AbstractCommonFilesMetadataBuilder.buildTabTitle.titleInterSingle=Common Files (Central Repository Case: %s, %s)",
+ })
+ abstract String buildTabTitle();
+
+ @NbBundle.Messages({
+ "AbstractCommonFilesMetadataBuilder.buildCategorySelectionString.doc=Documents",
+ "AbstractCommonFilesMetadataBuilder.buildCategorySelectionString.media=Media",
+ "AbstractCommonFilesMetadataBuilder.buildCategorySelectionString.all=All File Categories"
+ })
+ String buildCategorySelectionString() {
+ if (!this.isFilterByDoc() && !this.isFilterByMedia()) {
+ return Bundle.AbstractCommonFilesMetadataBuilder_buildCategorySelectionString_all();
+ } else {
+ List filters = new ArrayList<>();
+ if (this.isFilterByDoc()) {
+ filters.add(Bundle.AbstractCommonFilesMetadataBuilder_buildCategorySelectionString_doc());
+ }
+ if (this.isFilterByMedia()) {
+ filters.add(Bundle.AbstractCommonFilesMetadataBuilder_buildCategorySelectionString_media());
+ }
+ return String.join(", ", filters);
+ }
+ }
+
+ static Map> collateMatchesByNumberOfInstances(Map commonFiles) {
+ //collate matches by number of matching instances - doing this in sql doesnt seem efficient
+ Map> instanceCollatedCommonFiles = new TreeMap<>();
+ for(CommonAttributeValue md5Metadata : commonFiles.values()){
+ Integer size = md5Metadata.getInstanceCount();
+
+ if(instanceCollatedCommonFiles.containsKey(size)){
+ instanceCollatedCommonFiles.get(size).add(md5Metadata);
+ } else {
+ ArrayList value = new ArrayList<>();
+ value.add(md5Metadata);
+ instanceCollatedCommonFiles.put(size, value);
+ }
+ }
+ return instanceCollatedCommonFiles;
+ }
+
+ /*
+ * The set of the MIME types that will be checked for extension mismatches
+ * when checkType is ONLY_MEDIA.
+ * ".jpg", ".jpeg", ".png", ".psd", ".nef", ".tiff", ".bmp", ".tec"
+ * ".aaf", ".3gp", ".asf", ".avi", ".m1v", ".m2v", //NON-NLS
+ * ".m4v", ".mp4", ".mov", ".mpeg", ".mpg", ".mpe", ".mp4", ".rm", ".wmv", ".mpv", ".flv", ".swf"
+ */
+ static final Set MEDIA_PICS_VIDEO_MIME_TYPES = Stream.of(
+ "image/bmp", //NON-NLS
+ "image/gif", //NON-NLS
+ "image/jpeg", //NON-NLS
+ "image/png", //NON-NLS
+ "image/tiff", //NON-NLS
+ "image/vnd.adobe.photoshop", //NON-NLS
+ "image/x-raw-nikon", //NON-NLS
+ "image/x-ms-bmp", //NON-NLS
+ "image/x-icon", //NON-NLS
+ "video/webm", //NON-NLS
+ "video/3gpp", //NON-NLS
+ "video/3gpp2", //NON-NLS
+ "video/ogg", //NON-NLS
+ "video/mpeg", //NON-NLS
+ "video/mp4", //NON-NLS
+ "video/quicktime", //NON-NLS
+ "video/x-msvideo", //NON-NLS
+ "video/x-flv", //NON-NLS
+ "video/x-m4v", //NON-NLS
+ "video/x-ms-wmv", //NON-NLS
+ "application/vnd.ms-asf", //NON-NLS
+ "application/vnd.rn-realmedia", //NON-NLS
+ "application/x-shockwave-flash" //NON-NLS
+ ).collect(Collectors.toSet());
+
+ /*
+ * The set of the MIME types that will be checked for extension mismatches
+ * when checkType is ONLY_TEXT_FILES.
+ * ".doc", ".docx", ".odt", ".xls", ".xlsx", ".ppt", ".pptx"
+ * ".txt", ".rtf", ".log", ".text", ".xml"
+ * ".html", ".htm", ".css", ".js", ".php", ".aspx"
+ * ".pdf"
+ */
+ static final Set TEXT_FILES_MIME_TYPES = Stream.of(
+ "text/plain", //NON-NLS
+ "application/rtf", //NON-NLS
+ "application/pdf", //NON-NLS
+ "text/css", //NON-NLS
+ "text/html", //NON-NLS
+ "text/csv", //NON-NLS
+ "application/json", //NON-NLS
+ "application/javascript", //NON-NLS
+ "application/xml", //NON-NLS
+ "text/calendar", //NON-NLS
+ "application/x-msoffice", //NON-NLS
+ "application/x-ooxml", //NON-NLS
+ "application/msword", //NON-NLS
+ "application/vnd.openxmlformats-officedocument.wordprocessingml.document", //NON-NLS
+ "application/vnd.ms-powerpoint", //NON-NLS
+ "application/vnd.openxmlformats-officedocument.presentationml.presentation", //NON-NLS
+ "application/vnd.ms-excel", //NON-NLS
+ "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", //NON-NLS
+ "application/vnd.oasis.opendocument.presentation", //NON-NLS
+ "application/vnd.oasis.opendocument.spreadsheet", //NON-NLS
+ "application/vnd.oasis.opendocument.text" //NON-NLS
+ ).collect(Collectors.toSet());
+
+ /**
+ * @return the filterByMedia
+ */
+ boolean isFilterByMedia() {
+ return filterByMedia;
+ }
+
+ /**
+ * @param filterByMedia the filterByMedia to set
+ */
+ void setFilterByMedia(boolean filterByMedia) {
+ this.filterByMedia = filterByMedia;
+ }
+
+ /**
+ * @return the filterByDoc
+ */
+ boolean isFilterByDoc() {
+ return filterByDoc;
+ }
+
+ /**
+ * @param filterByDoc the filterByDoc to set
+ */
+ void setFilterByDoc(boolean filterByDoc) {
+ this.filterByDoc = filterByDoc;
+ }
+}
diff --git a/Core/src/org/sleuthkit/autopsy/commonfilesearch/AllInterCaseCommonAttributeSearcher.java b/Core/src/org/sleuthkit/autopsy/commonfilesearch/AllInterCaseCommonAttributeSearcher.java
new file mode 100644
index 0000000000..ca436b7809
--- /dev/null
+++ b/Core/src/org/sleuthkit/autopsy/commonfilesearch/AllInterCaseCommonAttributeSearcher.java
@@ -0,0 +1,61 @@
+/*
+ *
+ * Autopsy Forensic Browser
+ *
+ * Copyright 2018 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.commonfilesearch;
+
+import java.sql.SQLException;
+import java.util.List;
+import java.util.Map;
+import org.sleuthkit.autopsy.casemodule.Case;
+import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
+import org.sleuthkit.autopsy.centralrepository.datamodel.EamDbException;
+import org.sleuthkit.datamodel.TskCoreException;
+
+/**
+ * Algorithm which finds files anywhere in the Central Repo which also occur in
+ * present case.
+ */
+public class AllInterCaseCommonAttributeSearcher extends InterCaseCommonAttributeSearcher {
+
+ /**
+ *
+ * @param filterByMediaMimeType match only on files whose mime types can be
+ * broadly categorized as media types
+ * @param filterByDocMimeType match only on files whose mime types can be
+ * broadly categorized as document types
+ * @throws EamDbException
+ */
+ public AllInterCaseCommonAttributeSearcher(Map dataSourceIdMap, boolean filterByMediaMimeType, boolean filterByDocMimeType) throws EamDbException {
+ super(dataSourceIdMap, filterByMediaMimeType, filterByDocMimeType);
+ }
+
+ @Override
+ public CommonAttributeSearchResults findFiles() throws TskCoreException, NoCurrentCaseException, SQLException, EamDbException {
+ InterCaseSearchResultsProcessor eamDbAttrInst = new InterCaseSearchResultsProcessor(this.getDataSourceIdToNameMap());
+ Map> interCaseCommonFiles = eamDbAttrInst.findInterCaseCommonAttributeValues(Case.getCurrentCase());
+ return new CommonAttributeSearchResults(interCaseCommonFiles);
+ }
+
+ @Override
+ String buildTabTitle() {
+ final String buildCategorySelectionString = this.buildCategorySelectionString();
+ final String titleTemplate = Bundle.AbstractCommonFilesMetadataBuilder_buildTabTitle_titleInterAll();
+ return String.format(titleTemplate, new Object[]{buildCategorySelectionString});
+ }
+}
diff --git a/Core/src/org/sleuthkit/autopsy/commonfilesearch/AllDataSourcesCommonFilesAlgorithm.java b/Core/src/org/sleuthkit/autopsy/commonfilesearch/AllIntraCaseCommonAttributeSearcher.java
similarity index 79%
rename from Core/src/org/sleuthkit/autopsy/commonfilesearch/AllDataSourcesCommonFilesAlgorithm.java
rename to Core/src/org/sleuthkit/autopsy/commonfilesearch/AllIntraCaseCommonAttributeSearcher.java
index 111cf4aed9..eed61b0fc1 100644
--- a/Core/src/org/sleuthkit/autopsy/commonfilesearch/AllDataSourcesCommonFilesAlgorithm.java
+++ b/Core/src/org/sleuthkit/autopsy/commonfilesearch/AllIntraCaseCommonAttributeSearcher.java
@@ -25,9 +25,9 @@ import org.sleuthkit.datamodel.TskData.FileKnown;
/**
* Provides logic for selecting common files from all data sources.
*/
-final public class AllDataSourcesCommonFilesAlgorithm extends CommonFilesMetadataBuilder {
+final public class AllIntraCaseCommonAttributeSearcher extends IntraCaseCommonAttributeSearcher {
- private static final String WHERE_CLAUSE = "%s md5 in (select md5 from tsk_files where (known != "+ FileKnown.KNOWN.getFileKnownValue() + " OR known IS NULL)%s GROUP BY md5 HAVING COUNT(DISTINCT data_source_obj_id) > 1) order by md5"; //NON-NLS
+ private static final String WHERE_CLAUSE = "%s md5 in (select md5 from tsk_files where (known != "+ FileKnown.KNOWN.getFileKnownValue() + " OR known IS NULL)%s GROUP BY md5 HAVING COUNT(DISTINCT data_source_obj_id) > 1) order by md5"; //NON-NLS
/**
* Implements the algorithm for getting common files across all data
@@ -37,7 +37,7 @@ final public class AllDataSourcesCommonFilesAlgorithm extends CommonFilesMetadat
* @param filterByMediaMimeType match only on files whose mime types can be broadly categorized as media types
* @param filterByDocMimeType match only on files whose mime types can be broadly categorized as document types
*/
- public AllDataSourcesCommonFilesAlgorithm(Map dataSourceIdMap, boolean filterByMediaMimeType, boolean filterByDocMimeType) {
+ public AllIntraCaseCommonAttributeSearcher(Map dataSourceIdMap, boolean filterByMediaMimeType, boolean filterByDocMimeType) {
super(dataSourceIdMap, filterByMediaMimeType, filterByDocMimeType);
}
@@ -49,9 +49,9 @@ final public class AllDataSourcesCommonFilesAlgorithm extends CommonFilesMetadat
}
@Override
- protected String buildTabTitle() {
+ String buildTabTitle() {
final String buildCategorySelectionString = this.buildCategorySelectionString();
- final String titleTemplate = Bundle.CommonFilesMetadataBuilder_buildTabTitle_titleAll();
+ final String titleTemplate = Bundle.AbstractCommonFilesMetadataBuilder_buildTabTitle_titleIntraAll();
return String.format(titleTemplate, new Object[]{buildCategorySelectionString});
}
}
diff --git a/Core/src/org/sleuthkit/autopsy/commonfilesearch/Bundle.properties b/Core/src/org/sleuthkit/autopsy/commonfilesearch/Bundle.properties
index a2b42155d7..23598648ea 100644
--- a/Core/src/org/sleuthkit/autopsy/commonfilesearch/Bundle.properties
+++ b/Core/src/org/sleuthkit/autopsy/commonfilesearch/Bundle.properties
@@ -1,15 +1,25 @@
-CommonFilesPanel.searchButton.text=Search
-CommonFilesPanel.withinDataSourceRadioButton.text=At least one instance of a given MD5 must appear in the data source selected below:
-CommonFilesPanel.allDataSourcesRadioButton.text=Files can be in any data source
-CommonFilesPanel.cancelButton.text=Cancel
-CommonFilesPanel.cancelButton.actionCommand=Cancel
-CommonFilesPanel.selectedFileCategoriesButton.text=Match on the following file categories:
-CommonFilesPanel.selectedFileCategoriesButton.toolTipText=Select from the options below...
-CommonFilesPanel.pictureVideoCheckbox.text=Pictures and Videos
-CommonFilesPanel.documentsCheckbox.text=Documents
CommonFilesPanel.commonFilesSearchLabel.text=Find files in multiple data sources in the current case.
-CommonFilesPanel.allFileCategoriesRadioButton.toolTipText=No filtering applied to results...
-CommonFilesPanel.allFileCategoriesRadioButton.text=Match on all file types
CommonFilesPanel.text=Indicate which data sources to consider while searching for duplicates:
-CommonFilesPanel.categoriesLabel.text=Indicate which file types to include in results:
-CommonFilesPanel.errorText.text=In order to search, you must select a file category.
+CommonFilesPanel.jRadioButton1.text=jRadioButton1
+CommonFilesPanel.jRadioButton2.text=With previous cases in the Central Repository
+CommonFilesPanel.intraCaseRadio.label=Correlate within current case only
+CommonFilesPanel.interCaseRadio.label=Correlate amongst all known cases (uses Central Repo)
+IntraCasePanel.allDataSourcesRadioButton.text=Matches may be from any data source
+IntraCasePanel.withinDataSourceRadioButton.text=At least one match must appear in the data source selected below:
+IntraCasePanel.selectDataSourceComboBox.actionCommand=
+InterCasePanel.specificCentralRepoCaseRadio.text=Matches must be from the following Central Repo case:
+InterCasePanel.anyCentralRepoCaseRadio.text=Matches may be from any Central Repo case
+CommonAttributePanel.selectedFileCategoriesButton.toolTipText=Select from the options below...
+CommonAttributePanel.selectedFileCategoriesButton.text=Only the selected file types:
+CommonAttributePanel.allFileCategoriesRadioButton.toolTipText=No filtering applied to results...
+CommonAttributePanel.allFileCategoriesRadioButton.text=All file types
+CommonAttributePanel.cancelButton.actionCommand=Cancel
+CommonAttributePanel.cancelButton.text=Cancel
+CommonAttributePanel.searchButton.text=Search
+CommonAttributePanel.commonFilesSearchLabel2.text=Scope of Search
+CommonAttributePanel.intraCaseRadio.text=Within current case
+CommonAttributePanel.commonFilesSearchLabel1.text=Find common files to correlate data soures or cases.
+CommonAttributePanel.errorText.text=In order to search, you must select a file category.
+CommonAttributePanel.categoriesLabel.text=File Types To Include:
+CommonAttributePanel.documentsCheckbox.text=Documents
+CommonAttributePanel.pictureVideoCheckbox.text=Pictures and Videos
diff --git a/Core/src/org/sleuthkit/autopsy/commonfilesearch/CaseDBCommonAttributeInstance.java b/Core/src/org/sleuthkit/autopsy/commonfilesearch/CaseDBCommonAttributeInstance.java
new file mode 100644
index 0000000000..9221a5dc86
--- /dev/null
+++ b/Core/src/org/sleuthkit/autopsy/commonfilesearch/CaseDBCommonAttributeInstance.java
@@ -0,0 +1,73 @@
+/*
+ *
+ * Autopsy Forensic Browser
+ *
+ * Copyright 2018 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.commonfilesearch;
+
+import java.util.Arrays;
+import java.util.logging.Level;
+import org.sleuthkit.autopsy.casemodule.Case;
+import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
+import org.sleuthkit.autopsy.coreutils.Logger;
+import org.sleuthkit.autopsy.datamodel.DisplayableItemNode;
+import org.sleuthkit.datamodel.AbstractFile;
+import org.sleuthkit.datamodel.SleuthkitCase;
+import org.sleuthkit.datamodel.TskCoreException;
+
+/**
+ * Encapsulates data required to instantiate a FileInstanceNode
for an instance in the CaseDB
+ */
+final public class CaseDBCommonAttributeInstance extends AbstractCommonAttributeInstance {
+
+ private static final Logger LOGGER = Logger.getLogger(CaseDBCommonAttributeInstance.class.getName());
+
+
+ /**
+ * Create meta data required to find an abstract file and build a
+ * FileInstanceNode.
+ *
+ * @param objectId id of abstract file to find
+ * @param dataSourceName name of datasource where the object is found
+ */
+ CaseDBCommonAttributeInstance(Long abstractFileReference, String dataSource, String caseName) {
+ super(abstractFileReference, dataSource, caseName);
+ }
+
+ @Override
+ public DisplayableItemNode[] generateNodes() {
+ final CaseDBCommonAttributeInstanceNode intraCaseCommonAttributeInstanceNode = new CaseDBCommonAttributeInstanceNode(this.getAbstractFile(), this.getCaseName(), this.getDataSource());
+ return Arrays.asList(intraCaseCommonAttributeInstanceNode).toArray(new DisplayableItemNode[1]);
+ }
+
+ @Override
+ AbstractFile getAbstractFile() {
+
+ Case currentCase;
+ try {
+ currentCase = Case.getCurrentCaseThrows();
+
+ SleuthkitCase tskDb = currentCase.getSleuthkitCase();
+
+ return tskDb.findAllFilesWhere(String.format("obj_id in (%s)", this.getAbstractFileObjectId())).get(0);
+
+ } catch (TskCoreException | NoCurrentCaseException ex) {
+ LOGGER.log(Level.SEVERE, String.format("Unable to find AbstractFile for record with obj_id: %s. Node not created.", new Object[]{this.getAbstractFileObjectId()}), ex);
+ return null;
+ }
+ }
+}
diff --git a/Core/src/org/sleuthkit/autopsy/commonfilesearch/FileInstanceNode.java b/Core/src/org/sleuthkit/autopsy/commonfilesearch/CaseDBCommonAttributeInstanceNode.java
similarity index 81%
rename from Core/src/org/sleuthkit/autopsy/commonfilesearch/FileInstanceNode.java
rename to Core/src/org/sleuthkit/autopsy/commonfilesearch/CaseDBCommonAttributeInstanceNode.java
index 899aa5abbc..f105d72498 100644
--- a/Core/src/org/sleuthkit/autopsy/commonfilesearch/FileInstanceNode.java
+++ b/Core/src/org/sleuthkit/autopsy/commonfilesearch/CaseDBCommonAttributeInstanceNode.java
@@ -20,18 +20,18 @@ package org.sleuthkit.autopsy.commonfilesearch;
import org.apache.commons.lang3.StringUtils;
import org.openide.nodes.Sheet;
-import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.datamodel.DisplayableItemNodeVisitor;
import org.sleuthkit.autopsy.datamodel.FileNode;
import org.sleuthkit.autopsy.datamodel.NodeProperty;
import org.sleuthkit.datamodel.AbstractFile;
/**
- * Used by the Common Files search feature to encapsulate instances of a given
- MD5s matched in the search. These nodes will be children of Md5Node
s.
+ * Node that wraps CaseDBCommonAttributeInstance to represent a file instance stored
+ * in the CaseDB.
*/
-public class FileInstanceNode extends FileNode {
+public class CaseDBCommonAttributeInstanceNode extends FileNode {
+ private final String caseName;
private final String dataSource;
/**
@@ -41,11 +41,10 @@ public class FileInstanceNode extends FileNode {
* @param fsContent
* @param dataSource
*/
- public FileInstanceNode(AbstractFile fsContent, String dataSource) {
+ public CaseDBCommonAttributeInstanceNode(AbstractFile fsContent, String caseName, String dataSource) {
super(fsContent);
+ this.caseName = caseName;
this.dataSource = dataSource;
-
- this.setDisplayName(fsContent.getName());
}
@Override
@@ -59,11 +58,14 @@ public class FileInstanceNode extends FileNode {
return visitor.visit(this);
}
- String getDataSource() {
+ public String getCase(){
+ return this.caseName;
+ }
+
+ public String getDataSource() {
return this.dataSource;
}
- @NbBundle.Messages({"FileInstanceNode.createSheet.noDescription= "})
@Override
protected Sheet createSheet() {
Sheet sheet = new Sheet();
@@ -73,13 +75,14 @@ public class FileInstanceNode extends FileNode {
sheet.put(sheetSet);
}
- final String NO_DESCR = Bundle.FileInstanceNode_createSheet_noDescription();
+ final String NO_DESCR = Bundle.CommonFilesSearchResultsViewerTable_noDescText();
sheetSet.put(new NodeProperty<>(Bundle.CommonFilesSearchResultsViewerTable_filesColLbl(), Bundle.CommonFilesSearchResultsViewerTable_filesColLbl(), NO_DESCR, this.getContent().getName()));
sheetSet.put(new NodeProperty<>(Bundle.CommonFilesSearchResultsViewerTable_pathColLbl(), Bundle.CommonFilesSearchResultsViewerTable_pathColLbl(), NO_DESCR, this.getContent().getParentPath()));
sheetSet.put(new NodeProperty<>(Bundle.CommonFilesSearchResultsViewerTable_hashsetHitsColLbl(), Bundle.CommonFilesSearchResultsViewerTable_hashsetHitsColLbl(), NO_DESCR, getHashSetHitsCsvList(this.getContent())));
sheetSet.put(new NodeProperty<>(Bundle.CommonFilesSearchResultsViewerTable_dataSourceColLbl(), Bundle.CommonFilesSearchResultsViewerTable_dataSourceColLbl(), NO_DESCR, this.getDataSource()));
sheetSet.put(new NodeProperty<>(Bundle.CommonFilesSearchResultsViewerTable_mimeTypeColLbl(), Bundle.CommonFilesSearchResultsViewerTable_mimeTypeColLbl(), NO_DESCR, StringUtils.defaultString(this.getContent().getMIMEType())));
+ sheetSet.put(new NodeProperty<>(Bundle.CommonFilesSearchResultsViewerTable_caseColLbl1(), Bundle.CommonFilesSearchResultsViewerTable_caseColLbl1(), NO_DESCR, caseName));
this.addTagProperty(sheetSet);
diff --git a/Core/src/org/sleuthkit/autopsy/commonfilesearch/CentralRepoCommonAttributeInstance.java b/Core/src/org/sleuthkit/autopsy/commonfilesearch/CentralRepoCommonAttributeInstance.java
new file mode 100644
index 0000000000..0e70722b6d
--- /dev/null
+++ b/Core/src/org/sleuthkit/autopsy/commonfilesearch/CentralRepoCommonAttributeInstance.java
@@ -0,0 +1,137 @@
+/*
+ *
+ * Autopsy Forensic Browser
+ *
+ * Copyright 2018 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.commonfilesearch;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.logging.Level;
+import org.sleuthkit.autopsy.casemodule.Case;
+import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
+import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttribute;
+import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeInstance;
+import org.sleuthkit.autopsy.coreutils.Logger;
+import org.sleuthkit.autopsy.datamodel.DisplayableItemNode;
+import org.sleuthkit.datamodel.AbstractFile;
+import org.sleuthkit.datamodel.SleuthkitCase;
+import org.sleuthkit.datamodel.TskCoreException;
+
+/**
+ * Represents that a row in the CR was found in multiple cases.
+ *
+ * Generates a DisplayableItmeNode using a CentralRepositoryFile.
+ */
+final public class CentralRepoCommonAttributeInstance extends AbstractCommonAttributeInstance {
+
+ private static final Logger LOGGER = Logger.getLogger(CentralRepoCommonAttributeInstance.class.getName());
+ private final Integer crFileId;
+ private CorrelationAttribute currentAttribute;
+ private final Map dataSourceNameToIdMap;
+
+ CentralRepoCommonAttributeInstance(Integer attrInstId, Map dataSourceIdToNameMap) {
+ super();
+ this.crFileId = attrInstId;
+ this.dataSourceNameToIdMap = invertMap(dataSourceIdToNameMap);
+ }
+
+ void setCurrentAttributeInst(CorrelationAttribute attribute) {
+ this.currentAttribute = attribute;
+ }
+
+ @Override
+ AbstractFile getAbstractFile() {
+
+ Case currentCase;
+ if (this.currentAttribute != null) {
+
+ final CorrelationAttributeInstance currentAttributeInstance = this.currentAttribute.getInstances().get(0);
+
+ String currentFullPath = currentAttributeInstance.getFilePath();
+ String currentDataSource = currentAttributeInstance.getCorrelationDataSource().getName();
+
+
+ if(this.dataSourceNameToIdMap.containsKey(currentDataSource)){
+ Long dataSourceObjectId = this.dataSourceNameToIdMap.get(currentDataSource);
+
+ try {
+ currentCase = Case.getCurrentCaseThrows();
+
+ SleuthkitCase tskDb = currentCase.getSleuthkitCase();
+
+ File fileFromPath = new File(currentFullPath);
+ String fileName = fileFromPath.getName();
+ String parentPath = (fileFromPath.getParent() + File.separator).replace("\\", "/");
+
+ final String whereClause = String.format("lower(name) = '%s' AND md5 = '%s' AND lower(parent_path) = '%s' AND data_source_obj_id = %s", fileName, currentAttribute.getCorrelationValue(), parentPath, dataSourceObjectId);
+ List potentialAbstractFiles = tskDb.findAllFilesWhere(whereClause);
+
+ if(potentialAbstractFiles.isEmpty()){
+ return null;
+ } else if(potentialAbstractFiles.size() > 1){
+ LOGGER.log(Level.WARNING, String.format("Unable to find an exact match for AbstractFile for record with filePath: %s. May have returned the wrong file.", new Object[]{currentFullPath}));
+ return potentialAbstractFiles.get(0);
+ } else {
+ return potentialAbstractFiles.get(0);
+ }
+
+ } catch (TskCoreException | NoCurrentCaseException ex) {
+ LOGGER.log(Level.SEVERE, String.format("Unable to find AbstractFile for record with filePath: %s. Node not created.", new Object[]{currentFullPath}), ex);
+ return null;
+ }
+ } else {
+ return null;
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public DisplayableItemNode[] generateNodes() {
+
+ // @@@ We should be doing more of this work in teh generateKeys method. We want to do as little as possible in generateNodes
+ InterCaseSearchResultsProcessor eamDbAttrInst = new InterCaseSearchResultsProcessor();
+ CorrelationAttribute corrAttr = eamDbAttrInst.findSingleCorrelationAttribute(crFileId);
+ List attrInstNodeList = new ArrayList<>(0);
+ String currCaseDbName = Case.getCurrentCase().getDisplayName();
+
+ try {
+ this.setCurrentAttributeInst(corrAttr);
+
+ AbstractFile abstractFileForAttributeInstance = this.getAbstractFile();
+ DisplayableItemNode generatedInstNode = AbstractCommonAttributeInstance.createNode(corrAttr, abstractFileForAttributeInstance, currCaseDbName);
+ attrInstNodeList.add(generatedInstNode);
+
+ } catch (TskCoreException ex) {
+ LOGGER.log(Level.SEVERE, String.format("Unable to get DataSource for record with md5: %s. Node not created.", new Object[]{corrAttr.getCorrelationValue()}), ex);
+ }
+
+ return attrInstNodeList.toArray(new DisplayableItemNode[attrInstNodeList.size()]);
+ }
+
+ private Map invertMap(Map dataSourceIdToNameMap) {
+ HashMap invertedMap = new HashMap<>();
+ for (Map.Entry entry : dataSourceIdToNameMap.entrySet()){
+ invertedMap.put(entry.getValue(), entry.getKey());
+ }
+ return invertedMap;
+ }
+}
diff --git a/Core/src/org/sleuthkit/autopsy/commonfilesearch/CentralRepoCommonAttributeInstanceNode.java b/Core/src/org/sleuthkit/autopsy/commonfilesearch/CentralRepoCommonAttributeInstanceNode.java
new file mode 100644
index 0000000000..e0b8e928d7
--- /dev/null
+++ b/Core/src/org/sleuthkit/autopsy/commonfilesearch/CentralRepoCommonAttributeInstanceNode.java
@@ -0,0 +1,116 @@
+/*
+ *
+ * Autopsy Forensic Browser
+ *
+ * Copyright 2018 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.commonfilesearch;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import javax.swing.Action;
+import org.openide.nodes.Children;
+import org.openide.nodes.Sheet;
+import org.openide.util.lookup.Lookups;
+import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeInstance;
+import org.sleuthkit.autopsy.datamodel.DisplayableItemNode;
+import org.sleuthkit.autopsy.datamodel.DisplayableItemNodeVisitor;
+import org.sleuthkit.autopsy.datamodel.NodeProperty;
+
+/**
+ * Used by the Common Files search feature to encapsulate instances of a given
+ * MD5s matched in the search. These nodes will be children of Md5Node
s.
+ *
+ * Use this type for files which are not in the current case, but from the
+ * Central Repo. Contrast with SleuthkitCase
which should be used
+ * when the FileInstance was found in the case presently open in Autopsy.
+ */
+public class CentralRepoCommonAttributeInstanceNode extends DisplayableItemNode {
+
+ private final CorrelationAttributeInstance crFile;
+
+ CentralRepoCommonAttributeInstanceNode(CorrelationAttributeInstance content) {
+ super(Children.LEAF, Lookups.fixed(content));
+ this.crFile = content;
+ this.setDisplayName(new File(this.crFile.getFilePath()).getName());
+ }
+
+ public CorrelationAttributeInstance getCorrelationAttributeInstance(){
+ return this.crFile;
+ }
+
+ @Override
+ public Action[] getActions(boolean context){
+ List actionsList = new ArrayList<>();
+
+ actionsList.addAll(Arrays.asList(super.getActions(true)));
+
+ return actionsList.toArray(new Action[actionsList.size()]);
+ }
+
+ @Override
+ public T accept(DisplayableItemNodeVisitor visitor) {
+ return visitor.visit(this);
+ }
+
+ @Override
+ public boolean isLeafTypeNode() {
+ return true;
+ }
+
+ @Override
+ public String getItemType() {
+ //objects of type FileNode will co-occur in the treetable with objects
+ // of this type and they will need to provide the same key
+ return CaseDBCommonAttributeInstanceNode.class.getName();
+ }
+
+ @Override
+ protected Sheet createSheet(){
+ Sheet sheet = new Sheet();
+ Sheet.Set sheetSet = sheet.get(Sheet.PROPERTIES);
+
+ if(sheetSet == null){
+ sheetSet = Sheet.createPropertiesSet();
+ sheet.put(sheetSet);
+ }
+
+ final CorrelationAttributeInstance centralRepoFile = this.getCorrelationAttributeInstance();
+
+ final String fullPath = centralRepoFile.getFilePath();
+ final File file = new File(fullPath);
+
+ final String caseName = centralRepoFile.getCorrelationCase().getDisplayName();
+
+ final String name = file.getName();
+ final String parent = file.getParent();
+
+ final String dataSourceName = centralRepoFile.getCorrelationDataSource().getName();
+
+ final String NO_DESCR = Bundle.CommonFilesSearchResultsViewerTable_noDescText();
+
+ sheetSet.put(new NodeProperty<>(org.sleuthkit.autopsy.commonfilesearch.Bundle.CommonFilesSearchResultsViewerTable_filesColLbl(), org.sleuthkit.autopsy.commonfilesearch.Bundle.CommonFilesSearchResultsViewerTable_filesColLbl(), NO_DESCR, name));
+ sheetSet.put(new NodeProperty<>(org.sleuthkit.autopsy.commonfilesearch.Bundle.CommonFilesSearchResultsViewerTable_pathColLbl(), org.sleuthkit.autopsy.commonfilesearch.Bundle.CommonFilesSearchResultsViewerTable_pathColLbl(), NO_DESCR, parent));
+ sheetSet.put(new NodeProperty<>(org.sleuthkit.autopsy.commonfilesearch.Bundle.CommonFilesSearchResultsViewerTable_hashsetHitsColLbl(), org.sleuthkit.autopsy.commonfilesearch.Bundle.CommonFilesSearchResultsViewerTable_hashsetHitsColLbl(), NO_DESCR, ""));
+ sheetSet.put(new NodeProperty<>(org.sleuthkit.autopsy.commonfilesearch.Bundle.CommonFilesSearchResultsViewerTable_dataSourceColLbl(), org.sleuthkit.autopsy.commonfilesearch.Bundle.CommonFilesSearchResultsViewerTable_dataSourceColLbl(), NO_DESCR, dataSourceName));
+ sheetSet.put(new NodeProperty<>(org.sleuthkit.autopsy.commonfilesearch.Bundle.CommonFilesSearchResultsViewerTable_mimeTypeColLbl(), org.sleuthkit.autopsy.commonfilesearch.Bundle.CommonFilesSearchResultsViewerTable_mimeTypeColLbl(), NO_DESCR, ""));
+ sheetSet.put(new NodeProperty<>(org.sleuthkit.autopsy.commonfilesearch.Bundle.CommonFilesSearchResultsViewerTable_caseColLbl1(), org.sleuthkit.autopsy.commonfilesearch.Bundle.CommonFilesSearchResultsViewerTable_caseColLbl1(), NO_DESCR, caseName));
+
+ return sheet;
+ }
+}
diff --git a/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonAttributePanel.form b/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonAttributePanel.form
new file mode 100644
index 0000000000..c08c3c2d78
--- /dev/null
+++ b/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonAttributePanel.form
@@ -0,0 +1,301 @@
+
+
+
diff --git a/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonAttributePanel.java b/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonAttributePanel.java
new file mode 100644
index 0000000000..bf1765e310
--- /dev/null
+++ b/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonAttributePanel.java
@@ -0,0 +1,720 @@
+/*
+ * Autopsy Forensic Browser
+ *
+ * Copyright 2018 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.commonfilesearch;
+
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ExecutionException;
+import java.util.logging.Level;
+import javax.swing.JFrame;
+import javax.swing.SwingUtilities;
+import javax.swing.SwingWorker;
+import org.netbeans.api.progress.ProgressHandle;
+import org.openide.explorer.ExplorerManager;
+import org.openide.util.NbBundle;
+import org.openide.windows.WindowManager;
+import org.sleuthkit.autopsy.casemodule.Case;
+import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
+import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationCase;
+import org.sleuthkit.autopsy.centralrepository.datamodel.EamDb;
+import org.sleuthkit.autopsy.centralrepository.datamodel.EamDbException;
+import org.sleuthkit.autopsy.corecomponentinterfaces.DataResultViewer;
+import org.sleuthkit.autopsy.corecomponents.DataResultTopComponent;
+import org.sleuthkit.autopsy.corecomponents.DataResultViewerTable;
+import org.sleuthkit.autopsy.corecomponents.TableFilterNode;
+import org.sleuthkit.autopsy.coreutils.Logger;
+import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil;
+import org.sleuthkit.autopsy.directorytree.DataResultFilterNode;
+import org.sleuthkit.datamodel.TskCoreException;
+
+/**
+ * Panel used for common files search configuration and configuration business
+ * logic. Nested within CommonFilesDialog.
+ */
+@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives
+public final class CommonAttributePanel extends javax.swing.JDialog {
+
+ private static final long serialVersionUID = 1L;
+
+ private static final Long NO_DATA_SOURCE_SELECTED = -1L;
+
+ private static final Logger LOGGER = Logger.getLogger(CommonAttributePanel.class.getName());
+ private boolean pictureViewCheckboxState;
+ private boolean documentsCheckboxState;
+
+ /**
+ * Creates new form CommonFilesPanel
+ */
+ @NbBundle.Messages({
+ "CommonFilesPanel.title=Common Files Panel",
+ "CommonFilesPanel.exception=Unexpected Exception loading DataSources.",
+ "CommonFilesPanel.frame.title=Find Common Files",
+ "CommonFilesPanel.frame.msg=Find Common Files"})
+ public CommonAttributePanel() {
+ super(new JFrame(Bundle.CommonFilesPanel_frame_title()),
+ Bundle.CommonFilesPanel_frame_msg(), true);
+ initComponents();
+ this.setLocationRelativeTo(WindowManager.getDefault().getMainWindow());
+ this.errorText.setVisible(false);
+ this.setupDataSources();
+
+ if (CommonAttributePanel.isEamDbAvailable()) {
+ this.setupCases();
+ } else {
+ this.disableIntercaseSearch();
+ }
+ }
+
+ private static boolean isEamDbAvailable() {
+ try {
+ return EamDb.isEnabled() &&
+ EamDb.getInstance() != null &&
+ EamDb.getInstance().getCases().size() > 1 &&
+ Case.isCaseOpen() &&
+ Case.getCurrentCase() != null &&
+ EamDb.getInstance().getCase(Case.getCurrentCase()) != null;
+ } catch (EamDbException ex) {
+ LOGGER.log(Level.SEVERE, "Unexpected exception while checking for EamDB enabled.", ex);
+ }
+ return false;
+ }
+
+ private void disableIntercaseSearch() {
+ this.intraCaseRadio.setSelected(true);
+ this.interCaseRadio.setEnabled(false);
+ }
+
+ @NbBundle.Messages({
+ "CommonFilesPanel.search.results.titleAll=Common Files (All Data Sources)",
+ "CommonFilesPanel.search.results.titleSingle=Common Files (Match Within Data Source: %s)",
+ "CommonFilesPanel.search.results.pathText=Common Files Search Results",
+ "CommonFilesPanel.search.done.searchProgressGathering=Gathering Common Files Search Results.",
+ "CommonFilesPanel.search.done.searchProgressDisplay=Displaying Common Files Search Results.",
+ "CommonFilesPanel.search.done.tskCoreException=Unable to run query against DB.",
+ "CommonFilesPanel.search.done.noCurrentCaseException=Unable to open case file.",
+ "CommonFilesPanel.search.done.exception=Unexpected exception running Common Files Search.",
+ "CommonFilesPanel.search.done.interupted=Something went wrong finding common files.",
+ "CommonFilesPanel.search.done.sqlException=Unable to query db for files or data sources."})
+ private void search() {
+ String pathText = Bundle.CommonFilesPanel_search_results_pathText();
+
+ new SwingWorker() {
+
+ private String tabTitle;
+ private ProgressHandle progress;
+
+ private void setTitleForAllDataSources() {
+ this.tabTitle = Bundle.CommonFilesPanel_search_results_titleAll();
+ }
+
+ private void setTitleForSingleSource(Long dataSourceId) {
+ final String CommonFilesPanel_search_results_titleSingle = Bundle.CommonFilesPanel_search_results_titleSingle();
+ final Object[] dataSourceName = new Object[]{intraCasePanel.getDataSourceMap().get(dataSourceId)};
+
+ this.tabTitle = String.format(CommonFilesPanel_search_results_titleSingle, dataSourceName);
+ }
+
+ @Override
+ @SuppressWarnings({"BoxedValueEquality", "NumberEquality"})
+ protected CommonAttributeSearchResults doInBackground() throws TskCoreException, NoCurrentCaseException, SQLException, EamDbException {
+ progress = ProgressHandle.createHandle(Bundle.CommonFilesPanel_search_done_searchProgressGathering());
+ progress.start();
+ progress.switchToIndeterminate();
+
+ Long dataSourceId = intraCasePanel.getSelectedDataSourceId();
+ Integer caseId = interCasePanel.getSelectedCaseId();
+
+ AbstractCommonAttributeSearcher builder;
+ CommonAttributeSearchResults metadata;
+
+ boolean filterByMedia = false;
+ boolean filterByDocuments = false;
+ if (selectedFileCategoriesButton.isSelected()) {
+ if (pictureVideoCheckbox.isSelected()) {
+ filterByMedia = true;
+ }
+ if (documentsCheckbox.isSelected()) {
+ filterByDocuments = true;
+ }
+ }
+
+ if (CommonAttributePanel.this.interCaseRadio.isSelected()) {
+
+ if (caseId == InterCasePanel.NO_CASE_SELECTED) {
+ builder = new AllInterCaseCommonAttributeSearcher(intraCasePanel.getDataSourceMap(), filterByMedia, filterByDocuments);
+ } else {
+ builder = new SingleInterCaseCommonAttributeSearcher(caseId, intraCasePanel.getDataSourceMap(), filterByMedia, filterByDocuments);
+ }
+ } else {
+ if (dataSourceId == CommonAttributePanel.NO_DATA_SOURCE_SELECTED) {
+ builder = new AllIntraCaseCommonAttributeSearcher(intraCasePanel.getDataSourceMap(), filterByMedia, filterByDocuments);
+
+ setTitleForAllDataSources();
+ } else {
+ builder = new SingleIntraCaseCommonAttributeSearcher(dataSourceId, intraCasePanel.getDataSourceMap(), filterByMedia, filterByDocuments);
+
+ setTitleForSingleSource(dataSourceId);
+ }
+ }
+ metadata = builder.findFiles();
+ this.tabTitle = builder.buildTabTitle();
+
+ return metadata;
+ }
+
+ @Override
+ protected void done() {
+ try {
+ super.done();
+
+ CommonAttributeSearchResults metadata = this.get();
+
+ CommonAttributeSearchResultRootNode commonFilesNode = new CommonAttributeSearchResultRootNode(metadata);
+
+ // -3969
+ DataResultFilterNode dataResultFilterNode = new DataResultFilterNode(commonFilesNode, ExplorerManager.find(CommonAttributePanel.this));
+
+ TableFilterNode tableFilterWithDescendantsNode = new TableFilterNode(dataResultFilterNode, 3);
+
+ DataResultViewerTable table = new CommonAttributesSearchResultsViewerTable();
+
+ Collection viewers = new ArrayList<>(1);
+ viewers.add(table);
+
+ progress.setDisplayName(Bundle.CommonFilesPanel_search_done_searchProgressDisplay());
+ DataResultTopComponent.createInstance(tabTitle, pathText, tableFilterWithDescendantsNode, metadata.size(), viewers);
+ progress.finish();
+
+ } catch (InterruptedException ex) {
+ LOGGER.log(Level.SEVERE, "Interrupted while loading Common Files", ex);
+ MessageNotifyUtil.Message.error(Bundle.CommonFilesPanel_search_done_interupted());
+ } catch (ExecutionException ex) {
+ String errorMessage;
+ Throwable inner = ex.getCause();
+ if (inner instanceof TskCoreException) {
+ LOGGER.log(Level.SEVERE, "Failed to load files from database.", ex);
+ errorMessage = Bundle.CommonFilesPanel_search_done_tskCoreException();
+ } else if (inner instanceof NoCurrentCaseException) {
+ LOGGER.log(Level.SEVERE, "Current case has been closed.", ex);
+ errorMessage = Bundle.CommonFilesPanel_search_done_noCurrentCaseException();
+ } else if (inner instanceof SQLException) {
+ LOGGER.log(Level.SEVERE, "Unable to query db for files.", ex);
+ errorMessage = Bundle.CommonFilesPanel_search_done_sqlException();
+ } else {
+ LOGGER.log(Level.SEVERE, "Unexpected exception while running Common Files Search.", ex);
+ errorMessage = Bundle.CommonFilesPanel_search_done_exception();
+ }
+ MessageNotifyUtil.Message.error(errorMessage);
+ }
+ }
+ }.execute();
+ }
+
+
+ /**
+ * Sets up the data sources dropdown and returns the data sources map for
+ * future usage.
+ *
+ * @return a mapping of data correlationCase ids to data correlationCase
+ * names
+ */
+ @NbBundle.Messages({
+ "CommonFilesPanel.setupDataSources.done.tskCoreException=Unable to run query against DB.",
+ "CommonFilesPanel.setupDataSources.done.noCurrentCaseException=Unable to open case file.",
+ "CommonFilesPanel.setupDataSources.done.exception=Unexpected exception loading data sources.",
+ "CommonFilesPanel.setupDataSources.done.interupted=Something went wrong building the Common Files Search dialog box.",
+ "CommonFilesPanel.setupDataSources.done.sqlException=Unable to query db for data sources.",
+ "CommonFilesPanel.setupDataSources.updateUi.noDataSources=No data sources were found."})
+ private void setupDataSources() {
+
+ new SwingWorker