Move ChildFactories into classes which use them. Make CommonFilesMetaDataBuilder to handle queries/ list of model building / deduping files. Simplifies CommonFilesMetaData by separating business logic from model and CommonFilesPanel. Removes yet another query by re-using the dataSourceIdMap from CommonFilesPanel for model creation, so now CommonFiles is down to 2 queries total, including UI building.

This commit is contained in:
Andrew Ziehl 2018-04-02 15:32:38 -07:00 committed by Brian Sweeney
parent 084f29ae28
commit 4d75dabe23
7 changed files with 252 additions and 251 deletions

View File

@ -1,55 +0,0 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2018 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sleuthkit.autopsy.commonfilesearch;
import java.util.List;
import org.openide.nodes.ChildFactory;
import org.openide.nodes.Node;
import org.sleuthkit.autopsy.datamodel.CommonFileParentNode;
/**
* Makes nodes for common files search results.
*/
public final class CommonFilesChildren extends ChildFactory<CommonFilesMetaData> {
private List<CommonFilesMetaData> metaDataList;
public CommonFilesChildren(List<CommonFilesMetaData> theMetaDataList) {
super();
this.metaDataList = theMetaDataList;
}
protected void removeNotify() {
metaDataList = null;
}
@Override
protected Node createNodeForKey(CommonFilesMetaData metaData) {
return new CommonFileParentNode(metaData);
}
@Override
protected boolean createKeys(List<CommonFilesMetaData> toPopulate) {
toPopulate.addAll(metaDataList);
return true;
}
}

View File

@ -1,57 +0,0 @@
/*
*
* Autopsy Forensic Browser
*
* Copyright 2018 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sleuthkit.autopsy.commonfilesearch;
import java.util.List;
import java.util.Map;
import org.openide.nodes.ChildFactory;
import org.openide.nodes.Node;
import org.sleuthkit.autopsy.datamodel.CommonFileChildNode;
import org.sleuthkit.datamodel.AbstractFile;
/**
* Child generator for FileNodes of CommonFileParentNodes
*/
public class CommonFilesDescendants extends ChildFactory<AbstractFile> {
private final List<AbstractFile> descendants;
private Map<Long, String> dataSourceMap;
public CommonFilesDescendants(List<AbstractFile> descendants, Map<Long, String> dataSourceMap){
super();
this.descendants = descendants;
this.dataSourceMap = dataSourceMap;
}
@Override
protected Node createNodeForKey(AbstractFile file){
final String dataSource = this.dataSourceMap.get(file.getDataSourceObjectId());
return new CommonFileChildNode(file, dataSource);
}
@Override
protected boolean createKeys(List<AbstractFile> list) {
list.addAll(this.descendants);
return true;
}
}

View File

