Merge pull request #7452 from gdicristofaro/8183-treeEvents2

8183 tree events2
This commit is contained in:
Ann Priestman
2021-12-10 10:29:51 -05:00
committed by GitHub
37 changed files with 1449 additions and 630 deletions
@@ -36,7 +36,6 @@ import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
@@ -52,6 +51,7 @@ import org.sleuthkit.autopsy.ingest.ModuleDataEvent;
import org.sleuthkit.autopsy.mainui.datamodel.TreeResultsDTO.TreeDisplayCount;
import org.sleuthkit.autopsy.mainui.datamodel.TreeResultsDTO.TreeItemDTO;
import org.sleuthkit.autopsy.mainui.datamodel.events.DAOEventUtils;
import org.sleuthkit.autopsy.mainui.datamodel.events.KeywordHitEvent;
import org.sleuthkit.autopsy.mainui.datamodel.events.TreeEvent;
import org.sleuthkit.autopsy.mainui.datamodel.events.TreeCounts;
import org.sleuthkit.autopsy.mainui.nodes.DAOFetcher;
@@ -69,6 +69,8 @@ import org.sleuthkit.datamodel.Pool;
import org.sleuthkit.datamodel.SleuthkitCase;
import org.sleuthkit.datamodel.TskCoreException;
import org.sleuthkit.datamodel.TskData;
import static org.sleuthkit.datamodel.TskData.KeywordSearchQueryType.REGEX;
import static org.sleuthkit.datamodel.TskData.KeywordSearchQueryType.SUBSTRING;
import org.sleuthkit.datamodel.Volume;
import org.sleuthkit.datamodel.VolumeSystem;
@@ -353,18 +355,6 @@ public class AnalysisResultDAO extends BlackboardArtifactDAO {
return keywordHitCache.get(searchParams, () -> fetchKeywordHitsForTable(searchParams));
}
public void dropAnalysisResultCache() {
analysisResultCache.invalidateAll();
}
public void dropHashHitCache() {
setHitCache.invalidateAll();
}
public void dropKeywordHitCache() {
keywordHitCache.invalidateAll();
}
/**
* Returns a search results dto containing rows of counts data.
*
@@ -378,10 +368,22 @@ public class AnalysisResultDAO extends BlackboardArtifactDAO {
*/
public TreeResultsDTO<AnalysisResultSearchParam> getAnalysisResultCounts(Long dataSourceId) throws ExecutionException {
try {
Set<BlackboardArtifact.Type> indeterminateTypes = this.treeCounts.getEnqueued().stream()
.filter(evt -> dataSourceId == null || Objects.equals(evt.getDataSourceId(), dataSourceId))
.map(evt -> evt.getArtifactType())
.collect(Collectors.toSet());
// get row dto's sorted by display name
Map<BlackboardArtifact.Type, Long> typeCounts = getCounts(BlackboardArtifact.Category.ANALYSIS_RESULT, dataSourceId);
List<TreeResultsDTO.TreeItemDTO<AnalysisResultSearchParam>> treeItemRows = typeCounts.entrySet().stream()
.map(entry -> getTreeItem(entry.getKey(), dataSourceId, TreeDisplayCount.getDeterminate(entry.getValue())))
.map(entry -> {
TreeDisplayCount displayCount = indeterminateTypes.contains(entry.getKey())
? TreeDisplayCount.INDETERMINATE
: TreeDisplayCount.getDeterminate(entry.getValue());
return getTreeItem(entry.getKey(), dataSourceId, displayCount);
})
.sorted(Comparator.comparing(countRow -> countRow.getDisplayName()))
.collect(Collectors.toList());
@@ -395,7 +397,7 @@ public class AnalysisResultDAO extends BlackboardArtifactDAO {
private TreeItemDTO<AnalysisResultSearchParam> getTreeItem(BlackboardArtifact.Type type, Long dataSourceId, TreeDisplayCount displayCount) {
return new TreeItemDTO<>(
BlackboardArtifact.Category.ANALYSIS_RESULT.name(),
AnalysisResultSearchParam.getTypeId(),
new AnalysisResultSearchParam(type, dataSourceId),
type.getTypeID(),
type.getDisplayName(),
@@ -478,16 +480,29 @@ public class AnalysisResultDAO extends BlackboardArtifactDAO {
Long dataSourceId,
String nullSetName) throws IllegalArgumentException, ExecutionException {
Set<String> indeterminateSetNames = new HashSet<>();
for (AnalysisResultEvent evt : this.treeCounts.getEnqueued()) {
if (evt instanceof AnalysisResultSetEvent
&& (dataSourceId == null || Objects.equals(evt.getDataSourceId(), dataSourceId))
&& evt.getArtifactType().equals(type)) {
indeterminateSetNames.add(((AnalysisResultSetEvent) evt).getSetName());
}
}
List<TreeItemDTO<AnalysisResultSetSearchParam>> allSets
= getSetCountsMap(type, BlackboardAttribute.Type.TSK_SET_NAME, dataSourceId).entrySet().stream()
.filter(entry -> nullSetName != null || entry.getKey() != null)
.sorted((a, b) -> compareSetStrings(a.getKey(), b.getKey()))
.map(entry -> {
TreeDisplayCount displayCount = indeterminateSetNames.contains(entry.getKey())
? TreeDisplayCount.INDETERMINATE
: TreeDisplayCount.getDeterminate(entry.getValue());
return getSetTreeItem(type,
dataSourceId,
entry.getKey(),
entry.getKey() == null ? nullSetName : entry.getKey(),
TreeDisplayCount.getDeterminate(entry.getValue()));
displayCount);
})
.collect(Collectors.toList());
@@ -498,7 +513,7 @@ public class AnalysisResultDAO extends BlackboardArtifactDAO {
Long dataSourceId, String setName, String displayName, TreeDisplayCount displayCount) {
return new TreeItemDTO<>(
type.getTypeName(),
AnalysisResultSetSearchParam.getTypeId(),
new AnalysisResultSetSearchParam(type, dataSourceId, setName),
setName == null ? 0 : setName,
displayName,
@@ -548,6 +563,18 @@ public class AnalysisResultDAO extends BlackboardArtifactDAO {
throw new IllegalArgumentException("Expected data source id to be > 0");
}
Set<Pair<String, TskData.KeywordSearchQueryType>> indeterminateSearchTerms = new HashSet<>();
for (AnalysisResultEvent evt : this.treeCounts.getEnqueued()) {
if (evt instanceof KeywordHitEvent
&& (dataSourceId == null || Objects.equals(evt.getDataSourceId(), dataSourceId))
&& evt.getArtifactType().equals(BlackboardArtifact.Type.TSK_KEYWORD_HIT)
&& Objects.equals(((KeywordHitEvent) evt).getSetName(), setName)) {
KeywordHitEvent keywordEvt = (KeywordHitEvent) evt;
indeterminateSearchTerms.add(Pair.of(keywordEvt.getSearchString(), keywordEvt.getSearchType()));
}
}
String dataSourceClause = dataSourceId == null
? ""
: "AND art.data_source_obj_id = ?\n";
@@ -619,29 +646,24 @@ public class AnalysisResultDAO extends BlackboardArtifactDAO {
long count = resultSet.getLong("count");
boolean hasChildren = resultSet.getBoolean("has_children");
String searchTermModified;
switch (searchType) {
case 0:
searchTermModified = Bundle.AnalysisResultDAO_getKeywordSearchTermCounts_exactMatch(searchTerm == null ? "" : searchTerm);
break;
case 1:
searchTermModified = Bundle.AnalysisResultDAO_getKeywordSearchTermCounts_substringMatch(searchTerm == null ? "" : searchTerm);
break;
case 2:
searchTermModified = Bundle.AnalysisResultDAO_getKeywordSearchTermCounts_regexMatch(searchTerm == null ? "" : searchTerm);
break;
default:
logger.log(Level.WARNING, MessageFormat.format("Non-standard search type value: {0}.", searchType));
searchTermModified = searchTerm;
break;
}
TskData.KeywordSearchQueryType searchTypeEnum =
Stream.of(TskData.KeywordSearchQueryType.values())
.filter(tp -> tp.getType() == searchType)
.findFirst()
.orElse(TskData.KeywordSearchQueryType.LITERAL);
String searchTermModified = getSearchTermDisplayName(searchTerm, searchTypeEnum);
TreeDisplayCount displayCount = indeterminateSearchTerms.contains(Pair.of(searchTerm, searchType))
? TreeDisplayCount.INDETERMINATE
: TreeDisplayCount.getDeterminate(count);
TreeItemDTO<KeywordSearchTermParams> treeItem = new TreeItemDTO<>(
"KEYWORD_SEARCH_TERMS",
KeywordSearchTermParams.getTypeId(),
new KeywordSearchTermParams(setName, searchTerm, TskData.KeywordSearchQueryType.valueOf(searchType), hasChildren, dataSourceId),
searchTermModified,
searchTermModified,
TreeDisplayCount.getDeterminate(count)
displayCount
);
items.add(treeItem);
@@ -658,6 +680,34 @@ public class AnalysisResultDAO extends BlackboardArtifactDAO {
}
}
/**
* Returns the UI display name for a search term.
*
* @param searchTerm The search term.
* @param searchType The search type enum value.
*
* @return The display name.
*/
public String getSearchTermDisplayName(String searchTerm, TskData.KeywordSearchQueryType searchType) {
String searchTermModified;
switch (searchType) {
case LITERAL:
searchTermModified = Bundle.AnalysisResultDAO_getKeywordSearchTermCounts_exactMatch(searchTerm == null ? "" : searchTerm);
break;
case SUBSTRING:
searchTermModified = Bundle.AnalysisResultDAO_getKeywordSearchTermCounts_substringMatch(searchTerm == null ? "" : searchTerm);
break;
case REGEX:
searchTermModified = Bundle.AnalysisResultDAO_getKeywordSearchTermCounts_regexMatch(searchTerm == null ? "" : searchTerm);
break;
default:
logger.log(Level.WARNING, MessageFormat.format("Non-standard search type value: {0}.", searchType));
searchTermModified = searchTerm;
break;
}
return searchTermModified;
}
/**
* Get counts for string matches of a particular regex/substring search
* term.
@@ -707,6 +757,23 @@ public class AnalysisResultDAO extends BlackboardArtifactDAO {
+ "AND res.search_type = ?\n"
+ "GROUP BY keyword";
Set<String> indeterminateMatches = new HashSet<>();
for (AnalysisResultEvent evt : this.treeCounts.getEnqueued()) {
if (evt instanceof KeywordHitEvent
&& (dataSourceId == null || Objects.equals(evt.getDataSourceId(), dataSourceId))
&& evt.getArtifactType().equals(BlackboardArtifact.Type.TSK_KEYWORD_HIT)) {
KeywordHitEvent keywordEvt = (KeywordHitEvent) evt;
if (Objects.equals(keywordEvt.getSetName(), setName)
&& Objects.equals(keywordEvt.getSearchString(), regexStr)
&& keywordEvt.getSearchType() == searchType) {
indeterminateMatches.add(keywordEvt.getMatch());
}
}
}
try (CaseDbPreparedStatement preparedStatement = getCase().getCaseDbAccessManager().prepareSelect(query)) {
// get artifact types and counts
int paramIdx = 0;
@@ -728,12 +795,17 @@ public class AnalysisResultDAO extends BlackboardArtifactDAO {
String keyword = resultSet.getString("keyword");
long count = resultSet.getLong("count");
TreeDisplayCount displayCount = indeterminateMatches.contains(keyword)
? TreeDisplayCount.INDETERMINATE
: TreeDisplayCount.getDeterminate(count);
items.add(new TreeItemDTO<>(
"KEYWORD_MATCH",
KeywordMatchParams.getTypeId(),
new KeywordMatchParams(setName, regexStr, keyword, searchType, dataSourceId),
keyword,
keyword == null ? "" : keyword,
TreeDisplayCount.getDeterminate(count)));
displayCount
));
}
} catch (SQLException ex) {
logger.log(Level.WARNING, "An error occurred while fetching results from result set.", ex);
@@ -755,7 +827,7 @@ public class AnalysisResultDAO extends BlackboardArtifactDAO {
}
@Override
Set<DAOEvent> processEvent(PropertyChangeEvent evt) {
Set<? extends DAOEvent> processEvent(PropertyChangeEvent evt) {
// get a grouping of artifacts mapping the artifact type id to data source id.
Map<BlackboardArtifact.Type, Set<Long>> analysisResultMap = new HashMap<>();
Map<Pair<BlackboardArtifact.Type, String>, Set<Long>> setMap = new HashMap<>();
@@ -788,16 +860,11 @@ public class AnalysisResultDAO extends BlackboardArtifactDAO {
return Collections.emptySet();
}
clearRelevantCacheEntries(analysisResultMap, setMap);
SubDAOUtils.invalidateKeys(this.analysisResultCache, ar -> Pair.of(ar.getArtifactType(), ar.getDataSourceId()), analysisResultMap);
SubDAOUtils.invalidateKeys(this.setHitCache, ar -> Pair.of(Pair.of(ar.getArtifactType(), ar.getSetName()), ar.getDataSourceId()), setMap);
List<AnalysisResultEvent> daoEvents = getResultViewEvents(analysisResultMap, setMap);
Collection<TreeEvent> treeEvents = this.treeCounts.enqueueAll(daoEvents).stream()
.map(arEvt -> getTreeEvent(arEvt, false))
.collect(Collectors.toList());
return Stream.of(daoEvents, treeEvents)
.flatMap(lst -> lst.stream())
.collect(Collectors.toSet());
// GVDTODO handle keyword hits
return getResultViewEvents(analysisResultMap, setMap);
}
/**
@@ -813,7 +880,7 @@ public class AnalysisResultDAO extends BlackboardArtifactDAO {
*
* @return The list of dao events.
*/
private List<AnalysisResultEvent> getResultViewEvents(Map<BlackboardArtifact.Type, Set<Long>> analysisResultMap, Map<Pair<BlackboardArtifact.Type, String>, Set<Long>> resultsWithSetMap) {
private Set<? extends DAOEvent> getResultViewEvents(Map<BlackboardArtifact.Type, Set<Long>> analysisResultMap, Map<Pair<BlackboardArtifact.Type, String>, Set<Long>> resultsWithSetMap) {
Stream<AnalysisResultEvent> analysisResultEvts = analysisResultMap.entrySet().stream()
.flatMap(entry -> entry.getValue().stream().map(dsId -> new AnalysisResultEvent(entry.getKey(), dsId)));
@@ -821,82 +888,50 @@ public class AnalysisResultDAO extends BlackboardArtifactDAO {
.flatMap(entry -> entry.getValue().stream().map(dsId -> new AnalysisResultSetEvent(entry.getKey().getRight(), entry.getKey().getLeft(), dsId)));
// GVDTODO handle keyword hits
return Stream.of(analysisResultEvts, analysisResultSetEvts)
List<AnalysisResultEvent> daoEvents = Stream.of(analysisResultEvts, analysisResultSetEvts)
.flatMap(s -> s)
.collect(Collectors.toList());
Collection<TreeEvent> treeEvents = this.treeCounts.enqueueAll(daoEvents).stream()
.map(arEvt -> new TreeEvent(getTreeItem(arEvt, TreeDisplayCount.INDETERMINATE), false))
.collect(Collectors.toList());
return Stream.of(daoEvents, treeEvents)
.flatMap(lst -> lst.stream())
.collect(Collectors.toSet());
}
/**
* Clears cache entries given the provided digests of autopsy events.
*
* @param analysisResultMap Contains the analysis results that do not use a
* set name. A mapping of analysis result type ids
* to data sources where the results were created.
* @param resultsWithSetMap Contains the anlaysis results that do use a set
* name. A mapping of (analysis result type id, set
* name) to data sources where results were
* created.
*/
private void clearRelevantCacheEntries(Map<BlackboardArtifact.Type, Set<Long>> analysisResultMap, Map<Pair<BlackboardArtifact.Type, String>, Set<Long>> resultsWithSetMap) {
ConcurrentMap<SearchParams<BlackboardArtifactSearchParam>, AnalysisResultTableSearchResultsDTO> arConcurrentMap = this.analysisResultCache.asMap();
arConcurrentMap.forEach((k, v) -> {
BlackboardArtifactSearchParam searchParam = k.getParamData();
Set<Long> dsIds = analysisResultMap.get(searchParam.getArtifactType());
if (dsIds != null && (searchParam.getDataSourceId() == null || dsIds.contains(searchParam.getDataSourceId()))) {
arConcurrentMap.remove(k);
}
});
ConcurrentMap<SearchParams<AnalysisResultSetSearchParam>, AnalysisResultTableSearchResultsDTO> setConcurrentMap = this.setHitCache.asMap();
setConcurrentMap.forEach((k, v) -> {
AnalysisResultSetSearchParam searchParam = k.getParamData();
Set<Long> dsIds = resultsWithSetMap.get(Pair.of(searchParam.getArtifactType(), searchParam.getSetName()));
if (dsIds != null && (searchParam.getDataSourceId() == null || dsIds.contains(searchParam.getDataSourceId()))) {
arConcurrentMap.remove(k);
}
});
// GVDTODO handle clearing cache for keyword search hits
// private final Cache<SearchParams<KeywordHitSearchParam>, AnalysisResultTableSearchResultsDTO> keywordHitCache = CacheBuilder.newBuilder().maximumSize(1000).build();
}
/**
* Creates a TreeEvent instance based on the analysis result event and
* Creates a TreeItemDTO instance based on the analysis result event and
* whether or not this event should trigger a full refresh of counts.
*
* @param arEvt The analysis result event.
* @param shouldRefresh Whether or not this tree event should trigger a full
* refresh of counts.
* @param arEvt The analysis result event.
* @param displayCount The count to display.
*
* @return The tree event.
*/
private TreeEvent getTreeEvent(AnalysisResultEvent arEvt, boolean shouldRefresh) {
private TreeItemDTO<?> getTreeItem(AnalysisResultEvent arEvt, TreeDisplayCount displayCount) {
// GVDTODO handle keyword items when integrated
if (arEvt instanceof AnalysisResultSetEvent) {
AnalysisResultSetEvent setEvt = (AnalysisResultSetEvent) arEvt;
return new TreeEvent(getSetTreeItem(setEvt.getArtifactType(), setEvt.getDataSourceId(),
return getSetTreeItem(setEvt.getArtifactType(), setEvt.getDataSourceId(),
setEvt.getSetName(), setEvt.getSetName() == null ? "" : setEvt.getSetName(),
shouldRefresh ? TreeDisplayCount.UNSPECIFIED : TreeDisplayCount.INDETERMINATE),
shouldRefresh);
displayCount);
} else {
return new TreeEvent(getTreeItem(arEvt.getArtifactType(), arEvt.getDataSourceId(),
shouldRefresh ? TreeDisplayCount.UNSPECIFIED : TreeDisplayCount.INDETERMINATE),
shouldRefresh);
return getTreeItem(arEvt.getArtifactType(), arEvt.getDataSourceId(), displayCount);
}
}
@Override
Set<DAOEvent> handleIngestComplete() {
return this.treeCounts.flushEvents().stream()
.map(arEvt -> getTreeEvent(arEvt, true))
.collect(Collectors.toSet());
Set<? extends DAOEvent> handleIngestComplete() {
return SubDAOUtils.getIngestCompleteEvents(this.treeCounts, (arEvt, count) -> getTreeItem(arEvt, count));
}
@Override
Set<TreeEvent> shouldRefreshTree() {
return this.treeCounts.getEventTimeouts().stream()
.map(arEvt -> getTreeEvent(arEvt, true))
.collect(Collectors.toSet());
return SubDAOUtils.getRefreshEvents(this.treeCounts, (arEvt, count) -> getTreeItem(arEvt, count));
}
/**
@@ -24,8 +24,17 @@ import org.sleuthkit.datamodel.BlackboardArtifact;
* Key for analysis result in order to retrieve data from DAO.
*/
public class AnalysisResultSearchParam extends BlackboardArtifactSearchParam {
private static final String TYPE_ID = BlackboardArtifact.Category.ANALYSIS_RESULT.name();
/**
* @return The type id for this search parameter.
*/
public static String getTypeId() {
return TYPE_ID;
}
public AnalysisResultSearchParam(BlackboardArtifact.Type artifactType, Long dataSourceId) {
super(artifactType, dataSourceId);
}
}
}
@@ -25,14 +25,23 @@ import org.sleuthkit.datamodel.BlackboardArtifact;
* Base class for search params for analysis results that filter by set name.
*/
public class AnalysisResultSetSearchParam extends AnalysisResultSearchParam {
private static final String TYPE_ID = "ANALYSIS_RESULT_SET";
/**
* @return The type id for this search parameter.
*/
public static String getTypeId() {
return TYPE_ID;
}
private final String setName;
public AnalysisResultSetSearchParam(BlackboardArtifact.Type artifactType, Long dataSourceId, String setName) {
super(artifactType, dataSourceId);
this.setName = setName;
}
public String getSetName() {
return setName;
}
@@ -63,5 +72,4 @@ public class AnalysisResultSetSearchParam extends AnalysisResultSearchParam {
return super.equals(obj);
}
}
@@ -25,6 +25,16 @@ import org.sleuthkit.datamodel.BlackboardArtifact;
* Key for data artifact in order to retrieve data from DAO.
*/
public class BlackboardArtifactSearchParam {
private static final String TYPE_ID = "BLACKBOARD_ARTIFACT";
/**
* @return The type id for this search parameter.
*/
public static String getTypeId() {
return TYPE_ID;
}
private final BlackboardArtifact.Type artifactType;
private final Long dataSourceId;
@@ -160,7 +160,7 @@ public class CommAccountsDAO extends AbstractDAO {
private static TreeResultsDTO.TreeItemDTO<CommAccountsSearchParams> createAccountTreeItem(Account.Type accountType, Long dataSourceId, TreeResultsDTO.TreeDisplayCount count) {
return new TreeResultsDTO.TreeItemDTO<>(
"ACCOUNTS",
CommAccountsSearchParams.getTypeId(),
new CommAccountsSearchParams(accountType, dataSourceId),
accountType.getTypeName(),
accountType.getDisplayName(),
@@ -236,7 +236,7 @@ public class CommAccountsDAO extends AbstractDAO {
Set<? extends DAOEvent> handleIngestComplete() {
return SubDAOUtils.getIngestCompleteEvents(
this.accountCounts,
(daoEvt) -> createAccountTreeItem(daoEvt.getAccountType(), daoEvt.getDataSourceId(), TreeResultsDTO.TreeDisplayCount.UNSPECIFIED)
(daoEvt, count) -> createAccountTreeItem(daoEvt.getAccountType(), daoEvt.getDataSourceId(), count)
);
}
@@ -244,7 +244,7 @@ public class CommAccountsDAO extends AbstractDAO {
Set<TreeEvent> shouldRefreshTree() {
return SubDAOUtils.getRefreshEvents(
this.accountCounts,
(daoEvt) -> createAccountTreeItem(daoEvt.getAccountType(), daoEvt.getDataSourceId(), TreeResultsDTO.TreeDisplayCount.UNSPECIFIED)
(daoEvt, count) -> createAccountTreeItem(daoEvt.getAccountType(), daoEvt.getDataSourceId(), count)
);
}
@@ -27,9 +27,18 @@ import org.sleuthkit.datamodel.BlackboardArtifact;
*/
public class CommAccountsSearchParams extends DataArtifactSearchParam {
private static final String TYPE_ID = "DATA_ARTIFACT_ACCOUNT";
/**
* @return The type id for this search parameter.
*/
public static String getTypeId() {
return TYPE_ID;
}
private final Account.Type type;
private final Long dataSourceId;
public CommAccountsSearchParams(Account.Type type, Long dataSourceId) {
super(BlackboardArtifact.Type.TSK_ACCOUNT, dataSourceId);
this.type = type;
@@ -245,7 +245,7 @@ public class DataArtifactDAO extends BlackboardArtifactDAO {
private TreeItemDTO<DataArtifactSearchParam> createDataArtifactTreeItem(BlackboardArtifact.Type artifactType, Long dataSourceId, TreeDisplayCount displayCount) {
return new TreeResultsDTO.TreeItemDTO<>(
BlackboardArtifact.Category.DATA_ARTIFACT.name(),
DataArtifactSearchParam.getTypeId(),
new DataArtifactSearchParam(artifactType, dataSourceId),
artifactType.getTypeID(),
getDisplayName(artifactType),
@@ -255,13 +255,13 @@ public class DataArtifactDAO extends BlackboardArtifactDAO {
@Override
Set<? extends DAOEvent> handleIngestComplete() {
return SubDAOUtils.getIngestCompleteEvents(this.treeCounts,
(daoEvt) -> createDataArtifactTreeItem(daoEvt.getArtifactType(), daoEvt.getDataSourceId(), TreeDisplayCount.UNSPECIFIED));
(daoEvt, count) -> createDataArtifactTreeItem(daoEvt.getArtifactType(), daoEvt.getDataSourceId(), count));
}
@Override
Set<TreeEvent> shouldRefreshTree() {
return SubDAOUtils.getRefreshEvents(this.treeCounts,
(daoEvt) -> createDataArtifactTreeItem(daoEvt.getArtifactType(), daoEvt.getDataSourceId(), TreeDisplayCount.UNSPECIFIED));
(daoEvt, count) -> createDataArtifactTreeItem(daoEvt.getArtifactType(), daoEvt.getDataSourceId(), count));
}
@@ -25,7 +25,16 @@ import org.sleuthkit.datamodel.BlackboardArtifact;
*/
public class DataArtifactSearchParam extends BlackboardArtifactSearchParam {
private static final String TYPE_ID = BlackboardArtifact.Category.DATA_ARTIFACT.name();
/**
* @return The type id for this search parameter.
*/
public static String getTypeId() {
return TYPE_ID;
}
public DataArtifactSearchParam(BlackboardArtifact.Type artifactType, Long dataSourceId) {
super (artifactType, dataSourceId);
}
super(artifactType, dataSourceId);
}
}
@@ -24,7 +24,17 @@ import org.sleuthkit.autopsy.mainui.nodes.NodeSelectionInfo.ContentNodeSelection
/**
* Key for content object in order to retrieve data from DAO.
*/
public class FileSystemContentSearchParam implements ContentNodeSelectionInfo{
public class FileSystemContentSearchParam implements ContentNodeSelectionInfo {
private static final String TYPE_ID = "FILE_SYSTEM_CONTENT";
/**
* @return The type id for this search parameter.
*/
public static String getTypeId() {
return TYPE_ID;
}
private final Long contentObjectId;
// This param is can change, is not used as part of the search query and
@@ -23,11 +23,10 @@ import com.google.common.cache.CacheBuilder;
import com.google.common.collect.ImmutableSet;
import java.beans.PropertyChangeEvent;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ConcurrentMap;
@@ -58,11 +57,14 @@ import org.sleuthkit.autopsy.mainui.datamodel.FileRowDTO.LayoutFileRowDTO;
import org.sleuthkit.autopsy.mainui.datamodel.FileRowDTO.SlackFileRowDTO;
import org.sleuthkit.autopsy.mainui.datamodel.ContentRowDTO.PoolRowDTO;
import org.sleuthkit.autopsy.mainui.datamodel.TreeResultsDTO.TreeDisplayCount;
import org.sleuthkit.autopsy.mainui.datamodel.TreeResultsDTO.TreeItemDTO;
import org.sleuthkit.autopsy.mainui.datamodel.events.FileSystemContentEvent;
import org.sleuthkit.autopsy.mainui.datamodel.events.FileSystemHostEvent;
import org.sleuthkit.autopsy.mainui.datamodel.events.FileSystemPersonEvent;
import org.sleuthkit.autopsy.mainui.datamodel.events.TreeCounts;
import org.sleuthkit.autopsy.mainui.datamodel.events.TreeEvent;
import org.sleuthkit.autopsy.mainui.nodes.DAOFetcher;
import org.sleuthkit.datamodel.AbstractContent;
import org.sleuthkit.datamodel.AbstractFile;
import org.sleuthkit.datamodel.Content;
import org.sleuthkit.datamodel.DataSource;
@@ -110,8 +112,11 @@ public class FileSystemDAO extends AbstractDAO {
Case.Events.HOSTS_REMOVED_FROM_PERSON.toString()
);
private final Cache<SearchParams<?>, BaseSearchResultsDTO> searchParamsCache = CacheBuilder.newBuilder().maximumSize(CACHE_SIZE).expireAfterAccess(CACHE_DURATION, CACHE_DURATION_UNITS).build();
private final Cache<SearchParams<?>, BaseSearchResultsDTO> searchParamsCache
= CacheBuilder.newBuilder().maximumSize(CACHE_SIZE).expireAfterAccess(CACHE_DURATION, CACHE_DURATION_UNITS).build();
private final TreeCounts<DAOEvent> treeCounts = new TreeCounts<>();
private static final String FILE_SYSTEM_TYPE_ID = "FILE_SYSTEM";
private static FileSystemDAO instance = null;
@@ -130,7 +135,7 @@ public class FileSystemDAO extends AbstractDAO {
FileSystemContentEvent contentEvt = (FileSystemContentEvent) daoEvent;
return contentEvt.getContentObjectId() == null || key.getContentObjectId().equals(contentEvt.getContentObjectId());
return contentEvt.getContentObjectId() == null || Objects.equals(key.getContentObjectId(), contentEvt.getContentObjectId());
}
private boolean isSystemHostInvalidating(FileSystemHostSearchParam key, DAOEvent daoEvent) {
@@ -315,19 +320,13 @@ public class FileSystemDAO extends AbstractDAO {
return searchParamsCache.get(searchParams, () -> fetchHostsForTable(searchParams));
}
@Override
void clearCaches() {
this.searchParamsCache.invalidateAll();
}
private Long getHostFromDs(Content dataSource) {
private Host getHostFromDs(Content dataSource) {
if (!(dataSource instanceof DataSource)) {
return null;
}
try {
Host host = ((DataSource) dataSource).getHost();
return host == null ? null : host.getHostId();
return ((DataSource) dataSource).getHost();
} catch (TskCoreException ex) {
logger.log(Level.WARNING, "There was an error getting the host for data source with id: " + dataSource.getId(), ex);
return null;
@@ -358,24 +357,16 @@ public class FileSystemDAO extends AbstractDAO {
return false;
}
@Override
Set<DAOEvent> handleIngestComplete() {
// GVDTODO
return Collections.emptySet();
}
@Override
Set<TreeEvent> shouldRefreshTree() {
// GVDTODO
return Collections.emptySet();
}
@Override
Set<DAOEvent> processEvent(PropertyChangeEvent evt) {
// GVDTODO these can probably be rewritten now that it isn't handling a collection of autopsy events
Set<Long> affectedPersons = new HashSet<>();
Set<Long> affectedHosts = new HashSet<>();
Set<Long> affectedParentContent = new HashSet<>();
Content affectedContent = null;
Content affectedParentContent = null;
Host affectedParentHost = null;
// GVDTODO person parents and parent of persons not handled yet
// optional present but null indicates no person parent
Optional<Person> affectedParentPerson = Optional.empty();
boolean refreshAllContent = false;
Content content = DAOEventUtils.getDerivedFileContentFromFileEvent(evt);
@@ -388,132 +379,158 @@ public class FileSystemDAO extends AbstractDAO {
return Collections.emptySet();
}
if (parentContent == null) {
return Collections.emptySet();
}
if (invalidatesAllFileSystem(parentContent)) {
refreshAllContent = true;
} else {
affectedParentContent.add(parentContent.getId());
affectedContent = content;
affectedParentContent = parentContent;
}
} else if (evt instanceof DataSourceAddedEvent) {
Long hostId = getHostFromDs(((DataSourceAddedEvent) evt).getDataSource());
if (hostId != null) {
affectedHosts.add(hostId);
}
Host host = getHostFromDs(((DataSourceAddedEvent) evt).getDataSource());
affectedParentHost = host;
} else if (evt instanceof DataSourceNameChangedEvent) {
Long hostId = getHostFromDs(((DataSourceNameChangedEvent) evt).getDataSource());
if (hostId != null) {
affectedHosts.add(hostId);
}
Host host = getHostFromDs(((DataSourceNameChangedEvent) evt).getDataSource());
affectedParentHost = host;
} else if (evt instanceof HostsAddedEvent) {
// GVDTODO how best to handle host added?
} else if (evt instanceof HostsUpdatedEvent) {
// GVDTODO how best to handle host updated?
} else if (evt instanceof HostsAddedToPersonEvent) {
Person person = ((HostsAddedToPersonEvent) evt).getPerson();
affectedPersons.add(person == null ? null : person.getPersonId());
affectedParentPerson = Optional.of(person);
} else if (evt instanceof HostsRemovedFromPersonEvent) {
Person person = ((HostsRemovedFromPersonEvent) evt).getPerson();
affectedPersons.add(person == null ? null : person.getPersonId());
affectedParentPerson = Optional.of(person);
}
final boolean triggerFullRefresh = refreshAllContent;
// if nothing affected, return no events
if (!refreshAllContent && affectedContent == null && affectedParentHost == null && !affectedParentPerson.isPresent()) {
return Collections.emptySet();
}
// GVDTODO handling null ids versus the 'No Persons' option
ConcurrentMap<SearchParams<?>, BaseSearchResultsDTO> concurrentMap = this.searchParamsCache.asMap();
concurrentMap.forEach((k, v) -> {
Object searchParams = k.getParamData();
if (searchParams instanceof FileSystemPersonSearchParam) {
FileSystemPersonSearchParam personParam = (FileSystemPersonSearchParam) searchParams;
if (affectedPersons.contains(personParam.getPersonObjectId())) {
concurrentMap.remove(k);
}
} else if (searchParams instanceof FileSystemHostSearchParam) {
FileSystemHostSearchParam hostParams = (FileSystemHostSearchParam) searchParams;
if (affectedHosts.contains(hostParams.getHostObjectId())) {
concurrentMap.remove(k);
}
} else if (searchParams instanceof FileSystemContentSearchParam) {
FileSystemContentSearchParam contentParams = (FileSystemContentSearchParam) searchParams;
if (triggerFullRefresh
|| contentParams.getContentObjectId() == null
|| affectedParentContent.contains(contentParams.getContentObjectId())) {
concurrentMap.remove(k);
}
invalidateKeys(affectedParentPerson, affectedParentHost, affectedContent, refreshAllContent);
return getDAOEvents(affectedParentPerson, affectedParentHost, affectedContent, affectedParentContent, refreshAllContent);
}
private Set<DAOEvent> getDAOEvents(Optional<Person> affectedPerson, Host affectedHost, Content affectedContent, Content affectedParentContent, boolean triggerFullRefresh) {
List<DAOEvent> daoEvents = new ArrayList<>();
if (triggerFullRefresh) {
daoEvents.add(new FileSystemContentEvent(null, null, null));
} else if (affectedContent != null) {
Host parentHost = null;
try {
parentHost = (affectedContent instanceof DataSource)
? ((DataSource) affectedContent).getHost()
: null;
} catch (TskCoreException ex) {
logger.log(Level.WARNING, "An error occurred while fetching content id and host id for content with id of: " + affectedContent.getId(), ex);
}
daoEvents.add(new FileSystemContentEvent(affectedContent, affectedParentContent == null ? null : affectedParentContent.getId(), parentHost));
}
if (affectedHost != null) {
daoEvents.add(new FileSystemHostEvent(affectedHost.getHostId()));
}
affectedPerson.ifPresent((person) -> {
daoEvents.add(new FileSystemPersonEvent(person == null ? null : person.getPersonId()));
});
Stream<DAOEvent> fileEvts = triggerFullRefresh
? Stream.of(new FileSystemContentEvent(null))
: affectedParentContent.stream().map(id -> new FileSystemContentEvent(id));
List<TreeEvent> treeEvents = this.treeCounts.enqueueAll(daoEvents).stream()
.map(daoEvt -> createTreeEvent(daoEvt, TreeDisplayCount.INDETERMINATE, false))
.filter(evt -> evt != null)
.collect(Collectors.toList());
return Stream.of(
affectedPersons.stream().map(id -> new FileSystemPersonEvent(id)),
affectedHosts.stream().map(id -> new FileSystemHostEvent(id)),
fileEvts
)
.flatMap(s -> s)
return Stream.of(daoEvents, treeEvents)
.flatMap(lst -> lst.stream())
.collect(Collectors.toSet());
}
private void invalidateKeys(Optional<Person> affectedPerson, Host affectedHost, Content affectedContent, boolean triggerFullRefresh) {
ConcurrentMap<SearchParams<?>, ?> concurrentMap = this.searchParamsCache.asMap();
concurrentMap.forEach((k, v) -> {
Object searchParams = k.getParamData();
boolean shouldInvalidate = false;
if (searchParams instanceof FileSystemPersonSearchParam && affectedPerson.isPresent()) {
shouldInvalidate = Objects.equals(
((FileSystemPersonSearchParam) searchParams).getPersonObjectId(),
// to allow for null parent person
affectedPerson.flatMap(p -> Optional.ofNullable(p.getPersonId())).orElse(null)
);
} else if (searchParams instanceof FileSystemHostSearchParam && affectedHost != null) {
shouldInvalidate = Objects.equals(
((FileSystemHostSearchParam) searchParams).getHostObjectId(),
affectedHost.getHostId()
);
} else if (searchParams instanceof FileSystemContentSearchParam) {
if (triggerFullRefresh) {
shouldInvalidate = true;
} else if (affectedContent != null) {
shouldInvalidate = Objects.equals(
((FileSystemContentSearchParam) searchParams).getContentObjectId(),
affectedContent.getId()
);
}
}
if (shouldInvalidate) {
concurrentMap.remove(k);
}
});
}
/**
* Get all data sources belonging to a given host.
*
*
* @param host The host.
*
*
* @return Results containing all data sources for the given host.
*
* @throws ExecutionException
*
* @throws ExecutionException
*/
public TreeResultsDTO<FileSystemContentSearchParam> getDataSourcesForHost(Host host) throws ExecutionException {
try {
List<TreeResultsDTO.TreeItemDTO<FileSystemContentSearchParam>> treeItemRows = new ArrayList<>();
for (DataSource ds : Case.getCurrentCaseThrows().getSleuthkitCase().getHostManager().getDataSourcesForHost(host)) {
treeItemRows.add(new TreeResultsDTO.TreeItemDTO<>(
ds.getClass().getSimpleName(),
new FileSystemContentSearchParam(ds.getId()),
ds,
ds.getName(),
null
));
treeItemRows.add(createDisplayableContentTreeItem(ds, TreeDisplayCount.NOT_SHOWN));
}
return new TreeResultsDTO<>(treeItemRows);
} catch (NoCurrentCaseException | TskCoreException ex) {
throw new ExecutionException("An error occurred while fetching images for host with ID " + host.getHostId(), ex);
}
}
/**
* Create results for a single given data source ID (not its children).
*
*
* @param dataSourceObjId The data source object ID.
*
*
* @return Results containing just this data source.
*
* @throws ExecutionException
*
* @throws ExecutionException
*/
public TreeResultsDTO<FileSystemContentSearchParam> getSingleDataSource(long dataSourceObjId) throws ExecutionException {
try {
List<TreeResultsDTO.TreeItemDTO<FileSystemContentSearchParam>> treeItemRows = new ArrayList<>();
DataSource ds = Case.getCurrentCaseThrows().getSleuthkitCase().getDataSource(dataSourceObjId);
treeItemRows.add(new TreeResultsDTO.TreeItemDTO<>(
ds.getClass().getSimpleName(),
new FileSystemContentSearchParam(ds.getId()),
ds,
ds.getName(),
null
));
treeItemRows.add(createDisplayableContentTreeItem(ds, TreeDisplayCount.NOT_SHOWN));
return new TreeResultsDTO<>(treeItemRows);
} catch (NoCurrentCaseException | TskCoreException | TskDataException ex) {
throw new ExecutionException("An error occurred while fetching data source with ID " + dataSourceObjId, ex);
}
}
/**
* Get the children that will be displayed in the tree for a given content ID.
* Get the children that will be displayed in the tree for a given content
* ID.
*
* @param contentId Object ID of parent content.
*
@@ -523,23 +540,18 @@ public class FileSystemDAO extends AbstractDAO {
*/
public TreeResultsDTO<FileSystemContentSearchParam> getDisplayableContentChildren(Long contentId) throws ExecutionException {
try {
List<Content> treeChildren = FileSystemColumnUtils.getVisibleTreeNodeChildren(contentId);
List<TreeResultsDTO.TreeItemDTO<FileSystemContentSearchParam>> treeItemRows = new ArrayList<>();
for (Content child : treeChildren) {
Long countForNode = null;
if ((child instanceof AbstractFile)
&& ! (child instanceof LocalFilesDataSource)) {
&& !(child instanceof LocalFilesDataSource)) {
countForNode = getContentForTable(new FileSystemContentSearchParam(child.getId()), 0, null).getTotalResultsCount();
}
treeItemRows.add(new TreeResultsDTO.TreeItemDTO<>(
child.getClass().getSimpleName(),
new FileSystemContentSearchParam(child.getId()),
child,
getNameForContent(child),
countForNode == null ? TreeDisplayCount.NOT_SHOWN : TreeDisplayCount.getDeterminate(countForNode)
));
TreeDisplayCount displayCount = countForNode == null ? TreeDisplayCount.NOT_SHOWN : TreeDisplayCount.getDeterminate(countForNode);
treeItemRows.add(createDisplayableContentTreeItem(child, displayCount));
}
return new TreeResultsDTO<>(treeItemRows);
@@ -547,12 +559,23 @@ public class FileSystemDAO extends AbstractDAO {
throw new ExecutionException("An error occurred while fetching data artifact counts.", ex);
}
}
private FileSystemTreeItem createDisplayableContentTreeItem(Content child, TreeDisplayCount displayCount) {
return new FileSystemTreeItem(
FileSystemContentSearchParam.getTypeId(),
new FileSystemContentSearchParam(child == null ? null : child.getId()),
child,
child == null ? null : getNameForContent(child),
child instanceof AbstractFile ? ((AbstractFile) child).getMetaType() : null,
displayCount
);
}
/**
* Get display name for the given content.
*
*
* @param content The content.
*
*
* @return Display name for the content.
*/
private String getNameForContent(Content content) {
@@ -562,6 +585,132 @@ public class FileSystemDAO extends AbstractDAO {
return content.getName();
}
private TreeEvent createTreeEvent(DAOEvent daoEvent, TreeDisplayCount count, boolean fullRefresh) {
if (daoEvent instanceof FileSystemContentEvent) {
FileSystemContentEvent contentEvt = (FileSystemContentEvent) daoEvent;
return new FileSystemTreeEvent(
contentEvt.getParentObjId(),
contentEvt.getParentHost(),
createDisplayableContentTreeItem(contentEvt.getContent(), count),
fullRefresh);
} else if (daoEvent instanceof FileSystemHostEvent) {
// GVDTODO not currently integrated into tree
} else if (daoEvent instanceof FileSystemPersonEvent) {
// GVDTODO not currently integrated into tree
}
return null;
}
@Override
void clearCaches() {
this.searchParamsCache.invalidateAll();
handleIngestComplete();
}
@Override
Set<? extends DAOEvent> handleIngestComplete() {
return treeCounts.flushEvents().stream()
.map(daoEvt -> createTreeEvent(daoEvt, TreeDisplayCount.UNSPECIFIED, true))
.filter(evt -> evt != null)
.collect(Collectors.toSet());
}
@Override
Set<TreeEvent> shouldRefreshTree() {
return treeCounts.getEventTimeouts().stream()
.map(daoEvt -> createTreeEvent(daoEvt, TreeDisplayCount.UNSPECIFIED, true))
.filter(evt -> evt != null)
.collect(Collectors.toSet());
}
public static class DataSourceRefreshTreeEvent extends TreeEvent {
public DataSourceRefreshTreeEvent(boolean refresh) {
super(null, refresh);
}
}
public static class FileSystemTreeItem extends TreeItemDTO<FileSystemContentSearchParam> {
private final TskData.TSK_FS_META_TYPE_ENUM metaType;
FileSystemTreeItem(
String typeId,
FileSystemContentSearchParam searchParams,
Object id,
String displayName,
TskData.TSK_FS_META_TYPE_ENUM metaType,
TreeDisplayCount count) {
super(typeId, searchParams, id, displayName, count);
this.metaType = metaType;
}
public TskData.TSK_FS_META_TYPE_ENUM getMetaType() {
return metaType;
}
}
public static class FileSystemTreeEvent extends TreeEvent {
private final Long parentContentId;
private final Host parentHost;
private final FileSystemTreeItem itemRecord;
FileSystemTreeEvent(Long parentContentId, Host parentHost, FileSystemTreeItem itemRecord, boolean refreshRequired) {
super(itemRecord, refreshRequired);
this.parentContentId = parentContentId;
this.parentHost = parentHost;
this.itemRecord = itemRecord;
}
public Long getParentContentId() {
return parentContentId;
}
public Host getParentHost() {
return parentHost;
}
@Override
public FileSystemTreeItem getItemRecord() {
// override to be typed and contain extra information
return itemRecord;
}
@Override
public int hashCode() {
int hash = 5;
hash = 31 * hash + Objects.hashCode(this.itemRecord);
return hash;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final FileSystemTreeEvent other = (FileSystemTreeEvent) obj;
if (!Objects.equals(this.itemRecord, other.itemRecord)) {
return false;
}
return true;
}
}
/**
* Handles fetching and paging of data for file types by mime type.
*/
@@ -24,6 +24,16 @@ import java.util.Objects;
* Key for content object in order to retrieve data from DAO.
*/
public class FileSystemHostSearchParam {
private static final String TYPE_ID = "FILE_SYSTEM_HOST";
/**
* @return The type id for this search parameter.
*/
public static String getTypeId() {
return TYPE_ID;
}
private final Long hostObjectId;
public FileSystemHostSearchParam(Long hostObjectId) {
@@ -59,4 +69,3 @@ public class FileSystemHostSearchParam {
return true;
}
}
@@ -24,12 +24,23 @@ import java.util.Objects;
* Key for person object in order to retrieve data from DAO.
*/
public class FileSystemPersonSearchParam {
private static final String TYPE_ID = "FILE_SYSTEM_PERSON";
/**
* @return The type id for this search parameter.
*/
public static String getTypeId() {
return TYPE_ID;
}
private final Long personObjectId;
/**
* Create search param.
*
* @param personObjectId May be null to fetch hosts not associated with a Person
*
* @param personObjectId May be null to fetch hosts not associated with a
* Person
*/
public FileSystemPersonSearchParam(Long personObjectId) {
this.personObjectId = personObjectId;
@@ -25,6 +25,15 @@ import java.util.Objects;
*/
public class FileTypeExtensionsSearchParams {
private static final String TYPE_ID = "FILE_VIEWS_EXTENSION";
/**
* @return The type id for this search parameter.
*/
public static String getTypeId() {
return TYPE_ID;
}
private final FileExtSearchFilter filter;
private final Long dataSourceId;
@@ -25,9 +25,18 @@ import java.util.Objects;
*/
public class FileTypeMimeSearchParams {
private static final String TYPE_ID = "FILE_VIEWS_MIME";
/**
* @return The type id for this search parameter.
*/
public static String getTypeId() {
return TYPE_ID;
}
private final String mimeType;
private final Long dataSourceId;
public FileTypeMimeSearchParams(String mimeType, Long dataSourceId) {
this.mimeType = mimeType;
this.dataSourceId = dataSourceId;
@@ -70,5 +79,4 @@ public class FileTypeMimeSearchParams {
return true;
}
}
@@ -25,6 +25,14 @@ import java.util.Objects;
*/
public class FileTypeSizeSearchParams {
private static final String TYPE_ID = "FILE_VIEWS_SIZE";
/**
* @return The type id for this search parameter.
*/
public static String getTypeId() {
return TYPE_ID;
}
private final FileSizeFilter sizeFilter;
private final Long dataSourceId;
@@ -24,7 +24,16 @@ import org.sleuthkit.datamodel.BlackboardArtifact;
* Key for keyword hits in order to retrieve data from DAO.
*/
public class HashHitSearchParam extends AnalysisResultSetSearchParam {
private static final String TYPE_ID = "HASH_HIT";
/**
* @return The type id for this search parameter.
*/
public static String getTypeId() {
return TYPE_ID;
}
public HashHitSearchParam(Long dataSourceId, String setName) {
super(BlackboardArtifact.Type.TSK_HASHSET_HIT, dataSourceId, setName);
}
@@ -27,6 +27,15 @@ import org.sleuthkit.datamodel.TskData;
*/
public class KeywordHitSearchParam extends AnalysisResultSetSearchParam {
private static final String TYPE_ID = "KEYWORD_HIT";
/**
* @return The type id for this search parameter.
*/
public static String getTypeId() {
return TYPE_ID;
}
private final String keyword;
private final String regex;
private final TskData.KeywordSearchQueryType searchType;
@@ -37,11 +46,11 @@ public class KeywordHitSearchParam extends AnalysisResultSetSearchParam {
this.regex = regex;
this.searchType = searchType;
}
public String getRegex() {
return regex;
}
public String getKeyword() {
return keyword;
}
@@ -83,6 +92,5 @@ public class KeywordHitSearchParam extends AnalysisResultSetSearchParam {
}
return super.equals(obj);
}
}
@@ -25,6 +25,15 @@ import org.sleuthkit.datamodel.TskData;
*/
public class KeywordMatchParams {
private static final String TYPE_ID = "KEYWORD_MATCH";
/**
* @return The type id for this search parameter.
*/
public static String getTypeId() {
return TYPE_ID;
}
private final String setName;
private final String searchTerm;
private final String keywordMatch;
@@ -25,6 +25,15 @@ import org.sleuthkit.datamodel.TskData;
*/
public class KeywordSearchTermParams {
private static final String TYPE_ID = "KEYWORD_SEARCH_TERMS";
/**
* @return The type id for this search parameter.
*/
public static String getTypeId() {
return TYPE_ID;
}
private final String setName;
private final String searchTerm;
private final boolean hasChildren;
@@ -68,7 +68,8 @@ public class MainDAO extends AbstractDAO {
Case.Events.OS_ACCOUNTS_ADDED.toString(),
Case.Events.OS_ACCOUNTS_UPDATED.toString(),
Case.Events.OS_ACCOUNTS_DELETED.toString(),
Case.Events.OS_ACCT_INSTANCES_ADDED.toString()
Case.Events.OS_ACCT_INSTANCES_ADDED.toString(),
Case.Events.DATA_SOURCE_ADDED.toString()
);
private static final long WATCH_RESOLUTION_MILLIS = 30 * 1000;
@@ -259,6 +260,7 @@ public class MainDAO extends AbstractDAO {
return allDAOs.stream()
.map(subDAO -> subDAO.processEvent(evt))
.flatMap(evts -> evts == null ? Stream.empty() : evts.stream())
.filter(e -> e != null)
.collect(Collectors.toSet());
}
@@ -267,6 +269,7 @@ public class MainDAO extends AbstractDAO {
return allDAOs.stream()
.map((subDAO) -> subDAO.shouldRefreshTree())
.flatMap(evts -> evts == null ? Stream.empty() : evts.stream())
.filter(e -> e != null)
.collect(Collectors.toSet());
}
@@ -280,6 +283,7 @@ public class MainDAO extends AbstractDAO {
return daoStreamEvts.stream()
.flatMap(evts -> evts == null ? Stream.empty() : evts.stream())
.filter(evt -> evt != null)
.collect(Collectors.toSet());
}
@@ -24,7 +24,6 @@ import com.google.common.cache.CacheBuilder;
import java.beans.PropertyChangeEvent;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
@@ -187,13 +186,11 @@ public class OsAccountsDAO extends AbstractDAO {
@Override
Set<DAOEvent> handleIngestComplete() {
// GVDTODO
return Collections.emptySet();
}
@Override
Set<TreeEvent> shouldRefreshTree() {
// GVDTODO
return Collections.emptySet();
}
@@ -202,9 +199,9 @@ public class OsAccountsDAO extends AbstractDAO {
if (!OS_EVENTS.contains(evt.getPropertyName())) {
return Collections.emptySet();
}
this.searchParamsCache.invalidateAll();
this.searchParamsCache.invalidateAll();
return Collections.singleton(new OsAccountEvent());
}
@@ -25,8 +25,17 @@ import java.util.Objects;
*/
public class OsAccountsSearchParams {
private static final String TYPE_ID = "OS_ACCOUNTS";
/**
* @return The type id for this search parameter.
*/
public static String getTypeId() {
return TYPE_ID;
}
private final Long dataSourceId;
public OsAccountsSearchParams(Long dataSourceId) {
this.dataSourceId = dataSourceId;
}
@@ -19,14 +19,16 @@
package org.sleuthkit.autopsy.mainui.datamodel;
import com.google.common.cache.Cache;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentMap;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import org.apache.commons.lang3.tuple.Pair;
import org.sleuthkit.autopsy.mainui.datamodel.TreeResultsDTO.TreeDisplayCount;
import org.sleuthkit.autopsy.mainui.datamodel.TreeResultsDTO.TreeItemDTO;
import org.sleuthkit.autopsy.mainui.datamodel.events.TreeCounts;
import org.sleuthkit.autopsy.mainui.datamodel.events.TreeEvent;
@@ -46,32 +48,28 @@ public class SubDAOUtils {
* no data source filtering).
* @param itemDataSourceMapping The event digest.
*/
static <T, K> void invalidateKeys(Cache<SearchParams<K>, ?> cache, Function<K, Pair<T, Long>> getKeys, Map<T, Set<Long>> itemDataSourceMapping) {
invalidateKeys(cache, getKeys, Collections.singletonList(itemDataSourceMapping));
static <T, K> void invalidateKeys(Cache<SearchParams<K>, ?> cache, Function<K, Pair<T, Long>> getKeys, Map<T, Set<Long>> itemDsMapping) {
invalidateKeys(cache, (keyParams) -> {
Pair<T, Long> pairItems = getKeys.apply(keyParams);
T searchParamsKey = pairItems.getLeft();
Long searchParamsDsId = pairItems.getRight();
Set<Long> dsIds = itemDsMapping.get(searchParamsKey);
return (dsIds != null && (searchParamsDsId == null || dsIds.contains(searchParamsDsId)));
});
}
/**
* Using a digest of event information, clears keys in a cache that may be
* effected by events.
* Determines what keys should be kept in the cache while iterating through
* all the keys.
*
* @param cache The cache.
* @param getKeys Using a key from a cache, provides a tuple
* of the relevant key in the data source
* mapping and the data source id (or null if
* no data source filtering).
* @param itemDataSourceMapping The list of event digests.
* @param cache The cache.
* @param shouldInvalidate If the key should be removed from the cache.
*/
static <T, K> void invalidateKeys(Cache<SearchParams<K>, ?> cache, Function<K, Pair<T, Long>> getKeys, List<Map<T, Set<Long>>> itemDataSourceMapping) {
static <K> void invalidateKeys(Cache<SearchParams<K>, ?> cache, Predicate<K> shouldInvalidate) {
ConcurrentMap<SearchParams<K>, ?> concurrentMap = cache.asMap();
concurrentMap.forEach((k, v) -> {
Pair<T, Long> pairItems = getKeys.apply(k.getParamData());
T searchParamsKey = pairItems.getLeft();
Long searchParamsDsId = pairItems.getRight();
for (Map<T, Set<Long>> itemDsMapping : itemDataSourceMapping) {
Set<Long> dsIds = itemDsMapping.get(searchParamsKey);
if (dsIds != null && (searchParamsDsId == null || dsIds.contains(searchParamsDsId))) {
concurrentMap.remove(k);
}
if (shouldInvalidate.test(k.getParamData())) {
concurrentMap.remove(k);
}
});
}
@@ -86,9 +84,9 @@ public class SubDAOUtils {
*
* @return The generated tree events.
*/
static <E, T> Set<TreeEvent> getIngestCompleteEvents(TreeCounts<E> treeCounts, Function<E, TreeResultsDTO.TreeItemDTO<T>> converter) {
static <E, T> Set<TreeEvent> getIngestCompleteEvents(TreeCounts<E> treeCounts, BiFunction<E, TreeDisplayCount, TreeItemDTO<T>> converter) {
return treeCounts.flushEvents().stream()
.map(daoEvt -> new TreeEvent(converter.apply(daoEvt), true))
.map(daoEvt -> new TreeEvent(converter.apply(daoEvt, TreeDisplayCount.UNSPECIFIED), true))
.collect(Collectors.toSet());
}
@@ -102,9 +100,9 @@ public class SubDAOUtils {
*
* @return The generated tree events.
*/
static <E, T> Set<TreeEvent> getRefreshEvents(TreeCounts<E> treeCounts, Function<E, TreeResultsDTO.TreeItemDTO<T>> converter) {
static <E, T> Set<TreeEvent> getRefreshEvents(TreeCounts<E> treeCounts, BiFunction<E, TreeDisplayCount, TreeItemDTO<T>> converter) {
return treeCounts.getEventTimeouts().stream()
.map(daoEvt -> new TreeEvent(converter.apply(daoEvt), true))
.map(daoEvt -> new TreeEvent(converter.apply(daoEvt, TreeDisplayCount.UNSPECIFIED), true))
.collect(Collectors.toSet());
}
}
@@ -27,20 +27,14 @@ import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.commons.lang3.tuple.Triple;
import org.openide.util.NbBundle;
import org.openide.util.NbBundle.Messages;
import org.sleuthkit.autopsy.casemodule.Case;
@@ -52,7 +46,9 @@ import org.sleuthkit.autopsy.casemodule.events.ContentTagDeletedEvent;
import org.sleuthkit.autopsy.core.UserPreferences;
import org.sleuthkit.autopsy.coreutils.TimeZoneUtils;
import org.sleuthkit.autopsy.mainui.datamodel.TagsSearchParams.TagType;
import org.sleuthkit.autopsy.mainui.datamodel.TreeResultsDTO.TreeItemDTO;
import org.sleuthkit.autopsy.mainui.datamodel.events.TagsEvent;
import org.sleuthkit.autopsy.mainui.datamodel.events.TreeCounts;
import org.sleuthkit.autopsy.mainui.datamodel.events.TreeEvent;
import org.sleuthkit.autopsy.mainui.nodes.DAOFetcher;
import org.sleuthkit.datamodel.AbstractFile;
@@ -90,7 +86,6 @@ public class TagsDAO extends AbstractDAO {
private static final int CACHE_SIZE = 5; // rule of thumb: 5 entries times number of cached SearchParams sub-types
private static final long CACHE_DURATION = 2;
private static final TimeUnit CACHE_DURATION_UNITS = TimeUnit.MINUTES;
private final Cache<SearchParams<TagsSearchParams>, SearchResultsDTO> searchParamsCache = CacheBuilder.newBuilder().maximumSize(CACHE_SIZE).expireAfterAccess(CACHE_DURATION, CACHE_DURATION_UNITS).build();
private static final String USER_NAME_PROPERTY = "user.name"; //NON-NLS
@@ -129,6 +124,11 @@ public class TagsDAO extends AbstractDAO {
return new ColumnKey(name, name, Bundle.TagsDAO_fileColumns_noDescription());
}
private final Cache<SearchParams<TagsSearchParams>, SearchResultsDTO> searchParamsCache
= CacheBuilder.newBuilder().maximumSize(CACHE_SIZE).expireAfterAccess(CACHE_DURATION, CACHE_DURATION_UNITS).build();
private final TreeCounts<TagsEvent> treeCounts = new TreeCounts<>();
public SearchResultsDTO getTags(TagsSearchParams key, long startItem, Long maxCount) throws ExecutionException, IllegalArgumentException {
if (key.getTagName() == null) {
throw new IllegalArgumentException("Must have non-null tag name");
@@ -301,71 +301,59 @@ public class TagsDAO extends AbstractDAO {
}
TagsEvent tagEvt = (TagsEvent) daoEvt;
return (tagParams.getTagName().getId() == tagEvt.getTagNameId()
return (Objects.equals(tagParams.getTagName(), tagEvt.getTagName())
&& tagParams.getTagType().equals(tagEvt.getTagType())
&& (tagParams.getDataSourceId() == null
|| tagEvt.getDataSourceId() == null
|| tagParams.getDataSourceId() == tagEvt.getDataSourceId()));
}
@Override
void clearCaches() {
this.searchParamsCache.invalidateAll();
private TreeItemDTO<TagsSearchParams> getTreeItem(TagsEvent evt, TreeResultsDTO.TreeDisplayCount count) {
return new TreeItemDTO<>(
TagsSearchParams.getTypeId(),
new TagsSearchParams(evt.getTagName(), evt.getTagType(), evt.getDataSourceId()),
evt.getTagName().getId(),
evt.getTagName().getDisplayName(),
count);
}
@Override
Set<DAOEvent> handleIngestComplete() {
// GVDTODO
return Collections.emptySet();
void clearCaches() {
this.searchParamsCache.invalidateAll();
handleIngestComplete();
}
@Override
Set<? extends DAOEvent> handleIngestComplete() {
return SubDAOUtils.getIngestCompleteEvents(this.treeCounts, (evt, count) -> getTreeItem(evt, count));
}
@Override
Set<TreeEvent> shouldRefreshTree() {
// GVDTODO
return Collections.emptySet();
return SubDAOUtils.getRefreshEvents(this.treeCounts, (evt, count) -> getTreeItem(evt, count));
}
@Override
Set<DAOEvent> processEvent(PropertyChangeEvent evt) {
// GVDTODO this may be rewritten simpler now that it isn't processing a list of events
Map<Pair<TagType, Long>, Set<Optional<Long>>> mapping = new HashMap<>();
// tag type, tag name id, data source id (or null if unknown)
Triple<TagType, Long, Long> data = getTagData(evt);
if (data != null) {
mapping.computeIfAbsent(Pair.of(data.getLeft(), data.getMiddle()), k -> new HashSet<>())
.add(Optional.ofNullable(data.getRight()));
}
// don't continue if no mapping entries
if (mapping.isEmpty()) {
TagsEvent data = getTagData(evt);
if (data == null) {
return Collections.emptySet();
}
ConcurrentMap<SearchParams<TagsSearchParams>, SearchResultsDTO> concurrentMap = this.searchParamsCache.asMap();
concurrentMap.forEach((k, v) -> {
TagsSearchParams paramData = k.getParamData();
Set<Optional<Long>> affectedDataSources = mapping.get(Pair.of(paramData.getTagType(), paramData.getTagName().getId()));
// we only clear key if the tag name / type line up and either the parameters data source wasn't specified,
// there is a wild card data source for the event, or the data source is contained in the list of data sources
// affected by the event
if (affectedDataSources != null
&& (paramData.getDataSourceId() == null
|| affectedDataSources.contains(Optional.empty())
|| affectedDataSources.contains(Optional.of(paramData.getDataSourceId())))) {
concurrentMap.remove(k);
}
SubDAOUtils.invalidateKeys(this.searchParamsCache, (searchParams) -> {
return (Objects.equals(searchParams.getTagType(), data.getTagType())
&& Objects.equals(searchParams.getTagName(), data.getTagName())
&& (searchParams.getDataSourceId() == null || Objects.equals(searchParams.getDataSourceId(), data.getDataSourceId())));
});
return mapping.entrySet().stream()
.flatMap(entry -> {
TagType tagType = entry.getKey().getLeft();
Long tagNameId = entry.getKey().getRight();
Collection<TagsEvent> daoEvents = Collections.singletonList(data);
return entry.getValue().stream()
.map((dsIdOpt) -> new TagsEvent(tagType, tagNameId, dsIdOpt.orElse(null)));
})
Collection<TreeEvent> treeEvents = this.treeCounts.enqueueAll(daoEvents).stream()
.map(arEvt -> new TreeEvent(getTreeItem(arEvt, TreeResultsDTO.TreeDisplayCount.INDETERMINATE), false))
.collect(Collectors.toList());
return Stream.of(daoEvents, treeEvents)
.flatMap(lst -> lst.stream())
.collect(Collectors.toSet());
}
@@ -378,21 +366,21 @@ public class TagsDAO extends AbstractDAO {
* @return tag type, tag name id, data source id (or null if none determined
* from event).
*/
private Triple<TagType, Long, Long> getTagData(PropertyChangeEvent evt) {
private TagsEvent getTagData(PropertyChangeEvent evt) {
if (evt instanceof BlackBoardArtifactTagAddedEvent) {
BlackBoardArtifactTagAddedEvent event = (BlackBoardArtifactTagAddedEvent) evt;
// ensure tag added event has a valid content id
if (event.getAddedTag() != null
&& event.getAddedTag().getContent() != null
&& event.getAddedTag().getArtifact() != null) {
return Triple.of(TagType.RESULT, event.getAddedTag().getName().getId(), event.getAddedTag().getArtifact().getDataSourceObjectID());
return new TagsEvent(TagType.RESULT, event.getAddedTag().getName(), event.getAddedTag().getArtifact().getDataSourceObjectID());
}
} else if (evt instanceof BlackBoardArtifactTagDeletedEvent) {
BlackBoardArtifactTagDeletedEvent event = (BlackBoardArtifactTagDeletedEvent) evt;
BlackBoardArtifactTagDeletedEvent.DeletedBlackboardArtifactTagInfo deletedTagInfo = event.getDeletedTagInfo();
if (deletedTagInfo != null) {
return Triple.of(TagType.RESULT, deletedTagInfo.getName().getId(), null);
return new TagsEvent(TagType.RESULT, deletedTagInfo.getName(), null);
}
} else if (evt instanceof ContentTagAddedEvent) {
ContentTagAddedEvent event = (ContentTagAddedEvent) evt;
@@ -400,14 +388,14 @@ public class TagsDAO extends AbstractDAO {
if (event.getAddedTag() != null && event.getAddedTag().getContent() != null) {
Content content = event.getAddedTag().getContent();
Long dsId = content instanceof AbstractFile ? ((AbstractFile) content).getDataSourceObjectId() : null;
return Triple.of(TagType.FILE, event.getAddedTag().getName().getId(), dsId);
return new TagsEvent(TagType.FILE, event.getAddedTag().getName(), dsId);
}
} else if (evt instanceof ContentTagDeletedEvent) {
ContentTagDeletedEvent event = (ContentTagDeletedEvent) evt;
// ensure tag deleted event has a valid content id
ContentTagDeletedEvent.DeletedContentTagInfo deletedTagInfo = event.getDeletedTagInfo();
if (deletedTagInfo != null) {
return Triple.of(TagType.FILE, deletedTagInfo.getName().getId(), null);
return new TagsEvent(TagType.FILE, deletedTagInfo.getName(), null);
}
}
return null;
@@ -25,7 +25,16 @@ import org.sleuthkit.datamodel.TagName;
* Key for accessing data about tags from the DAO.
*/
public class TagsSearchParams {
private static final String TYPE_ID = "TAG";
/**
* @return The type id for this search parameter.
*/
public static String getTypeId() {
return TYPE_ID;
}
public enum TagType {
FILE,
RESULT;
@@ -34,7 +43,7 @@ public class TagsSearchParams {
private final TagType type;
private final TagName tagName;
private final Long dataSourceId;
public TagsSearchParams(TagName tagName, TagType type, Long dataSourceId) {
this.tagName = tagName;
this.type = type;
@@ -98,6 +98,7 @@ public class TreeResultsDTO<T> {
case INDETERMINATE:
return " (...)";
case NOT_SHOWN:
case UNSPECIFIED:
default:
return "";
}
@@ -21,9 +21,11 @@ package org.sleuthkit.autopsy.mainui.datamodel;
import org.sleuthkit.autopsy.mainui.datamodel.events.DAOEvent;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.google.common.collect.ImmutableSet;
import java.beans.PropertyChangeEvent;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
@@ -33,9 +35,9 @@ import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.function.Predicate;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.Collectors;
@@ -53,6 +55,7 @@ import org.sleuthkit.autopsy.mainui.datamodel.events.DAOEventUtils;
import org.sleuthkit.autopsy.mainui.datamodel.events.FileTypeExtensionsEvent;
import org.sleuthkit.autopsy.mainui.datamodel.events.FileTypeMimeEvent;
import org.sleuthkit.autopsy.mainui.datamodel.events.FileTypeSizeEvent;
import org.sleuthkit.autopsy.mainui.datamodel.events.TreeCounts;
import org.sleuthkit.autopsy.mainui.datamodel.events.TreeEvent;
import org.sleuthkit.autopsy.mainui.nodes.DAOFetcher;
import org.sleuthkit.datamodel.AbstractFile;
@@ -60,6 +63,7 @@ import org.sleuthkit.datamodel.CaseDbAccessManager.CaseDbPreparedStatement;
import org.sleuthkit.datamodel.SleuthkitCase;
import org.sleuthkit.datamodel.TskCoreException;
import org.sleuthkit.datamodel.TskData;
import org.sleuthkit.datamodel.TskData.TSK_FS_NAME_TYPE_ENUM;
/**
* Provides information to populate the results viewer for data in the views
@@ -72,7 +76,14 @@ public class ViewsDAO extends AbstractDAO {
private static final int CACHE_SIZE = 15; // rule of thumb: 5 entries times number of cached SearchParams sub-types
private static final long CACHE_DURATION = 2;
private static final TimeUnit CACHE_DURATION_UNITS = TimeUnit.MINUTES;
private final Cache<SearchParams<?>, SearchResultsDTO> searchParamsCache = CacheBuilder.newBuilder().maximumSize(CACHE_SIZE).expireAfterAccess(CACHE_DURATION, CACHE_DURATION_UNITS).build();
private static final Map<String, Set<FileExtSearchFilter>> EXTENSION_FILTER_MAP
= Stream.of((FileExtSearchFilter[]) FileExtRootFilter.values(), FileExtDocumentFilter.values(), FileExtExecutableFilter.values())
.flatMap(arr -> Stream.of(arr))
.flatMap(filter -> filter.getFilter().stream().map(ext -> Pair.of(ext, filter)))
.collect(Collectors.groupingBy(
pair -> pair.getKey(),
Collectors.mapping(pair -> pair.getValue(),
Collectors.toSet())));
private static final String FILE_VIEW_EXT_TYPE_ID = "FILE_VIEW_BY_EXT";
@@ -86,6 +97,9 @@ public class ViewsDAO extends AbstractDAO {
return instance;
}
private final Cache<SearchParams<Object>, SearchResultsDTO> searchParamsCache = CacheBuilder.newBuilder().maximumSize(CACHE_SIZE).expireAfterAccess(CACHE_DURATION, CACHE_DURATION_UNITS).build();
private final TreeCounts<DAOEvent> treeCounts = new TreeCounts<>();
private SleuthkitCase getCase() throws NoCurrentCaseException {
return Case.getCurrentCaseThrows().getSleuthkitCase();
}
@@ -97,7 +111,7 @@ public class ViewsDAO extends AbstractDAO {
throw new IllegalArgumentException("Data source id must be greater than 0 or null");
}
SearchParams<FileTypeExtensionsSearchParams> searchParams = new SearchParams<>(key, startItem, maxCount);
SearchParams<Object> searchParams = new SearchParams<>(key, startItem, maxCount);
return searchParamsCache.get(searchParams, () -> fetchExtensionSearchResultsDTOs(key.getFilter(), key.getDataSourceId(), startItem, maxCount));
}
@@ -108,7 +122,7 @@ public class ViewsDAO extends AbstractDAO {
throw new IllegalArgumentException("Data source id must be greater than 0 or null");
}
SearchParams<FileTypeMimeSearchParams> searchParams = new SearchParams<>(key, startItem, maxCount);
SearchParams<Object> searchParams = new SearchParams<>(key, startItem, maxCount);
return searchParamsCache.get(searchParams, () -> fetchMimeSearchResultsDTOs(key.getMimeType(), key.getDataSourceId(), startItem, maxCount));
}
@@ -119,7 +133,7 @@ public class ViewsDAO extends AbstractDAO {
throw new IllegalArgumentException("Data source id must be greater than 0 or null");
}
SearchParams<FileTypeSizeSearchParams> searchParams = new SearchParams<>(key, startItem, maxCount);
SearchParams<Object> searchParams = new SearchParams<>(key, startItem, maxCount);
return searchParamsCache.get(searchParams, () -> fetchSizeSearchResultsDTOs(key.getSizeFilter(), key.getDataSourceId(), startItem, maxCount));
}
@@ -129,9 +143,8 @@ public class ViewsDAO extends AbstractDAO {
}
FileTypeExtensionsEvent extEvt = (FileTypeExtensionsEvent) eventData;
String extension = extEvt.getExtension().toLowerCase();
return key.getFilter().getFilter().contains(extension)
&& (key.getDataSourceId() == null || key.getDataSourceId().equals(extEvt.getDataSourceId()));
return (extEvt.getExtensionFilter() == null || key.getFilter().equals(extEvt.getExtensionFilter()))
&& (key.getDataSourceId() == null || extEvt.getDataSourceId() == null || key.getDataSourceId().equals(extEvt.getDataSourceId()));
}
private boolean isFilesByMimeInvalidating(FileTypeMimeSearchParams key, DAOEvent eventData) {
@@ -150,8 +163,8 @@ public class ViewsDAO extends AbstractDAO {
}
FileTypeSizeEvent sizeEvt = (FileTypeSizeEvent) eventData;
return sizeEvt.getSizeFilter().equals(key.getSizeFilter())
&& (key.getDataSourceId() == null || Objects.equals(key.getDataSourceId(), sizeEvt.getDataSourceId()));
return (sizeEvt.getSizeFilter() == null || sizeEvt.getSizeFilter().equals(key.getSizeFilter()))
&& (key.getDataSourceId() == null || sizeEvt.getDataSourceId() == null || Objects.equals(key.getDataSourceId(), sizeEvt.getDataSourceId()));
}
/**
@@ -184,6 +197,22 @@ public class ViewsDAO extends AbstractDAO {
.collect(Collectors.joining(", ")) + ")";
}
/**
* @return If user preference of hide known files, returns sql and clause to
* hide known files or returns empty string otherwise.
*/
private String getHideKnownAndClause() {
return (hideKnownFilesInViewsTree() ? (" AND (known IS NULL OR known <> " + TskData.FileKnown.KNOWN.getFileKnownValue() + ") ") : "");
}
/**
* @return A clause (no 'and' or 'where' prefixed) indicating the dir_type
* is regular.
*/
private String getRegDirTypeClause() {
return "(dir_type = " + TskData.TSK_FS_NAME_TYPE_ENUM.REG.getValue() + ")";
}
/**
* Returns a clause that will filter out files that aren't to be counted in
* the file extensions view.
@@ -191,8 +220,7 @@ public class ViewsDAO extends AbstractDAO {
* @return The filter that will need to be proceeded with 'where' or 'and'.
*/
private String getBaseFileExtensionFilter() {
return "(dir_type = " + TskData.TSK_FS_NAME_TYPE_ENUM.REG.getValue() + ")"
+ (hideKnownFilesInViewsTree() ? (" AND (known IS NULL OR known <> " + TskData.FileKnown.KNOWN.getFileKnownValue() + ")") : "");
return getRegDirTypeClause() + getHideKnownAndClause();
}
/**
@@ -212,6 +240,22 @@ public class ViewsDAO extends AbstractDAO {
return whereClause;
}
/**
* @return The TSK_DB_FILES_TYPE_ENUm values allowed for mime type view
* items.
*/
private Set<TskData.TSK_DB_FILES_TYPE_ENUM> getMimeDbFilesTypes() {
return Stream.of(
TskData.TSK_DB_FILES_TYPE_ENUM.FS,
TskData.TSK_DB_FILES_TYPE_ENUM.CARVED,
TskData.TSK_DB_FILES_TYPE_ENUM.DERIVED,
TskData.TSK_DB_FILES_TYPE_ENUM.LAYOUT_FILE,
TskData.TSK_DB_FILES_TYPE_ENUM.LOCAL,
(hideSlackFilesInViewsTree() ? null : (TskData.TSK_DB_FILES_TYPE_ENUM.SLACK)))
.filter(ordinal -> ordinal != null)
.collect(Collectors.toSet());
}
/**
* Returns a statement to be proceeded with 'where' or 'and' that will
* filter out results that should not be viewed in mime types view.
@@ -219,15 +263,10 @@ public class ViewsDAO extends AbstractDAO {
* @return A statement to be proceeded with 'and' or 'where'.
*/
private String getBaseFileMimeFilter() {
return "(dir_type = " + TskData.TSK_FS_NAME_TYPE_ENUM.REG.getValue() + ")"
+ (hideKnownFilesInViewsTree() ? (" AND (known IS NULL OR known != " + TskData.FileKnown.KNOWN.getFileKnownValue() + ")") : "")
return getRegDirTypeClause()
+ getHideKnownAndClause()
+ " AND (type IN ("
+ TskData.TSK_DB_FILES_TYPE_ENUM.FS.ordinal() + ","
+ TskData.TSK_DB_FILES_TYPE_ENUM.CARVED.ordinal() + ","
+ TskData.TSK_DB_FILES_TYPE_ENUM.DERIVED.ordinal() + ","
+ TskData.TSK_DB_FILES_TYPE_ENUM.LAYOUT_FILE.ordinal() + ","
+ TskData.TSK_DB_FILES_TYPE_ENUM.LOCAL.ordinal()
+ (hideSlackFilesInViewsTree() ? "" : ("," + TskData.TSK_DB_FILES_TYPE_ENUM.SLACK.ordinal()))
+ getMimeDbFilesTypes().stream().map(v -> Integer.toString(v.ordinal())).collect(Collectors.joining(", "))
+ "))";
}
@@ -270,8 +309,7 @@ public class ViewsDAO extends AbstractDAO {
*/
private String getBaseFileSizeFilter() {
// Ignore unallocated block files.
return "(type != " + TskData.TSK_DB_FILES_TYPE_ENUM.UNALLOC_BLOCKS.getFileType() + ")"
+ ((hideKnownFilesInViewsTree() ? (" AND (known IS NULL OR known != " + TskData.FileKnown.KNOWN.getFileKnownValue() + ")") : "")); //NON-NLS
return "(type != " + TskData.TSK_DB_FILES_TYPE_ENUM.UNALLOC_BLOCKS.getFileType() + ")" + getHideKnownAndClause();
}
/**
@@ -306,6 +344,22 @@ public class ViewsDAO extends AbstractDAO {
* @throws ExecutionException
*/
public TreeResultsDTO<FileTypeExtensionsSearchParams> getFileExtCounts(Collection<FileExtSearchFilter> filters, Long dataSourceId) throws IllegalArgumentException, ExecutionException {
Set<FileExtSearchFilter> indeterminateFilters = new HashSet<>();
for (DAOEvent evt : this.treeCounts.getEnqueued()) {
if (evt instanceof FileTypeExtensionsEvent) {
FileTypeExtensionsEvent extEvt = (FileTypeExtensionsEvent) evt;
if (dataSourceId == null || extEvt.getDataSourceId() == null || Objects.equals(extEvt.getDataSourceId(), dataSourceId)) {
if (extEvt.getExtensionFilter() == null) {
// add all filters if extension filter is null and keep going
indeterminateFilters.addAll(filters);
break;
} else if (filters.contains(extEvt.getExtensionFilter())) {
indeterminateFilters.add(extEvt.getExtensionFilter());
}
}
}
}
Map<FileExtSearchFilter, String> whereClauses = filters.stream()
.collect(Collectors.toMap(
filter -> filter,
@@ -315,12 +369,11 @@ public class ViewsDAO extends AbstractDAO {
List<TreeItemDTO<FileTypeExtensionsSearchParams>> treeList = countsByFilter.entrySet().stream()
.map(entry -> {
return new TreeItemDTO<>(
"FILE_EXT",
new FileTypeExtensionsSearchParams(entry.getKey(), dataSourceId),
entry.getKey(),
entry.getKey().getDisplayName(),
TreeDisplayCount.getDeterminate(entry.getValue()));
TreeDisplayCount displayCount = indeterminateFilters.contains(entry.getKey())
? TreeDisplayCount.INDETERMINATE
: TreeDisplayCount.getDeterminate(entry.getValue());
return createExtensionTreeItem(entry.getKey(), dataSourceId, displayCount);
})
.sorted((a, b) -> a.getDisplayName().compareToIgnoreCase(b.getDisplayName()))
.collect(Collectors.toList());
@@ -328,6 +381,24 @@ public class ViewsDAO extends AbstractDAO {
return new TreeResultsDTO<>(treeList);
}
/**
* Creates an extension tree item.
*
* @param filter The extension filter.
* @param dataSourceId The data source id or null.
* @param displayCount The count to display.
*
* @return The extension tree item.
*/
private TreeItemDTO<FileTypeExtensionsSearchParams> createExtensionTreeItem(FileExtSearchFilter filter, Long dataSourceId, TreeDisplayCount displayCount) {
return new TreeItemDTO<>(
FileTypeExtensionsSearchParams.getTypeId(),
new FileTypeExtensionsSearchParams(filter, dataSourceId),
filter,
filter == null ? "" : filter.getDisplayName(),
displayCount);
}
/**
* Returns counts for file size categories.
*
@@ -340,6 +411,22 @@ public class ViewsDAO extends AbstractDAO {
* @throws ExecutionException
*/
public TreeResultsDTO<FileTypeSizeSearchParams> getFileSizeCounts(Long dataSourceId) throws IllegalArgumentException, ExecutionException {
Set<FileSizeFilter> indeterminateFilters = new HashSet<>();
for (DAOEvent evt : this.treeCounts.getEnqueued()) {
if (evt instanceof FileTypeSizeEvent) {
FileTypeSizeEvent sizeEvt = (FileTypeSizeEvent) evt;
if (dataSourceId == null || sizeEvt.getDataSourceId() == null || Objects.equals(sizeEvt.getDataSourceId(), dataSourceId)) {
if (sizeEvt.getSizeFilter() == null) {
// if null size filter, indicates full refresh and all file sizes need refresh.
indeterminateFilters.addAll(Arrays.asList(FileSizeFilter.values()));
break;
} else {
indeterminateFilters.add(sizeEvt.getSizeFilter());
}
}
}
}
Map<FileSizeFilter, String> whereClauses = Stream.of(FileSizeFilter.values())
.collect(Collectors.toMap(
filter -> filter,
@@ -349,12 +436,11 @@ public class ViewsDAO extends AbstractDAO {
List<TreeItemDTO<FileTypeSizeSearchParams>> treeList = countsByFilter.entrySet().stream()
.map(entry -> {
return new TreeItemDTO<>(
"FILE_SIZE",
new FileTypeSizeSearchParams(entry.getKey(), dataSourceId),
entry.getKey(),
entry.getKey().getDisplayName(),
TreeDisplayCount.getDeterminate(entry.getValue()));
TreeDisplayCount displayCount = indeterminateFilters.contains(entry.getKey())
? TreeDisplayCount.INDETERMINATE
: TreeDisplayCount.getDeterminate(entry.getValue());
return createSizeTreeItem(entry.getKey(), dataSourceId, displayCount);
})
.sorted((a, b) -> a.getDisplayName().compareToIgnoreCase(b.getDisplayName()))
.collect(Collectors.toList());
@@ -362,6 +448,24 @@ public class ViewsDAO extends AbstractDAO {
return new TreeResultsDTO<>(treeList);
}
/**
* Creates a size tree item.
*
* @param filter The file size filter.
* @param dataSourceId The data source id.
* @param displayCount The display count.
*
* @return The tree item.
*/
private TreeItemDTO<FileTypeSizeSearchParams> createSizeTreeItem(FileSizeFilter filter, Long dataSourceId, TreeDisplayCount displayCount) {
return new TreeItemDTO<>(
FileTypeSizeSearchParams.getTypeId(),
new FileTypeSizeSearchParams(filter, dataSourceId),
filter,
filter == null ? "" : filter.getDisplayName(),
displayCount);
}
/**
* Returns counts for file mime type categories.
*
@@ -379,6 +483,22 @@ public class ViewsDAO extends AbstractDAO {
String prefixWithSlash = StringUtils.isNotBlank(prefix) ? prefix.replaceAll("/", "") + "/" : null;
String likeItem = StringUtils.isNotBlank(prefixWithSlash) ? prefixWithSlash.replaceAll("%", "") + "%" : null;
Set<String> indeterminateMimeTypes = new HashSet<>();
for (DAOEvent evt : this.treeCounts.getEnqueued()) {
if (evt instanceof FileTypeMimeEvent) {
FileTypeMimeEvent mimeEvt = (FileTypeMimeEvent) evt;
if ((dataSourceId == null || Objects.equals(mimeEvt.getDataSourceId(), dataSourceId))
&& (prefixWithSlash == null || mimeEvt.getMimeType().startsWith(prefixWithSlash))) {
String mimePortion = prefixWithSlash != null
? mimeEvt.getMimeType().substring(prefixWithSlash.length())
: mimeEvt.getMimeType().substring(0, mimeEvt.getMimeType().indexOf("/"));
indeterminateMimeTypes.add(mimePortion);
}
}
}
String baseFilter = "WHERE " + getBaseFileMimeFilter()
+ getDataSourceAndClause(dataSourceId)
+ (StringUtils.isNotBlank(prefix) ? " AND mime_type LIKE ? " : " AND mime_type IS NOT NULL ");
@@ -434,12 +554,11 @@ public class ViewsDAO extends AbstractDAO {
? entry.getKey().substring(prefixWithSlash.length())
: entry.getKey();
return new TreeItemDTO<>(
"FILE_MIME_TYPE",
new FileTypeMimeSearchParams(entry.getKey(), dataSourceId),
name,
name,
TreeDisplayCount.getDeterminate(entry.getValue()));
TreeDisplayCount displayCount = indeterminateMimeTypes.contains(name)
? TreeDisplayCount.INDETERMINATE
: TreeDisplayCount.getDeterminate(entry.getValue());
return createMimeTreeItem(entry.getKey(), name, dataSourceId, displayCount);
})
.sorted((a, b) -> stringCompare(a.getSearchParams().getMimeType(), b.getSearchParams().getMimeType()))
.collect(Collectors.toList());
@@ -453,6 +572,26 @@ public class ViewsDAO extends AbstractDAO {
}
}
/**
* Creates a mime type tree item.
*
* @param fullMime The full mime type.
* @param mimeName The mime type segment that will be displayed (suffix
* or prefix).
* @param dataSourceId The data source id.
* @param displayCount The count to display.
*
* @return The created tree item.
*/
private TreeItemDTO<FileTypeMimeSearchParams> createMimeTreeItem(String fullMime, String mimeName, Long dataSourceId, TreeDisplayCount displayCount) {
return new TreeItemDTO<>(
FileTypeMimeSearchParams.getTypeId(),
new FileTypeMimeSearchParams(fullMime, dataSourceId),
mimeName,
mimeName,
displayCount);
}
/**
* Provides case insensitive comparator integer for strings that may be
* null.
@@ -612,11 +751,6 @@ public class ViewsDAO extends AbstractDAO {
return new BaseSearchResultsDTO(FILE_VIEW_EXT_TYPE_ID, displayName, FileSystemColumnUtils.getColumnKeysForAbstractfile(), fileRows, AbstractFile.class.getName(), startItem, totalResultsCount);
}
@Override
void clearCaches() {
this.searchParamsCache.invalidateAll();
}
private Pair<String, String> getMimePieces(String mimeType) {
int idx = mimeType.indexOf("/");
String mimePrefix = idx > 0 ? mimeType.substring(0, idx) : mimeType;
@@ -624,167 +758,212 @@ public class ViewsDAO extends AbstractDAO {
return Pair.of(mimePrefix, mimeSuffix);
}
private TreeItemDTO<?> createTreeItem(DAOEvent daoEvent, TreeDisplayCount count) {
if (daoEvent instanceof FileTypeExtensionsEvent) {
FileTypeExtensionsEvent extEvt = (FileTypeExtensionsEvent) daoEvent;
return createExtensionTreeItem(extEvt.getExtensionFilter(), extEvt.getDataSourceId(), count);
} else if (daoEvent instanceof FileTypeMimeEvent) {
FileTypeMimeEvent mimeEvt = (FileTypeMimeEvent) daoEvent;
Pair<String, String> mimePieces = getMimePieces(mimeEvt.getMimeType());
String mimeName = mimePieces.getRight() == null ? mimePieces.getLeft() : mimePieces.getRight();
return createMimeTreeItem(mimeEvt.getMimeType(), mimeName, mimeEvt.getDataSourceId(), count);
} else if (daoEvent instanceof FileTypeSizeEvent) {
FileTypeSizeEvent sizeEvt = (FileTypeSizeEvent) daoEvent;
return createSizeTreeItem(sizeEvt.getSizeFilter(), sizeEvt.getDataSourceId(), count);
} else {
return null;
}
}
@Override
Set<DAOEvent> handleIngestComplete() {
// GVDTODO
return Collections.emptySet();
void clearCaches() {
this.searchParamsCache.invalidateAll();
handleIngestComplete();
}
@Override
Set<? extends DAOEvent> handleIngestComplete() {
SubDAOUtils.invalidateKeys(this.searchParamsCache,
(searchParams) -> searchParamsMatchEvent(null, null, null, null, true, searchParams));
Set<? extends DAOEvent> treeEvts = SubDAOUtils.getIngestCompleteEvents(this.treeCounts,
(daoEvt, count) -> createTreeItem(daoEvt, count));
Set<? extends DAOEvent> fileViewRefreshEvents = getFileViewRefreshEvents(null);
List<? extends DAOEvent> fileViewRefreshTreeEvents = fileViewRefreshEvents.stream()
.map(evt -> new TreeEvent(createTreeItem(evt, TreeDisplayCount.UNSPECIFIED), true))
.collect(Collectors.toList());
return Stream.of(treeEvts, fileViewRefreshEvents, fileViewRefreshTreeEvents)
.flatMap(c -> c.stream())
.collect(Collectors.toSet());
}
@Override
Set<TreeEvent> shouldRefreshTree() {
// GVDTODO
return Collections.emptySet();
return SubDAOUtils.getRefreshEvents(this.treeCounts,
(daoEvt, count) -> createTreeItem(daoEvt, count));
}
@Override
Set<DAOEvent> processEvent(PropertyChangeEvent evt) {
// GVDTODO maps may not be necessary now that this isn't processing a list of events.
Map<String, Set<Long>> fileExtensionDsMap = new HashMap<>();
Map<String, Map<String, Set<Long>>> mimeTypeDsMap = new HashMap<>();
Map<FileSizeFilter, Set<Long>> fileSizeDsMap = new HashMap<>();
Long dsId = null;
boolean dataSourceAdded = false;
Set<FileExtSearchFilter> evtExtFilters = null;
String evtMimeType = null;
FileSizeFilter evtFileSize = null;
AbstractFile af = DAOEventUtils.getFileFromFileEvent(evt);
if (af == null) {
return Collections.emptySet();
}
if (Case.Events.DATA_SOURCE_ADDED.toString().equals(evt.getPropertyName())) {
dsId = evt.getNewValue() instanceof Long ? (Long) evt.getNewValue() : null;
dataSourceAdded = true;
} else {
AbstractFile af = DAOEventUtils.getFileFromFileEvent(evt);
if (af == null) {
return Collections.emptySet();
} else if (hideKnownFilesInViewsTree() && TskData.FileKnown.KNOWN.equals(af.getKnown())) {
return Collections.emptySet();
}
// create an extension mapping if extension present
if (!StringUtils.isBlank(af.getNameExtension())) {
fileExtensionDsMap
.computeIfAbsent("." + af.getNameExtension(), (k) -> new HashSet<>())
.add(af.getDataSourceObjectId());
}
dsId = af.getDataSourceObjectId();
// create a mime type mapping if mime type present
if (!StringUtils.isBlank(af.getMIMEType())) {
Pair<String, String> mimePieces = getMimePieces(af.getMIMEType());
mimeTypeDsMap
.computeIfAbsent(mimePieces.getKey(), (k) -> new HashMap<>())
.computeIfAbsent(mimePieces.getValue(), (k) -> new HashSet<>())
.add(af.getDataSourceObjectId());
}
// create an extension mapping if extension present
if (!StringUtils.isBlank(af.getNameExtension()) && TSK_FS_NAME_TYPE_ENUM.REG.equals(af.getDirType())) {
evtExtFilters = EXTENSION_FILTER_MAP.getOrDefault("." + af.getNameExtension(), Collections.emptySet());
}
// create a size mapping if size present
FileSizeFilter sizeFilter = Stream.of(FileSizeFilter.values())
.filter(filter -> af.getSize() >= filter.getMinBound() && (filter.getMaxBound() == null || af.getSize() < filter.getMaxBound()))
.findFirst()
.orElse(null);
// create a mime type mapping if mime type present
if (!StringUtils.isBlank(af.getMIMEType()) && TSK_FS_NAME_TYPE_ENUM.REG.equals(af.getDirType()) && getMimeDbFilesTypes().contains(af.getType())) {
evtMimeType = af.getMIMEType();
}
if (sizeFilter != null) {
fileSizeDsMap
.computeIfAbsent(sizeFilter, (k) -> new HashSet<>())
.add(af.getDataSourceObjectId());
}
// create a size mapping if size present in filters
if (!TskData.TSK_DB_FILES_TYPE_ENUM.UNALLOC_BLOCKS.equals(af.getType())) {
evtFileSize = Stream.of(FileSizeFilter.values())
.filter(filter -> af.getSize() >= filter.getMinBound() && (filter.getMaxBound() == null || af.getSize() < filter.getMaxBound()))
.findFirst()
.orElse(null);
}
if (fileExtensionDsMap.isEmpty() && mimeTypeDsMap.isEmpty() && fileSizeDsMap.isEmpty()) {
return Collections.emptySet();
}
clearRelevantCacheEntries(fileExtensionDsMap, mimeTypeDsMap, fileSizeDsMap);
return getDAOEvents(fileExtensionDsMap, mimeTypeDsMap, fileSizeDsMap);
}
/**
*
* Clears relevant cache entries from cache based on digest of autopsy
* events.
*
* @param fileExtensionDsMap Maps the file extension to the data sources
* where files were found with that extension.
* @param mimeTypeDsMap Maps the mime type to the data sources where
* files were found with that mime type.
* @param fileSizeDsMap Maps the size to the data sources where files
*
* @return The list of affected dao events.
*/
private Set<DAOEvent> getDAOEvents(Map<String, Set<Long>> fileExtensionDsMap,
Map<String, Map<String, Set<Long>>> mimeTypeDsMap,
Map<FileSizeFilter, Set<Long>> fileSizeDsMap) {
Stream<DAOEvent> fileExtStream = fileExtensionDsMap.entrySet().stream()
.flatMap(entry -> entry.getValue().stream().map(dsId -> new FileTypeExtensionsEvent(entry.getKey(), dsId)));
Set<DAOEvent> fileMimeList = new HashSet<>();
for (Entry<String, Map<String, Set<Long>>> prefixEntry : mimeTypeDsMap.entrySet()) {
String mimePrefix = prefixEntry.getKey();
for (Entry<String, Set<Long>> suffixEntry : prefixEntry.getValue().entrySet()) {
String mimeSuffix = suffixEntry.getKey();
for (long dsId : suffixEntry.getValue()) {
String mimeType = mimePrefix + (mimeSuffix == null ? "" : ("/" + mimeSuffix));
fileMimeList.add(new FileTypeMimeEvent(mimeType, dsId));
}
if (evtExtFilters == null || evtExtFilters.isEmpty() && evtMimeType == null && evtFileSize == null) {
return Collections.emptySet();
}
}
Stream<DAOEvent> fileSizeStream = fileSizeDsMap.entrySet().stream()
.flatMap(entry -> entry.getValue().stream().map(dsId -> new FileTypeSizeEvent(entry.getKey(), dsId)));
return invalidateAndReturnEvents(evtExtFilters, evtMimeType, evtFileSize, dsId, dataSourceAdded);
}
return Stream.of(fileExtStream, fileMimeList.stream(), fileSizeStream)
.flatMap(stream -> stream)
/**
* Handles invalidating caches and returning events based on digest.
*
* @param evtExtFilters The file extension filters or empty set.
* @param evtMimeType The mime type or null.
* @param evtFileSize The file size filter or null.
* @param dsId The data source id or null.
* @param dataSourceAdded Whether or not this is a data source added event.
*
* @return The set of dao events to be fired.
*/
private Set<DAOEvent> invalidateAndReturnEvents(Set<FileExtSearchFilter> evtExtFilters, String evtMimeType,
FileSizeFilter evtFileSize, Long dsId, boolean dataSourceAdded) {
SubDAOUtils.invalidateKeys(this.searchParamsCache,
(Predicate<Object>) (searchParams) -> searchParamsMatchEvent(evtExtFilters, evtMimeType,
evtFileSize, dsId, dataSourceAdded, searchParams));
return getDAOEvents(evtExtFilters, evtMimeType, evtFileSize, dsId, dataSourceAdded);
}
private boolean searchParamsMatchEvent(Set<FileExtSearchFilter> evtExtFilters,
String evtMimeType,
FileSizeFilter evtFileSize,
Long dsId,
boolean dataSourceAdded,
Object searchParams) {
if (searchParams instanceof FileTypeExtensionsSearchParams) {
FileTypeExtensionsSearchParams extParams = (FileTypeExtensionsSearchParams) searchParams;
// if data source added or evtExtFilters contain param filter
return (dataSourceAdded || (evtExtFilters != null && evtExtFilters.contains(extParams.getFilter())))
// and data source is either null or they are equal data source ids
&& (extParams.getDataSourceId() == null || dsId == null || Objects.equals(extParams.getDataSourceId(), dsId));
} else if (searchParams instanceof FileTypeMimeSearchParams) {
FileTypeMimeSearchParams mimeParams = (FileTypeMimeSearchParams) searchParams;
return evtMimeType != null && evtMimeType.startsWith(mimeParams.getMimeType())
&& (mimeParams.getDataSourceId() == null || Objects.equals(mimeParams.getDataSourceId(), dsId));
} else if (searchParams instanceof FileTypeSizeSearchParams) {
FileTypeSizeSearchParams sizeParams = (FileTypeSizeSearchParams) searchParams;
// if data source added or size filter is equal to param filter
return (dataSourceAdded || Objects.equals(sizeParams.getSizeFilter(), evtFileSize))
// and data source is either null or they are equal data source ids
&& (sizeParams.getDataSourceId() == null || dsId == null || Objects.equals(sizeParams.getDataSourceId(), dsId));
} else {
return false;
}
}
/**
* Clears relevant cache entries from cache based on digest of autopsy
* events.
*
* @param extFilters The set of affected extension filters.
* @param mimeType The affected mime type or null.
* @param sizeFilter The affected size filter or null.
* @param dsId The file object id.
* @param dataSourceAdded A data source was added.
*
* @return The list of affected dao events.
*/
private Set<DAOEvent> getDAOEvents(Set<FileExtSearchFilter> extFilters, String mimeType, FileSizeFilter sizeFilter, Long dsId, boolean dataSourceAdded) {
List<DAOEvent> daoEvents = extFilters == null
? new ArrayList<>()
: extFilters.stream()
.map(extFilter -> new FileTypeExtensionsEvent(extFilter, dsId))
.collect(Collectors.toList());
if (mimeType != null) {
daoEvents.add(new FileTypeMimeEvent(mimeType, dsId));
}
if (sizeFilter != null) {
daoEvents.add(new FileTypeSizeEvent(sizeFilter, dsId));
}
List<TreeEvent> treeEvents = this.treeCounts.enqueueAll(daoEvents).stream()
.map(daoEvt -> new TreeEvent(createTreeItem(daoEvt, TreeDisplayCount.INDETERMINATE), false))
.collect(Collectors.toList());
// data source added events are not necessarily fired before ingest completed/cancelled, so don't handle dataSourceAdded events with delay.
Set<DAOEvent> forceRefreshEvents = (dataSourceAdded)
? getFileViewRefreshEvents(dsId)
: Collections.emptySet();
List<TreeEvent> forceRefreshTreeEvents = forceRefreshEvents.stream()
.map(evt -> new TreeEvent(createTreeItem(evt, TreeDisplayCount.UNSPECIFIED), true))
.collect(Collectors.toList());
return Stream.of(daoEvents, treeEvents, forceRefreshEvents, forceRefreshTreeEvents)
.flatMap(lst -> lst.stream())
.collect(Collectors.toSet());
}
/**
* Clears relevant cache entries from cache based on digest of autopsy
* events.
* Returns events for when a full refresh is required because module content
* events will not necessarily provide events for files (i.e. data source
* added, ingest cancelled/completed).
*
* @param fileExtensionDsMap Maps the file extension to the data sources
* where files were found with that extension.
* @param mimeTypeDsMap Maps the mime type to the data sources where
* files were found with that mime type.
* @param fileSizeDsMap Maps the size to the data sources where files
* were found within that size filter.
* @param dataSourceId The data source id or null if not applicable.
*
* @return The set of events that apply in this situation.
*/
private void clearRelevantCacheEntries(Map<String, Set<Long>> fileExtensionDsMap,
Map<String, Map<String, Set<Long>>> mimeTypeDsMap,
Map<FileSizeFilter, Set<Long>> fileSizeDsMap) {
// invalidate cache entries that are affected by events
ConcurrentMap<SearchParams<?>, SearchResultsDTO> concurrentMap = this.searchParamsCache.asMap();
concurrentMap.forEach((k, v) -> {
Object baseParams = k.getParamData();
if (baseParams instanceof FileTypeExtensionsSearchParams) {
FileTypeExtensionsSearchParams extParams = (FileTypeExtensionsSearchParams) baseParams;
// if search params have a filter where extension is present and the data source id is null or ==
boolean isMatch = extParams.getFilter().getFilter().stream().anyMatch((ext) -> {
Set<Long> dsIds = fileExtensionDsMap.get(ext);
return (dsIds != null && (extParams.getDataSourceId() == null || dsIds.contains(extParams.getDataSourceId())));
});
if (isMatch) {
concurrentMap.remove(k);
}
} else if (baseParams instanceof FileTypeMimeSearchParams) {
FileTypeMimeSearchParams mimeParams = (FileTypeMimeSearchParams) baseParams;
Pair<String, String> mimePieces = getMimePieces(mimeParams.getMimeType());
Map<String, Set<Long>> suffixes = mimeTypeDsMap.get(mimePieces.getKey());
if (suffixes == null) {
return;
}
// if search params is top level mime prefix (without suffix) and data source is null or ==.
if (mimePieces.getValue() == null
&& (mimeParams.getDataSourceId() == null
|| suffixes.values().stream().flatMap(set -> set.stream()).anyMatch(ds -> Objects.equals(mimeParams.getDataSourceId(), ds)))) {
concurrentMap.remove(k);
// otherwise, see if suffix is present
} else {
Set<Long> dataSources = suffixes.get(mimePieces.getValue());
if (dataSources != null && (mimeParams.getDataSourceId() == null || dataSources.contains(mimeParams.getDataSourceId()))) {
concurrentMap.remove(k);
}
}
} else if (baseParams instanceof FileTypeSizeSearchParams) {
FileTypeSizeSearchParams sizeParams = (FileTypeSizeSearchParams) baseParams;
Set<Long> dataSources = fileSizeDsMap.get(sizeParams.getSizeFilter());
if (dataSources != null && (sizeParams.getDataSourceId() == null || dataSources.contains(sizeParams.getDataSourceId()))) {
concurrentMap.remove(k);
}
}
});
private Set<DAOEvent> getFileViewRefreshEvents(Long dataSourceId) {
return ImmutableSet.of(
new FileTypeSizeEvent(null, dataSourceId),
new FileTypeExtensionsEvent(null, dataSourceId)
);
}
/**
@@ -19,6 +19,8 @@
package org.sleuthkit.autopsy.mainui.datamodel.events;
import java.util.Objects;
import org.sleuthkit.datamodel.Content;
import org.sleuthkit.datamodel.Host;
/**
* An event signaling that children files were added or removed from the given
@@ -26,26 +28,40 @@ import java.util.Objects;
*/
public class FileSystemContentEvent implements DAOEvent {
private final Long contentObjectId;
private final Content content;
private final Long parentObjId;
private final Host parentHost;
public FileSystemContentEvent(Content content, Long parentObjId, Host parentHost) {
this.content = content;
this.parentObjId = parentObjId;
this.parentHost = parentHost;
}
public Long getParentObjId() {
return parentObjId;
}
public Host getParentHost() {
return parentHost;
}
/**
* Main constructor.
*
* @param contentObjectId The parent content object id. If null, performs
* full refresh of file tree.
* @return The content associated with the event, if null, triggers a full
* refresh.
*/
public FileSystemContentEvent(Long contentObjectId) {
this.contentObjectId = contentObjectId;
public Content getContent() {
return content;
}
public Long getContentObjectId() {
return contentObjectId;
return (content == null) ? null : content.getId();
}
@Override
public int hashCode() {
int hash = 7;
hash = 67 * hash + Objects.hashCode(this.contentObjectId);
hash = 71 * hash + Objects.hashCode(this.content);
return hash;
}
@@ -61,7 +77,7 @@ public class FileSystemContentEvent implements DAOEvent {
return false;
}
final FileSystemContentEvent other = (FileSystemContentEvent) obj;
if (!Objects.equals(this.contentObjectId, other.contentObjectId)) {
if (!Objects.equals(this.content, other.content)) {
return false;
}
return true;
@@ -19,34 +19,42 @@
package org.sleuthkit.autopsy.mainui.datamodel.events;
import java.util.Objects;
import org.sleuthkit.autopsy.mainui.datamodel.FileExtSearchFilter;
/**
* An event to signal that files have been added or removed
* with the given extension on the given data source.
* An event to signal that files have been added or removed with the given
* extension on the given data source.
*/
public class FileTypeExtensionsEvent implements DAOEvent {
private final String extension;
private final long dataSourceId;
private final FileExtSearchFilter extensionFilter;
private final Long dataSourceId;
public FileTypeExtensionsEvent(String extension, long dataSourceId) {
this.extension = extension;
/**
* Main constructor.
*
* @param extensionFilter The extension filter. If null, indicates full
* refresh necessary.
* @param dataSourceId The data source id.
*/
public FileTypeExtensionsEvent(FileExtSearchFilter extensionFilter, Long dataSourceId) {
this.extensionFilter = extensionFilter;
this.dataSourceId = dataSourceId;
}
public String getExtension() {
return extension;
public FileExtSearchFilter getExtensionFilter() {
return extensionFilter;
}
public long getDataSourceId() {
public Long getDataSourceId() {
return dataSourceId;
}
@Override
public int hashCode() {
int hash = 7;
hash = 59 * hash + Objects.hashCode(this.extension);
hash = 59 * hash + (int) (this.dataSourceId ^ (this.dataSourceId >>> 32));
int hash = 3;
hash = 89 * hash + Objects.hashCode(this.extensionFilter);
hash = 89 * hash + Objects.hashCode(this.dataSourceId);
return hash;
}
@@ -62,10 +70,10 @@ public class FileTypeExtensionsEvent implements DAOEvent {
return false;
}
final FileTypeExtensionsEvent other = (FileTypeExtensionsEvent) obj;
if (this.dataSourceId != other.dataSourceId) {
if (!Objects.equals(this.extensionFilter, other.extensionFilter)) {
return false;
}
if (!Objects.equals(this.extension, other.extension)) {
if (!Objects.equals(this.dataSourceId, other.dataSourceId)) {
return false;
}
return true;
@@ -22,14 +22,21 @@ import java.util.Objects;
import org.sleuthkit.autopsy.mainui.datamodel.FileSizeFilter;
/**
* An event to signal that files have been added or removed
* within the given size range on the given data source.
* An event to signal that files have been added or removed within the given
* size range on the given data source.
*/
public class FileTypeSizeEvent implements DAOEvent {
private final FileSizeFilter sizeFilter;
private final Long dataSourceId;
/**
* Main constructor.
*
* @param sizeFilter The size filter. If null, indicates full refresh is
* necessary.
* @param dataSourceId The data source id or null.
*/
public FileTypeSizeEvent(FileSizeFilter sizeFilter, Long dataSourceId) {
this.sizeFilter = sizeFilter;
this.dataSourceId = dataSourceId;
@@ -45,9 +52,9 @@ public class FileTypeSizeEvent implements DAOEvent {
@Override
public int hashCode() {
int hash = 7;
hash = 53 * hash + Objects.hashCode(this.sizeFilter);
hash = 53 * hash + Objects.hashCode(this.dataSourceId);
int hash = 5;
hash = 73 * hash + Objects.hashCode(this.sizeFilter);
hash = 73 * hash + Objects.hashCode(this.dataSourceId);
return hash;
}
@@ -72,6 +79,8 @@ public class FileTypeSizeEvent implements DAOEvent {
return true;
}
@Override
public Type getType() {
return Type.RESULT;
@@ -18,7 +18,9 @@
*/
package org.sleuthkit.autopsy.mainui.datamodel.events;
import java.util.Objects;
import org.sleuthkit.datamodel.BlackboardArtifact;
import org.sleuthkit.datamodel.TskData;
/**
* An event for an artifact added or changed of a particular type possibly for a
@@ -26,20 +28,71 @@ import org.sleuthkit.datamodel.BlackboardArtifact;
*/
public class KeywordHitEvent extends AnalysisResultSetEvent {
private final String regex;
private final String searchString;
private final String match;
private final TskData.KeywordSearchQueryType searchType;
public KeywordHitEvent(String regex, String match, String setName, BlackboardArtifact.Type artifactType, long dataSourceId) {
/**
* Main constructor.
*
* @param searchString The search string or regex.
* @param match The match string.
* @param searchType THe search type.
* @param setName The set name.
* @param artifactType The artifact type.
* @param dataSourceId The data source id.
*/
public KeywordHitEvent(String searchString, String match, TskData.KeywordSearchQueryType searchType, String setName, BlackboardArtifact.Type artifactType, long dataSourceId) {
super(setName, artifactType, dataSourceId);
this.regex = regex;
this.searchString = searchString;
this.match = match;
this.searchType = searchType;
}
public String getRegex() {
return regex;
public String getSearchString() {
return searchString;
}
public String getMatch() {
return match;
}
public TskData.KeywordSearchQueryType getSearchType() {
return searchType;
}
@Override
public int hashCode() {
int hash = 7;
hash = 67 * hash + Objects.hashCode(this.searchString);
hash = 67 * hash + Objects.hashCode(this.match);
hash = 67 * hash + Objects.hashCode(this.searchType);
return hash;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final KeywordHitEvent other = (KeywordHitEvent) obj;
if (!Objects.equals(this.searchString, other.searchString)) {
return false;
}
if (!Objects.equals(this.match, other.match)) {
return false;
}
if (this.searchType != other.searchType) {
return false;
}
return true;
}
}
@@ -20,6 +20,7 @@ package org.sleuthkit.autopsy.mainui.datamodel.events;
import java.util.Objects;
import org.sleuthkit.autopsy.mainui.datamodel.TagsSearchParams.TagType;
import org.sleuthkit.datamodel.TagName;
/**
* An event to signal that tags have been added or removed on the
@@ -28,21 +29,22 @@ import org.sleuthkit.autopsy.mainui.datamodel.TagsSearchParams.TagType;
public class TagsEvent implements DAOEvent {
private final TagType type;
private final Long tagNameId;
private final TagName tagName;
private final Long dataSourceId;
public TagsEvent(TagType type, Long tagNameId, Long dataSourceId) {
public TagsEvent(TagType type, TagName tagName, Long dataSourceId) {
this.type = type;
this.tagNameId = tagNameId;
this.tagName = tagName;
this.dataSourceId = dataSourceId;
}
public TagType getTagType() {
return type;
}
public Long getTagNameId() {
return tagNameId;
public TagName getTagName() {
return tagName;
}
/**
@@ -57,7 +59,7 @@ public class TagsEvent implements DAOEvent {
public int hashCode() {
int hash = 7;
hash = 97 * hash + Objects.hashCode(this.type);
hash = 97 * hash + Objects.hashCode(this.tagNameId);
hash = 97 * hash + Objects.hashCode(this.tagName);
hash = 97 * hash + Objects.hashCode(this.dataSourceId);
return hash;
}
@@ -77,7 +79,7 @@ public class TagsEvent implements DAOEvent {
if (this.type != other.type) {
return false;
}
if (!Objects.equals(this.tagNameId, other.tagNameId)) {
if (!Objects.equals(this.tagName, other.tagName)) {
return false;
}
if (!Objects.equals(this.dataSourceId, other.dataSourceId)) {
@@ -86,6 +88,8 @@ public class TagsEvent implements DAOEvent {
return true;
}
@Override
public Type getType() {
return Type.RESULT;
@@ -22,6 +22,7 @@ import org.sleuthkit.autopsy.mainui.datamodel.KeywordSearchTermParams;
import org.sleuthkit.autopsy.mainui.datamodel.KeywordMatchParams;
import com.google.common.collect.ImmutableSet;
import java.util.Comparator;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import org.openide.nodes.ChildFactory;
@@ -29,11 +30,13 @@ import org.openide.nodes.Children;
import org.openide.util.NbBundle.Messages;
import org.sleuthkit.autopsy.corecomponents.DataResultTopComponent;
import org.sleuthkit.autopsy.datamodel.utils.IconsUtil;
import org.sleuthkit.autopsy.mainui.datamodel.AnalysisResultDAO;
import org.sleuthkit.autopsy.mainui.datamodel.AnalysisResultSearchParam;
import org.sleuthkit.autopsy.mainui.datamodel.AnalysisResultSetSearchParam;
import org.sleuthkit.autopsy.mainui.datamodel.KeywordHitSearchParam;
import org.sleuthkit.autopsy.mainui.datamodel.MainDAO;
import org.sleuthkit.autopsy.mainui.datamodel.TreeResultsDTO;
import org.sleuthkit.autopsy.mainui.datamodel.TreeResultsDTO.TreeItemDTO;
import org.sleuthkit.autopsy.mainui.datamodel.events.TreeEvent;
import static org.sleuthkit.autopsy.mainui.nodes.TreeNode.getDefaultLookup;
import org.sleuthkit.datamodel.BlackboardArtifact;
@@ -50,7 +53,8 @@ public class AnalysisResultTypeFactory extends TreeChildFactory<AnalysisResultSe
private static Set<Integer> SET_TREE_ARTIFACTS = ImmutableSet.of(
BlackboardArtifact.Type.TSK_HASHSET_HIT.getTypeID(),
BlackboardArtifact.Type.TSK_INTERESTING_ARTIFACT_HIT.getTypeID(),
BlackboardArtifact.Type.TSK_INTERESTING_FILE_HIT.getTypeID()
BlackboardArtifact.Type.TSK_INTERESTING_FILE_HIT.getTypeID(),
BlackboardArtifact.Type.TSK_INTERESTING_ITEM.getTypeID()
);
/**
@@ -93,14 +97,29 @@ public class AnalysisResultTypeFactory extends TreeChildFactory<AnalysisResultSe
}
@Override
protected TreeResultsDTO.TreeItemDTO<? extends AnalysisResultSearchParam> getOrCreateRelevantChild(TreeEvent daoEvt) {
// GVDTODO
protected TreeResultsDTO.TreeItemDTO<? extends AnalysisResultSearchParam> getOrCreateRelevantChild(TreeEvent treeEvt) {
TreeResultsDTO.TreeItemDTO<AnalysisResultSearchParam> originalTreeItem = super.getTypedTreeItem(treeEvt, AnalysisResultSearchParam.class);
if (originalTreeItem != null
&& !AnalysisResultDAO.getIgnoredTreeTypes().contains(originalTreeItem.getSearchParams().getArtifactType())
&& (this.dataSourceId == null || Objects.equals(this.dataSourceId, originalTreeItem.getSearchParams().getDataSourceId()))) {
// generate new type so that if it is a subtree event (i.e. keyword hits), the right tree item is created.
AnalysisResultSearchParam searchParam = originalTreeItem.getSearchParams();
return new TreeResultsDTO.TreeItemDTO<>(
AnalysisResultSearchParam.getTypeId(),
new AnalysisResultSearchParam(searchParam.getArtifactType(), this.dataSourceId),
searchParam.getArtifactType().getTypeID(),
searchParam.getArtifactType().getDisplayName(),
originalTreeItem.getDisplayCount());
}
return null;
}
@Override
public int compare(AnalysisResultSearchParam o1, AnalysisResultSearchParam o2) {
return o1.getArtifactType().getDisplayName().compareTo(o2.getArtifactType().getDisplayName());
public int compare(TreeItemDTO<? extends AnalysisResultSearchParam> o1, TreeItemDTO<? extends AnalysisResultSearchParam> o2) {
return o1.getSearchParams().getArtifactType().getDisplayName().compareTo(o2.getSearchParams().getArtifactType().getDisplayName());
}
/**
@@ -180,14 +199,28 @@ public class AnalysisResultTypeFactory extends TreeChildFactory<AnalysisResultSe
}
@Override
protected TreeResultsDTO.TreeItemDTO<? extends AnalysisResultSetSearchParam> getOrCreateRelevantChild(TreeEvent daoEvt) {
// GVDTODO
protected TreeResultsDTO.TreeItemDTO<? extends AnalysisResultSetSearchParam> getOrCreateRelevantChild(TreeEvent treeEvt) {
TreeResultsDTO.TreeItemDTO<AnalysisResultSetSearchParam> originalTreeItem = super.getTypedTreeItem(treeEvt, AnalysisResultSetSearchParam.class);
if (originalTreeItem != null
&& originalTreeItem.getSearchParams().getArtifactType().equals(this.artifactType)
&& (this.dataSourceId == null || Objects.equals(this.dataSourceId, originalTreeItem.getSearchParams().getDataSourceId()))) {
// generate new type so that if it is a subtree event (i.e. keyword hits), the right tree item is created.
AnalysisResultSetSearchParam searchParam = originalTreeItem.getSearchParams();
return new TreeResultsDTO.TreeItemDTO<>(
AnalysisResultSetSearchParam.getTypeId(),
new AnalysisResultSetSearchParam(this.artifactType, this.dataSourceId, searchParam.getSetName()),
searchParam.getSetName(),
searchParam.getSetName() == null ? nullSetName : searchParam.getSetName(),
originalTreeItem.getDisplayCount());
}
return null;
}
@Override
public int compare(AnalysisResultSetSearchParam o1, AnalysisResultSetSearchParam o2) {
return STRING_COMPARATOR.compare(o1.getSetName(), o2.getSetName());
public int compare(TreeItemDTO<? extends AnalysisResultSetSearchParam> o1, TreeItemDTO<? extends AnalysisResultSetSearchParam> o2) {
return STRING_COMPARATOR.compare(o1.getSearchParams().getSetName(), o2.getSearchParams().getSetName());
}
}
@@ -277,14 +310,38 @@ public class AnalysisResultTypeFactory extends TreeChildFactory<AnalysisResultSe
}
@Override
protected TreeResultsDTO.TreeItemDTO<? extends KeywordSearchTermParams> getOrCreateRelevantChild(TreeEvent daoEvt) {
// GVDTODO
protected TreeResultsDTO.TreeItemDTO<? extends KeywordSearchTermParams> getOrCreateRelevantChild(TreeEvent treeEvt) {
TreeResultsDTO.TreeItemDTO<KeywordSearchTermParams> originalTreeItem = super.getTypedTreeItem(treeEvt, KeywordSearchTermParams.class);
if (originalTreeItem != null
&& Objects.equals(originalTreeItem.getSearchParams().getSetName(), this.setParams.getSetName())
&& (this.setParams.getDataSourceId() == null
|| Objects.equals(this.setParams.getDataSourceId(), originalTreeItem.getSearchParams().getDataSourceId()))) {
KeywordSearchTermParams searchParam = originalTreeItem.getSearchParams();
String searchTermDisplayName = MainDAO.getInstance().getAnalysisResultDAO()
.getSearchTermDisplayName(searchParam.getSearchTerm(), searchParam.getSearchType());
return new TreeResultsDTO.TreeItemDTO<>(
KeywordSearchTermParams.getTypeId(),
new KeywordSearchTermParams(
this.setParams.getSetName(),
searchParam.getSearchTerm(),
searchParam.getSearchType(),
searchParam.hasChildren(),
this.setParams.getDataSourceId()
),
searchTermDisplayName,
searchTermDisplayName,
originalTreeItem.getDisplayCount()
);
}
return null;
}
@Override
public int compare(KeywordSearchTermParams o1, KeywordSearchTermParams o2) {
return STRING_COMPARATOR.compare(o1.getSearchTerm(), o2.getSearchTerm());
public int compare(TreeItemDTO<? extends KeywordSearchTermParams> o1, TreeItemDTO<? extends KeywordSearchTermParams> o2) {
return STRING_COMPARATOR.compare(o1.getSearchParams().getSearchTerm(), o2.getSearchParams().getSearchTerm());
}
}
@@ -360,14 +417,36 @@ public class AnalysisResultTypeFactory extends TreeChildFactory<AnalysisResultSe
}
@Override
protected TreeResultsDTO.TreeItemDTO<? extends KeywordMatchParams> getOrCreateRelevantChild(TreeEvent daoEvt) {
// GVDTODO
protected TreeResultsDTO.TreeItemDTO<? extends KeywordMatchParams> getOrCreateRelevantChild(TreeEvent treeEvt) {
TreeResultsDTO.TreeItemDTO<KeywordMatchParams> originalTreeItem = super.getTypedTreeItem(treeEvt, KeywordMatchParams.class);
if (originalTreeItem != null
&& Objects.equals(originalTreeItem.getSearchParams().getSetName(), this.setParams.getSetName())
&& (this.setParams.getDataSourceId() == null
|| Objects.equals(this.setParams.getDataSourceId(), originalTreeItem.getSearchParams().getDataSourceId()))) {
// generate new type so that if it is a subtree event (i.e. keyword hits), the right tree item is created.
KeywordMatchParams searchParam = originalTreeItem.getSearchParams();
return new TreeResultsDTO.TreeItemDTO<>(
KeywordMatchParams.getTypeId(),
new KeywordMatchParams(
this.setParams.getSetName(),
this.setParams.getSearchTerm(),
searchParam.getKeywordMatch(),
this.setParams.getSearchType(),
this.setParams.getDataSourceId()
),
searchParam.getKeywordMatch(),
searchParam.getKeywordMatch() == null ? "" : searchParam.getKeywordMatch(),
originalTreeItem.getDisplayCount()
);
}
return null;
}
@Override
public int compare(KeywordMatchParams o1, KeywordMatchParams o2) {
return STRING_COMPARATOR.compare(o1.getKeywordMatch(), o2.getKeywordMatch());
public int compare(TreeItemDTO<? extends KeywordMatchParams> o1, TreeItemDTO<? extends KeywordMatchParams> o2) {
return STRING_COMPARATOR.compare(o1.getSearchParams().getKeywordMatch(), o2.getSearchParams().getKeywordMatch());
}
}
@@ -77,7 +77,7 @@ public class DataArtifactTypeFactory extends TreeChildFactory<DataArtifactSearch
DataArtifactSearchParam searchParam = originalTreeItem.getSearchParams();
return new TreeItemDTO<>(
BlackboardArtifact.Category.DATA_ARTIFACT.name(),
DataArtifactSearchParam.getTypeId(),
new DataArtifactSearchParam(searchParam.getArtifactType(), this.dataSourceId),
searchParam.getArtifactType().getTypeID(),
MainDAO.getInstance().getDataArtifactsDAO().getDisplayName(searchParam.getArtifactType()),
@@ -87,9 +87,9 @@ public class DataArtifactTypeFactory extends TreeChildFactory<DataArtifactSearch
}
@Override
public int compare(DataArtifactSearchParam o1, DataArtifactSearchParam o2) {
public int compare(TreeItemDTO<? extends DataArtifactSearchParam> o1, TreeItemDTO<? extends DataArtifactSearchParam> o2) {
DataArtifactDAO dao = MainDAO.getInstance().getDataArtifactsDAO();
return dao.getDisplayName(o1.getArtifactType()).compareToIgnoreCase(dao.getDisplayName(o2.getArtifactType()));
return dao.getDisplayName(o1.getSearchParams().getArtifactType()).compareToIgnoreCase(dao.getDisplayName(o2.getSearchParams().getArtifactType()));
}
private static String getIconPath(BlackboardArtifact.Type artType) {
@@ -206,8 +206,8 @@ public class DataArtifactTypeFactory extends TreeChildFactory<DataArtifactSearch
}
@Override
public int compare(CommAccountsSearchParams o1, CommAccountsSearchParams o2) {
return o1.getType().getDisplayName().compareToIgnoreCase(o2.getType().getDisplayName());
public int compare(TreeItemDTO<? extends CommAccountsSearchParams> o1, TreeItemDTO<? extends CommAccountsSearchParams> o2) {
return o1.getSearchParams().getType().getDisplayName().compareToIgnoreCase(o2.getSearchParams().getType().getDisplayName());
}
}
@@ -18,6 +18,7 @@
*/
package org.sleuthkit.autopsy.mainui.nodes;
import java.util.Objects;
import java.util.Optional;
import org.openide.nodes.Children;
import org.openide.nodes.Node;
@@ -35,9 +36,12 @@ import org.sleuthkit.autopsy.directorytree.ExtractUnallocAction;
import org.sleuthkit.autopsy.directorytree.FileSystemDetailsAction;
import org.sleuthkit.autopsy.mainui.datamodel.FileSystemContentSearchParam;
import org.sleuthkit.autopsy.mainui.datamodel.FileSystemColumnUtils;
import org.sleuthkit.autopsy.mainui.datamodel.FileSystemDAO.FileSystemTreeEvent;
import org.sleuthkit.autopsy.mainui.datamodel.FileSystemDAO.FileSystemTreeItem;
import org.sleuthkit.autopsy.mainui.datamodel.MediaTypeUtils;
import org.sleuthkit.autopsy.mainui.datamodel.MainDAO;
import org.sleuthkit.autopsy.mainui.datamodel.TreeResultsDTO;
import org.sleuthkit.autopsy.mainui.datamodel.TreeResultsDTO.TreeItemDTO;
import org.sleuthkit.autopsy.mainui.datamodel.events.TreeEvent;
import static org.sleuthkit.autopsy.mainui.nodes.NodeIconUtil.CARVED_FILE;
import static org.sleuthkit.autopsy.mainui.nodes.NodeIconUtil.DELETED_FILE;
@@ -140,14 +144,31 @@ public class FileSystemFactory extends TreeChildFactory<FileSystemContentSearchP
@Override
protected TreeResultsDTO.TreeItemDTO<? extends FileSystemContentSearchParam> getOrCreateRelevantChild(TreeEvent treeEvt) {
// GVDTODO
if (treeEvt instanceof FileSystemTreeEvent) {
FileSystemTreeEvent fsTreeEvent = (FileSystemTreeEvent) treeEvt;
// when getContentObjectId == null, trigger refresh, otherwise, see if common parent
if (fsTreeEvent.getItemRecord().getSearchParams().getContentObjectId() == null
|| (Objects.equals(this.host, fsTreeEvent.getParentHost())
&& Objects.equals(this.contentId, fsTreeEvent.getParentContentId()))) {
return fsTreeEvent.getItemRecord();
}
}
return null;
}
@Override
public int compare(FileSystemContentSearchParam o1, FileSystemContentSearchParam o2) {
// GVDTODO
return 0;
public int compare(TreeItemDTO<? extends FileSystemContentSearchParam> o1, TreeItemDTO<? extends FileSystemContentSearchParam> o2) {
if (o1 instanceof FileSystemTreeItem && o2 instanceof FileSystemTreeItem) {
FileSystemTreeItem fs1 = (FileSystemTreeItem) o1;
FileSystemTreeItem fs2 = (FileSystemTreeItem) o2;
// ordering taken from SELECT_FILES_BY_PARENT in SleuthkitCase
if (fs1.getMetaType().getValue() != fs2.getMetaType().getValue()) {
return -Short.compare(fs1.getMetaType().getValue(), fs2.getMetaType().getValue());
}
}
return o1.getDisplayName().compareToIgnoreCase(o2.getDisplayName());
}
/**
@@ -196,14 +217,23 @@ public class FileSystemFactory extends TreeChildFactory<FileSystemContentSearchP
@Override
protected TreeResultsDTO.TreeItemDTO<? extends FileSystemContentSearchParam> getOrCreateRelevantChild(TreeEvent treeEvt) {
// GVDTODO
if (treeEvt instanceof FileSystemTreeEvent) {
FileSystemTreeEvent fsTreeEvent = (FileSystemTreeEvent) treeEvt;
// when getContentObjectId == null, trigger refresh, otherwise, see if common parent
if (fsTreeEvent.getItemRecord().getSearchParams().getContentObjectId() == null
|| Objects.equals(fsTreeEvent.getItemRecord().getSearchParams().getContentObjectId(), dataSourceId)) {
return fsTreeEvent.getItemRecord();
}
}
return null;
}
@Override
public int compare(FileSystemContentSearchParam o1, FileSystemContentSearchParam o2) {
// GVDTODO
return 0;
public int compare(TreeItemDTO<? extends FileSystemContentSearchParam> o1, TreeItemDTO<? extends FileSystemContentSearchParam> o2) {
return o1.getDisplayName().compareToIgnoreCase(o2.getDisplayName());
}
}
@@ -42,27 +42,13 @@ import org.sleuthkit.autopsy.mainui.datamodel.events.TreeEvent;
/**
* Factory for populating child nodes in a tree based on TreeResultsDTO
*/
public abstract class TreeChildFactory<T> extends ChildFactory.Detachable<Object> implements Comparator<T> {
public abstract class TreeChildFactory<T> extends ChildFactory.Detachable<Object> implements Comparator<TreeItemDTO<? extends T>> {
private static final Logger logger = Logger.getLogger(TreeChildFactory.class.getName());
private final PropertyChangeListener pcl = (PropertyChangeEvent evt) -> {
if (evt.getNewValue() instanceof DAOAggregateEvent) {
DAOAggregateEvent aggEvt = (DAOAggregateEvent) evt.getNewValue();
for (DAOEvent daoEvt : aggEvt.getEvents()) {
if (daoEvt instanceof TreeEvent) {
TreeEvent treeEvt = (TreeEvent) daoEvt;
TreeItemDTO<? extends T> item = getOrCreateRelevantChild(treeEvt);
if (item != null) {
if (treeEvt.isRefreshRequired()) {
update();
break;
} else {
updateNodeData(item);
}
}
}
}
handleDAOAggregateEvent((DAOAggregateEvent) evt.getNewValue());
}
};
@@ -83,20 +69,45 @@ public abstract class TreeChildFactory<T> extends ChildFactory.Detachable<Object
// maps the Node key (ID) to its DTO
private Map<Object, TreeItemDTO<? extends T>> idMapping = new HashMap<>();
/**
* Handles processing and updating due to an aggregate event. This method
* can be overridden for custom behavior while handling DAO aggregate
* events.
*
* @param aggEvt The aggregate event.
*/
protected void handleDAOAggregateEvent(DAOAggregateEvent aggEvt) {
for (DAOEvent daoEvt : aggEvt.getEvents()) {
if (daoEvt instanceof TreeEvent) {
TreeEvent treeEvt = (TreeEvent) daoEvt;
TreeItemDTO<? extends T> item = getOrCreateRelevantChild(treeEvt);
if (item != null) {
if (treeEvt.isRefreshRequired()) {
update();
break;
} else {
updateNodeData(item);
}
}
}
}
}
@Override
protected boolean createKeys(List<Object> toPopulate) {
List<TreeItemDTO<? extends T>> itemsList;
synchronized (resultsUpdateLock) {
// Load data from DAO if we haven't already
if (curResults == null) {
try {
updateData();
} catch (IllegalArgumentException | ExecutionException ex) {
logger.log(Level.WARNING, "An error occurred while fetching keys", ex);
return false;
}
// Load data from DAO if we haven't already
if (curResults == null) {
try {
updateData();
} catch (IllegalArgumentException | ExecutionException ex) {
logger.log(Level.WARNING, "An error occurred while fetching keys", ex);
return false;
}
// make copy to avoid concurrent modification
}
// make copy to avoid concurrent modification
synchronized (resultsUpdateLock) {
itemsList = new ArrayList<>(curItemsList);
}
@@ -140,7 +151,8 @@ public abstract class TreeChildFactory<T> extends ChildFactory.Detachable<Object
// insert in sorted position
int insertIndex = 0;
for (; insertIndex < this.curItemsList.size(); insertIndex++) {
if (this.compare(item.getSearchParams(), this.curItemsList.get(insertIndex).getSearchParams()) < 0) {
TreeItemDTO<? extends T> curItem = this.curItemsList.get(insertIndex);
if (this.compare(item, curItem) < 0) {
break;
}
}
@@ -159,8 +171,9 @@ public abstract class TreeChildFactory<T> extends ChildFactory.Detachable<Object
* @throws ExecutionException
*/
protected void updateData() throws IllegalArgumentException, ExecutionException {
TreeResultsDTO<? extends T> newResults = getChildResults();
synchronized (resultsUpdateLock) {
this.curResults = getChildResults();
this.curResults = newResults;
Map<Object, TreeItemDTO<? extends T>> idMapping = new HashMap<>();
List<TreeItemDTO<? extends T>> curItemsList = new ArrayList<>();
for (TreeItemDTO<? extends T> item : this.curResults.getItems()) {
@@ -18,15 +18,15 @@
*/
package org.sleuthkit.autopsy.mainui.nodes;
import java.beans.PropertyChangeEvent;
import java.util.Collection;
import java.util.Comparator;
import java.util.Objects;
import java.util.concurrent.ExecutionException;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.openide.nodes.Children;
import org.sleuthkit.autopsy.corecomponents.DataResultTopComponent;
import org.sleuthkit.autopsy.ingest.ModuleContentEvent;
import org.sleuthkit.autopsy.mainui.datamodel.AnalysisResultSearchParam;
import org.sleuthkit.autopsy.mainui.datamodel.FileExtDocumentFilter;
import org.sleuthkit.autopsy.mainui.datamodel.FileExtExecutableFilter;
import org.sleuthkit.autopsy.mainui.datamodel.FileExtRootFilter;
@@ -36,8 +36,10 @@ import org.sleuthkit.autopsy.mainui.datamodel.FileTypeMimeSearchParams;
import org.sleuthkit.autopsy.mainui.datamodel.FileTypeSizeSearchParams;
import org.sleuthkit.autopsy.mainui.datamodel.MainDAO;
import org.sleuthkit.autopsy.mainui.datamodel.TreeResultsDTO;
import org.sleuthkit.autopsy.mainui.datamodel.TreeResultsDTO.TreeItemDTO;
import org.sleuthkit.autopsy.mainui.datamodel.events.DAOAggregateEvent;
import org.sleuthkit.autopsy.mainui.datamodel.events.DAOEvent;
import org.sleuthkit.autopsy.mainui.datamodel.events.TreeEvent;
import org.sleuthkit.datamodel.AbstractFile;
/**
*
@@ -74,14 +76,47 @@ public class ViewsTypeFactory {
}
@Override
protected TreeResultsDTO.TreeItemDTO<? extends FileTypeSizeSearchParams> getOrCreateRelevantChild(TreeEvent daoEvt) {
// GVDTODO
protected void handleDAOAggregateEvent(DAOAggregateEvent aggEvt) {
for (DAOEvent evt : aggEvt.getEvents()) {
if (evt instanceof TreeEvent) {
TreeResultsDTO.TreeItemDTO<FileTypeSizeSearchParams> treeItem = super.getTypedTreeItem((TreeEvent) evt, FileTypeSizeSearchParams.class);
// if file type size search params has null filter, trigger full refresh
if (treeItem != null && treeItem.getSearchParams().getSizeFilter() == null) {
super.update();
return;
}
}
}
super.handleDAOAggregateEvent(aggEvt);
}
@Override
protected TreeResultsDTO.TreeItemDTO<? extends FileTypeSizeSearchParams> getOrCreateRelevantChild(TreeEvent treeEvt) {
TreeResultsDTO.TreeItemDTO<FileTypeSizeSearchParams> originalTreeItem = super.getTypedTreeItem(treeEvt, FileTypeSizeSearchParams.class);
if (originalTreeItem != null
// only create child if size filter is present (if null, update should be triggered separately)
&& originalTreeItem.getSearchParams().getSizeFilter() != null
&& (this.dataSourceId == null
|| originalTreeItem.getSearchParams().getDataSourceId() == null
|| Objects.equals(this.dataSourceId, originalTreeItem.getSearchParams().getDataSourceId()))) {
// generate new type so that if it is a subtree event (i.e. keyword hits), the right tree item is created.
FileTypeSizeSearchParams searchParam = originalTreeItem.getSearchParams();
return new TreeResultsDTO.TreeItemDTO<>(
AnalysisResultSearchParam.getTypeId(),
new FileTypeSizeSearchParams(searchParam.getSizeFilter(), this.dataSourceId),
searchParam.getSizeFilter(),
searchParam.getSizeFilter().getDisplayName(),
originalTreeItem.getDisplayCount());
}
return null;
}
@Override
public int compare(FileTypeSizeSearchParams o1, FileTypeSizeSearchParams o2) {
return Integer.compare(o1.getSizeFilter().getId(), o2.getSizeFilter().getId());
public int compare(TreeItemDTO<? extends FileTypeSizeSearchParams> o1, TreeItemDTO<? extends FileTypeSizeSearchParams> o2) {
return Integer.compare(o1.getSearchParams().getSizeFilter().getId(), o2.getSearchParams().getSizeFilter().getId());
}
/**
@@ -133,14 +168,34 @@ public class ViewsTypeFactory {
}
@Override
protected TreeResultsDTO.TreeItemDTO<? extends FileTypeMimeSearchParams> getOrCreateRelevantChild(TreeEvent daoEvt) {
// GVDTODO
protected TreeResultsDTO.TreeItemDTO<? extends FileTypeMimeSearchParams> getOrCreateRelevantChild(TreeEvent treeEvt) {
TreeResultsDTO.TreeItemDTO<FileTypeMimeSearchParams> originalTreeItem = super.getTypedTreeItem(treeEvt, FileTypeMimeSearchParams.class);
if (originalTreeItem != null
&& (this.dataSourceId == null || Objects.equals(this.dataSourceId, originalTreeItem.getSearchParams().getDataSourceId()))) {
// generate new type so that if it is a subtree event (i.e. keyword hits), the right tree item is created.
FileTypeMimeSearchParams searchParam = originalTreeItem.getSearchParams();
String mimePrefix = searchParam.getMimeType() == null ? "" : searchParam.getMimeType();
int indexOfSlash = mimePrefix.indexOf("/");
if (indexOfSlash >= 0) {
mimePrefix = mimePrefix.substring(0, indexOfSlash);
}
return new TreeResultsDTO.TreeItemDTO<>(
AnalysisResultSearchParam.getTypeId(),
new FileTypeMimeSearchParams(mimePrefix, this.dataSourceId),
mimePrefix,
mimePrefix,
originalTreeItem.getDisplayCount());
}
return null;
}
@Override
public int compare(FileTypeMimeSearchParams o1, FileTypeMimeSearchParams o2) {
return STRING_COMPARATOR.compare(o1.getMimeType(), o2.getMimeType());
public int compare(TreeItemDTO<? extends FileTypeMimeSearchParams> o1, TreeItemDTO<? extends FileTypeMimeSearchParams> o2) {
return STRING_COMPARATOR.compare(o1.getSearchParams().getMimeType(), o2.getSearchParams().getMimeType());
}
static class FileMimePrefixNode extends TreeNode<FileTypeMimeSearchParams> {
@@ -193,14 +248,30 @@ public class ViewsTypeFactory {
}
@Override
protected TreeResultsDTO.TreeItemDTO<? extends FileTypeMimeSearchParams> getOrCreateRelevantChild(TreeEvent daoEvt) {
// GVDTODO
protected TreeResultsDTO.TreeItemDTO<? extends FileTypeMimeSearchParams> getOrCreateRelevantChild(TreeEvent treeEvt) {
TreeResultsDTO.TreeItemDTO<FileTypeMimeSearchParams> originalTreeItem = super.getTypedTreeItem(treeEvt, FileTypeMimeSearchParams.class);
String prefixWithSlash = this.mimeTypePrefix + "/";
if (originalTreeItem != null
&& (originalTreeItem.getSearchParams().getMimeType().startsWith(prefixWithSlash))
&& (this.dataSourceId == null || Objects.equals(this.dataSourceId, originalTreeItem.getSearchParams().getDataSourceId()))) {
// generate new type so that if it is a subtree event (i.e. keyword hits), the right tree item is created.
FileTypeMimeSearchParams searchParam = originalTreeItem.getSearchParams();
String mimeSuffix = searchParam.getMimeType().substring(prefixWithSlash.length());
return new TreeResultsDTO.TreeItemDTO<>(
AnalysisResultSearchParam.getTypeId(),
new FileTypeMimeSearchParams(searchParam.getMimeType(), this.dataSourceId),
mimeSuffix,
mimeSuffix,
originalTreeItem.getDisplayCount());
}
return null;
}
@Override
public int compare(FileTypeMimeSearchParams o1, FileTypeMimeSearchParams o2) {
return STRING_COMPARATOR.compare(o1.getMimeType(), o2.getMimeType());
public int compare(TreeItemDTO<? extends FileTypeMimeSearchParams> o1, TreeItemDTO<? extends FileTypeMimeSearchParams> o2) {
return STRING_COMPARATOR.compare(o1.getSearchParams().getMimeType(), o2.getSearchParams().getMimeType());
}
/**
@@ -278,14 +349,46 @@ public class ViewsTypeFactory {
}
@Override
protected TreeResultsDTO.TreeItemDTO<? extends FileTypeExtensionsSearchParams> getOrCreateRelevantChild(TreeEvent daoEvt) {
//GVDTODO
protected void handleDAOAggregateEvent(DAOAggregateEvent aggEvt) {
for (DAOEvent evt : aggEvt.getEvents()) {
if (evt instanceof TreeEvent) {
TreeResultsDTO.TreeItemDTO<FileTypeExtensionsSearchParams> treeItem = super.getTypedTreeItem((TreeEvent) evt, FileTypeExtensionsSearchParams.class);
// if search params has null filter, trigger full refresh
if (treeItem != null && treeItem.getSearchParams().getFilter() == null) {
super.update();
return;
}
}
}
super.handleDAOAggregateEvent(aggEvt);
}
@Override
protected TreeResultsDTO.TreeItemDTO<? extends FileTypeExtensionsSearchParams> getOrCreateRelevantChild(TreeEvent treeEvt) {
TreeResultsDTO.TreeItemDTO<FileTypeExtensionsSearchParams> originalTreeItem = super.getTypedTreeItem(treeEvt, FileTypeExtensionsSearchParams.class);
if (originalTreeItem != null
// if filter is null, this should trigger a full refresh which should be handled in handleDAOAggregateEvent
&& originalTreeItem.getSearchParams().getFilter() != null
&& this.childFilters.contains(originalTreeItem.getSearchParams().getFilter())
&& (this.dataSourceId == null || Objects.equals(this.dataSourceId, originalTreeItem.getSearchParams().getDataSourceId()))) {
// generate new type so that if it is a subtree event (i.e. keyword hits), the right tree item is created.
FileTypeExtensionsSearchParams searchParam = originalTreeItem.getSearchParams();
return new TreeResultsDTO.TreeItemDTO<>(
AnalysisResultSearchParam.getTypeId(),
new FileTypeExtensionsSearchParams(searchParam.getFilter(), this.dataSourceId),
searchParam.getFilter(),
searchParam.getFilter().getDisplayName(),
originalTreeItem.getDisplayCount());
}
return null;
}
@Override
public int compare(FileTypeExtensionsSearchParams o1, FileTypeExtensionsSearchParams o2) {
return STRING_COMPARATOR.compare(o1.getFilter().getDisplayName(), o2.getFilter().getDisplayName());
public int compare(TreeItemDTO<? extends FileTypeExtensionsSearchParams> o1, TreeItemDTO<? extends FileTypeExtensionsSearchParams> o2) {
return STRING_COMPARATOR.compare(o1.getSearchParams().getFilter().getDisplayName(), o2.getSearchParams().getFilter().getDisplayName());
}
/**