diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/CasePreferences.java b/Core/src/org/sleuthkit/autopsy/casemodule/CasePreferences.java index 1c853c7951..dcee43fc82 100755 --- a/Core/src/org/sleuthkit/autopsy/casemodule/CasePreferences.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/CasePreferences.java @@ -98,16 +98,22 @@ public final class CasePreferences { Properties props = new Properties(); props.load(inputStream); String groupByDataSourceValue = props.getProperty(KEY_GROUP_BY_DATA_SOURCE); - switch (groupByDataSourceValue) { - case VALUE_TRUE: - groupItemsInTreeByDataSource = true; - break; - case VALUE_FALSE: - groupItemsInTreeByDataSource = false; - break; - default: - groupItemsInTreeByDataSource = null; - break; + if (groupByDataSourceValue != null) { + switch (groupByDataSourceValue) { + case VALUE_TRUE: + groupItemsInTreeByDataSource = true; + break; + case VALUE_FALSE: + groupItemsInTreeByDataSource = false; + break; + default: + logger.log(Level.WARNING, String.format("Unexpected value '%s' for key '%s'. Using 'null' instead.", + groupByDataSourceValue, KEY_GROUP_BY_DATA_SOURCE)); + groupItemsInTreeByDataSource = null; + break; + } + } else { + groupItemsInTreeByDataSource = null; } } catch (IOException ex) { logger.log(Level.SEVERE, "Error reading settings file", ex); diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/AbstractSqlEamDb.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/AbstractSqlEamDb.java index 833c27ffcc..2aae94cb24 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/AbstractSqlEamDb.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/AbstractSqlEamDb.java @@ -18,6 +18,9 @@ */ package org.sleuthkit.autopsy.centralrepository.datamodel; +import com.google.common.cache.Cache; +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.CacheLoader; import java.net.UnknownHostException; import java.util.ArrayList; import java.util.List; @@ -34,6 +37,8 @@ import java.time.LocalDate; import java.util.HashMap; import java.util.Map; import java.util.Set; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; import java.util.logging.Level; import org.sleuthkit.autopsy.casemodule.Case; import static org.sleuthkit.autopsy.centralrepository.datamodel.EamDbUtil.updateSchemaVersion; @@ -57,7 +62,21 @@ abstract class AbstractSqlEamDb implements EamDb { private int bulkArtifactsCount; protected int bulkArtifactsThreshold; private final Map> bulkArtifacts; - + private static final int CASE_CACHE_TIMEOUT = 5; + private static final int DATA_SOURCE_CACHE_TIMEOUT = 5; + private static final Cache typeCache = CacheBuilder.newBuilder().build(); + private static final Cache caseCacheByUUID = CacheBuilder.newBuilder() + .expireAfterWrite(CASE_CACHE_TIMEOUT, TimeUnit.MINUTES). + build(); + private static final Cache caseCacheById = CacheBuilder.newBuilder() + .expireAfterWrite(CASE_CACHE_TIMEOUT, TimeUnit.MINUTES). + build(); + private static final Cache dataSourceCacheByDeviceId = CacheBuilder.newBuilder() + .expireAfterWrite(DATA_SOURCE_CACHE_TIMEOUT, TimeUnit.MINUTES). + build(); + private static final Cache dataSourceCacheById = CacheBuilder.newBuilder() + .expireAfterWrite(DATA_SOURCE_CACHE_TIMEOUT, TimeUnit.MINUTES). + build(); // Maximum length for the value column in the instance tables static final int MAX_VALUE_LENGTH = 256; @@ -88,7 +107,7 @@ 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 @@ -149,10 +168,21 @@ abstract class AbstractSqlEamDb implements EamDb { return value; } + /** + * Reset the contents of the caches associated with EamDb results. + */ + protected final void clearCaches() { + typeCache.invalidateAll(); + caseCacheByUUID.invalidateAll(); + caseCacheById.invalidateAll(); + dataSourceCacheByDeviceId.invalidateAll(); + dataSourceCacheById.invalidateAll(); + } + /** * 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 @@ -201,9 +231,9 @@ abstract class AbstractSqlEamDb implements EamDb { + "examiner_name, examiner_email, examiner_phone, notes) " + "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) " + getConflictClause(); - + ResultSet resultSet = null; try { - preparedStatement = conn.prepareStatement(sql); + preparedStatement = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS); preparedStatement.setString(1, eamCase.getCaseUUID()); if (null == eamCase.getOrg()) { @@ -240,9 +270,21 @@ abstract class AbstractSqlEamDb implements EamDb { } preparedStatement.executeUpdate(); + //update the case in the caches + resultSet = preparedStatement.getGeneratedKeys(); + if (!resultSet.next()) { + throw new EamDbException(String.format("Failed to INSERT case %s in central repo", eamCase.getCaseUUID())); + } + int caseID = resultSet.getInt(1); //last_insert_rowid() + CorrelationCase correlationCase = new CorrelationCase(caseID, eamCase.getCaseUUID(), eamCase.getOrg(), + eamCase.getDisplayName(), eamCase.getCreationDate(), eamCase.getCaseNumber(), eamCase.getExaminerName(), + eamCase.getExaminerEmail(), eamCase.getExaminerPhone(), eamCase.getNotes()); + caseCacheByUUID.put(eamCase.getCaseUUID(), correlationCase); + caseCacheById.put(caseID, correlationCase); } catch (SQLException ex) { throw new EamDbException("Error inserting new case.", ex); // NON-NLS } finally { + EamDbUtil.closeResultSet(resultSet); EamDbUtil.closeStatement(preparedStatement); EamDbUtil.closeConnection(conn); } @@ -339,6 +381,9 @@ abstract class AbstractSqlEamDb implements EamDb { preparedStatement.setString(9, eamCase.getCaseUUID()); preparedStatement.executeUpdate(); + //update the case in the cache + caseCacheById.put(eamCase.getID(), eamCase); + caseCacheByUUID.put(eamCase.getCaseUUID(), eamCase); } catch (SQLException ex) { throw new EamDbException("Error updating case.", ex); // NON-NLS } finally { @@ -347,6 +392,25 @@ abstract class AbstractSqlEamDb implements EamDb { } } + /** + * Retrieves Case details based on Case UUID from the central repo + * + * @param caseUUID unique identifier for a case + * + * @return The retrieved case + */ + @Override + public CorrelationCase getCaseByUUID(String caseUUID) throws EamDbException { + try { + return caseCacheByUUID.get(caseUUID, () -> getCaseByUUIDFromCr(caseUUID)); + } catch (CacheLoader.InvalidCacheLoadException ignored) { + //lambda valueloader returned a null value and cache can not store null values this is normal if the case does not exist in the central repo yet + return null; + } catch (ExecutionException ex) { + throw new EamDbException("Error getting autopsy case from Central repo", ex); + } + } + /** * Retrieves Case details based on Case UUID * @@ -354,10 +418,7 @@ abstract class AbstractSqlEamDb implements EamDb { * * @return The retrieved case */ - @Override - public CorrelationCase getCaseByUUID(String caseUUID) throws EamDbException { - // @@@ We should have a cache here... - + private CorrelationCase getCaseByUUIDFromCr(String caseUUID) throws EamDbException { Connection conn = connect(); CorrelationCase eamCaseResult = null; @@ -377,6 +438,10 @@ abstract class AbstractSqlEamDb implements EamDb { if (resultSet.next()) { eamCaseResult = getEamCaseFromResultSet(resultSet); } + if (eamCaseResult != null) { + //Update the version in the other cache + caseCacheById.put(eamCaseResult.getID(), eamCaseResult); + } } catch (SQLException ex) { throw new EamDbException("Error getting case details.", ex); // NON-NLS } finally { @@ -397,8 +462,24 @@ abstract class AbstractSqlEamDb implements EamDb { */ @Override public CorrelationCase getCaseById(int caseId) throws EamDbException { - // @@@ We should have a cache here... + try { + return caseCacheById.get(caseId, () -> getCaseByIdFromCr(caseId)); + } catch (CacheLoader.InvalidCacheLoadException ignored) { + //lambda valueloader returned a null value and cache can not store null values this is normal if the case does not exist in the central repo yet + return null; + } catch (ExecutionException ex) { + throw new EamDbException("Error getting autopsy case from Central repo", ex); + } + } + /** + * Retrieves Case details based on Case ID + * + * @param caseID unique identifier for a case + * + * @return The retrieved case + */ + private CorrelationCase getCaseByIdFromCr(int caseId) throws EamDbException { Connection conn = connect(); CorrelationCase eamCaseResult = null; @@ -410,7 +491,6 @@ abstract class AbstractSqlEamDb implements EamDb { + "FROM cases " + "LEFT JOIN organizations ON cases.org_id=organizations.id " + "WHERE cases.id=?"; - try { preparedStatement = conn.prepareStatement(sql); preparedStatement.setInt(1, caseId); @@ -418,6 +498,10 @@ abstract class AbstractSqlEamDb implements EamDb { if (resultSet.next()) { eamCaseResult = getEamCaseFromResultSet(resultSet); } + if (eamCaseResult != null) { + //Update the version in the other cache + caseCacheByUUID.put(eamCaseResult.getCaseUUID(), eamCaseResult); + } } catch (SQLException ex) { throw new EamDbException("Error getting case details.", ex); // NON-NLS } finally { @@ -466,6 +550,32 @@ abstract class AbstractSqlEamDb implements EamDb { return cases; } + /** + * Create a key to the DataSourceCacheByDeviceId + * + * @param caseId - the id of the CorrelationCase in the Central + * Repository + * @param dataSourceDeviceId - the device Id of the data source + * + * @return a String to be used as a key for the dataSourceCacheByDeviceId + */ + private static String getDataSourceByDeviceIdCacheKey(int caseId, String dataSourceDeviceId) { + return "Case" + caseId + "DeviceId" + dataSourceDeviceId; //NON-NLS + } + + /** + * Create a key to the DataSourceCacheById + * + * @param caseId - the id of the CorrelationCase in the Central + * Repository + * @param dataSourceId - the id of the datasource in the central repository + * + * @return a String to be used as a key for the dataSourceCacheById + */ + private static String getDataSourceByIdCacheKey(int caseId, int dataSourceId) { + return "Case" + caseId + "Id" + dataSourceId; //NON-NLS + } + /** * Creates new Data Source in the database * @@ -485,18 +595,27 @@ abstract class AbstractSqlEamDb implements EamDb { String sql = "INSERT INTO data_sources(device_id, case_id, name) VALUES (?, ?, ?) " + getConflictClause(); - + ResultSet resultSet = null; try { - preparedStatement = conn.prepareStatement(sql); + preparedStatement = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS); preparedStatement.setString(1, eamDataSource.getDeviceID()); preparedStatement.setInt(2, eamDataSource.getCaseID()); preparedStatement.setString(3, eamDataSource.getName()); preparedStatement.executeUpdate(); + resultSet = preparedStatement.getGeneratedKeys(); + if (!resultSet.next()) { + throw new EamDbException(String.format("Failed to INSERT data source %s in central repo", eamDataSource.getName())); + } + int dataSourceId = resultSet.getInt(1); //last_insert_rowid() + CorrelationDataSource dataSource = new CorrelationDataSource(eamDataSource.getCaseID(), dataSourceId, eamDataSource.getDeviceID(), eamDataSource.getName()); + dataSourceCacheByDeviceId.put(getDataSourceByDeviceIdCacheKey(dataSource.getCaseID(), dataSource.getDeviceID()), dataSource); + dataSourceCacheById.put(getDataSourceByIdCacheKey(dataSource.getCaseID(), dataSource.getID()), dataSource); } catch (SQLException ex) { throw new EamDbException("Error inserting new data source.", ex); // NON-NLS } finally { + EamDbUtil.closeResultSet(resultSet); EamDbUtil.closeStatement(preparedStatement); EamDbUtil.closeConnection(conn); } @@ -505,18 +624,43 @@ 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 + * + * @throws EamDbException */ @Override public CorrelationDataSource getDataSource(CorrelationCase correlationCase, String dataSourceDeviceId) throws EamDbException { + if (correlationCase == null) { throw new EamDbException("Correlation case is null"); } + try { + return dataSourceCacheByDeviceId.get(getDataSourceByDeviceIdCacheKey(correlationCase.getID(), dataSourceDeviceId), () -> getDataSourceFromCr(correlationCase, dataSourceDeviceId)); + } catch (CacheLoader.InvalidCacheLoadException ignored) { + //lambda valueloader returned a null value and cache can not store null values this is normal if the dataSource does not exist in the central repo yet + return null; + } catch (ExecutionException ex) { + throw new EamDbException("Error getting data source from central repository", ex); + } + } + /** + * Gets the Data Source details based on data source device ID from the + * central repository. + * + * @param correlationCase the current CorrelationCase used for ensuring + * uniqueness of DataSource + * @param dataSourceDeviceId the data source device ID number + * + * @return The data source + * + * @throws EamDbException + */ + private CorrelationDataSource getDataSourceFromCr(CorrelationCase correlationCase, String dataSourceDeviceId) throws EamDbException { Connection conn = connect(); CorrelationDataSource eamDataSourceResult = null; @@ -533,6 +677,9 @@ abstract class AbstractSqlEamDb implements EamDb { if (resultSet.next()) { eamDataSourceResult = getEamDataSourceFromResultSet(resultSet); } + if (eamDataSourceResult != null) { + dataSourceCacheById.put(getDataSourceByIdCacheKey(correlationCase.getID(), eamDataSourceResult.getID()), eamDataSourceResult); + } } catch (SQLException ex) { throw new EamDbException("Error getting data source.", ex); // NON-NLS } finally { @@ -548,8 +695,8 @@ abstract class AbstractSqlEamDb implements EamDb { * 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 + * uniqueness of DataSource + * @param dataSourceId the data source ID number * * @return The data source */ @@ -558,7 +705,26 @@ abstract class AbstractSqlEamDb implements EamDb { if (correlationCase == null) { throw new EamDbException("Correlation case is null"); } + try { + return dataSourceCacheById.get(getDataSourceByIdCacheKey(correlationCase.getID(), dataSourceId), () -> getDataSourceByIdFromCr(correlationCase, dataSourceId)); + } catch (CacheLoader.InvalidCacheLoadException ignored) { + //lambda valueloader returned a null value and cache can not store null values this is normal if the dataSource does not exist in the central repo yet + return null; + } catch (ExecutionException ex) { + throw new EamDbException("Error getting data source from central repository", ex); + } + } + /** + * 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 + */ + private CorrelationDataSource getDataSourceByIdFromCr(CorrelationCase correlationCase, int dataSourceId) throws EamDbException { Connection conn = connect(); CorrelationDataSource eamDataSourceResult = null; @@ -575,6 +741,9 @@ abstract class AbstractSqlEamDb implements EamDb { if (resultSet.next()) { eamDataSourceResult = getEamDataSourceFromResultSet(resultSet); } + if (eamDataSourceResult != null) { + dataSourceCacheByDeviceId.put(getDataSourceByDeviceIdCacheKey(correlationCase.getID(), eamDataSourceResult.getDeviceID()), eamDataSourceResult); + } } catch (SQLException ex) { throw new EamDbException("Error getting data source.", ex); // NON-NLS } finally { @@ -715,7 +884,7 @@ abstract class AbstractSqlEamDb implements EamDb { public List getArtifactInstancesByTypeValue(CorrelationAttributeInstance.Type aType, String value) throws EamDbException, CorrelationAttributeNormalizationException { String normalizedValue = CorrelationAttributeNormalizer.normalize(aType, value); - + Connection conn = connect(); List artifactInstances = new ArrayList<>(); @@ -764,7 +933,7 @@ 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 @@ -835,7 +1004,7 @@ abstract class AbstractSqlEamDb implements EamDb { * @param value The correlation value * * @return Number of artifact instances having ArtifactType and - * ArtifactValue. + * ArtifactValue. */ @Override public Long getCountArtifactInstancesByTypeValue(CorrelationAttributeInstance.Type aType, String value) throws EamDbException, CorrelationAttributeNormalizationException { @@ -957,11 +1126,11 @@ 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 { @@ -1224,7 +1393,7 @@ abstract class AbstractSqlEamDb implements EamDb { * associated CorrelationAttribute object. * * @param eamArtifact The correlation attribute whose database instance will - * be updated. + * be updated. * * @throws EamDbException */ @@ -1274,11 +1443,11 @@ abstract class AbstractSqlEamDb implements EamDb { * Find a correlation attribute in the Central Repository database given the * instance type, case, data source, value, and file path. * - * @param type The type of instance. - * @param correlationCase The case tied to the instance. + * @param type The type of instance. + * @param correlationCase The case tied to the instance. * @param correlationDataSource The data source tied to the instance. - * @param value The value tied to the instance. - * @param filePath The file path tied to the instance. + * @param value The value tied to the instance. + * @param filePath The file path tied to the instance. * * @return The correlation attribute if it exists; otherwise null. * @@ -1287,7 +1456,7 @@ abstract class AbstractSqlEamDb implements EamDb { @Override public CorrelationAttributeInstance getCorrelationAttributeInstance(CorrelationAttributeInstance.Type type, CorrelationCase correlationCase, CorrelationDataSource correlationDataSource, String value, String filePath) throws EamDbException, CorrelationAttributeNormalizationException { - + if (correlationCase == null) { throw new EamDbException("Correlation case is null"); } @@ -1306,7 +1475,7 @@ abstract class AbstractSqlEamDb implements EamDb { try { String normalizedValue = CorrelationAttributeNormalizer.normalize(type, value); - + String tableName = EamDbUtil.correlationTypeToInstanceTableName(type); String sql = "SELECT id, known_status, comment FROM " @@ -1349,7 +1518,7 @@ abstract class AbstractSqlEamDb implements EamDb { * * @param eamArtifact Artifact containing exactly one (1) ArtifactInstance. * @param knownStatus The status to change the artifact to. Should never be - * KNOWN + * KNOWN */ @Override public void setAttributeInstanceKnownStatus(CorrelationAttributeInstance eamArtifact, TskData.FileKnown knownStatus) throws EamDbException { @@ -1548,7 +1717,7 @@ abstract class AbstractSqlEamDb implements EamDb { artifactInstances.add(artifactInstance); } catch (CorrelationAttributeNormalizationException ex) { logger.log(Level.INFO, "Unable to get artifact instance from resultset.", ex); - } + } } } catch (SQLException ex) { throw new EamDbException("Error getting notable artifact instances.", ex); // NON-NLS @@ -1571,7 +1740,7 @@ abstract class AbstractSqlEamDb implements EamDb { */ @Override public Long getCountArtifactInstancesKnownBad(CorrelationAttributeInstance.Type aType, String value) throws EamDbException, CorrelationAttributeNormalizationException { - + String normalizedValue = CorrelationAttributeNormalizer.normalize(aType, value); Connection conn = connect(); @@ -1612,13 +1781,13 @@ 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 */ @Override public List getListCasesHavingArtifactInstancesKnownBad(CorrelationAttributeInstance.Type aType, String value) throws EamDbException, CorrelationAttributeNormalizationException { - + String normalizedValue = CorrelationAttributeNormalizer.normalize(aType, value); Connection conn = connect(); @@ -1778,7 +1947,7 @@ abstract class AbstractSqlEamDb implements EamDb { public boolean isValueInReferenceSet(String value, int referenceSetID, int correlationTypeID) throws EamDbException, CorrelationAttributeNormalizationException { String normalizeValued = CorrelationAttributeNormalizer.normalize(this.getCorrelationTypeById(correlationTypeID), value); - + Connection conn = connect(); Long matchingInstances = 0L; @@ -1816,10 +1985,10 @@ abstract class AbstractSqlEamDb implements EamDb { */ @Override public boolean isArtifactKnownBadByReference(CorrelationAttributeInstance.Type aType, String value) throws EamDbException, CorrelationAttributeNormalizationException { - + //this should be done here so that we can be certain that aType and value are valid before we proceed String normalizeValued = CorrelationAttributeNormalizer.normalize(aType, value); - + // TEMP: Only support file correlation type if (aType.getId() != CorrelationAttributeInstance.FILES_TYPE_ID) { return false; @@ -1832,7 +2001,7 @@ abstract class AbstractSqlEamDb implements EamDb { ResultSet resultSet = null; String sql = "SELECT count(*) FROM %s WHERE value=? AND known_status=?"; - try { + try { preparedStatement = conn.prepareStatement(String.format(sql, EamDbUtil.correlationTypeToReferenceTableName(aType))); preparedStatement.setString(1, normalizeValued); preparedStatement.setByte(2, TskData.FileKnown.BAD.getFileKnownValue()); @@ -1853,7 +2022,7 @@ abstract class AbstractSqlEamDb implements EamDb { /** * Process the Artifact instance in the EamDb * - * @param type EamArtifact.Type to search for + * @param type EamArtifact.Type to search for * @param instanceTableCallback callback to process the instance * * @throws EamDbException @@ -1892,9 +2061,10 @@ abstract class AbstractSqlEamDb implements EamDb { /** * Process the Artifact instance in the EamDb give a where clause * - * @param type EamArtifact.Type to search for + * @param type EamArtifact.Type to search for * @param instanceTableCallback callback to process the instance - * @param whereClause query string to execute + * @param whereClause query string to execute + * * @throws EamDbException */ @Override @@ -2076,7 +2246,7 @@ 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 */ @@ -2279,7 +2449,8 @@ 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 */ @@ -2407,7 +2578,7 @@ 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 @@ -2440,7 +2611,7 @@ abstract class AbstractSqlEamDb implements EamDb { EamDbUtil.closeResultSet(resultSet); EamDbUtil.closeConnection(conn); } - + return globalFileInstances; } @@ -2607,7 +2778,7 @@ 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 */ @@ -2642,7 +2813,7 @@ 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 */ @@ -2694,7 +2865,7 @@ abstract class AbstractSqlEamDb implements EamDb { preparedStatement.setInt(4, aType.isEnabled() ? 1 : 0); preparedStatement.setInt(5, aType.getId()); preparedStatement.executeUpdate(); - + typeCache.put(aType.getId(), aType); } catch (SQLException ex) { throw new EamDbException("Error updating correlation type.", ex); // NON-NLS } finally { @@ -2715,6 +2886,26 @@ abstract class AbstractSqlEamDb implements EamDb { */ @Override public CorrelationAttributeInstance.Type getCorrelationTypeById(int typeId) throws EamDbException { + try { + return typeCache.get(CorrelationAttributeInstance.FILES_TYPE_ID, () -> getCorrelationTypeByIdFromCr(typeId)); + } catch (CacheLoader.InvalidCacheLoadException ignored) { + //lambda valueloader returned a null value and cache can not store null values this is normal if the correlation type does not exist in the central repo yet + return null; + } catch (ExecutionException ex) { + throw new EamDbException("Error getting correlation type", ex); + } + } + + /** + * Get the EamArtifact.Type that has the given Type.Id from the central repo + * + * @param typeId Type.Id of Correlation Type to get + * + * @return EamArtifact.Type or null if it doesn't exist. + * + * @throws EamDbException + */ + private CorrelationAttributeInstance.Type getCorrelationTypeByIdFromCr(int typeId) throws EamDbException { Connection conn = connect(); CorrelationAttributeInstance.Type aType; @@ -2746,7 +2937,7 @@ 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 * @@ -2816,7 +3007,7 @@ 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 * diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/PostgresEamDb.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/PostgresEamDb.java index 5db232b51e..97abd1dec9 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/PostgresEamDb.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/PostgresEamDb.java @@ -1,7 +1,7 @@ /* * Central Repository * - * Copyright 2015-2017 Basis Technology Corp. + * Copyright 2015-2018 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -78,6 +78,7 @@ final class PostgresEamDb extends AbstractSqlEamDb { connectionPool.close(); connectionPool = null; // force it to be re-created on next connect() } + clearCaches(); } } catch (SQLException ex) { throw new EamDbException("Failed to close existing database connections.", ex); // NON-NLS diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/SqliteEamDb.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/SqliteEamDb.java index 4805e4e0b0..a75f4648ff 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/SqliteEamDb.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/SqliteEamDb.java @@ -1,7 +1,7 @@ /* * Central Repository * - * Copyright 2015-2017 Basis Technology Corp. + * Copyright 2015-2018 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -85,6 +85,7 @@ final class SqliteEamDb extends AbstractSqlEamDb { connectionPool.close(); connectionPool = null; // force it to be re-created on next connect() } + clearCaches(); } } catch (SQLException ex) { throw new EamDbException("Failed to close existing database connections.", ex); // NON-NLS diff --git a/Core/src/org/sleuthkit/autopsy/commonfilesearch/AbstractCommonAttributeSearcher.java b/Core/src/org/sleuthkit/autopsy/commonfilesearch/AbstractCommonAttributeSearcher.java index f79737c116..2e95e83007 100644 --- a/Core/src/org/sleuthkit/autopsy/commonfilesearch/AbstractCommonAttributeSearcher.java +++ b/Core/src/org/sleuthkit/autopsy/commonfilesearch/AbstractCommonAttributeSearcher.java @@ -39,22 +39,16 @@ import org.sleuthkit.datamodel.TskCoreException; */ public abstract class AbstractCommonAttributeSearcher { - private final Map dataSourceIdToNameMap; private boolean filterByMedia; private boolean filterByDoc; final int frequencyPercentageThreshold; - AbstractCommonAttributeSearcher(Map dataSourceIdMap, boolean filterByMedia, boolean filterByDoc, int percentageThreshold) { + AbstractCommonAttributeSearcher(boolean filterByMedia, boolean filterByDoc, int percentageThreshold) { this.filterByDoc = filterByDoc; this.filterByMedia = filterByMedia; - this.dataSourceIdToNameMap = dataSourceIdMap; this.frequencyPercentageThreshold = percentageThreshold; } - 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 diff --git a/Core/src/org/sleuthkit/autopsy/commonfilesearch/AllInterCaseCommonAttributeSearcher.java b/Core/src/org/sleuthkit/autopsy/commonfilesearch/AllInterCaseCommonAttributeSearcher.java index f28d636be0..a64d8b0758 100644 --- a/Core/src/org/sleuthkit/autopsy/commonfilesearch/AllInterCaseCommonAttributeSearcher.java +++ b/Core/src/org/sleuthkit/autopsy/commonfilesearch/AllInterCaseCommonAttributeSearcher.java @@ -43,13 +43,13 @@ public class AllInterCaseCommonAttributeSearcher extends InterCaseCommonAttribut * * @throws EamDbException */ - public AllInterCaseCommonAttributeSearcher(Map dataSourceIdMap, boolean filterByMediaMimeType, boolean filterByDocMimeType, Type corAttrType, int percentageThreshold) throws EamDbException { - super(dataSourceIdMap, filterByMediaMimeType, filterByDocMimeType, corAttrType, percentageThreshold); + public AllInterCaseCommonAttributeSearcher(boolean filterByMediaMimeType, boolean filterByDocMimeType, Type corAttrType, int percentageThreshold) throws EamDbException { + super(filterByMediaMimeType, filterByDocMimeType, corAttrType, percentageThreshold); } @Override public CommonAttributeSearchResults findMatches() throws TskCoreException, NoCurrentCaseException, SQLException, EamDbException { - InterCaseSearchResultsProcessor eamDbAttrInst = new InterCaseSearchResultsProcessor(this.getDataSourceIdToNameMap(), corAttrType); + InterCaseSearchResultsProcessor eamDbAttrInst = new InterCaseSearchResultsProcessor(corAttrType); Map interCaseCommonFiles = eamDbAttrInst.findInterCaseCommonAttributeValues(Case.getCurrentCase()); return new CommonAttributeSearchResults(interCaseCommonFiles, this.frequencyPercentageThreshold, this.corAttrType); } diff --git a/Core/src/org/sleuthkit/autopsy/commonfilesearch/CentralRepoCommonAttributeInstance.java b/Core/src/org/sleuthkit/autopsy/commonfilesearch/CentralRepoCommonAttributeInstance.java index df6d24b0bb..855fb3e7c1 100644 --- a/Core/src/org/sleuthkit/autopsy/commonfilesearch/CentralRepoCommonAttributeInstance.java +++ b/Core/src/org/sleuthkit/autopsy/commonfilesearch/CentralRepoCommonAttributeInstance.java @@ -24,6 +24,7 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.logging.Level; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; @@ -31,6 +32,7 @@ import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeIns import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.datamodel.DisplayableItemNode; import org.sleuthkit.datamodel.AbstractFile; +import org.sleuthkit.datamodel.DataSource; import org.sleuthkit.datamodel.SleuthkitCase; import org.sleuthkit.datamodel.TskCoreException; @@ -45,12 +47,10 @@ final public class CentralRepoCommonAttributeInstance extends AbstractCommonAttr private final Integer crFileId; private CorrelationAttributeInstance currentAttribute; private final CorrelationAttributeInstance.Type correlationType; - private final Map dataSourceNameToIdMap; - CentralRepoCommonAttributeInstance(Integer attrInstId, Map dataSourceIdToNameMap, CorrelationAttributeInstance.Type correlationType) { + CentralRepoCommonAttributeInstance(Integer attrInstId, CorrelationAttributeInstance.Type correlationType) { super(); this.crFileId = attrInstId; - this.dataSourceNameToIdMap = invertMap(dataSourceIdToNameMap); this.correlationType = correlationType; } @@ -71,22 +71,35 @@ final public class CentralRepoCommonAttributeInstance extends AbstractCommonAttr final CorrelationAttributeInstance currentAttributeInstance = this.currentAttribute; - String currentFullPath = currentAttributeInstance.getFilePath(); - String currentDataSource = currentAttributeInstance.getCorrelationDataSource().getName(); + try { + String currentFullPath = currentAttributeInstance.getFilePath(); + currentCase = Case.getCurrentCaseThrows(); - if (this.dataSourceNameToIdMap.containsKey(currentDataSource)) { - Long dataSourceObjectId = this.dataSourceNameToIdMap.get(currentDataSource); - - try { - currentCase = Case.getCurrentCaseThrows(); + // Only attempt to make the abstract file if the attribute is from the current case + if (currentCase.getName().equals(currentAttributeInstance.getCorrelationCase().getCaseUUID())) { SleuthkitCase tskDb = currentCase.getSleuthkitCase(); + + // Find the correct data source + Optional dataSource = tskDb.getDataSources().stream() + .filter(p -> p.getDeviceId().equals(currentAttribute.getCorrelationDataSource().getDeviceID())) + .findFirst(); + if (! dataSource.isPresent()) { + LOGGER.log(Level.WARNING, String.format("Unable to find data source with device ID %s in the current case", currentAttribute.getCorrelationDataSource().getDeviceID())); + return null; + } File fileFromPath = new File(currentFullPath); String fileName = fileFromPath.getName(); - String parentPath = (fileFromPath.getParent() + File.separator).replace("\\", "/"); + + // Create the parent path. Make sure not to add a separator if there is already one there. + String parentPath = fileFromPath.getParent(); + if (! parentPath.endsWith(File.separator)) { + parentPath = parentPath + File.separator; + } + parentPath = parentPath.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); + 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, dataSource.get().getId()); List potentialAbstractFiles = tskDb.findAllFilesWhere(whereClause); if (potentialAbstractFiles.isEmpty()) { @@ -97,14 +110,14 @@ final public class CentralRepoCommonAttributeInstance extends AbstractCommonAttr } 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); + } else { return null; } - } else { + } catch (TskCoreException | NoCurrentCaseException ex) { + LOGGER.log(Level.SEVERE, String.format("Unable to find AbstractFile for record with filePath: %s. Node not created.", new Object[]{currentAttributeInstance.getFilePath()}), ex); return null; } + } return null; } @@ -131,12 +144,4 @@ final public class CentralRepoCommonAttributeInstance extends AbstractCommonAttr 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/CommonAttributePanel.java b/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonAttributePanel.java index c4aa8019f2..40324ab64a 100644 --- a/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonAttributePanel.java +++ b/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonAttributePanel.java @@ -231,10 +231,10 @@ final class CommonAttributePanel extends javax.swing.JDialog implements Observer corType = CorrelationAttributeInstance.getDefaultCorrelationTypes().get(0); } if (caseId == InterCasePanel.NO_CASE_SELECTED) { - builder = new AllInterCaseCommonAttributeSearcher(intraCasePanel.getDataSourceMap(), filterByMedia, filterByDocuments, corType, percentageThreshold); + builder = new AllInterCaseCommonAttributeSearcher(filterByMedia, filterByDocuments, corType, percentageThreshold); } else { - builder = new SingleInterCaseCommonAttributeSearcher(caseId, intraCasePanel.getDataSourceMap(), filterByMedia, filterByDocuments, corType, percentageThreshold); + builder = new SingleInterCaseCommonAttributeSearcher(caseId, filterByMedia, filterByDocuments, corType, percentageThreshold); } } else { diff --git a/Core/src/org/sleuthkit/autopsy/commonfilesearch/InterCaseCommonAttributeSearcher.java b/Core/src/org/sleuthkit/autopsy/commonfilesearch/InterCaseCommonAttributeSearcher.java index 931ee7ffcc..8219ab9196 100644 --- a/Core/src/org/sleuthkit/autopsy/commonfilesearch/InterCaseCommonAttributeSearcher.java +++ b/Core/src/org/sleuthkit/autopsy/commonfilesearch/InterCaseCommonAttributeSearcher.java @@ -48,8 +48,8 @@ abstract class InterCaseCommonAttributeSearcher extends AbstractCommonAttributeS * * @throws EamDbException */ - InterCaseCommonAttributeSearcher(Map dataSourceIdMap, boolean filterByMediaMimeType, boolean filterByDocMimeType, Type corAttrType, int percentageThreshold) throws EamDbException { - super(dataSourceIdMap, filterByMediaMimeType, filterByDocMimeType, percentageThreshold); + InterCaseCommonAttributeSearcher(boolean filterByMediaMimeType, boolean filterByDocMimeType, Type corAttrType, int percentageThreshold) throws EamDbException { + super(filterByMediaMimeType, filterByDocMimeType, percentageThreshold); dbManager = EamDb.getInstance(); this.corAttrType = corAttrType; } diff --git a/Core/src/org/sleuthkit/autopsy/commonfilesearch/InterCaseSearchResultsProcessor.java b/Core/src/org/sleuthkit/autopsy/commonfilesearch/InterCaseSearchResultsProcessor.java index 2aab1d3078..af5941b8ae 100644 --- a/Core/src/org/sleuthkit/autopsy/commonfilesearch/InterCaseSearchResultsProcessor.java +++ b/Core/src/org/sleuthkit/autopsy/commonfilesearch/InterCaseSearchResultsProcessor.java @@ -44,8 +44,6 @@ import org.sleuthkit.datamodel.HashUtility; */ final class InterCaseSearchResultsProcessor { - private Map dataSources; - /** * The CorrelationAttributeInstance.Type this Processor will query on */ @@ -70,9 +68,8 @@ final class InterCaseSearchResultsProcessor { * @param dataSources the cases to filter and correlate on * @param theType the type of CR data to search */ - InterCaseSearchResultsProcessor(Map dataSources, CorrelationAttributeInstance.Type theType) { + InterCaseSearchResultsProcessor(CorrelationAttributeInstance.Type theType) { this.correlationType = theType; - this.dataSources = dataSources; interCaseWhereClause = getInterCaseWhereClause(); singleInterCaseWhereClause = getSingleInterCaseWhereClause(); } @@ -101,18 +98,6 @@ final class InterCaseSearchResultsProcessor { return sqlString.toString(); } - /** - * Used in the CentralRepoCommonAttributeInstance to find common attribute - * instances and generate nodes at the UI level. - * - * @param theType the type of CR data to search - */ - InterCaseSearchResultsProcessor(CorrelationAttributeInstance.Type theType) { - this.correlationType = theType; - interCaseWhereClause = getInterCaseWhereClause(); - singleInterCaseWhereClause = getSingleInterCaseWhereClause(); - } - /** * Finds a single CorrelationAttribute given an id. * @@ -257,7 +242,7 @@ final class InterCaseSearchResultsProcessor { // we don't *have* all the information for the rows in the CR, // so we need to consult the present case via the SleuthkitCase object // Later, when the FileInstanceNode is built. Therefore, build node generators for now. - AbstractCommonAttributeInstance searchResult = new CentralRepoCommonAttributeInstance(resultId, InterCaseSearchResultsProcessor.this.dataSources, correlationType); + AbstractCommonAttributeInstance searchResult = new CentralRepoCommonAttributeInstance(resultId, correlationType); commonAttributeValue.addInstance(searchResult); } diff --git a/Core/src/org/sleuthkit/autopsy/commonfilesearch/IntraCaseCommonAttributeSearcher.java b/Core/src/org/sleuthkit/autopsy/commonfilesearch/IntraCaseCommonAttributeSearcher.java index 569ae5232f..ac8a009b11 100644 --- a/Core/src/org/sleuthkit/autopsy/commonfilesearch/IntraCaseCommonAttributeSearcher.java +++ b/Core/src/org/sleuthkit/autopsy/commonfilesearch/IntraCaseCommonAttributeSearcher.java @@ -21,6 +21,7 @@ package org.sleuthkit.autopsy.commonfilesearch; import java.sql.ResultSet; import java.sql.SQLException; +import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -46,6 +47,8 @@ public abstract class IntraCaseCommonAttributeSearcher extends AbstractCommonAtt private static final String FILTER_BY_MIME_TYPES_WHERE_CLAUSE = " and mime_type in (%s)"; //NON-NLS // where %s is csv list of mime_types to filter on + private final Map dataSourceIdToNameMap; + /** * Subclass this to implement different algorithms for getting common files. * @@ -56,7 +59,13 @@ public abstract class IntraCaseCommonAttributeSearcher extends AbstractCommonAtt * broadly categorized as document types */ IntraCaseCommonAttributeSearcher(Map dataSourceIdMap, boolean filterByMediaMimeType, boolean filterByDocMimeType, int percentageThreshold) { - super(dataSourceIdMap, filterByMediaMimeType, filterByDocMimeType, percentageThreshold); + super(filterByMediaMimeType, filterByDocMimeType, percentageThreshold); + this.dataSourceIdToNameMap = dataSourceIdMap; + } + + + Map getDataSourceIdToNameMap() { + return Collections.unmodifiableMap(this.dataSourceIdToNameMap); } /** diff --git a/Core/src/org/sleuthkit/autopsy/commonfilesearch/SingleInterCaseCommonAttributeSearcher.java b/Core/src/org/sleuthkit/autopsy/commonfilesearch/SingleInterCaseCommonAttributeSearcher.java index 3d2abda13c..de8abfce20 100644 --- a/Core/src/org/sleuthkit/autopsy/commonfilesearch/SingleInterCaseCommonAttributeSearcher.java +++ b/Core/src/org/sleuthkit/autopsy/commonfilesearch/SingleInterCaseCommonAttributeSearcher.java @@ -46,9 +46,9 @@ public class SingleInterCaseCommonAttributeSearcher extends InterCaseCommonAttri * * @throws EamDbException */ - public SingleInterCaseCommonAttributeSearcher(int correlationCaseId, Map dataSourceIdMap, boolean filterByMediaMimeType, + public SingleInterCaseCommonAttributeSearcher(int correlationCaseId, boolean filterByMediaMimeType, boolean filterByDocMimeType, Type corAttrType, int percentageThreshold) throws EamDbException { - super(dataSourceIdMap, filterByMediaMimeType, filterByDocMimeType, corAttrType, percentageThreshold); + super(filterByMediaMimeType, filterByDocMimeType, corAttrType, percentageThreshold); this.corrleationCaseId = correlationCaseId; this.correlationCaseName = ""; @@ -77,7 +77,7 @@ public class SingleInterCaseCommonAttributeSearcher extends InterCaseCommonAttri } CommonAttributeSearchResults findFiles(CorrelationCase correlationCase) throws TskCoreException, NoCurrentCaseException, SQLException, EamDbException { - InterCaseSearchResultsProcessor eamDbAttrInst = new InterCaseSearchResultsProcessor(this.getDataSourceIdToNameMap(), this.corAttrType); + InterCaseSearchResultsProcessor eamDbAttrInst = new InterCaseSearchResultsProcessor(this.corAttrType); Map interCaseCommonFiles = eamDbAttrInst.findSingleInterCaseCommonAttributeValues(Case.getCurrentCase(), correlationCase); return new CommonAttributeSearchResults(interCaseCommonFiles, this.frequencyPercentageThreshold, this.corAttrType); diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.java b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.java index 4b93eba8cc..25709df0df 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.java @@ -854,6 +854,8 @@ public class DataResultViewerTable extends AbstractDataResultViewer { "DataResultViewerTable.commentRenderer.noComment.toolTip=No comments found"}) @Override public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { + Component component = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column); + setBackground(component.getBackground()); //inherit highlighting for selection setHorizontalAlignment(CENTER); Object switchValue = null; if ((value instanceof NodeProperty)) { @@ -908,6 +910,8 @@ public class DataResultViewerTable extends AbstractDataResultViewer { @Override public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { + Component component = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column); + setBackground(component.getBackground()); //inherit highlighting for selection setHorizontalAlignment(CENTER); Object switchValue = null; if ((value instanceof NodeProperty)) { @@ -955,6 +959,8 @@ public class DataResultViewerTable extends AbstractDataResultViewer { @Override public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { + Component component = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column); + setBackground(component.getBackground()); //inherit highlighting for selection setHorizontalAlignment(LEFT); Object countValue = null; if ((value instanceof NodeProperty)) { diff --git a/Core/test/qa-functional/src/org/sleuthkit/autopsy/commonfilessearch/CommonAttributeSearchInterCaseTests.java b/Core/test/qa-functional/src/org/sleuthkit/autopsy/commonfilessearch/CommonAttributeSearchInterCaseTests.java index 966d0b8bce..e6ff7b743d 100644 --- a/Core/test/qa-functional/src/org/sleuthkit/autopsy/commonfilessearch/CommonAttributeSearchInterCaseTests.java +++ b/Core/test/qa-functional/src/org/sleuthkit/autopsy/commonfilessearch/CommonAttributeSearchInterCaseTests.java @@ -21,7 +21,6 @@ package org.sleuthkit.autopsy.commonfilessearch; import java.nio.file.Path; import java.sql.SQLException; -import java.util.Map; import junit.framework.Assert; import junit.framework.Test; import org.netbeans.junit.NbModuleSuite; @@ -111,9 +110,8 @@ public class CommonAttributeSearchInterCaseTests extends NbTestCase { private void assertResultsAreOfType(CorrelationAttributeInstance.Type type) { try { - Map dataSources = this.utils.getDataSourceMap(); - AbstractCommonAttributeSearcher builder = new AllInterCaseCommonAttributeSearcher(dataSources, false, false, type, 0); + AbstractCommonAttributeSearcher builder = new AllInterCaseCommonAttributeSearcher(false, false, type, 0); CommonAttributeSearchResults metadata = builder.findMatches(); @@ -146,22 +144,21 @@ public class CommonAttributeSearchInterCaseTests extends NbTestCase { */ public void testTwo() { try { - Map dataSources = this.utils.getDataSourceMap(); AbstractCommonAttributeSearcher builder; CommonAttributeSearchResults metadata; - builder = new AllInterCaseCommonAttributeSearcher(dataSources, false, false, this.utils.USB_ID_TYPE, 100); + builder = new AllInterCaseCommonAttributeSearcher(false, false, this.utils.USB_ID_TYPE, 100); metadata = builder.findMatches(); metadata.size(); //assertTrue("This should yield 13 results.", verifyInstanceCount(metadata, 13)); - builder = new AllInterCaseCommonAttributeSearcher(dataSources, false, false, this.utils.USB_ID_TYPE, 20); + builder = new AllInterCaseCommonAttributeSearcher(false, false, this.utils.USB_ID_TYPE, 20); metadata = builder.findMatches(); metadata.size(); //assertTrue("This should yield no results.", verifyInstanceCount(metadata, 0)); - builder = new AllInterCaseCommonAttributeSearcher(dataSources, false, false, this.utils.USB_ID_TYPE, 90); + builder = new AllInterCaseCommonAttributeSearcher(false, false, this.utils.USB_ID_TYPE, 90); metadata = builder.findMatches(); metadata.size(); //assertTrue("This should yield 2 results.", verifyInstanceCount(metadata, 2)); diff --git a/Core/test/qa-functional/src/org/sleuthkit/autopsy/commonfilessearch/IngestedWithHashAndFileTypeInterCaseTests.java b/Core/test/qa-functional/src/org/sleuthkit/autopsy/commonfilessearch/IngestedWithHashAndFileTypeInterCaseTests.java index 8f4f96e7a3..9c32eb0f94 100644 --- a/Core/test/qa-functional/src/org/sleuthkit/autopsy/commonfilessearch/IngestedWithHashAndFileTypeInterCaseTests.java +++ b/Core/test/qa-functional/src/org/sleuthkit/autopsy/commonfilessearch/IngestedWithHashAndFileTypeInterCaseTests.java @@ -21,7 +21,6 @@ package org.sleuthkit.autopsy.commonfilessearch; import java.nio.file.Path; import java.sql.SQLException; -import java.util.Map; import junit.framework.Test; import org.netbeans.junit.NbModuleSuite; import org.netbeans.junit.NbTestCase; @@ -96,10 +95,8 @@ public class IngestedWithHashAndFileTypeInterCaseTests extends NbTestCase { */ public void testOne() { try { - Map dataSources = this.utils.getDataSourceMap(); - //note that the params false and false are presently meaningless because that feature is not supported yet - AbstractCommonAttributeSearcher builder = new AllInterCaseCommonAttributeSearcher(dataSources, false, false, this.utils.FILE_TYPE, 0); + AbstractCommonAttributeSearcher builder = new AllInterCaseCommonAttributeSearcher(false, false, this.utils.FILE_TYPE, 0); CommonAttributeSearchResults metadata = builder.findMatches(); assertTrue("Results should not be empty", metadata.size() != 0); @@ -146,11 +143,10 @@ public class IngestedWithHashAndFileTypeInterCaseTests extends NbTestCase { */ public void testTwo() { try { - Map dataSources = this.utils.getDataSourceMap(); int matchesMustAlsoBeFoundInThisCase = this.utils.getCaseMap().get(CASE2); CorrelationAttributeInstance.Type fileType = CorrelationAttributeInstance.getDefaultCorrelationTypes().get(0); - AbstractCommonAttributeSearcher builder = new SingleInterCaseCommonAttributeSearcher(matchesMustAlsoBeFoundInThisCase, dataSources, false, false, fileType, 0); + AbstractCommonAttributeSearcher builder = new SingleInterCaseCommonAttributeSearcher(matchesMustAlsoBeFoundInThisCase, false, false, fileType, 0); CommonAttributeSearchResults metadata = builder.findMatches(); @@ -199,11 +195,10 @@ public class IngestedWithHashAndFileTypeInterCaseTests extends NbTestCase { */ public void testThree(){ try { - Map dataSources = this.utils.getDataSourceMap(); //note that the params false and false are presently meaningless because that feature is not supported yet CorrelationAttributeInstance.Type fileType = CorrelationAttributeInstance.getDefaultCorrelationTypes().get(0); - AbstractCommonAttributeSearcher builder = new AllInterCaseCommonAttributeSearcher(dataSources, false, false, fileType, 50); + AbstractCommonAttributeSearcher builder = new AllInterCaseCommonAttributeSearcher(false, false, fileType, 50); CommonAttributeSearchResults metadata = builder.findMatches(); diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryTopComponent.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryTopComponent.java index 9263913748..055b0cc623 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryTopComponent.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryTopComponent.java @@ -229,45 +229,47 @@ public final class ImageGalleryTopComponent extends TopComponent implements Expl } synchronized private void setController(ImageGalleryController controller) { - if (this.controller != null && notEqual(this.controller, controller)) { - this.controller.reset(); - } - this.controller = controller; - Platform.runLater(new Runnable() { - @Override - public void run() { - //initialize jfx ui - fullUIStack = new StackPane(); //this is passed into controller - myScene = new Scene(fullUIStack); - jfxPanel.setScene(myScene); - groupPane = new GroupPane(controller); - centralStack = new StackPane(groupPane); //this is passed into controller - fullUIStack.getChildren().add(borderPane); - splitPane = new SplitPane(); - borderPane.setCenter(splitPane); - Toolbar toolbar = new Toolbar(controller); - borderPane.setTop(toolbar); - borderPane.setBottom(new StatusBar(controller)); - metaDataTable = new MetaDataPane(controller); - groupTree = new GroupTree(controller); - hashHitList = new HashHitGroupList(controller); - TabPane tabPane = new TabPane(groupTree, hashHitList); - tabPane.setPrefWidth(TabPane.USE_COMPUTED_SIZE); - tabPane.setMinWidth(TabPane.USE_PREF_SIZE); - VBox.setVgrow(tabPane, Priority.ALWAYS); - leftPane = new VBox(tabPane, new SummaryTablePane(controller)); - SplitPane.setResizableWithParent(leftPane, Boolean.FALSE); - SplitPane.setResizableWithParent(groupPane, Boolean.TRUE); - SplitPane.setResizableWithParent(metaDataTable, Boolean.FALSE); - splitPane.getItems().addAll(leftPane, centralStack, metaDataTable); - splitPane.setDividerPositions(0.1, 1.0); - - controller.regroupDisabledProperty().addListener((Observable observable) -> checkForGroups()); - controller.getGroupManager().getAnalyzedGroups().addListener((Observable observable) -> Platform.runLater(() -> checkForGroups())); - - Platform.runLater(() -> checkForGroups()); + if (notEqual(this.controller, controller)) { + if (this.controller != null) { + this.controller.reset(); } - }); + this.controller = controller; + Platform.runLater(new Runnable() { + @Override + public void run() { + //initialize jfx ui + fullUIStack = new StackPane(); //this is passed into controller + myScene = new Scene(fullUIStack); + jfxPanel.setScene(myScene); + groupPane = new GroupPane(controller); + centralStack = new StackPane(groupPane); //this is passed into controller + fullUIStack.getChildren().add(borderPane); + splitPane = new SplitPane(); + borderPane.setCenter(splitPane); + Toolbar toolbar = new Toolbar(controller); + borderPane.setTop(toolbar); + borderPane.setBottom(new StatusBar(controller)); + metaDataTable = new MetaDataPane(controller); + groupTree = new GroupTree(controller); + hashHitList = new HashHitGroupList(controller); + TabPane tabPane = new TabPane(groupTree, hashHitList); + tabPane.setPrefWidth(TabPane.USE_COMPUTED_SIZE); + tabPane.setMinWidth(TabPane.USE_PREF_SIZE); + VBox.setVgrow(tabPane, Priority.ALWAYS); + leftPane = new VBox(tabPane, new SummaryTablePane(controller)); + SplitPane.setResizableWithParent(leftPane, Boolean.FALSE); + SplitPane.setResizableWithParent(groupPane, Boolean.TRUE); + SplitPane.setResizableWithParent(metaDataTable, Boolean.FALSE); + splitPane.getItems().addAll(leftPane, centralStack, metaDataTable); + splitPane.setDividerPositions(0.1, 1.0); + + controller.regroupDisabledProperty().addListener((Observable observable) -> checkForGroups()); + controller.getGroupManager().getAnalyzedGroups().addListener((Observable observable) -> Platform.runLater(() -> checkForGroups())); + + Platform.runLater(() -> checkForGroups()); + } + }); + } } /** diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupManager.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupManager.java index 95d00ee84a..f36e3b66f2 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupManager.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupManager.java @@ -42,6 +42,7 @@ import java.util.TreeSet; import java.util.concurrent.CancellationException; import java.util.concurrent.ExecutionException; import java.util.concurrent.Executors; +import java.util.function.Consumer; import java.util.logging.Level; import java.util.regex.Pattern; import java.util.stream.Collectors; @@ -286,17 +287,19 @@ public class GroupManager { group.removeFile(fileID); // If we're grouping by category, we don't want to remove empty groups. - if (groupKey.getAttribute() != DrawableAttribute.CATEGORY - && group.getFileIDs().isEmpty()) { - if (analyzedGroups.contains(group)) { - analyzedGroups.remove(group); - sortAnalyzedGroups(); + if (group.getFileIDs().isEmpty()) { + markGroupSeen(group, true); + if (groupKey.getAttribute() != DrawableAttribute.CATEGORY) { + if (analyzedGroups.contains(group)) { + analyzedGroups.remove(group); + sortAnalyzedGroups(); + } + + if (unSeenGroups.contains(group)) { + unSeenGroups.remove(group); + sortUnseenGroups(); + } } - if (unSeenGroups.contains(group)) { - unSeenGroups.remove(group); - sortUnseenGroups(); - } - } return group; } @@ -447,7 +450,8 @@ public class GroupManager { if (!Case.isCaseOpen()) { return; } - + setSortBy(sortBy); + setSortOrder(sortOrder); //only re-query the db if the data source or group by attribute changed or it is forced if (dataSource != getDataSource() || groupBy != getGroupBy() @@ -455,13 +459,11 @@ public class GroupManager { setDataSource(dataSource); setGroupBy(groupBy); - setSortBy(sortBy); - setSortOrder(sortOrder); + Platform.runLater(regrouper::restart); } else { // resort the list of groups - setSortBy(sortBy); - setSortOrder(sortOrder); + sortAnalyzedGroups(); sortUnseenGroups(); } @@ -501,21 +503,26 @@ public class GroupManager { //if there is aleady a group that was previously deemed fully analyzed, then add this newly analyzed file to it. group.addFile(fileID); } + markGroupSeen(group, false); } @Subscribe synchronized public void handleTagDeleted(ContentTagDeletedEvent evt) { GroupKey groupKey = null; final ContentTagDeletedEvent.DeletedContentTagInfo deletedTagInfo = evt.getDeletedTagInfo(); - final TagName tagName = deletedTagInfo.getName(); - if (getGroupBy() == DrawableAttribute.CATEGORY && CategoryManager.isCategoryTagName(tagName)) { - groupKey = new GroupKey<>(DrawableAttribute.CATEGORY, CategoryManager.categoryFromTagName(tagName), getDataSource()); - } else if (getGroupBy() == DrawableAttribute.TAGS && CategoryManager.isNotCategoryTagName(tagName)) { - groupKey = new GroupKey<>(DrawableAttribute.TAGS, tagName, getDataSource()); + final TagName deletedTagName = deletedTagInfo.getName(); + if (getGroupBy() == DrawableAttribute.CATEGORY && CategoryManager.isCategoryTagName(deletedTagName)) { + groupKey = new GroupKey<>(DrawableAttribute.CATEGORY, CategoryManager.categoryFromTagName(deletedTagName), null); + } else if (getGroupBy() == DrawableAttribute.TAGS && CategoryManager.isNotCategoryTagName(deletedTagName)) { + groupKey = new GroupKey<>(DrawableAttribute.TAGS, deletedTagName, null); } if (groupKey != null) { final long fileID = deletedTagInfo.getContentID(); DrawableGroup g = removeFromGroup(groupKey, fileID); + + if (controller.getCategoryManager().getTagName(DhsImageCategory.ZERO).equals(deletedTagName) == false) { + addFileToGroup(null, new GroupKey<>(DrawableAttribute.CATEGORY, DhsImageCategory.ZERO, null), fileID); + } } } diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/SortChooser.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/SortChooser.java index 49543d5f2d..5f14f1d8b9 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/SortChooser.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/SortChooser.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2016 Basis Technology Corp. + * Copyright 2016-18 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -54,7 +54,7 @@ public class SortChooser> extends HBox { private final ReadOnlyObjectWrapper sortOrder = new ReadOnlyObjectWrapper<>(SortOrder.ASCENDING); private final SimpleBooleanProperty sortOrderDisabled = new SimpleBooleanProperty(false); - private final SimpleObjectProperty valueType = new SimpleObjectProperty<>(ValueType.NUMERIC); + private final SimpleObjectProperty valueType = new SimpleObjectProperty<>(ValueType.LEXICOGRAPHIC); public SortChooser(ObservableList comps) { this.comparators = comps; @@ -73,15 +73,13 @@ public class SortChooser> extends HBox { descRadio.getStyleClass().remove("radio-button"); descRadio.getStyleClass().add("toggle-button"); - valueType.addListener((observable, oldValue, newValue) -> { - ascRadio.setGraphic(new ImageView(newValue.getAscendingImage())); - descRadio.setGraphic(new ImageView(newValue.getDescendingImage())); - }); + valueType.addListener(observable -> setValueTypeIcon(valueType.getValue())); + setValueTypeIcon(valueType.getValue()); ascRadio.disableProperty().bind(sortOrderDisabled); descRadio.disableProperty().bind(sortOrderDisabled); ascRadio.selectedProperty().addListener(selectedToggle -> { - sortOrder.set(orderGroup.getSelectedToggle() == ascRadio ? SortOrder.ASCENDING : SortOrder.DESCENDING); + sortOrder.set(ascRadio.isSelected() ? SortOrder.ASCENDING : SortOrder.DESCENDING); }); sortByBox.setItems(comparators); @@ -89,6 +87,11 @@ public class SortChooser> extends HBox { sortByBox.setButtonCell(new ComparatorCell()); } + private void setValueTypeIcon(ValueType newValue) { + ascRadio.setGraphic(new ImageView(newValue.getAscendingImage())); + descRadio.setGraphic(new ImageView(newValue.getDescendingImage())); + } + public ValueType getValueType() { return valueType.get(); } diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/navpanel/GroupComparators.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/navpanel/GroupComparators.java index 16ac00d0a2..52b5ffc525 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/navpanel/GroupComparators.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/navpanel/GroupComparators.java @@ -56,6 +56,10 @@ final class GroupComparators> implements Comparator extractor; private final Function valueFormatter; private final boolean orderReveresed; + + boolean isOrderReveresed() { + return orderReveresed; + } private final String displayName; private GroupComparators(String displayName, Function extractor, Function formatter, boolean defaultOrderReversed) { diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/navpanel/NavPanel.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/navpanel/NavPanel.java index e8c97c1379..46131cb4e5 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/navpanel/NavPanel.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/navpanel/NavPanel.java @@ -123,10 +123,12 @@ abstract class NavPanel extends Tab { */ @ThreadConfined(type = ThreadConfined.ThreadType.JFX) Comparator getComparator() { - Comparator comparator = sortChooser.getComparator(); - return (sortChooser.getSortOrder() == SortOrder.ASCENDING) + GroupComparators comparator = sortChooser.getComparator(); + Comparator comparator2 = (sortChooser.getSortOrder() == SortOrder.ASCENDING) ? comparator : comparator.reversed(); + + return comparator.isOrderReveresed() ? comparator2.reversed() : comparator2; } /**