@ -28,7 +28,7 @@ import org.sleuthkit.datamodel.AbstractFile;
import org.sleuthkit.datamodel.TskCoreException;
/**
* Utility and wrapper around data required for Common Files Search results.
* Utility and wrapper model around data required for Common Files Search results.
* Subclass this to implement different selections of files from the case.
*/
public class CommonFilesMetaData {

View File

@ -0,0 +1,121 @@
/*
*
* Autopsy Forensic Browser
*
* Copyright 2018 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sleuthkit.autopsy.commonfilesearch;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.openide.util.Exceptions;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
import org.sleuthkit.datamodel.AbstractFile;
import org.sleuthkit.datamodel.SleuthkitCase;
import org.sleuthkit.datamodel.TskCoreException;
/**
*
* Generates a List<CommonFilesMetaData> when collateFiles() is called, which organizes
* AbstractFiles by md5 to prepare to display in viewer.
*/
class CommonFilesMetaDataBuilder {
private final Long selectedDataSourceId;
private final Map<Long, String> dataSourceIdToNameMap;
private final String singleDataSourceWhereClause = "md5 in (select md5 from tsk_files where data_source_obj_id=%s and (known != 1 OR known IS NULL) GROUP BY md5 HAVING COUNT(*) > 1) AND data_source_obj_id=%s order by md5";
private final String allDataSourcesWhereClause = "md5 in (select md5 from tsk_files where (known != 1 OR known IS NULL) GROUP BY md5 HAVING COUNT(*) > 1) order by md5";
CommonFilesMetaDataBuilder(Long dataSourceId, Map<Long, String> dataSourceIdMap) {
selectedDataSourceId = dataSourceId;
dataSourceIdToNameMap = dataSourceIdMap;
}
private void addDataSource(Set<String> dataSources, AbstractFile file, Map<Long, String> dataSourceIdToNameMap) {
long datasourceId = file.getDataSourceObjectId();
String dataSourceName = dataSourceIdToNameMap.get(datasourceId);
dataSources.add(dataSourceName);
}
/**
* Sorts files in selection into a parent/child hierarchy where actual files
* are nested beneath a parent node which represents the common match.
*
* @return returns a reference to itself for ease of use.
* @throws TskCoreException
*/
List<CommonFilesMetaData> collateFiles() throws TskCoreException, SQLException {
List<CommonFilesMetaData> metaDataModels = new ArrayList<>();
Map<String, Set<String>> md5ToDataSourcesStringMap = new HashMap<>();
try {
List<AbstractFile> files = FindCommonFiles();
Map<String, List<AbstractFile>> parentNodes = new HashMap<>();
collateParentChildRelationships(files, parentNodes, md5ToDataSourcesStringMap);
for (String key : parentNodes.keySet()) {
metaDataModels.add(new CommonFilesMetaData(key, parentNodes.get(key), String.join(", ", md5ToDataSourcesStringMap.get(key)), dataSourceIdToNameMap));
}
} catch (NoCurrentCaseException ex) {
Exceptions.printStackTrace(ex);
}
return metaDataModels;
}
private void collateParentChildRelationships(List<AbstractFile> files, Map<String, List<AbstractFile>> parentNodes, Map<String, Set<String>> md5ToDataSourcesStringMap) {
for (AbstractFile file : files) {
String currentMd5 = file.getMd5Hash();
if (parentNodes.containsKey(currentMd5)) {
parentNodes.get(currentMd5).add(file);
Set<String> currenDataSources = md5ToDataSourcesStringMap.get(currentMd5);
addDataSource(currenDataSources, file, dataSourceIdToNameMap);
md5ToDataSourcesStringMap.put(currentMd5, currenDataSources);
} else {
List<AbstractFile> children = new ArrayList<>();
Set<String> dataSources = new HashSet<>();
children.add(file);
parentNodes.put(currentMd5, children);
addDataSource(dataSources, file, dataSourceIdToNameMap);
md5ToDataSourcesStringMap.put(currentMd5, dataSources);
}
}
}
private List<AbstractFile> FindCommonFiles() throws TskCoreException, NoCurrentCaseException {
SleuthkitCase sleuthkitCase;
sleuthkitCase = Case.getOpenCase().getSleuthkitCase();
String whereClause = allDataSourcesWhereClause;
if (selectedDataSourceId != 0L) {
Object[] args = new String[]{Long.toString(selectedDataSourceId), Long.toString(selectedDataSourceId)};
whereClause = String.format(singleDataSourceWhereClause, args);
}
List<AbstractFile> files = sleuthkitCase.findAllFilesWhere(whereClause);
return files;
}
}

View File

@ -58,7 +58,7 @@ public final class CommonFilesPanel extends javax.swing.JPanel {
private static final long serialVersionUID = 1L;
private final ComboBoxModel<String> dataSourcesList;
private final Map<Long, String> dataSourceMap;
private final Map<Long, String> dataSourceMap;
private static final Logger LOGGER = Logger.getLogger(CommonFilesPanel.class.getName());
private boolean singleDataSource;
@ -67,7 +67,7 @@ public final class CommonFilesPanel extends javax.swing.JPanel {
/**
* Creates new form CommonFilesPanel
*/
@NbBundle.Messages({
@NbBundle.Messages({
"CommonFilesPanel.title=Common Files Panel",
"CommonFilesPanel.exception=Unexpected Exception loading DataSources."})
public CommonFilesPanel() {
@ -81,8 +81,8 @@ public final class CommonFilesPanel extends javax.swing.JPanel {
dataSourcesNames = dataSourceMap.values().toArray(dataSourcesNames);
} catch (TskCoreException | NoCurrentCaseException | SQLException ex) {
LOGGER.log(Level.SEVERE, "Interrupted while loading Common Files Panel Data Sources", ex);
MessageNotifyUtil.Message.error(Bundle.CommonFilesPanel_exception());
LOGGER.log(Level.SEVERE, "Interrupted while loading Common Files Panel Data Sources", ex);
MessageNotifyUtil.Message.error(Bundle.CommonFilesPanel_exception());
}
this.dataSourcesList = new DataSourceComboBoxModel(dataSourcesNames);
initComponents();
@ -93,114 +93,34 @@ public final class CommonFilesPanel extends javax.swing.JPanel {
boolean multipleDataSources = this.caseHasMultipleSources();
allDataSourcesRadioButton.setEnabled(multipleDataSources);
allDataSourcesRadioButton.setSelected(multipleDataSources);
if(!multipleDataSources) {
withinDataSourceRadioButton.setSelected(true);
withinDataSourceSelected(true);
if (!multipleDataSources) {
withinDataSourceRadioButton.setSelected(true);
withinDataSourceSelected(true);
}
}
private boolean caseHasMultipleSources(){
private boolean caseHasMultipleSources() {
return dataSourceMap.size() >= 2;
}
private void buildDataSourceMap(Map<Long, String> dataSourceMap) throws TskCoreException, NoCurrentCaseException, SQLException {
Case currentCase = Case.getOpenCase();
SleuthkitCase tskDb = currentCase.getSleuthkitCase();
CaseDbQuery query = tskDb.executeQuery("select obj_id, name from tsk_files where obj_id in (SELECT obj_id FROM tsk_objects WHERE obj_id in (select obj_id from data_source_info))");
private void buildDataSourceMap(Map<Long, String> dataSourceMap) throws TskCoreException, NoCurrentCaseException, SQLException {
Case currentCase = Case.getOpenCase();
SleuthkitCase tskDb = currentCase.getSleuthkitCase();
CaseDbQuery query = tskDb.executeQuery("select obj_id, name from tsk_files where obj_id in (SELECT obj_id FROM tsk_objects WHERE obj_id in (select obj_id from data_source_info))");
ResultSet resultSet = query.getResultSet();
while(resultSet.next()){
Long objectId = resultSet.getLong(1);
String dataSourceName = resultSet.getString(2);
dataSourceMap.put(objectId, dataSourceName);
}
ResultSet resultSet = query.getResultSet();
while (resultSet.next()) {
Long objectId = resultSet.getLong(1);
String dataSourceName = resultSet.getString(2);
dataSourceMap.put(objectId, dataSourceName);
}
}
void addListenerToAll(ActionListener l) { //TODO double click the button
this.searchButton.addActionListener(l);
}
private Map<Long, String> loadDataSourcesMap(SleuthkitCase sleuthkitCase) throws SQLException, TskCoreException {
Map<Long, String> dataSourceIdToNameMap = new HashMap<>();
try (
SleuthkitCase.CaseDbQuery query = sleuthkitCase.executeQuery("select obj_id, name from tsk_files where obj_id in (SELECT obj_id FROM tsk_objects WHERE obj_id in (select obj_id from data_source_info))");
ResultSet resultSet = query.getResultSet()) {
while (resultSet.next()) {
Long objectId = resultSet.getLong(1);
String dataSourceName = resultSet.getString(2);
dataSourceIdToNameMap.put(objectId, dataSourceName);
}
}
return dataSourceIdToNameMap;
}
private void addDataSource(Set<String> dataSources, AbstractFile file, Map<Long,String> dataSourceIdToNameMap) {
long datasourceId = file.getDataSourceObjectId();
String dataSourceName = dataSourceIdToNameMap.get(datasourceId);
dataSources.add(dataSourceName);
}
/**
* Sorts files in selection into a parent/child hierarchy where actual files
* are nested beneath a parent node which represents the common match.
*
* @return returns a reference to itself for ease of use.
* @throws TskCoreException
*/
private List<CommonFilesMetaData> collateFiles(Long selectedObjId) throws TskCoreException, SQLException {
SleuthkitCase sleuthkitCase;
List<CommonFilesMetaData> metaDataModels = new ArrayList<>();
Map<String, Set<String>> md5ToDataSourcesStringMap = new HashMap<>();
try {
sleuthkitCase = Case.getOpenCase().getSleuthkitCase();
Map<Long, String> dataSourceIdToNameMap = loadDataSourcesMap(sleuthkitCase);
String whereClause = "md5 in (select md5 from tsk_files where (known != 1 OR known IS NULL) GROUP BY md5 HAVING COUNT(*) > 1) order by md5";
if(selectedObjId != 0L) {
Object[] args = new String[] {Long.toString(selectedObjId), Long.toString(selectedObjId)};
whereClause = String.format(
"md5 in (select md5 from tsk_files where data_source_obj_id=%s and (known != 1 OR known IS NULL) GROUP BY md5 HAVING COUNT(*) > 1) AND data_source_obj_id=%s order by md5",
args);
}
List<AbstractFile> files = sleuthkitCase.findAllFilesWhere(whereClause);
Map<String, List<AbstractFile>> parentNodes = new HashMap<>();
for (AbstractFile file : files) {
String currentMd5 = file.getMd5Hash();
if (parentNodes.containsKey(currentMd5)) {
parentNodes.get(currentMd5).add(file);
Set<String> currenDataSources = md5ToDataSourcesStringMap.get(currentMd5);
addDataSource(currenDataSources, file, dataSourceIdToNameMap);
md5ToDataSourcesStringMap.put(currentMd5, currenDataSources);
} else {
List<AbstractFile> children = new ArrayList<>();
Set<String> dataSources = new HashSet<>();
children.add(file);
parentNodes.put(currentMd5, children);
addDataSource(dataSources, file, dataSourceIdToNameMap);
md5ToDataSourcesStringMap.put(currentMd5, dataSources);
}
}
for (String key : parentNodes.keySet()) {
metaDataModels.add(new CommonFilesMetaData(key, parentNodes.get(key), String.join(", ", md5ToDataSourcesStringMap.get(key)), dataSourceIdToNameMap));
}
} catch (NoCurrentCaseException ex) {
Exceptions.printStackTrace(ex);
}
return metaDataModels;
}
@NbBundle.Messages({
"CommonFilesPanel.search.results.title=Common Files",
"CommonFilesPanel.search.results.pathText=Common Files Search Results",
@ -216,21 +136,24 @@ public final class CommonFilesPanel extends javax.swing.JPanel {
new SwingWorker<List<CommonFilesMetaData>, Void>() {
private Long determineDataSourceId() {
Long selectedObjId = 0L;
if (singleDataSource) {
for (Entry<Long, String> dataSource : dataSourceMap.entrySet()) {
if (dataSource.getValue().equals(selectedDataSource)) {
selectedObjId = dataSource.getKey();
break;
}
}
}
return selectedObjId;
}
@Override
@SuppressWarnings("FinallyDiscardsException")
protected List<CommonFilesMetaData> doInBackground() throws TskCoreException, NoCurrentCaseException, SQLException {
//TODO cleanup - encapsulate business logic
Long selectedObjId = 0L;
if(singleDataSource) {
for (Entry<Long, String> dataSource : dataSourceMap.entrySet()) {
if (dataSource.getValue().equals(selectedDataSource)) {
selectedObjId = dataSource.getKey();
break;
}
}
}
return collateFiles(selectedObjId);
Long selectedObjId = determineDataSourceId();
return new CommonFilesMetaDataBuilder(selectedObjId, dataSourceMap).collateFiles();
}
@ -250,9 +173,8 @@ public final class CommonFilesPanel extends javax.swing.JPanel {
DataResultTopComponent component = DataResultTopComponent.createInstance(title);
//component.enableTreeMode();
int totalNodes = 0;
for(CommonFilesMetaData meta : metadata) {
for (CommonFilesMetaData meta : metadata) {
totalNodes += meta.getChildren().size();
}
DataResultTopComponent.initInstance(pathText, tableFilterWithDescendantsNode, totalNodes, component);
@ -291,6 +213,7 @@ public final class CommonFilesPanel extends javax.swing.JPanel {
DataSourceComboBoxModel(String[] theDataSoureList) {
dataSourceList = theDataSoureList;
}
@Override
public void setSelectedItem(Object anItem) {
selection = (String) anItem;
@ -308,21 +231,20 @@ public final class CommonFilesPanel extends javax.swing.JPanel {
@Override
public String getElementAt(int index) {
return dataSourceList[index];
return dataSourceList[index];
}
@Override
public void addListDataListener(ListDataListener l) {
this.listenerList.add(ListDataListener.class, l);
this.listenerList.add(ListDataListener.class, l);
}
@Override
public void removeListDataListener(ListDataListener l) {
this.listenerList.remove(ListDataListener.class, l);
this.listenerList.remove(ListDataListener.class, l);
}
}
/**
@ -409,8 +331,8 @@ public final class CommonFilesPanel extends javax.swing.JPanel {
}//GEN-LAST:event_searchButtonActionPerformed
private void allDataSourcesRadioButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_allDataSourcesRadioButtonActionPerformed
selectDataSourceComboBox.setEnabled(!allDataSourcesRadioButton.isSelected());
singleDataSource = false;
selectDataSourceComboBox.setEnabled(!allDataSourcesRadioButton.isSelected());
singleDataSource = false;
}//GEN-LAST:event_allDataSourcesRadioButtonActionPerformed
@ -424,7 +346,7 @@ public final class CommonFilesPanel extends javax.swing.JPanel {
private void withinDataSourceSelected(boolean selected) {
selectDataSourceComboBox.setEnabled(selected);
if(selectDataSourceComboBox.isEnabled()) {
if (selectDataSourceComboBox.isEnabled()) {
selectDataSourceComboBox.setSelectedIndex(0);
singleDataSource = true;
}

View File

@ -19,20 +19,23 @@
package org.sleuthkit.autopsy.commonfilesearch;
import java.util.List;
import org.openide.nodes.ChildFactory;
import org.openide.nodes.Children;
import org.openide.nodes.Node;
import org.openide.util.NbBundle;
import org.openide.util.lookup.Lookups;
import org.sleuthkit.autopsy.datamodel.CommonFileParentNode;
import org.sleuthkit.autopsy.datamodel.DisplayableItemNode;
import org.sleuthkit.autopsy.datamodel.DisplayableItemNodeVisitor;
/**
* Encapsulates data used to display common files search results in the top
* right pane.
* Wrapper node for CommonFilesParentNode used to display common files search results in the top
* right pane. Calls CommonFilesParentFactory.
*/
final public class CommonFilesSearchNode extends DisplayableItemNode {
CommonFilesSearchNode(List<CommonFilesMetaData> metaDataList) {
super(Children.create(new CommonFilesChildren(metaDataList), true), Lookups.singleton(metaDataList));
super(Children.create(new CommonFilesParentFactory(metaDataList), true), Lookups.singleton(metaDataList));
}
@NbBundle.Messages({
@ -56,4 +59,39 @@ final public class CommonFilesSearchNode extends DisplayableItemNode {
public String getItemType() {
return getClass().getName();
}
/**
* ChildFactory which builds CommonFileParentNodes from the CommonFilesMetaaData models.
*/
static class CommonFilesParentFactory extends ChildFactory<CommonFilesMetaData> {
/**
* List of models, each of which is a parent node matching a single md5, containing children FileNodes.
*/
private List<CommonFilesMetaData> metaDataList;
CommonFilesParentFactory(List<CommonFilesMetaData> theMetaDataList) {
this.metaDataList = theMetaDataList;
}
protected void removeNotify() {
metaDataList = null;
}
@Override
protected Node createNodeForKey(CommonFilesMetaData metaData) {
return new CommonFileParentNode(metaData);
}
@Override
protected boolean createKeys(List<CommonFilesMetaData> toPopulate) {
toPopulate.addAll(metaDataList);
return true;
}
}
}

View File

@ -20,13 +20,16 @@
package org.sleuthkit.autopsy.datamodel;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.openide.nodes.ChildFactory;
import org.openide.nodes.Children;
import org.openide.nodes.Node;
import org.openide.nodes.Sheet;
import org.openide.util.NbBundle;
import org.openide.util.lookup.Lookups;
import org.sleuthkit.autopsy.commonfilesearch.CommonFilesDescendants;
import org.sleuthkit.autopsy.commonfilesearch.CommonFilesMetaData;
import org.sleuthkit.datamodel.AbstractFile;
/**
* Represents a common files match - two or more files which appear to be the
@ -40,7 +43,7 @@ public class CommonFileParentNode extends DisplayableItemNode {
public CommonFileParentNode(CommonFilesMetaData metaData) {
super(Children.create(
new CommonFilesDescendants(metaData.getChildren(),
new CommonFilesChildFactory(metaData.getChildren(),
metaData.getDataSourceIdToNameMap()), true),
Lookups.singleton(metaData));
this.commonFileCount = metaData.getChildren().size();
@ -111,6 +114,35 @@ public class CommonFileParentNode extends DisplayableItemNode {
return getClass().getName();
}
/**
* Child generator for FileNodes of CommonFileParentNodes
*/
static class CommonFilesChildFactory extends ChildFactory<AbstractFile> {
private final List<AbstractFile> descendants;
private final Map<Long, String> dataSourceMap;
CommonFilesChildFactory(List<AbstractFile> descendants, Map<Long, String> dataSourceMap){
this.descendants = descendants;
this.dataSourceMap = dataSourceMap;
}
@Override
protected Node createNodeForKey(AbstractFile file){
final String dataSource = this.dataSourceMap.get(file.getDataSourceObjectId());
return new CommonFileChildNode(file, dataSource);
}
@Override
protected boolean createKeys(List<AbstractFile> list) {
list.addAll(this.descendants);
return true;
}
}
@NbBundle.Messages({
"CommonFileParentPropertyType.fileColLbl=File",
"CommonFileParentPropertyType.instanceColLbl=Instance Count",