Merge branch 'develop' of github.com:sleuthkit/autopsy into 4965_paging_ui

This commit is contained in:
esaunders
2019-04-23 16:50:29 -04:00
158 changed files with 3983 additions and 1601 deletions
+1 -1
View File
@@ -2,7 +2,7 @@ Manifest-Version: 1.0
OpenIDE-Module: org.sleuthkit.autopsy.core/10
OpenIDE-Module-Localizing-Bundle: org/sleuthkit/autopsy/core/Bundle.properties
OpenIDE-Module-Layer: org/sleuthkit/autopsy/core/layer.xml
OpenIDE-Module-Implementation-Version: 26
OpenIDE-Module-Implementation-Version: 27
OpenIDE-Module-Requires: org.openide.windows.WindowManager
AutoUpdate-Show-In-Client: true
AutoUpdate-Essential-Module: true
+1 -1
View File
@@ -83,5 +83,5 @@ nbm.homepage=http://www.sleuthkit.org/
nbm.module.author=Brian Carrier
nbm.needs.restart=true
source.reference.curator-recipes-2.8.0.jar=release/modules/ext/curator-recipes-2.8.0-sources.jar
spec.version.base=10.14
spec.version.base=10.15
@@ -81,6 +81,7 @@ import org.sleuthkit.autopsy.casemodule.events.ContentTagDeletedEvent;
import org.sleuthkit.autopsy.casemodule.events.DataSourceAddedEvent;
import org.sleuthkit.autopsy.casemodule.events.DataSourceNameChangedEvent;
import org.sleuthkit.autopsy.casemodule.events.ReportAddedEvent;
import org.sleuthkit.autopsy.casemodule.multiusercases.CaseNodeData.CaseNodeDataException;
import org.sleuthkit.autopsy.casemodule.multiusercases.CoordinationServiceUtils;
import org.sleuthkit.autopsy.casemodule.services.Services;
import org.sleuthkit.autopsy.commonpropertiessearch.CommonAttributeSearchAction;
@@ -1620,11 +1621,10 @@ public class Case {
}
if (getCaseType() == CaseType.MULTI_USER_CASE && !oldCaseDetails.getCaseDisplayName().equals(caseDetails.getCaseDisplayName())) {
try {
CoordinationService coordinationService = CoordinationService.getInstance();
CaseNodeData nodeData = new CaseNodeData(coordinationService.getNodeData(CategoryNode.CASES, metadata.getCaseDirectory()));
CaseNodeData nodeData = CaseNodeData.readCaseNodeData(metadata.getCaseDirectory());
nodeData.setDisplayName(caseDetails.getCaseDisplayName());
coordinationService.setNodeData(CategoryNode.CASES, metadata.getCaseDirectory(), nodeData.toArray());
} catch (CoordinationServiceException | InterruptedException | IOException ex) {
CaseNodeData.writeCaseNodeData(nodeData);
} catch (CaseNodeDataException | InterruptedException ex) {
throw new CaseActionException(Bundle.Case_exceptionMessage_couldNotUpdateCaseNodeData(ex.getLocalizedMessage()), ex);
}
}
@@ -2005,10 +2005,8 @@ public class Case {
if (getCaseType() == CaseType.MULTI_USER_CASE) {
progressIndicator.progress(Bundle.Case_progressMessage_creatingCaseNodeData());
try {
CoordinationService coordinationService = CoordinationService.getInstance();
CaseNodeData nodeData = new CaseNodeData(metadata);
coordinationService.setNodeData(CategoryNode.CASES, metadata.getCaseDirectory(), nodeData.toArray());
} catch (CoordinationServiceException | InterruptedException | ParseException | IOException ex) {
CaseNodeData.createCaseNodeData(metadata);
} catch (CaseNodeDataException | InterruptedException ex) {
throw new CaseActionException(Bundle.Case_exceptionMessage_couldNotCreateCaseNodeData(ex.getLocalizedMessage()), ex);
}
}
@@ -2033,27 +2031,10 @@ public class Case {
if (getCaseType() == CaseType.MULTI_USER_CASE) {
progressIndicator.progress(Bundle.Case_progressMessage_updatingCaseNodeData());
try {
CaseNodeData nodeData;
CoordinationService coordinationService = CoordinationService.getInstance();
byte[] nodeBytes = coordinationService.getNodeData(CategoryNode.CASES, metadata.getCaseDirectory());
if (nodeBytes != null && nodeBytes.length > 0) {
/*
* Update the last access date in the coordination service
* node data for the case.
*/
nodeData = new CaseNodeData(nodeBytes);
nodeData.setLastAccessDate(new Date());
} else {
/*
* This is a "legacy" case with no data stored in its case
* directory coordination service node yet, or the node is
* empty due to some error, so create the coordination
* service node data from the case metadata.
*/
nodeData = new CaseNodeData(metadata);
}
coordinationService.setNodeData(CategoryNode.CASES, metadata.getCaseDirectory(), nodeData.toArray());
} catch (CoordinationServiceException | InterruptedException | ParseException | IOException ex) {
CaseNodeData nodeData = CaseNodeData.readCaseNodeData(metadata.getCaseDirectory());
nodeData.setLastAccessDate(new Date());
CaseNodeData.writeCaseNodeData(nodeData);
} catch (CaseNodeDataException | InterruptedException ex) {
throw new CaseActionException(Bundle.Case_exceptionMessage_couldNotUpdateCaseNodeData(ex.getLocalizedMessage()), ex);
}
}
@@ -2633,9 +2614,8 @@ public class Case {
progressIndicator.progress(Bundle.Case_progressMessage_fetchingCoordSvcNodeData());
try {
byte[] nodeBytes = coordinationService.getNodeData(CoordinationService.CategoryNode.CASES, metadata.getCaseDirectory());
caseNodeData = new CaseNodeData(nodeBytes);
} catch (CoordinationServiceException | InterruptedException | IOException ex) {
caseNodeData = CaseNodeData.readCaseNodeData(metadata.getCaseDirectory());
} catch (CaseNodeDataException | InterruptedException ex) {
logger.log(Level.SEVERE, String.format("Failed to get coordination service node data %s (%s) in %s", metadata.getCaseDisplayName(), metadata.getCaseName(), metadata.getCaseDirectory()), ex); //NON-NLS
throw new CaseActionException(Bundle.Case_exceptionMessage_failedToFetchCoordSvcNodeData(ex.getLocalizedMessage()));
}
@@ -2899,9 +2879,8 @@ public class Case {
private static void setDeletedItemFlag(CaseNodeData caseNodeData, CaseNodeData.DeletedFlags flag) throws InterruptedException {
try {
caseNodeData.setDeletedFlag(flag);
CoordinationService coordinationService = CoordinationService.getInstance();
coordinationService.setNodeData(CategoryNode.CASES, caseNodeData.getDirectory().toString(), caseNodeData.toArray());
} catch (IOException | CoordinationServiceException ex) {
CaseNodeData.writeCaseNodeData(caseNodeData);
} catch (CaseNodeDataException ex) {
logger.log(Level.SEVERE, String.format("Error updating deleted item flag %s for %s (%s) in %s", flag.name(), caseNodeData.getDisplayName(), caseNodeData.getName(), caseNodeData.getDirectory()), ex);
}
}
@@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2011-2019 Basis Technology Corp.
* Copyright 2016-2019 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -38,7 +38,7 @@ import org.sleuthkit.datamodel.TskCoreException;
import org.sleuthkit.datamodel.DataSource;
/**
* Panel for displaying ingest job history.
* Panel for displaying ingest job history for a data source.
*/
@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives
public final class IngestJobInfoPanel extends javax.swing.JPanel {
@@ -79,11 +79,11 @@ public final class IngestJobInfoPanel extends javax.swing.JPanel {
}
/**
* Update the data source which ingest jobs are being displayed for
* Changes the data source for which ingest jobs are being displayed.
*
* @param selectedDataSource the data source to display ingest jobs for
* @param selectedDataSource The data source.
*/
public void updateIngestHistoryData(DataSource selectedDataSource) {
public void setDataSource(DataSource selectedDataSource) {
this.selectedDataSource = selectedDataSource;
ingestJobsForSelectedDataSource.clear();
if (selectedDataSource != null) {
@@ -109,7 +109,7 @@ public final class IngestJobInfoPanel extends javax.swing.JPanel {
try {
SleuthkitCase skCase = Case.getCurrentCaseThrows().getSleuthkitCase();
this.ingestJobs = skCase.getIngestJobs();
updateIngestHistoryData(selectedDataSource);
setDataSource(selectedDataSource);
} catch (TskCoreException | NoCurrentCaseException ex) {
logger.log(Level.SEVERE, "Failed to load ingest jobs.", ex);
JOptionPane.showMessageDialog(this, Bundle.IngestJobInfoPanel_loadIngestJob_error_text(), Bundle.IngestJobInfoPanel_loadIngestJob_error_title(), JOptionPane.ERROR_MESSAGE);
@@ -69,7 +69,7 @@ final class DataSourceSummaryDialog extends javax.swing.JDialog implements Obser
DataSource selectedDataSource = dataSourcesPanel.getSelectedDataSource();
countsPanel.updateCountsTableData(selectedDataSource);
detailsPanel.updateDetailsPanelData(selectedDataSource);
ingestHistoryPanel.updateIngestHistoryData(selectedDataSource);
ingestHistoryPanel.setDataSource(selectedDataSource);
this.repaint();
}
});
@@ -22,25 +22,33 @@ import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.text.ParseException;
import java.util.Date;
import java.util.logging.Level;
import org.sleuthkit.autopsy.casemodule.CaseMetadata;
import org.sleuthkit.autopsy.casemodule.CaseMetadata.CaseMetadataException;
import org.sleuthkit.autopsy.coordinationservice.CoordinationService;
import org.sleuthkit.autopsy.coordinationservice.CoordinationService.CoordinationServiceException;
import org.sleuthkit.autopsy.coreutils.Logger;
/**
* An object that converts data for a case directory lock coordination service
* node to and from byte arrays.
* Case data stored in a case directory coordination service node.
*/
public final class CaseNodeData {
private static final int CURRENT_VERSION = 1;
private static final int MAJOR_VERSION = 2;
private static final int MINOR_VERSION = 0;
private static final Logger logger = Logger.getLogger(CaseNodeData.class.getName());
/*
* Version 0 fields.
* Version 0 fields. Note that version 0 node data was only written to the
* coordination service node if an auto ingest job error occurred.
*/
private final int version;
private int version;
private boolean errorsOccurred;
/*
@@ -53,28 +61,191 @@ public final class CaseNodeData {
private String displayName;
private short deletedItemFlags;
/**
* Gets the current version of the case directory lock coordination service
* node data.
*
* @return The version number.
/*
* Version 2 fields.
*/
public static int getCurrentVersion() {
return CaseNodeData.CURRENT_VERSION;
private int minorVersion;
/**
* Creates case node data from the metadata for a case and writes it to the
* appropriate case directory coordination service node, which must already
* exist.
*
* @param metadata The case metadata.
*
* @return The case node data that was written to the coordination service
* node.
*
* @throws CaseNodeDataException If there is an error creating or writing
* the case node data.
* @throws InterruptedException If the current thread is interrupted while
* waiting for the coordination service.
*/
public static CaseNodeData createCaseNodeData(final CaseMetadata metadata) throws CaseNodeDataException, InterruptedException {
try {
final CaseNodeData nodeData = new CaseNodeData(metadata);
CoordinationService.getInstance().setNodeData(CoordinationService.CategoryNode.CASES, nodeData.getDirectory().toString(), nodeData.toArray());
return nodeData;
} catch (ParseException | IOException | CoordinationServiceException ex) {
throw new CaseNodeDataException(String.format("Error creating case node data for coordination service node with path %s", metadata.getCaseDirectory().toUpperCase()), ex); //NON-NLS
}
}
/**
* Uses a CaseMetadata object to construct an object that converts data for
* a case directory lock coordination service node to and from byte arrays.
* Reads case data from a case directory coordination service node. If the
* data is missing, corrupted, or from an older version of the software, an
* attempt is made to remedy the situation using the case metadata.
*
* @param nodePath The case directory coordination service node path.
*
* @return The case node data.
*
* @throws CaseNodeDataException If there is an error reading or writing the
* case node data.
* @throws InterruptedException If the current thread is interrupted while
* waiting for the coordination service.
*/
public static CaseNodeData readCaseNodeData(String nodePath) throws CaseNodeDataException, InterruptedException {
try {
CaseNodeData nodeData;
final byte[] nodeBytes = CoordinationService.getInstance().getNodeData(CoordinationService.CategoryNode.CASES, nodePath);
if (nodeBytes != null && nodeBytes.length > 0) {
try {
nodeData = new CaseNodeData(nodeBytes);
} catch (IOException ex) {
/*
* The existing case node data is corrupted.
*/
logger.log(Level.WARNING, String.format("Error reading node data for coordination service node with path %s, will attempt to replace it", nodePath.toUpperCase()), ex); //NON-NLS
final CaseMetadata metadata = getCaseMetadata(nodePath);
nodeData = createCaseNodeData(metadata);
logger.log(Level.INFO, String.format("Replaced corrupt node data for coordination service node with path %s", nodePath.toUpperCase())); //NON-NLS
}
} else {
/*
* The case node data is missing. Version 0 node data was only
* written to the coordination service node if an auto ingest
* job error occurred.
*/
logger.log(Level.INFO, String.format("Missing node data for coordination service node with path %s, will attempt to create it", nodePath.toUpperCase())); //NON-NLS
final CaseMetadata metadata = getCaseMetadata(nodePath);
nodeData = createCaseNodeData(metadata);
logger.log(Level.INFO, String.format("Created node data for coordination service node with path %s", nodePath.toUpperCase())); //NON-NLS
}
if (nodeData.getVersion() < CaseNodeData.MAJOR_VERSION) {
nodeData = upgradeCaseNodeData(nodePath, nodeData);
}
return nodeData;
} catch (CaseNodeDataException | CaseMetadataException | ParseException | IOException | CoordinationServiceException ex) {
throw new CaseNodeDataException(String.format("Error reading/writing node data coordination service node with path %s", nodePath.toUpperCase()), ex); //NON-NLS
}
}
/**
* Writes case data to a case directory coordination service node. Obtain
* the case data to be updated and written by calling createCaseNodeData()
* or readCaseNodeData().
*
* @param nodeData The case node data.
*
* @throws CaseNodeDataException If there is an error writing the case node
* data.
* @throws InterruptedException If the current thread is interrupted while
* waiting for the coordination service.
*/
public static void writeCaseNodeData(CaseNodeData nodeData) throws CaseNodeDataException, InterruptedException {
try {
CoordinationService.getInstance().setNodeData(CoordinationService.CategoryNode.CASES, nodeData.getDirectory().toString(), nodeData.toArray());
} catch (IOException | CoordinationServiceException ex) {
throw new CaseNodeDataException(String.format("Error writing node data coordination service node with path %s", nodeData.getDirectory().toString().toUpperCase()), ex); //NON-NLS
}
}
/**
* Upgrades older versions of node data to the current version and writes
* the data back to the case directory coordination service node.
*
* @param nodePath The case directory coordination service node path.
* @param oldNodeData The outdated node data.
*
* @return The updated node data.
*
* @throws CaseNodeDataException If the case meta data file or case
* directory do not exist.
* @throws CaseMetadataException If the case metadata cannot be read.
*/
private static CaseNodeData upgradeCaseNodeData(String nodePath, CaseNodeData oldNodeData) throws CaseNodeDataException, CaseMetadataException, ParseException, IOException, CoordinationServiceException, InterruptedException {
CaseNodeData nodeData;
switch (oldNodeData.getVersion()) {
case 0:
/*
* Version 0 node data consisted of only the version number and
* the errors occurred flag and was only written when an auto
* ingest job error occurred. To upgrade from version 0, the
* version 1 fields need to be set from the case metadata and
* the errors occurred flag needs to be carried forward. Note
* that the last accessed date gets advanced to now, since it is
* otherwise unknown.
*/
final CaseMetadata metadata = getCaseMetadata(nodePath);
nodeData = new CaseNodeData(metadata);
nodeData.setErrorsOccurred(oldNodeData.getErrorsOccurred());
break;
case 1:
/*
* Version 1 node data did not have a minor version number
* field.
*/
oldNodeData.setMinorVersion(MINOR_VERSION);
nodeData = oldNodeData;
break;
default:
nodeData = oldNodeData;
break;
}
writeCaseNodeData(nodeData);
return nodeData;
}
/**
* Gets the metadata for a case.
*
* @param nodePath The case directory coordination service node path for the
* case.
*
* @return The case metadata.
*
* @throws CaseNodeDataException If the case metadata file or the case
* directory does not exist.
* @throws CaseMetadataException If the case metadata cannot be read.
*/
private static CaseMetadata getCaseMetadata(String nodePath) throws CaseNodeDataException, CaseMetadataException {
final Path caseDirectoryPath = Paths.get(nodePath);
final File caseDirectory = caseDirectoryPath.toFile();
if (!caseDirectory.exists()) {
throw new CaseNodeDataException("Case directory does not exist"); // NON-NLS
}
final Path metadataFilePath = CaseMetadata.getCaseMetadataFilePath(caseDirectoryPath);
if (metadataFilePath == null) {
throw new CaseNodeDataException("Case meta data file does not exist"); // NON-NLS
}
return new CaseMetadata(metadataFilePath);
}
/**
* Uses case metadata to construct the case data to store in a case
* directory coordination service node.
*
* @param metadata The case meta data.
*
* @throws java.text.ParseException If there is an error parsing dates from
* string representations of dates in the
* meta data.
* @throws ParseException If there is an error parsing dates from string
* representations of dates in the meta data.
*/
public CaseNodeData(CaseMetadata metadata) throws ParseException {
this.version = CURRENT_VERSION;
private CaseNodeData(CaseMetadata metadata) throws ParseException {
this.version = MAJOR_VERSION;
this.errorsOccurred = false;
this.directory = Paths.get(metadata.getCaseDirectory());
this.createDate = CaseMetadata.getDateFormat().parse(metadata.getCreatedDate());
@@ -82,51 +253,64 @@ public final class CaseNodeData {
this.name = metadata.getCaseName();
this.displayName = metadata.getCaseDisplayName();
this.deletedItemFlags = 0;
this.minorVersion = MINOR_VERSION;
}
/**
* Uses coordination service node data to construct an object that converts
* data for a case directory lock coordination service node to and from byte
* arrays.
* Uses the raw bytes from a case directory coordination service node to
* construct a case node data object.
*
* @param nodeData The raw bytes received from the coordination service.
*
* @throws IOException If there is an error reading the node data.
*/
public CaseNodeData(byte[] nodeData) throws IOException {
private CaseNodeData(byte[] nodeData) throws IOException {
if (nodeData == null || nodeData.length == 0) {
throw new IOException(null == nodeData ? "Null node data byte array" : "Zero-length node data byte array");
}
DataInputStream inputStream = new DataInputStream(new ByteArrayInputStream(nodeData));
this.version = inputStream.readInt();
if (this.version > 0) {
this.errorsOccurred = inputStream.readBoolean();
} else {
short legacyErrorsOccurred = inputStream.readByte();
this.errorsOccurred = (legacyErrorsOccurred < 0);
}
if (this.version > 0) {
this.directory = Paths.get(inputStream.readUTF());
this.createDate = new Date(inputStream.readLong());
this.lastAccessDate = new Date(inputStream.readLong());
this.name = inputStream.readUTF();
this.displayName = inputStream.readUTF();
this.deletedItemFlags = inputStream.readShort();
try (ByteArrayInputStream byteStream = new ByteArrayInputStream(nodeData); DataInputStream inputStream = new DataInputStream(byteStream)) {
this.version = inputStream.readInt();
if (this.version == 1) {
this.errorsOccurred = inputStream.readBoolean();
} else {
byte errorsOccurredByte = inputStream.readByte();
this.errorsOccurred = (errorsOccurredByte < 0);
}
if (this.version > 0) {
this.directory = Paths.get(inputStream.readUTF());
this.createDate = new Date(inputStream.readLong());
this.lastAccessDate = new Date(inputStream.readLong());
this.name = inputStream.readUTF();
this.displayName = inputStream.readUTF();
this.deletedItemFlags = inputStream.readShort();
}
if (this.version > 1) {
this.minorVersion = inputStream.readInt();
}
}
}
/**
* Gets the node data version number of this node.
* Gets the version number of this node data.
*
* @return The version number.
*/
public int getVersion() {
private int getVersion() {
return this.version;
}
/**
* Sets the minor version number of this node data.
*
* @param minorVersion The version number.
*/
private void setMinorVersion(int minorVersion) {
this.minorVersion = minorVersion;
}
/**
* Gets whether or not any errors occurred during the processing of any auto
* ingest job for the case represented by this node data.
* ingest job for the case.
*
* @return True or false.
*/
@@ -136,7 +320,7 @@ public final class CaseNodeData {
/**
* Sets whether or not any errors occurred during the processing of any auto
* ingest job for the case represented by this node data.
* ingest job for the case.
*
* @param errorsOccurred True or false.
*/
@@ -145,8 +329,7 @@ public final class CaseNodeData {
}
/**
* Gets the path of the case directory of the case represented by this node
* data.
* Gets the path of the case directory.
*
* @return The case directory path.
*/
@@ -155,17 +338,7 @@ public final class CaseNodeData {
}
/**
* Sets the path of the case directory of the case represented by this node
* data.
*
* @param caseDirectory The case directory path.
*/
public void setDirectory(Path caseDirectory) {
this.directory = caseDirectory;
}
/**
* Gets the date the case represented by this node data was created.
* Gets the date the case was created.
*
* @return The create date.
*/
@@ -174,16 +347,7 @@ public final class CaseNodeData {
}
/**
* Sets the date the case represented by this node data was created.
*
* @param createDate The create date.
*/
public void setCreateDate(Date createDate) {
this.createDate = new Date(createDate.getTime());
}
/**
* Gets the date the case represented by this node data last accessed.
* Gets the date the case was last accessed.
*
* @return The last access date.
*/
@@ -192,7 +356,7 @@ public final class CaseNodeData {
}
/**
* Sets the date the case represented by this node data was last accessed.
* Sets the date the case was last accessed.
*
* @param lastAccessDate The last access date.
*/
@@ -201,8 +365,7 @@ public final class CaseNodeData {
}
/**
* Gets the unique and immutable (user cannot change it) name of the case
* represented by this node data.
* Gets the unique and immutable name of the case.
*
* @return The case name.
*/
@@ -211,17 +374,7 @@ public final class CaseNodeData {
}
/**
* Sets the unique and immutable (user cannot change it) name of the case
* represented by this node data.
*
* @param name The case name.
*/
public void setName(String name) {
this.name = name;
}
/**
* Gets the display name of the case represented by this node data.
* Gets the display name of the case.
*
* @return The case display name.
*/
@@ -230,7 +383,7 @@ public final class CaseNodeData {
}
/**
* Sets the display name of the case represented by this node data.
* Sets the display name of the case.
*
* @param displayName The case display name.
*/
@@ -239,19 +392,18 @@ public final class CaseNodeData {
}
/**
* Checks whether a deleted item flag is set for the case represented by
* this node data.
* Checks whether a given deleted item flag is set for the case.
*
* @param flag The flag to check.
*
* @return
* @return True or false.
*/
public boolean isDeletedFlagSet(DeletedFlags flag) {
return (this.deletedItemFlags & flag.getValue()) == flag.getValue();
}
/**
* Sets a deleted item flag for the case represented by this node data.
* Sets a given deleted item flag.
*
* @param flag The flag to set.
*/
@@ -265,22 +417,24 @@ public final class CaseNodeData {
*
* @return The node data as a byte array.
*
* @throws IOException If there is an error writing the node data.
* @throws IOException If there is an error writing the node data to the
* array.
*/
public byte[] toArray() throws IOException {
ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
DataOutputStream outputStream = new DataOutputStream(byteStream);
outputStream.writeInt(this.version);
outputStream.writeBoolean(this.errorsOccurred);
outputStream.writeUTF(this.directory.toString());
outputStream.writeLong(this.createDate.getTime());
outputStream.writeLong(this.lastAccessDate.getTime());
outputStream.writeUTF(this.name);
outputStream.writeUTF(this.displayName);
outputStream.writeShort(this.deletedItemFlags);
outputStream.flush();
byteStream.flush();
return byteStream.toByteArray();
private byte[] toArray() throws IOException {
try (ByteArrayOutputStream byteStream = new ByteArrayOutputStream(); DataOutputStream outputStream = new DataOutputStream(byteStream)) {
outputStream.writeInt(this.version);
outputStream.writeByte((byte) (this.errorsOccurred ? 0x80 : 0));
outputStream.writeUTF(this.directory.toString());
outputStream.writeLong(this.createDate.getTime());
outputStream.writeLong(this.lastAccessDate.getTime());
outputStream.writeUTF(this.name);
outputStream.writeUTF(this.displayName);
outputStream.writeShort(this.deletedItemFlags);
outputStream.writeInt(this.minorVersion);
outputStream.flush();
byteStream.flush();
return byteStream.toByteArray();
}
}
/**
@@ -316,4 +470,34 @@ public final class CaseNodeData {
}
/**
* Exception thrown when there is an error reading or writing case node
* data.
*/
public static final class CaseNodeDataException extends Exception {
private static final long serialVersionUID = 1L;
/**
* Constructs an exception to throw when there is an error reading or
* writing case node data.
*
* @param message The exception message.
*/
private CaseNodeDataException(String message) {
super(message);
}
/**
* Constructs an exception to throw when there is an error reading or
* writing case node data.
*
* @param message The exception message.
* @param cause The cause of the exception.
*/
private CaseNodeDataException(String message, Throwable cause) {
super(message, cause);
}
}
}
@@ -18,31 +18,28 @@
*/
package org.sleuthkit.autopsy.casemodule.multiusercases;
import java.io.File;
import java.io.IOException;
import java.nio.file.LinkOption;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import org.sleuthkit.autopsy.casemodule.CaseMetadata;
import org.sleuthkit.autopsy.casemodule.multiusercases.CaseNodeData.CaseNodeDataException;
import static org.sleuthkit.autopsy.casemodule.multiusercases.CoordinationServiceUtils.isCaseAutoIngestLogNodePath;
import static org.sleuthkit.autopsy.casemodule.multiusercases.CoordinationServiceUtils.isCaseNameNodePath;
import static org.sleuthkit.autopsy.casemodule.multiusercases.CoordinationServiceUtils.isCaseResourcesNodePath;
import org.sleuthkit.autopsy.coordinationservice.CoordinationService;
import org.sleuthkit.autopsy.coordinationservice.CoordinationService.CoordinationServiceException;
import org.sleuthkit.autopsy.coreutils.Logger;
/**
* Queries the coordination service to collect the multi-user case node data
* stored in the case directory lock ZooKeeper nodes.
* Collects the multi-user case node data stored in the case directory
* coordination service nodes.
*/
final public class CaseNodeDataCollector {
private static final Logger logger = Logger.getLogger(CaseNodeDataCollector.class.getName());
/**
* Queries the coordination service to collect the multi-user case node data
* stored in the case directory lock ZooKeeper nodes.
* Collects the multi-user case node data stored in the case directory
* coordination service nodes.
*
* @return The node data for the multi-user cases known to the coordination
* service.
@@ -54,128 +51,30 @@ final public class CaseNodeDataCollector {
* service.
*/
public static List<CaseNodeData> getNodeData() throws CoordinationServiceException, InterruptedException {
final List<CaseNodeData> cases = new ArrayList<>();
final List<CaseNodeData> nodeDataList = new ArrayList<>();
final CoordinationService coordinationService = CoordinationService.getInstance();
final List<String> nodeList = coordinationService.getNodeList(CoordinationService.CategoryNode.CASES);
for (String nodeName : nodeList) {
if (CoordinationServiceUtils.isCaseNameNodePath(nodeName)
|| CoordinationServiceUtils.isCaseResourcesNodePath(nodeName)
|| CoordinationServiceUtils.isCaseAutoIngestLogNodePath(nodeName)) {
final List<String> nodePaths = coordinationService.getNodeList(CoordinationService.CategoryNode.CASES);
for (String nodePath : nodePaths) {
/*
* Skip the case name, case resources, and case auto ingest log
* coordination service nodes. They are not used to store case data.
*/
if (isCaseNameNodePath(nodePath) || isCaseResourcesNodePath(nodePath) || isCaseAutoIngestLogNodePath(nodePath)) {
continue;
}
/*
* Get the data from the case directory lock node. This data may not
* exist or may exist only in an older version. If it is missing or
* incomplete, create or update it.
* Get the case node data from the case directory coordination service node.
*/
try {
CaseNodeData nodeData;
final byte[] nodeBytes = coordinationService.getNodeData(CoordinationService.CategoryNode.CASES, nodeName);
if (nodeBytes != null && nodeBytes.length > 0) {
nodeData = new CaseNodeData(nodeBytes);
if (nodeData.getVersion() < CaseNodeData.getCurrentVersion()) {
nodeData = updateNodeData(nodeName, nodeData);
}
} else {
nodeData = updateNodeData(nodeName, null);
}
if (nodeData != null) {
cases.add(nodeData);
}
} catch (CoordinationService.CoordinationServiceException | InterruptedException | IOException | ParseException | CaseMetadata.CaseMetadataException ex) {
logger.log(Level.SEVERE, String.format("Error getting coordination service node data for %s", nodeName), ex);
final CaseNodeData nodeData = CaseNodeData.readCaseNodeData(nodePath);
nodeDataList.add(nodeData);
} catch (CaseNodeDataException | InterruptedException ex) {
logger.log(Level.WARNING, String.format("Error reading case node data from %s", nodePath), ex);
}
}
return cases;
}
/**
* Updates the case directory lock coordination service node data for a
* case.
*
* @param nodeName The coordination service node name, i.e., the case
* directory path.
* @param oldNodeData The node data to be updated.
*
* @return A CaseNodedata object or null if the coordination service node is
* an "orphan" with no corresponding case directry.
*
* @throws IOException If there is an error writing the
* node data to a byte array.
* @throws CaseMetadataException If there is an error reading the
* case metadata file.
* @throws ParseException If there is an error parsing a date
* from the case metadata file.
* @throws CoordinationServiceException If there is an error interacting
* with the coordination service.
* @throws InterruptedException If a coordination service operation
* is interrupted.
*/
private static CaseNodeData updateNodeData(String nodeName, CaseNodeData oldNodeData) throws IOException, CaseMetadata.CaseMetadataException, ParseException, CoordinationService.CoordinationServiceException, InterruptedException {
Path caseDirectoryPath = Paths.get(nodeName).toRealPath(LinkOption.NOFOLLOW_LINKS);
File caseDirectory = caseDirectoryPath.toFile();
if (!caseDirectory.exists()) {
logger.log(Level.WARNING, String.format("Found orphan coordination service node %s, attempting clean up", caseDirectoryPath));
deleteLockNodes(CoordinationService.getInstance(), caseDirectoryPath);
return null;
}
CaseNodeData nodeData = null;
if (oldNodeData == null || oldNodeData.getVersion() == 0) {
File[] files = caseDirectory.listFiles();
for (File file : files) {
String name = file.getName().toLowerCase();
if (name.endsWith(CaseMetadata.getFileExtension())) {
CaseMetadata metadata = new CaseMetadata(Paths.get(file.getAbsolutePath()));
nodeData = new CaseNodeData(metadata);
if (oldNodeData != null) {
/*
* Version 0 case node data was only written if errors
* occurred during an auto ingest job.
*/
nodeData.setErrorsOccurred(true);
}
break;
}
}
}
if (nodeData != null) {
CoordinationService.getInstance().setNodeData(CoordinationService.CategoryNode.CASES, nodeName, nodeData.toArray());
}
return nodeData;
}
/**
* Attempts to delete the coordination service lock nodes for a case,
* logging any failures.
*
* @param coordinationService The coordination service.
* @param caseDirectoryPath The case directory path.
*/
private static void deleteLockNodes(CoordinationService coordinationService, Path caseDirectoryPath) {
deleteCoordinationServiceNode(coordinationService, CoordinationServiceUtils.getCaseResourcesNodePath(caseDirectoryPath));
deleteCoordinationServiceNode(coordinationService, CoordinationServiceUtils.getCaseAutoIngestLogNodePath(caseDirectoryPath));
deleteCoordinationServiceNode(coordinationService, CoordinationServiceUtils.getCaseDirectoryNodePath(caseDirectoryPath));
deleteCoordinationServiceNode(coordinationService, CoordinationServiceUtils.getCaseNameNodePath(caseDirectoryPath));
}
/**
* Attempts to delete a coordination service node, logging failure.
*
* @param coordinationService The coordination service.
* @param nodeName A node name.
*/
private static void deleteCoordinationServiceNode(CoordinationService coordinationService, String nodeName) {
try {
coordinationService.deleteNode(CoordinationService.CategoryNode.CASES, nodeName);
} catch (CoordinationService.CoordinationServiceException | InterruptedException ex) {
logger.log(Level.WARNING, String.format("Error deleting coordination service node %s", nodeName), ex);
}
return nodeDataList;
}
/**
@@ -183,5 +82,5 @@ final public class CaseNodeDataCollector {
*/
private CaseNodeDataCollector() {
}
}
@@ -43,6 +43,8 @@ import org.sleuthkit.autopsy.casemodule.multiusercasesbrowser.MultiUserCaseBrows
public final class MultiUserCasesBrowserPanel extends javax.swing.JPanel implements ExplorerManager.Provider {
private static final long serialVersionUID = 1L;
private static final int NAME_COLUMN_INDEX = 0;
private static final int NAME_COLUMN_WIDTH = 150;
private final ExplorerManager explorerManager;
private final MultiUserCaseBrowserCustomizer customizer;
private final OutlineView outlineView;
@@ -103,6 +105,11 @@ public final class MultiUserCasesBrowserPanel extends javax.swing.JPanel impleme
}
}
/*
* Give the case name column a greater width.
*/
outline.getColumnModel().getColumn(NAME_COLUMN_INDEX).setPreferredWidth(NAME_COLUMN_WIDTH);
/*
* Hide the root node and configure the node selection mode.
*/
@@ -26,13 +26,29 @@ DataContentViewerOtherCases.earliestCaseLabel.text=Central Repository Starting D
DataContentViewerOtherCases.foundInLabel.text=
DataContentViewerOtherCases.title=Other Occurrences
DataContentViewerOtherCases.toolTip=Displays instances of the selected file/artifact from other occurrences.
DataContentViewerOtherCasesModel.csvHeader.attribute=Matched Attribute
DataContentViewerOtherCasesModel.csvHeader.case=Case
DataContentViewerOtherCasesModel.csvHeader.comment=Comment
DataContentViewerOtherCasesModel.csvHeader.dataSource=Data Source
DataContentViewerOtherCasesModel.csvHeader.device=Device
DataContentViewerOtherCasesModel.csvHeader.known=Known
DataContentViewerOtherCasesModel.csvHeader.path=Path
DataContentViewerOtherCasesModel.csvHeader.value=Attribute Value
OccurrencePanel.caseCreatedDateLabel.text=Created Date:
OccurrencePanel.caseDetails.text=Case Details
OccurrencePanel.caseNameLabel.text=Name:
OccurrencePanel.commonProperties.text=Common Properties
OccurrencePanel.commonPropertyCommentLabel.text=Comment:
OccurrencePanel.commonPropertyKnownStatusLabel.text=Known Status:
OccurrencePanel.commonPropertyTypeLabel.text=Type:
OccurrencePanel.commonPropertyValueLabel.text=Value:
OccurrencePanel.dataSourceDetails.text=Data Source Details
OccurrencePanel.dataSourceNameLabel.text=Name:
OccurrencePanel.fileDetails.text=File Details
OccurrencePanel.filePathLabel.text=File Path:
OtherOccurrencesCasesTableModel.case=Case
OtherOccurrencesCasesTableModel.noData=No Data.
OtherOccurrencesFilesTableModel.attribute=Matched Attribute
OtherOccurrencesFilesTableModel.comment=Comment
OtherOccurrencesFilesTableModel.dataSource=Data Source
OtherOccurrencesFilesTableModel.device=Device
OtherOccurrencesFilesTableModel.known=Known
OtherOccurrencesDataSourcesTableModel.dataSourceName=Data Source Name
OtherOccurrencesDataSourcesTableModel.noData=No Data.
OtherOccurrencesFilesTableModel.fileName=File Name
OtherOccurrencesFilesTableModel.noData=No Data.
OtherOccurrencesFilesTableModel.path=Path
OtherOccurrencesFilesTableModel.value=Attribute Value
@@ -46,11 +46,11 @@
</NonVisualComponents>
<Properties>
<Property name="minimumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[1500, 10]"/>
<Dimension value="[600, 10]"/>
</Property>
<Property name="opaque" type="boolean" value="false"/>
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[1500, 44]"/>
<Dimension value="[600, 63]"/>
</Property>
</Properties>
<AuxValues>
@@ -68,123 +68,114 @@
<Layout>
<DimensionLayout dim="0">
<Group type="103" groupAlignment="0" attributes="0">
<Component id="otherCasesPanel" alignment="0" pref="1500" max="32767" attributes="0"/>
<Component id="tableContainerPanel" alignment="0" max="32767" attributes="0"/>
</Group>
</DimensionLayout>
<DimensionLayout dim="1">
<Group type="103" groupAlignment="0" attributes="0">
<Component id="otherCasesPanel" max="32767" attributes="0"/>
<Group type="102" alignment="0" attributes="0">
<Component id="tableContainerPanel" pref="64" max="32767" attributes="0"/>
<EmptySpace min="0" pref="0" max="-2" attributes="0"/>
</Group>
</Group>
</DimensionLayout>
</Layout>
<SubComponents>
<Container class="javax.swing.JPanel" name="otherCasesPanel">
<Container class="javax.swing.JPanel" name="tableContainerPanel">
<Properties>
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[921, 62]"/>
<Dimension value="[600, 63]"/>
</Property>
<Property name="requestFocusEnabled" type="boolean" value="false"/>
</Properties>
<Layout>
<DimensionLayout dim="0">
<Group type="103" groupAlignment="0" attributes="0">
<EmptySpace min="0" pref="921" max="32767" attributes="0"/>
<Group type="103" rootIndex="1" groupAlignment="0" attributes="0">
<Group type="102" alignment="0" attributes="0">
<Component id="tableContainerPanel" max="32767" attributes="0"/>
<EmptySpace min="-2" pref="0" max="-2" attributes="0"/>
<Group type="102" attributes="0">
<Group type="103" groupAlignment="0" attributes="0">
<Group type="102" attributes="0">
<Component id="earliestCaseLabel" min="-2" pref="161" max="-2" attributes="0"/>
<EmptySpace min="-2" max="-2" attributes="0"/>
<Component id="earliestCaseDate" max="32767" attributes="0"/>
<EmptySpace max="32767" attributes="0"/>
<Component id="foundInLabel" max="32767" attributes="0"/>
</Group>
<Component id="tablesViewerSplitPane" pref="590" max="32767" attributes="0"/>
</Group>
<EmptySpace min="-2" max="-2" attributes="0"/>
</Group>
</Group>
</DimensionLayout>
<DimensionLayout dim="1">
<Group type="103" groupAlignment="0" attributes="0">
<EmptySpace min="0" pref="62" max="32767" attributes="0"/>
<Group type="103" rootIndex="1" groupAlignment="0" attributes="0">
<Group type="102" alignment="0" attributes="0">
<Component id="tableContainerPanel" pref="62" max="32767" attributes="0"/>
<EmptySpace min="-2" pref="0" max="-2" attributes="0"/>
<Group type="102" attributes="0">
<EmptySpace min="-2" pref="0" max="-2" attributes="0"/>
<Component id="tablesViewerSplitPane" pref="33" max="32767" attributes="0"/>
<EmptySpace min="-2" max="-2" attributes="0"/>
<Group type="103" groupAlignment="1" attributes="0">
<Group type="103" groupAlignment="3" attributes="0">
<Component id="earliestCaseLabel" alignment="3" min="-2" max="-2" attributes="0"/>
<Component id="earliestCaseDate" alignment="3" min="-2" max="-2" attributes="0"/>
</Group>
<Component id="foundInLabel" min="-2" pref="14" max="-2" attributes="0"/>
</Group>
<EmptySpace min="-2" max="-2" attributes="0"/>
</Group>
</Group>
</DimensionLayout>
</Layout>
<SubComponents>
<Container class="javax.swing.JPanel" name="tableContainerPanel">
<Component class="javax.swing.JLabel" name="earliestCaseLabel">
<Properties>
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[1500, 63]"/>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/centralrepository/contentviewer/Bundle.properties" key="DataContentViewerOtherCases.earliestCaseLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
<Property name="toolTipText" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/centralrepository/contentviewer/Bundle.properties" key="DataContentViewerOtherCases.earliestCaseLabel.toolTipText" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
</Component>
<Component class="javax.swing.JLabel" name="earliestCaseDate">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/centralrepository/contentviewer/Bundle.properties" key="DataContentViewerOtherCases.earliestCaseDate.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
</Component>
<Component class="javax.swing.JLabel" name="foundInLabel">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/centralrepository/contentviewer/Bundle.properties" key="DataContentViewerOtherCases.foundInLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
</Component>
<Container class="javax.swing.JSplitPane" name="tablesViewerSplitPane">
<Properties>
<Property name="dividerLocation" type="int" value="450"/>
<Property name="resizeWeight" type="double" value="0.5"/>
</Properties>
<Layout>
<DimensionLayout dim="0">
<Group type="103" groupAlignment="0" attributes="0">
<Group type="102" attributes="0">
<Group type="103" groupAlignment="0" attributes="0">
<Group type="102" attributes="0">
<Component id="earliestCaseLabel" min="-2" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="earliestCaseDate" min="-2" max="-2" attributes="0"/>
<EmptySpace min="-2" pref="66" max="-2" attributes="0"/>
<Component id="foundInLabel" min="-2" max="-2" attributes="0"/>
</Group>
<Component id="jSplitPane2" alignment="0" pref="911" max="32767" attributes="0"/>
</Group>
<EmptySpace min="-2" max="-2" attributes="0"/>
</Group>
</Group>
</DimensionLayout>
<DimensionLayout dim="1">
<Group type="103" groupAlignment="0" attributes="0">
<Group type="102" alignment="1" attributes="0">
<Component id="jSplitPane2" pref="31" max="32767" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Group type="103" groupAlignment="3" attributes="0">
<Component id="earliestCaseLabel" alignment="3" min="-2" max="-2" attributes="0"/>
<Component id="earliestCaseDate" alignment="3" min="-2" max="-2" attributes="0"/>
<Component id="foundInLabel" alignment="3" min="-2" max="-2" attributes="0"/>
</Group>
<EmptySpace min="-2" max="-2" attributes="0"/>
</Group>
</Group>
</DimensionLayout>
</Layout>
<Layout class="org.netbeans.modules.form.compat2.layouts.support.JSplitPaneSupportLayout"/>
<SubComponents>
<Component class="javax.swing.JLabel" name="earliestCaseLabel">
<Container class="javax.swing.JSplitPane" name="caseDatasourceFileSplitPane">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/centralrepository/contentviewer/Bundle.properties" key="DataContentViewerOtherCases.earliestCaseLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
<Property name="toolTipText" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/centralrepository/contentviewer/Bundle.properties" key="DataContentViewerOtherCases.earliestCaseLabel.toolTipText" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
</Component>
<Component class="javax.swing.JLabel" name="earliestCaseDate">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/centralrepository/contentviewer/Bundle.properties" key="DataContentViewerOtherCases.earliestCaseDate.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
</Component>
<Component class="javax.swing.JLabel" name="foundInLabel">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/centralrepository/contentviewer/Bundle.properties" key="DataContentViewerOtherCases.foundInLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
</Component>
<Container class="javax.swing.JSplitPane" name="jSplitPane2">
<Properties>
<Property name="dividerLocation" type="int" value="470"/>
<Property name="dividerLocation" type="int" value="300"/>
<Property name="resizeWeight" type="double" value="0.6"/>
<Property name="toolTipText" type="java.lang.String" value=""/>
</Properties>
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.support.JSplitPaneSupportLayout" value="org.netbeans.modules.form.compat2.layouts.support.JSplitPaneSupportLayout$JSplitPaneConstraintsDescription">
<JSplitPaneConstraints position="left"/>
</Constraint>
</Constraints>
<Layout class="org.netbeans.modules.form.compat2.layouts.support.JSplitPaneSupportLayout"/>
<SubComponents>
<Container class="javax.swing.JSplitPane" name="jSplitPane3">
<Container class="javax.swing.JSplitPane" name="caseDatasourceSplitPane">
<Properties>
<Property name="dividerLocation" type="int" value="150"/>
<Property name="resizeWeight" type="double" value="0.5"/>
</Properties>
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.support.JSplitPaneSupportLayout" value="org.netbeans.modules.form.compat2.layouts.support.JSplitPaneSupportLayout$JSplitPaneConstraintsDescription">
@@ -195,6 +186,11 @@
<Layout class="org.netbeans.modules.form.compat2.layouts.support.JSplitPaneSupportLayout"/>
<SubComponents>
<Container class="javax.swing.JScrollPane" name="caseScrollPane">
<Properties>
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[140, 30]"/>
</Property>
</Properties>
<AuxValues>
<AuxValue name="autoScrollPane" type="java.lang.Boolean" value="true"/>
</AuxValues>
@@ -217,6 +213,11 @@
</SubComponents>
</Container>
<Container class="javax.swing.JScrollPane" name="dataSourceScrollPane">
<Properties>
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[140, 30]"/>
</Property>
</Properties>
<AuxValues>
<AuxValue name="autoScrollPane" type="java.lang.Boolean" value="true"/>
</AuxValues>
@@ -231,11 +232,8 @@
<Component class="javax.swing.JTable" name="dataSourcesTable">
<Properties>
<Property name="autoCreateRowSorter" type="boolean" value="true"/>
<Property name="model" type="javax.swing.table.TableModel" editor="org.netbeans.modules.form.editors2.TableModelEditor">
<Table columnCount="2" rowCount="0">
<Column editable="false" title="Data Source Name" type="java.lang.Object"/>
<Column editable="false" title="Device ID" type="java.lang.Object"/>
</Table>
<Property name="model" type="javax.swing.table.TableModel" editor="org.netbeans.modules.form.RADConnectionPropertyEditor">
<Connection code="dataSourcesTableModel" type="code"/>
</Property>
</Properties>
</Component>
@@ -243,10 +241,10 @@
</Container>
</SubComponents>
</Container>
<Container class="javax.swing.JScrollPane" name="propertiesTableScrollPane">
<Container class="javax.swing.JScrollPane" name="filesTableScrollPane">
<Properties>
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[1000, 30]"/>
<Dimension value="[140, 30]"/>
</Property>
</Properties>
<Constraints>
@@ -261,7 +259,7 @@
<Properties>
<Property name="autoCreateRowSorter" type="boolean" value="true"/>
<Property name="model" type="javax.swing.table.TableModel" editor="org.netbeans.modules.form.RADConnectionPropertyEditor">
<Connection code="tableModel" type="code"/>
<Connection code="filesTableModel" type="code"/>
</Property>
<Property name="toolTipText" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/centralrepository/contentviewer/Bundle.properties" key="DataContentViewerOtherCases.table.toolTip.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
@@ -273,7 +271,7 @@
<ComponentRef name="rightClickPopupMenu"/>
</Property>
<Property name="selectionModel" type="javax.swing.ListSelectionModel" editor="org.netbeans.modules.form.editors2.JTableSelectionModelEditor">
<JTableSelectionModel selectionMode="1"/>
<JTableSelectionModel selectionMode="0"/>
</Property>
<Property name="tableHeader" type="javax.swing.table.JTableHeader" editor="org.netbeans.modules.form.editors2.JTableHeaderEditor">
<TableHeader reorderingAllowed="true" resizingAllowed="true"/>
@@ -284,6 +282,21 @@
</Container>
</SubComponents>
</Container>
<Container class="javax.swing.JScrollPane" name="detailsPanelScrollPane">
<Properties>
<Property name="horizontalScrollBarPolicy" type="int" value="31"/>
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[200, 100]"/>
</Property>
</Properties>
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.support.JSplitPaneSupportLayout" value="org.netbeans.modules.form.compat2.layouts.support.JSplitPaneSupportLayout$JSplitPaneConstraintsDescription">
<JSplitPaneConstraints position="right"/>
</Constraint>
</Constraints>
<Layout class="org.netbeans.modules.form.compat2.layouts.support.JScrollPaneSupportLayout"/>
</Container>
</SubComponents>
</Container>
</SubComponents>
@@ -1,7 +1,7 @@
/*
* Central Repository
*
* Copyright 2015-2019 Basis Technology Corp.
* Copyright 2017-2019 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -21,6 +21,8 @@ package org.sleuthkit.autopsy.centralrepository.contentviewer;
import java.awt.Component;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.io.BufferedWriter;
import java.io.File;
import java.io.IOException;
@@ -48,9 +50,6 @@ import static javax.swing.JOptionPane.PLAIN_MESSAGE;
import static javax.swing.JOptionPane.ERROR_MESSAGE;
import javax.swing.JPanel;
import javax.swing.filechooser.FileNameExtensionFilter;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableColumn;
import javax.swing.table.TableModel;
import javax.swing.table.TableRowSorter;
import org.joda.time.DateTimeZone;
@@ -94,49 +93,57 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi
private static final Logger LOGGER = Logger.getLogger(DataContentViewerOtherCases.class.getName());
private static final CorrelationCaseWrapper NO_ARTIFACTS_CASE = new CorrelationCaseWrapper(Bundle.DataContentViewerOtherCases_table_noArtifacts());
private static final CorrelationCaseWrapper NO_RESULTS_CASE = new CorrelationCaseWrapper(Bundle.DataContentViewerOtherCases_table_noResultsFound());
private static final int DEFAULT_MIN_CELL_WIDTH = 15;
private final OtherOccurrencesFilesTableModel tableModel;
private final OtherOccurrencesFilesTableModel filesTableModel;
private final OtherOccurrencesCasesTableModel casesTableModel;
private final OtherOccurrencesDataSourcesTableModel dataSourcesTableModel;
private OccurrencePanel occurrencePanel;
private final Collection<CorrelationAttributeInstance> correlationAttributes;
private String dataSourceName = "";
private String deviceId = "";
private String dataSourceName = ""; //the data source of the file which the content viewer is being populated for
private String deviceId = ""; //the device id of the data source for the file which the content viewer is being populated for
/**
* Could be null.
*/
private AbstractFile file;
private AbstractFile file; //the file which the content viewer is being populated for
/**
* Creates new form DataContentViewerOtherCases
*/
public DataContentViewerOtherCases() {
this.tableModel = new OtherOccurrencesFilesTableModel();
this.filesTableModel = new OtherOccurrencesFilesTableModel();
this.casesTableModel = new OtherOccurrencesCasesTableModel();
this.dataSourcesTableModel = new OtherOccurrencesDataSourcesTableModel();
this.correlationAttributes = new ArrayList<>();
occurrencePanel = new OccurrencePanel();
initComponents();
customizeComponents();
detailsPanelScrollPane.addComponentListener(new ComponentAdapter() {
@Override
public void componentResized(ComponentEvent componentEvent) {
//when its resized make sure the width of the panel resizes to match the scroll pane width to avoid a horizontal scroll bar
occurrencePanel.setPreferredSize(new java.awt.Dimension(detailsPanelScrollPane.getPreferredSize().width, occurrencePanel.getPreferredSize().height));
detailsPanelScrollPane.setViewportView(occurrencePanel);
}
});
reset();
}
private void customizeComponents() {
ActionListener actList = new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
JMenuItem jmi = (JMenuItem) e.getSource();
if (jmi.equals(selectAllMenuItem)) {
filesTable.selectAll();
} else if (jmi.equals(showCaseDetailsMenuItem)) {
showCaseDetails(filesTable.getSelectedRow());
} else if (jmi.equals(exportToCSVMenuItem)) {
try {
saveToCSV();
} catch (NoCurrentCaseException ex) {
LOGGER.log(Level.SEVERE, "Exception while getting open case.", ex); // NON-NLS
}
} else if (jmi.equals(showCommonalityMenuItem)) {
showCommonalityDetails();
ActionListener actList = (ActionEvent e) -> {
JMenuItem jmi = (JMenuItem) e.getSource();
if (jmi.equals(selectAllMenuItem)) {
filesTable.selectAll();
} else if (jmi.equals(showCaseDetailsMenuItem)) {
showCaseDetails(filesTable.getSelectedRow());
} else if (jmi.equals(exportToCSVMenuItem)) {
try {
saveToCSV();
} catch (NoCurrentCaseException ex) {
LOGGER.log(Level.SEVERE, "Exception while getting open case.", ex); // NON-NLS
}
} else if (jmi.equals(showCommonalityMenuItem)) {
showCommonalityDetails();
}
};
@@ -145,10 +152,6 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi
showCaseDetailsMenuItem.addActionListener(actList);
showCommonalityMenuItem.addActionListener(actList);
// Set background of every nth row as light grey.
TableCellRenderer renderer = new OtherOccurrencesFilesTableCellRenderer();
filesTable.setDefaultRenderer(Object.class, renderer);
// Configure column sorting.
TableRowSorter<TableModel> sorter = new TableRowSorter<>(filesTable.getModel());
filesTable.setRowSorter(sorter);
@@ -162,8 +165,18 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi
updateOnDataSourceSelection();
}
});
//alows resizing of the 4th section
filesTable.getSelectionModel().addListSelectionListener((e) -> {
if (Case.isCaseOpen()) {
occurrencePanel = new OccurrencePanel();
updateOnFileSelection();
}
});
//sort tables alphabetically initially
casesTable.getRowSorter().toggleSortOrder(0);
dataSourcesTable.getRowSorter().toggleSortOrder(0);
filesTable.getRowSorter().toggleSortOrder(0);
}
@Messages({"DataContentViewerOtherCases.correlatedArtifacts.isEmpty=There are no files or artifacts to correlate.",
@@ -218,54 +231,43 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi
"DataContentViewerOtherCases.caseDetailsDialog.noCaseNameError=Error",
"DataContentViewerOtherCases.noOpenCase.errMsg=No open case available."})
private void showCaseDetails(int selectedRowViewIdx) {
String caseDisplayName = Bundle.DataContentViewerOtherCases_caseDetailsDialog_noCaseNameError();
String details = Bundle.DataContentViewerOtherCases_caseDetailsDialog_noDetails();
try {
if (-1 != selectedRowViewIdx) {
EamDb dbManager = EamDb.getInstance();
int selectedRowModelIdx = filesTable.convertRowIndexToModel(selectedRowViewIdx);
OtherOccurrenceNodeInstanceData nodeData = (OtherOccurrenceNodeInstanceData) tableModel.getRow(selectedRowModelIdx);
CorrelationCase eamCasePartial = nodeData.getCorrelationAttributeInstance().getCorrelationCase();
if (eamCasePartial == null) {
JOptionPane.showConfirmDialog(showCaseDetailsMenuItem,
Bundle.DataContentViewerOtherCases_caseDetailsDialog_noDetailsReference(),
caseDisplayName,
DEFAULT_OPTION, PLAIN_MESSAGE);
return;
List<OtherOccurrenceNodeData> rowList = filesTableModel.getListOfNodesForFile(selectedRowModelIdx);
if (!rowList.isEmpty()) {
if (rowList.get(0) instanceof OtherOccurrenceNodeInstanceData) {
CorrelationCase eamCasePartial = ((OtherOccurrenceNodeInstanceData) rowList.get(0)).getCorrelationAttributeInstance().getCorrelationCase();
caseDisplayName = eamCasePartial.getDisplayName();
// query case details
CorrelationCase eamCase = dbManager.getCaseByUUID(eamCasePartial.getCaseUUID());
if (eamCase != null) {
details = eamCase.getCaseDetailsOptionsPaneDialog();
} else {
details = Bundle.DataContentViewerOtherCases_caseDetailsDialog_noDetails();
}
} else {
details = Bundle.DataContentViewerOtherCases_caseDetailsDialog_notSelected();
}
} else {
details = Bundle.DataContentViewerOtherCases_caseDetailsDialog_noDetailsReference();
}
caseDisplayName = eamCasePartial.getDisplayName();
// query case details
CorrelationCase eamCase = dbManager.getCaseByUUID(eamCasePartial.getCaseUUID());
if (eamCase == null) {
JOptionPane.showConfirmDialog(showCaseDetailsMenuItem,
Bundle.DataContentViewerOtherCases_caseDetailsDialog_noDetails(),
caseDisplayName,
DEFAULT_OPTION, PLAIN_MESSAGE);
return;
}
// display case details
JOptionPane.showConfirmDialog(showCaseDetailsMenuItem,
eamCase.getCaseDetailsOptionsPaneDialog(),
caseDisplayName,
DEFAULT_OPTION, PLAIN_MESSAGE);
} else {
JOptionPane.showConfirmDialog(showCaseDetailsMenuItem,
Bundle.DataContentViewerOtherCases_caseDetailsDialog_notSelected(),
caseDisplayName,
DEFAULT_OPTION, PLAIN_MESSAGE);
}
} catch (EamDbException ex) {
LOGGER.log(Level.SEVERE, "Error loading case details", ex);
} finally {
JOptionPane.showConfirmDialog(showCaseDetailsMenuItem,
Bundle.DataContentViewerOtherCases_caseDetailsDialog_noDetails(),
details,
caseDisplayName,
DEFAULT_OPTION, PLAIN_MESSAGE);
}
}
private void saveToCSV() throws NoCurrentCaseException {
if (0 != filesTable.getSelectedRowCount()) {
if (casesTableModel.getRowCount() > 0) {
Calendar now = Calendar.getInstance();
String fileName = String.format("%1$tY%1$tm%1$te%1$tI%1$tM%1$tS_other_data_sources.csv", now);
CSVFileChooser.setCurrentDirectory(new File(Case.getCurrentCaseThrows().getExportDirectory()));
@@ -279,45 +281,46 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi
if (!selectedFile.getName().endsWith(".csv")) { // NON-NLS
selectedFile = new File(selectedFile.toString() + ".csv"); // NON-NLS
}
writeSelectedRowsToFileAsCSV(selectedFile);
writeOtherOccurrencesToFileAsCSV(selectedFile);
}
}
}
private void writeSelectedRowsToFileAsCSV(File destFile) {
StringBuilder content;
int[] selectedRowViewIndices = filesTable.getSelectedRows();
int colCount = tableModel.getColumnCount();
@Messages({
"DataContentViewerOtherCasesModel.csvHeader.case=Case",
"DataContentViewerOtherCasesModel.csvHeader.device=Device",
"DataContentViewerOtherCasesModel.csvHeader.dataSource=Data Source",
"DataContentViewerOtherCasesModel.csvHeader.attribute=Matched Attribute",
"DataContentViewerOtherCasesModel.csvHeader.value=Attribute Value",
"DataContentViewerOtherCasesModel.csvHeader.known=Known",
"DataContentViewerOtherCasesModel.csvHeader.path=Path",
"DataContentViewerOtherCasesModel.csvHeader.comment=Comment"
})
/**
* Write data for all cases in the content viewer to a CSV file
*/
private void writeOtherOccurrencesToFileAsCSV(File destFile) {
try (BufferedWriter writer = Files.newBufferedWriter(destFile.toPath())) {
// write column names
content = new StringBuilder("");
for (int colIdx = 0; colIdx < colCount; colIdx++) {
content.append('"').append(tableModel.getColumnName(colIdx)).append('"');
if (colIdx < (colCount - 1)) {
content.append(",");
//write headers
StringBuilder headers = new StringBuilder("\"");
headers.append(Bundle.DataContentViewerOtherCasesModel_csvHeader_case())
.append(OtherOccurrenceNodeInstanceData.getCsvItemSeparator()).append(Bundle.DataContentViewerOtherCasesModel_csvHeader_dataSource())
.append(OtherOccurrenceNodeInstanceData.getCsvItemSeparator()).append(Bundle.DataContentViewerOtherCasesModel_csvHeader_attribute())
.append(OtherOccurrenceNodeInstanceData.getCsvItemSeparator()).append(Bundle.DataContentViewerOtherCasesModel_csvHeader_value())
.append(OtherOccurrenceNodeInstanceData.getCsvItemSeparator()).append(Bundle.DataContentViewerOtherCasesModel_csvHeader_known())
.append(OtherOccurrenceNodeInstanceData.getCsvItemSeparator()).append(Bundle.DataContentViewerOtherCasesModel_csvHeader_path())
.append(OtherOccurrenceNodeInstanceData.getCsvItemSeparator()).append(Bundle.DataContentViewerOtherCasesModel_csvHeader_comment())
.append('"').append(System.getProperty("line.separator"));
writer.write(headers.toString());
//write content
for (CorrelationAttributeInstance corAttr : correlationAttributes) {
Map<UniquePathKey, OtherOccurrenceNodeInstanceData> correlatedNodeDataMap = new HashMap<>(0);
// get correlation and reference set instances from DB
correlatedNodeDataMap.putAll(getCorrelatedInstances(corAttr, dataSourceName, deviceId));
for (OtherOccurrenceNodeInstanceData nodeData : correlatedNodeDataMap.values()) {
writer.write(nodeData.toCsvString());
}
}
content.append(System.getProperty("line.separator"));
writer.write(content.toString());
// write rows
for (int rowViewIdx : selectedRowViewIndices) {
content = new StringBuilder("");
for (int colIdx = 0; colIdx < colCount; colIdx++) {
int rowModelIdx = filesTable.convertRowIndexToModel(rowViewIdx);
content.append('"').append(tableModel.getValueAt(rowModelIdx, colIdx)).append('"');
if (colIdx < (colCount - 1)) {
content.append(",");
}
}
content.append(System.getProperty("line.separator"));
writer.write(content.toString());
}
} catch (IOException ex) {
LOGGER.log(Level.SEVERE, "Error writing selected rows to CSV.", ex);
}
@@ -329,11 +332,15 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi
private void reset() {
// start with empty table
casesTableModel.clearTable();
((DefaultTableModel) dataSourcesTable.getModel()).setRowCount(0);
tableModel.clearTable();
dataSourcesTableModel.clearTable();
filesTableModel.clearTable();
correlationAttributes.clear();
earliestCaseDate.setText(Bundle.DataContentViewerOtherCases_earliestCaseNotAvailable());
foundInLabel.setText("");
//calling getPreferredSize has a side effect of ensuring it has a preferred size which reflects the contents which are visible
occurrencePanel = new OccurrencePanel();
occurrencePanel.getPreferredSize();
detailsPanelScrollPane.setViewportView(occurrencePanel);
}
@Override
@@ -364,6 +371,7 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi
@Override
public int isPreferred(Node node) {
return 1;
}
/**
@@ -558,7 +566,6 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi
try {
final Case openCase = Case.getCurrentCaseThrows();
String caseUUID = openCase.getName();
HashMap<UniquePathKey, OtherOccurrenceNodeInstanceData> nodeDataMap = new HashMap<>();
if (EamDb.isEnabled()) {
@@ -583,7 +590,6 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi
}
}
}
if (corAttr.getCorrelationType().getDisplayName().equals("Files")) {
List<AbstractFile> caseDbFiles = getCaseDbMatches(corAttr, openCase);
@@ -591,7 +597,6 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi
addOrUpdateNodeData(openCase, nodeDataMap, caseDbFile);
}
}
return nodeDataMap;
} catch (EamDbException ex) {
LOGGER.log(Level.SEVERE, "Error getting artifact instances from database.", ex); // NON-NLS
@@ -748,14 +753,14 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi
dataSources.add(makeDataSourceString(nodeData.getCorrelationAttributeInstance().getCorrelationCase().getCaseUUID(), nodeData.getDeviceID(), nodeData.getDataSourceName()));
caseNames.put(nodeData.getCorrelationAttributeInstance().getCorrelationCase().getCaseUUID(), nodeData.getCorrelationAttributeInstance().getCorrelationCase());
} catch (EamDbException ex) {
LOGGER.log(Level.WARNING, "Unable to get correlation case for displaying other occurrence for case: " + nodeData.getCaseName());
LOGGER.log(Level.WARNING, "Unable to get correlation case for displaying other occurrence for case: " + nodeData.getCaseName(), ex);
}
} else {
try {
dataSources.add(makeDataSourceString(Case.getCurrentCaseThrows().getName(), nodeData.getDeviceID(), nodeData.getDataSourceName()));
caseNames.put(Case.getCurrentCaseThrows().getName(), new CorrelationCase(Case.getCurrentCaseThrows().getName(), Case.getCurrentCaseThrows().getDisplayName()));
} catch (NoCurrentCaseException ex) {
LOGGER.log(Level.WARNING, "No current case open for other occurrences");
LOGGER.log(Level.WARNING, "No current case open for other occurrences", ex);
}
}
totalCount++;
@@ -770,7 +775,6 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi
} else if (caseCount == 0) {
casesTableModel.addCorrelationCase(NO_RESULTS_CASE);
}
setColumnWidths();
setEarliestCaseDate();
foundInLabel.setText(String.format(Bundle.DataContentViewerOtherCases_foundIn_text(), totalCount, caseCount, dataSources.size()));
if (caseCount > 0) {
@@ -791,33 +795,40 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi
*/
private void updateOnCaseSelection() {
int[] selectedCaseIndexes = casesTable.getSelectedRows();
DefaultTableModel dataSourceModel = (DefaultTableModel) dataSourcesTable.getModel();
dataSourceModel.setRowCount(0);
tableModel.clearTable();
for (CorrelationAttributeInstance corAttr : correlationAttributes) {
Map<UniquePathKey, OtherOccurrenceNodeInstanceData> correlatedNodeDataMap = new HashMap<>(0);
dataSourcesTableModel.clearTable();
filesTableModel.clearTable();
// get correlation and reference set instances from DB
correlatedNodeDataMap.putAll(getCorrelatedInstances(corAttr, dataSourceName, deviceId));
for (OtherOccurrenceNodeInstanceData nodeData : correlatedNodeDataMap.values()) {
for (int selectedRow : selectedCaseIndexes) {
try {
if (nodeData.isCentralRepoNode()) {
if (casesTableModel.getCorrelationCase(casesTable.convertRowIndexToModel(selectedRow)) != null
&& ((CorrelationCase) casesTableModel.getCorrelationCase(casesTable.convertRowIndexToModel(selectedRow))).getCaseUUID().equals(nodeData.getCorrelationAttributeInstance().getCorrelationCase().getCaseUUID())) {
dataSourceModel.addRow(new Object[]{nodeData.getDataSourceName(), nodeData.getDeviceID()});
if (selectedCaseIndexes.length == 0) {
//special case when no cases are selected
occurrencePanel = new OccurrencePanel();
occurrencePanel.getPreferredSize();
detailsPanelScrollPane.setViewportView(occurrencePanel);
} else {
for (CorrelationAttributeInstance corAttr : correlationAttributes) {
Map<UniquePathKey, OtherOccurrenceNodeInstanceData> correlatedNodeDataMap = new HashMap<>(0);
// get correlation and reference set instances from DB
correlatedNodeDataMap.putAll(getCorrelatedInstances(corAttr, dataSourceName, deviceId));
for (OtherOccurrenceNodeInstanceData nodeData : correlatedNodeDataMap.values()) {
for (int selectedRow : selectedCaseIndexes) {
try {
if (nodeData.isCentralRepoNode()) {
if (casesTableModel.getCorrelationCase(casesTable.convertRowIndexToModel(selectedRow)) != null
&& casesTableModel.getCorrelationCase(casesTable.convertRowIndexToModel(selectedRow)).getCaseUUID().equals(nodeData.getCorrelationAttributeInstance().getCorrelationCase().getCaseUUID())) {
dataSourcesTableModel.addNodeData(nodeData);
}
} else {
dataSourcesTableModel.addNodeData(nodeData);
}
} else {
dataSourceModel.addRow(new Object[]{nodeData.getDataSourceName(), nodeData.getDeviceID()});
} catch (EamDbException ex) {
LOGGER.log(Level.WARNING, "Unable to get correlation attribute instance from OtherOccurrenceNodeInstanceData for case " + nodeData.getCaseName(), ex);
}
} catch (EamDbException ex) {
LOGGER.log(Level.WARNING, "Unable to get correlation attribute instance from OtherOccurrenceNodeInstanceData for case " + nodeData.getCaseName());
}
}
}
}
if (dataSourcesTable.getRowCount() > 0) {
dataSourcesTable.setRowSelectionInterval(0, 0);
if (dataSourcesTable.getRowCount() > 0) {
dataSourcesTable.setRowSelectionInterval(0, 0);
}
}
}
@@ -827,9 +838,8 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi
*/
private void updateOnDataSourceSelection() {
int[] selectedCaseIndexes = casesTable.getSelectedRows();
DefaultTableModel dataSourceModel = (DefaultTableModel) dataSourcesTable.getModel();
int[] selectedDataSources = dataSourcesTable.getSelectedRows();
tableModel.clearTable();
filesTableModel.clearTable();
for (CorrelationAttributeInstance corAttr : correlationAttributes) {
Map<UniquePathKey, OtherOccurrenceNodeInstanceData> correlatedNodeDataMap = new HashMap<>(0);
@@ -841,43 +851,94 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi
try {
if (nodeData.isCentralRepoNode()) {
if (casesTableModel.getCorrelationCase(casesTable.convertRowIndexToModel(selectedCaseRow)) != null
&& ((CorrelationCase) casesTableModel.getCorrelationCase(casesTable.convertRowIndexToModel(selectedCaseRow))).getCaseUUID().equals(nodeData.getCorrelationAttributeInstance().getCorrelationCase().getCaseUUID())
&& dataSourceModel.getValueAt(dataSourcesTable.convertRowIndexToModel(selectedDataSourceRow), 1).toString().equals(nodeData.getDeviceID())) {
tableModel.addNodeData(nodeData);
&& casesTableModel.getCorrelationCase(casesTable.convertRowIndexToModel(selectedCaseRow)).getCaseUUID().equals(nodeData.getCorrelationAttributeInstance().getCorrelationCase().getCaseUUID())
&& dataSourcesTableModel.getDeviceIdForRow(dataSourcesTable.convertRowIndexToModel(selectedDataSourceRow)).equals(nodeData.getDeviceID())) {
filesTableModel.addNodeData(nodeData);
}
} else {
if (dataSourceModel.getValueAt(dataSourcesTable.convertRowIndexToModel(selectedDataSourceRow), 1).toString().equals(nodeData.getDeviceID())) {
tableModel.addNodeData(nodeData);
if (dataSourcesTableModel.getDeviceIdForRow(dataSourcesTable.convertRowIndexToModel(selectedDataSourceRow)).equals(nodeData.getDeviceID())) {
filesTableModel.addNodeData(nodeData);
}
}
} catch (EamDbException ex) {
LOGGER.log(Level.WARNING, "Unable to get correlation attribute instance from OtherOccurrenceNodeInstanceData for case " + nodeData.getCaseName());
LOGGER.log(Level.WARNING, "Unable to get correlation attribute instance from OtherOccurrenceNodeInstanceData for case " + nodeData.getCaseName(), ex);
}
}
}
}
}
if (filesTable.getRowCount() > 0) {
filesTable.setRowSelectionInterval(0, 0);
}
}
/**
* Adjust column widths to their preferred values.
* Update the data displayed in the details section to be correct for the
* currently selected File
*/
private void setColumnWidths() {
for (int idx = 0; idx < tableModel.getColumnCount(); idx++) {
TableColumn column = filesTable.getColumnModel().getColumn(idx);
column.setMinWidth(DEFAULT_MIN_CELL_WIDTH);
int columnWidth = tableModel.getColumnPreferredWidth(idx);
if (columnWidth > 0) {
column.setPreferredWidth(columnWidth);
private void updateOnFileSelection() {
if (filesTable.getSelectedRowCount() == 1) {
//if there is one file selected update the deatils to show the data for that file
occurrencePanel = new OccurrencePanel(filesTableModel.getListOfNodesForFile(filesTable.convertRowIndexToModel(filesTable.getSelectedRow())));
} else if (dataSourcesTable.getSelectedRowCount() == 1) {
//if no files were selected and only one data source is selected update the information to reflect the data source
String caseName = dataSourcesTableModel.getCaseNameForRow(dataSourcesTable.convertRowIndexToModel(dataSourcesTable.getSelectedRow()));
String dsName = dataSourcesTableModel.getValueAt(dataSourcesTable.convertRowIndexToModel(dataSourcesTable.getSelectedRow()), 0).toString();
String caseCreatedDate = "";
for (int row : casesTable.getSelectedRows()) {
if (casesTableModel.getValueAt(casesTable.convertRowIndexToModel(row), 0).toString().equals(caseName)) {
caseCreatedDate = getCaseCreatedDate(row);
break;
}
}
}
for (int idx = 0; idx < dataSourcesTable.getColumnCount(); idx++) {
if (dataSourcesTable.getColumnModel().getColumn(idx).getHeaderValue().toString().equals(Bundle.DataContentViewerOtherCases_dataSources_header_text())) {
dataSourcesTable.getColumnModel().getColumn(idx).setPreferredWidth(100);
occurrencePanel = new OccurrencePanel(caseName, caseCreatedDate, dsName);
} else if (casesTable.getSelectedRowCount() == 1) {
//if no files were selected and a number of data source other than 1 are selected
//update the information to reflect the case
String createdDate = "";
String caseName = "";
if (casesTable.getRowCount() > 0) {
caseName = casesTableModel.getValueAt(casesTable.convertRowIndexToModel(casesTable.getSelectedRow()), 0).toString();
}
if (caseName.isEmpty()) {
occurrencePanel = new OccurrencePanel();
} else {
dataSourcesTable.getColumnModel().getColumn(idx).setPreferredWidth(210);
createdDate = getCaseCreatedDate(casesTable.getSelectedRow());
occurrencePanel = new OccurrencePanel(caseName, createdDate);
}
} else {
//else display an empty details area
occurrencePanel = new OccurrencePanel();
}
//calling getPreferredSize has a side effect of ensuring it has a preferred size which reflects the contents which are visible
occurrencePanel.getPreferredSize();
detailsPanelScrollPane.setViewportView(occurrencePanel);
}
/**
* Get the date a case was created
*
* @param caseTableRowIdx the row from the casesTable representing the case
*
* @return A string representing the date the case was created or an empty
* string if the date could not be determined
*/
private String getCaseCreatedDate(int caseTableRowIdx) {
try {
if (EamDb.isEnabled()) {
CorrelationCase partialCase;
partialCase = casesTableModel.getCorrelationCase(casesTable.convertRowIndexToModel(caseTableRowIdx));
if (partialCase == null){
return "";
}
return EamDb.getInstance().getCaseByUUID(partialCase.getCaseUUID()).getCreationDate();
} else {
return Case.getCurrentCase().getCreatedDate();
}
} catch (EamDbException ex) {
LOGGER.log(Level.WARNING, "Error getting case created date for row: " + caseTableRowIdx, ex);
}
return "";
}
/**
@@ -895,19 +956,20 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi
showCaseDetailsMenuItem = new javax.swing.JMenuItem();
showCommonalityMenuItem = new javax.swing.JMenuItem();
CSVFileChooser = new javax.swing.JFileChooser();
otherCasesPanel = new javax.swing.JPanel();
tableContainerPanel = new javax.swing.JPanel();
earliestCaseLabel = new javax.swing.JLabel();
earliestCaseDate = new javax.swing.JLabel();
foundInLabel = new javax.swing.JLabel();
jSplitPane2 = new javax.swing.JSplitPane();
jSplitPane3 = new javax.swing.JSplitPane();
tablesViewerSplitPane = new javax.swing.JSplitPane();
caseDatasourceFileSplitPane = new javax.swing.JSplitPane();
caseDatasourceSplitPane = new javax.swing.JSplitPane();
caseScrollPane = new javax.swing.JScrollPane();
casesTable = new javax.swing.JTable();
dataSourceScrollPane = new javax.swing.JScrollPane();
dataSourcesTable = new javax.swing.JTable();
propertiesTableScrollPane = new javax.swing.JScrollPane();
filesTableScrollPane = new javax.swing.JScrollPane();
filesTable = new javax.swing.JTable();
detailsPanelScrollPane = new javax.swing.JScrollPane();
rightClickPopupMenu.addPopupMenuListener(new javax.swing.event.PopupMenuListener() {
public void popupMenuCanceled(javax.swing.event.PopupMenuEvent evt) {
@@ -931,13 +993,12 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi
org.openide.awt.Mnemonics.setLocalizedText(showCommonalityMenuItem, org.openide.util.NbBundle.getMessage(DataContentViewerOtherCases.class, "DataContentViewerOtherCases.showCommonalityMenuItem.text")); // NOI18N
rightClickPopupMenu.add(showCommonalityMenuItem);
setMinimumSize(new java.awt.Dimension(1500, 10));
setMinimumSize(new java.awt.Dimension(600, 10));
setOpaque(false);
setPreferredSize(new java.awt.Dimension(1500, 44));
setPreferredSize(new java.awt.Dimension(600, 63));
otherCasesPanel.setPreferredSize(new java.awt.Dimension(921, 62));
tableContainerPanel.setPreferredSize(new java.awt.Dimension(1500, 63));
tableContainerPanel.setPreferredSize(new java.awt.Dimension(600, 63));
tableContainerPanel.setRequestFocusEnabled(false);
org.openide.awt.Mnemonics.setLocalizedText(earliestCaseLabel, org.openide.util.NbBundle.getMessage(DataContentViewerOtherCases.class, "DataContentViewerOtherCases.earliestCaseLabel.text")); // NOI18N
earliestCaseLabel.setToolTipText(org.openide.util.NbBundle.getMessage(DataContentViewerOtherCases.class, "DataContentViewerOtherCases.earliestCaseLabel.toolTipText")); // NOI18N
@@ -946,49 +1007,50 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi
org.openide.awt.Mnemonics.setLocalizedText(foundInLabel, org.openide.util.NbBundle.getMessage(DataContentViewerOtherCases.class, "DataContentViewerOtherCases.foundInLabel.text")); // NOI18N
jSplitPane2.setDividerLocation(470);
tablesViewerSplitPane.setDividerLocation(450);
tablesViewerSplitPane.setResizeWeight(0.5);
jSplitPane3.setDividerLocation(150);
caseDatasourceFileSplitPane.setDividerLocation(300);
caseDatasourceFileSplitPane.setResizeWeight(0.6);
caseDatasourceFileSplitPane.setToolTipText("");
caseDatasourceSplitPane.setDividerLocation(150);
caseDatasourceSplitPane.setResizeWeight(0.5);
caseScrollPane.setPreferredSize(new java.awt.Dimension(140, 30));
casesTable.setAutoCreateRowSorter(true);
casesTable.setModel(casesTableModel);
caseScrollPane.setViewportView(casesTable);
jSplitPane3.setLeftComponent(caseScrollPane);
caseDatasourceSplitPane.setLeftComponent(caseScrollPane);
dataSourceScrollPane.setPreferredSize(new java.awt.Dimension(140, 30));
dataSourcesTable.setAutoCreateRowSorter(true);
dataSourcesTable.setModel(new javax.swing.table.DefaultTableModel(
new Object [][] {
},
new String [] {
"Data Source Name", "Device ID"
}
) {
boolean[] canEdit = new boolean [] {
false, false
};
public boolean isCellEditable(int rowIndex, int columnIndex) {
return canEdit [columnIndex];
}
});
dataSourcesTable.setModel(dataSourcesTableModel);
dataSourceScrollPane.setViewportView(dataSourcesTable);
jSplitPane3.setRightComponent(dataSourceScrollPane);
caseDatasourceSplitPane.setRightComponent(dataSourceScrollPane);
jSplitPane2.setLeftComponent(jSplitPane3);
caseDatasourceFileSplitPane.setLeftComponent(caseDatasourceSplitPane);
propertiesTableScrollPane.setPreferredSize(new java.awt.Dimension(1000, 30));
filesTableScrollPane.setPreferredSize(new java.awt.Dimension(140, 30));
filesTable.setAutoCreateRowSorter(true);
filesTable.setModel(tableModel);
filesTable.setModel(filesTableModel);
filesTable.setToolTipText(org.openide.util.NbBundle.getMessage(DataContentViewerOtherCases.class, "DataContentViewerOtherCases.table.toolTip.text")); // NOI18N
filesTable.setComponentPopupMenu(rightClickPopupMenu);
filesTable.setSelectionMode(javax.swing.ListSelectionModel.SINGLE_INTERVAL_SELECTION);
propertiesTableScrollPane.setViewportView(filesTable);
filesTable.setSelectionMode(javax.swing.ListSelectionModel.SINGLE_SELECTION);
filesTableScrollPane.setViewportView(filesTable);
jSplitPane2.setRightComponent(propertiesTableScrollPane);
caseDatasourceFileSplitPane.setRightComponent(filesTableScrollPane);
tablesViewerSplitPane.setLeftComponent(caseDatasourceFileSplitPane);
detailsPanelScrollPane.setHorizontalScrollBarPolicy(javax.swing.ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
detailsPanelScrollPane.setPreferredSize(new java.awt.Dimension(200, 100));
tablesViewerSplitPane.setRightComponent(detailsPanelScrollPane);
javax.swing.GroupLayout tableContainerPanelLayout = new javax.swing.GroupLayout(tableContainerPanel);
tableContainerPanel.setLayout(tableContainerPanelLayout);
@@ -997,65 +1059,49 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi
.addGroup(tableContainerPanelLayout.createSequentialGroup()
.addGroup(tableContainerPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(tableContainerPanelLayout.createSequentialGroup()
.addComponent(earliestCaseLabel)
.addComponent(earliestCaseLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 161, javax.swing.GroupLayout.PREFERRED_SIZE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(earliestCaseDate)
.addGap(66, 66, 66)
.addComponent(foundInLabel))
.addComponent(jSplitPane2, javax.swing.GroupLayout.DEFAULT_SIZE, 911, Short.MAX_VALUE))
.addComponent(earliestCaseDate, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.addComponent(foundInLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
.addComponent(tablesViewerSplitPane, javax.swing.GroupLayout.DEFAULT_SIZE, 590, Short.MAX_VALUE))
.addContainerGap())
);
tableContainerPanelLayout.setVerticalGroup(
tableContainerPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(javax.swing.GroupLayout.Alignment.TRAILING, tableContainerPanelLayout.createSequentialGroup()
.addComponent(jSplitPane2, javax.swing.GroupLayout.DEFAULT_SIZE, 31, Short.MAX_VALUE)
.addGroup(tableContainerPanelLayout.createSequentialGroup()
.addGap(0, 0, 0)
.addComponent(tablesViewerSplitPane, javax.swing.GroupLayout.DEFAULT_SIZE, 33, Short.MAX_VALUE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addGroup(tableContainerPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
.addComponent(earliestCaseLabel)
.addComponent(earliestCaseDate)
.addComponent(foundInLabel))
.addGroup(tableContainerPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING)
.addGroup(tableContainerPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
.addComponent(earliestCaseLabel)
.addComponent(earliestCaseDate))
.addComponent(foundInLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 14, javax.swing.GroupLayout.PREFERRED_SIZE))
.addContainerGap())
);
javax.swing.GroupLayout otherCasesPanelLayout = new javax.swing.GroupLayout(otherCasesPanel);
otherCasesPanel.setLayout(otherCasesPanelLayout);
otherCasesPanelLayout.setHorizontalGroup(
otherCasesPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGap(0, 921, Short.MAX_VALUE)
.addGroup(otherCasesPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(otherCasesPanelLayout.createSequentialGroup()
.addComponent(tableContainerPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.addGap(0, 0, 0)))
);
otherCasesPanelLayout.setVerticalGroup(
otherCasesPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGap(0, 62, Short.MAX_VALUE)
.addGroup(otherCasesPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(otherCasesPanelLayout.createSequentialGroup()
.addComponent(tableContainerPanel, javax.swing.GroupLayout.DEFAULT_SIZE, 62, Short.MAX_VALUE)
.addGap(0, 0, 0)))
);
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
this.setLayout(layout);
layout.setHorizontalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(otherCasesPanel, javax.swing.GroupLayout.DEFAULT_SIZE, 1500, Short.MAX_VALUE)
.addComponent(tableContainerPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
);
layout.setVerticalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(otherCasesPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.addGroup(layout.createSequentialGroup()
.addComponent(tableContainerPanel, javax.swing.GroupLayout.DEFAULT_SIZE, 64, Short.MAX_VALUE)
.addGap(0, 0, 0))
);
}// </editor-fold>//GEN-END:initComponents
private void rightClickPopupMenuPopupMenuWillBecomeVisible(javax.swing.event.PopupMenuEvent evt) {//GEN-FIRST:event_rightClickPopupMenuPopupMenuWillBecomeVisible
boolean enableCentralRepoActions = false;
if (EamDb.isEnabled() && filesTable.getSelectedRowCount() == 1) {
int rowIndex = filesTable.getSelectedRow();
OtherOccurrenceNodeData selectedNode = (OtherOccurrenceNodeData) tableModel.getRow(rowIndex);
if (selectedNode instanceof OtherOccurrenceNodeInstanceData) {
OtherOccurrenceNodeInstanceData instanceData = (OtherOccurrenceNodeInstanceData) selectedNode;
List<OtherOccurrenceNodeData> selectedFile = filesTableModel.getListOfNodesForFile(rowIndex);
if (!selectedFile.isEmpty() && selectedFile.get(0) instanceof OtherOccurrenceNodeInstanceData) {
OtherOccurrenceNodeInstanceData instanceData = (OtherOccurrenceNodeInstanceData) selectedFile.get(0);
enableCentralRepoActions = instanceData.isCentralRepoNode();
}
}
@@ -1065,24 +1111,25 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi
// Variables declaration - do not modify//GEN-BEGIN:variables
private javax.swing.JFileChooser CSVFileChooser;
private javax.swing.JSplitPane caseDatasourceFileSplitPane;
private javax.swing.JSplitPane caseDatasourceSplitPane;
private javax.swing.JScrollPane caseScrollPane;
private javax.swing.JTable casesTable;
private javax.swing.JScrollPane dataSourceScrollPane;
private javax.swing.JTable dataSourcesTable;
private javax.swing.JScrollPane detailsPanelScrollPane;
private javax.swing.JLabel earliestCaseDate;
private javax.swing.JLabel earliestCaseLabel;
private javax.swing.JMenuItem exportToCSVMenuItem;
private javax.swing.JTable filesTable;
private javax.swing.JScrollPane filesTableScrollPane;
private javax.swing.JLabel foundInLabel;
private javax.swing.JSplitPane jSplitPane2;
private javax.swing.JSplitPane jSplitPane3;
private javax.swing.JPanel otherCasesPanel;
private javax.swing.JScrollPane propertiesTableScrollPane;
private javax.swing.JPopupMenu rightClickPopupMenu;
private javax.swing.JMenuItem selectAllMenuItem;
private javax.swing.JMenuItem showCaseDetailsMenuItem;
private javax.swing.JMenuItem showCommonalityMenuItem;
private javax.swing.JPanel tableContainerPanel;
private javax.swing.JSplitPane tablesViewerSplitPane;
// End of variables declaration//GEN-END:variables
/**
@@ -1119,9 +1166,6 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi
@Override
public int hashCode() {
//int hash = 7;
//hash = 67 * hash + this.dataSourceID.hashCode();
//hash = 67 * hash + this.filePath.hashCode();
return Objects.hash(getDataSourceID(), getFilePath(), getType());
}
@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8" ?>
<Form version="1.4" maxVersion="1.9" type="org.netbeans.modules.form.forminfo.JPanelFormInfo">
<Properties>
<Property name="minimumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[50, 30]"/>
</Property>
</Properties>
<AuxValues>
<AuxValue name="FormSettings_autoResourcing" type="java.lang.Integer" value="1"/>
<AuxValue name="FormSettings_autoSetComponentName" type="java.lang.Boolean" value="false"/>
<AuxValue name="FormSettings_generateFQN" type="java.lang.Boolean" value="true"/>
<AuxValue name="FormSettings_generateMnemonicsCode" type="java.lang.Boolean" value="true"/>
<AuxValue name="FormSettings_i18nAutoMode" type="java.lang.Boolean" value="true"/>
<AuxValue name="FormSettings_layoutCodeTarget" type="java.lang.Integer" value="1"/>
<AuxValue name="FormSettings_listenerGenerationStyle" type="java.lang.Integer" value="0"/>
<AuxValue name="FormSettings_variablesLocal" type="java.lang.Boolean" value="false"/>
<AuxValue name="FormSettings_variablesModifier" type="java.lang.Integer" value="2"/>
<AuxValue name="designerSize" type="java.awt.Dimension" value="-84,-19,0,5,115,114,0,18,106,97,118,97,46,97,119,116,46,68,105,109,101,110,115,105,111,110,65,-114,-39,-41,-84,95,68,20,2,0,2,73,0,6,104,101,105,103,104,116,73,0,5,119,105,100,116,104,120,112,0,0,0,68,0,0,0,-116"/>
</AuxValues>
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout"/>
</Form>
@@ -0,0 +1,370 @@
/*
* Central Repository
*
* Copyright 2019 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.centralrepository.contentviewer;
import java.awt.Color;
import java.awt.Font;
import java.util.ArrayList;
import java.util.Map;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.logging.Level;
import org.openide.util.NbBundle.Messages;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationCase;
import org.sleuthkit.autopsy.centralrepository.datamodel.EamDb;
import org.sleuthkit.autopsy.centralrepository.datamodel.EamDbException;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.datamodel.TskData;
/**
* Panel for displaying other occurrence details.
*/
final class OccurrencePanel extends javax.swing.JPanel {
private static final Logger LOGGER = Logger.getLogger(OccurrencePanel.class.getName());
private static final int LEFT_INSET = 10;
private static final int RIGHT_INSET = 10;
private static final int TOP_INSET = 10;
private static final int BOTTOM_INSET = 10;
private static final int VERTICAL_GAP = 6;
private static final int HORIZONTAL_GAP = 4;
private static final long serialVersionUID = 1L;
private int gridY = 0;
private final List<OtherOccurrenceNodeData> nodeDataList;
private final Map<String, String> caseNamesAndDates = new HashMap<>();
private final Set<String> dataSourceNames = new HashSet<>();
private final Set<String> filePaths = new HashSet<>();
/**
* Construct an empty OccurrencePanel
*/
OccurrencePanel() {
nodeDataList = new ArrayList<>();
customizeComponents();
}
/**
* Construct an OccurrencePanel which will display only Case information
*
* @param caseName the name of the case
* @param caseCreatedDate the date the case was created
*/
OccurrencePanel(String caseName, String caseCreatedDate) {
nodeDataList = new ArrayList<>();
caseNamesAndDates.put(caseName, caseCreatedDate);
customizeComponents();
}
/**
* Construct an OccurrencePanel which will display only case and data source
* information
*
* @param caseName the name of the case
* @param caseCreatedDate the date the case was created
* @param dataSourceName the name of the data source
*/
OccurrencePanel(String caseName, String caseCreatedDate, String dataSourceName) {
nodeDataList = new ArrayList<>();
caseNamesAndDates.put(caseName, caseCreatedDate);
dataSourceNames.add(dataSourceName);
customizeComponents();
}
/**
* Construct a OccurrencePanel which will display details for all other
* occurrences associated with a file
*
* @param nodeDataList the list of OtherOccurrenceNodeData representing
* common properties for the file
*/
OccurrencePanel(List<OtherOccurrenceNodeData> nodeDataList) {
this.nodeDataList = nodeDataList;
customizeComponents();
}
/**
* Do all the construction of gui elements and adding of the appropriate
* elements to the gridbaglayout
*/
private void customizeComponents() {
initComponents();
if (!this.nodeDataList.isEmpty()) {
//if addInstanceDetails is going to be called it should be called
// before addFileDetails, addDataSourceDetails, and addCaseDetails
//because it also collects the information they display
addInstanceDetails();
if (!filePaths.isEmpty()) {
addFileDetails();
}
}
if (!dataSourceNames.isEmpty()) {
addDataSourceDetails();
}
if (!caseNamesAndDates.keySet().isEmpty()) {
addCaseDetails();
}
//add filler to keep everything else at the top
addItemToBag(gridY, 0, 0, 0, new javax.swing.Box.Filler(new java.awt.Dimension(0, 0), new java.awt.Dimension(0, 0), new java.awt.Dimension(0, 32767)));
}
@Messages({
"OccurrencePanel.commonProperties.text=Common Properties",
"OccurrencePanel.commonPropertyTypeLabel.text=Type:",
"OccurrencePanel.commonPropertyValueLabel.text=Value:",
"OccurrencePanel.commonPropertyKnownStatusLabel.text=Known Status:",
"OccurrencePanel.commonPropertyCommentLabel.text=Comment:"
})
/**
* Add the Common Property instance details to the gridbaglayout supports
* adding multiple common properties
*
* Also collects the case, data source, and file path information to be
* displayed
*/
private void addInstanceDetails() {
javax.swing.JLabel commonPropertiesLabel = new javax.swing.JLabel();
org.openide.awt.Mnemonics.setLocalizedText(commonPropertiesLabel, Bundle.OccurrencePanel_commonProperties_text());
commonPropertiesLabel.setFont(commonPropertiesLabel.getFont().deriveFont(Font.BOLD, commonPropertiesLabel.getFont().getSize()));
addItemToBag(gridY, 0, TOP_INSET, 0, commonPropertiesLabel);
gridY++;
//for each other occurrence
for (OtherOccurrenceNodeData occurrence : nodeDataList) {
if (occurrence instanceof OtherOccurrenceNodeInstanceData) {
String type = ((OtherOccurrenceNodeInstanceData) occurrence).getType();
if (!type.isEmpty()) {
javax.swing.JLabel typeLabel = new javax.swing.JLabel();
org.openide.awt.Mnemonics.setLocalizedText(typeLabel, Bundle.OccurrencePanel_commonPropertyTypeLabel_text());
addItemToBag(gridY, 0, VERTICAL_GAP, 0, typeLabel);
javax.swing.JLabel typeFieldValue = new javax.swing.JLabel();
typeFieldValue.setText(type);
addItemToBag(gridY, 1, VERTICAL_GAP, 0, typeFieldValue);
gridY++;
}
String value = ((OtherOccurrenceNodeInstanceData) occurrence).getValue();
if (!value.isEmpty()) {
javax.swing.JLabel valueLabel = new javax.swing.JLabel();
org.openide.awt.Mnemonics.setLocalizedText(valueLabel, Bundle.OccurrencePanel_commonPropertyValueLabel_text());
addItemToBag(gridY, 0, 0, 0, valueLabel);
javax.swing.JLabel valueFieldValue = new javax.swing.JLabel();
valueFieldValue.setText(value);
addItemToBag(gridY, 1, 0, 0, valueFieldValue);
gridY++;
}
TskData.FileKnown knownStatus = ((OtherOccurrenceNodeInstanceData) occurrence).getKnown();
javax.swing.JLabel knownStatusLabel = new javax.swing.JLabel();
org.openide.awt.Mnemonics.setLocalizedText(knownStatusLabel, Bundle.OccurrencePanel_commonPropertyKnownStatusLabel_text());
addItemToBag(gridY, 0, 0, 0, knownStatusLabel);
javax.swing.JLabel knownStatusValue = new javax.swing.JLabel();
knownStatusValue.setText(knownStatus.toString());
if (knownStatus == TskData.FileKnown.BAD) {
knownStatusValue.setForeground(Color.RED);
}
addItemToBag(gridY, 1, 0, 0, knownStatusValue);
gridY++;
String comment = ((OtherOccurrenceNodeInstanceData) occurrence).getComment();
if (!comment.isEmpty()) {
javax.swing.JLabel commentLabel = new javax.swing.JLabel();
org.openide.awt.Mnemonics.setLocalizedText(commentLabel, Bundle.OccurrencePanel_commonPropertyCommentLabel_text());
addItemToBag(gridY, 0, 0, VERTICAL_GAP, commentLabel);
javax.swing.JTextArea commentValue = new javax.swing.JTextArea();
commentValue.setText(comment);
commentValue.setEditable(false);
commentValue.setColumns(20);
commentValue.setLineWrap(true);
commentValue.setRows(3);
commentValue.setTabSize(4);
commentValue.setWrapStyleWord(true);
commentValue.setBorder(javax.swing.BorderFactory.createEtchedBorder());
commentValue.setBackground(javax.swing.UIManager.getDefaults().getColor("TextArea.disabledBackground"));
addItemToBag(gridY, 1, 0, VERTICAL_GAP, commentValue);
gridY++;
}
String caseDate = "";
try {
OtherOccurrenceNodeInstanceData nodeData = ((OtherOccurrenceNodeInstanceData) occurrence);
if (nodeData.isCentralRepoNode()) {
if (EamDb.isEnabled()) {
CorrelationCase partialCase = nodeData.getCorrelationAttributeInstance().getCorrelationCase();
caseDate = EamDb.getInstance().getCaseByUUID(partialCase.getCaseUUID()).getCreationDate();
}
} else {
caseDate = Case.getCurrentCase().getCreatedDate();
}
} catch (EamDbException ex) {
LOGGER.log(Level.WARNING, "Error getting case created date for other occurrence content viewer", ex);
}
//Collect the data that is necessary for the other sections
caseNamesAndDates.put(((OtherOccurrenceNodeInstanceData) occurrence).getCaseName(), caseDate);
dataSourceNames.add(((OtherOccurrenceNodeInstanceData) occurrence).getDataSourceName());
filePaths.add(((OtherOccurrenceNodeInstanceData) occurrence).getFilePath());
}
}
//end for each
}
@Messages({
"OccurrencePanel.fileDetails.text=File Details",
"OccurrencePanel.filePathLabel.text=File Path:"
})
/**
* Add the File specific details such as file path to the gridbaglayout
*/
private void addFileDetails() {
String filePath = filePaths.size() > 1 ? "" : filePaths.iterator().next();
if (!filePath.isEmpty()) {
javax.swing.JLabel fileDetailsLabel = new javax.swing.JLabel();
org.openide.awt.Mnemonics.setLocalizedText(fileDetailsLabel, Bundle.OccurrencePanel_fileDetails_text());
fileDetailsLabel.setFont(fileDetailsLabel.getFont().deriveFont(Font.BOLD, fileDetailsLabel.getFont().getSize()));
addItemToBag(gridY, 0, TOP_INSET, 0, fileDetailsLabel);
gridY++;
javax.swing.JLabel filePathLabel = new javax.swing.JLabel();
org.openide.awt.Mnemonics.setLocalizedText(filePathLabel, Bundle.OccurrencePanel_filePathLabel_text());
addItemToBag(gridY, 0, VERTICAL_GAP, VERTICAL_GAP, filePathLabel);
javax.swing.JTextArea filePathValue = new javax.swing.JTextArea();
filePathValue.setText(filePath);
filePathValue.setEditable(false);
filePathValue.setColumns(20);
filePathValue.setLineWrap(true);
filePathValue.setRows(3);
filePathValue.setTabSize(4);
filePathValue.setWrapStyleWord(true);
filePathValue.setBorder(javax.swing.BorderFactory.createEtchedBorder());
filePathValue.setBackground(javax.swing.UIManager.getDefaults().getColor("TextArea.disabledBackground"));
addItemToBag(gridY, 1, VERTICAL_GAP, VERTICAL_GAP, filePathValue);
gridY++;
}
}
@Messages({
"OccurrencePanel.dataSourceDetails.text=Data Source Details",
"OccurrencePanel.dataSourceNameLabel.text=Name:"
})
/**
* Add the data source specific details such as data source name to the
* gridbaglayout
*/
private void addDataSourceDetails() {
String dataSourceName = dataSourceNames.size() > 1 ? "" : dataSourceNames.iterator().next();
if (!dataSourceName.isEmpty()) {
javax.swing.JLabel dataSourceDetailsLabel = new javax.swing.JLabel();
org.openide.awt.Mnemonics.setLocalizedText(dataSourceDetailsLabel, Bundle.OccurrencePanel_dataSourceDetails_text());
dataSourceDetailsLabel.setFont(dataSourceDetailsLabel.getFont().deriveFont(Font.BOLD, dataSourceDetailsLabel.getFont().getSize()));
addItemToBag(gridY, 0, TOP_INSET, 0, dataSourceDetailsLabel);
gridY++;
javax.swing.JLabel dataSourceNameLabel = new javax.swing.JLabel();
org.openide.awt.Mnemonics.setLocalizedText(dataSourceNameLabel, Bundle.OccurrencePanel_dataSourceNameLabel_text());
addItemToBag(gridY, 0, VERTICAL_GAP, VERTICAL_GAP, dataSourceNameLabel);
javax.swing.JLabel dataSourceNameValue = new javax.swing.JLabel();
dataSourceNameValue.setText(dataSourceName);
addItemToBag(gridY, 1, VERTICAL_GAP, VERTICAL_GAP, dataSourceNameValue);
gridY++;
}
}
@Messages({
"OccurrencePanel.caseDetails.text=Case Details",
"OccurrencePanel.caseNameLabel.text=Name:",
"OccurrencePanel.caseCreatedDateLabel.text=Created Date:"
})
/**
* Add the case specific details such as case name to the gridbaglayout
*/
private void addCaseDetails() {
javax.swing.JLabel caseDetailsLabel = new javax.swing.JLabel();
org.openide.awt.Mnemonics.setLocalizedText(caseDetailsLabel, Bundle.OccurrencePanel_caseDetails_text());
caseDetailsLabel.setFont(caseDetailsLabel.getFont().deriveFont(Font.BOLD, caseDetailsLabel.getFont().getSize()));
addItemToBag(gridY, 0, TOP_INSET, 0, caseDetailsLabel);
gridY++;
String caseName = caseNamesAndDates.keySet().size() > 1 ? "" : caseNamesAndDates.keySet().iterator().next();
if (!caseName.isEmpty()) {
javax.swing.JLabel caseNameLabel = new javax.swing.JLabel();
org.openide.awt.Mnemonics.setLocalizedText(caseNameLabel, Bundle.OccurrencePanel_caseNameLabel_text());
addItemToBag(gridY, 0, VERTICAL_GAP, 0, caseNameLabel);
javax.swing.JLabel caseNameValue = new javax.swing.JLabel();
caseNameValue.setText(caseName);
addItemToBag(gridY, 1, VERTICAL_GAP, 0, caseNameValue);
gridY++;
}
String caseCreatedDate = caseNamesAndDates.keySet().size() > 1 ? "" : caseNamesAndDates.get(caseName);
if (caseCreatedDate != null && !caseCreatedDate.isEmpty()) {
javax.swing.JLabel caseCreatedLabel = new javax.swing.JLabel();
org.openide.awt.Mnemonics.setLocalizedText(caseCreatedLabel, Bundle.OccurrencePanel_caseCreatedDateLabel_text());
addItemToBag(gridY, 0, 0, BOTTOM_INSET, caseCreatedLabel);
javax.swing.JLabel caseCreatedValue = new javax.swing.JLabel();
caseCreatedValue.setText(caseCreatedDate);
addItemToBag(gridY, 1, 0, BOTTOM_INSET, caseCreatedValue);
gridY++;
}
}
/**
* Add a JComponent to the gridbaglayout
*
* @param gridYLocation the row number the item should be added at
* @param gridXLocation the column number the item should be added at
* @param topInset the gap from the top of the cell which should exist
* @param bottomInset the gap from the bottom of the cell which should
* exist
* @param item the JComponent to add to the gridbaglayout
*/
private void addItemToBag(int gridYLocation, int gridXLocation, int topInset, int bottomInset, javax.swing.JComponent item) {
java.awt.GridBagConstraints gridBagConstraints;
gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.gridx = gridXLocation;
gridBagConstraints.gridy = gridYLocation;
gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
gridBagConstraints.anchor = java.awt.GridBagConstraints.FIRST_LINE_START;
int leftInset = LEFT_INSET;
int rightInset = HORIZONTAL_GAP;
//change formating a bit if it is the value instead of the label
if (gridXLocation == 1) {
leftInset = 0;
rightInset = RIGHT_INSET;
gridBagConstraints.weightx = 0.1;
gridBagConstraints.gridwidth = 2;
}
gridBagConstraints.insets = new java.awt.Insets(topInset, leftInset, bottomInset, rightInset);
//if the item is a filler item ensure it will resize vertically
if (item instanceof javax.swing.Box.Filler) {
gridBagConstraints.weighty = 0.1;
}
add(item, gridBagConstraints);
}
/**
* This method is called from within the constructor to initialize the form.
* WARNING: Do NOT modify this code. The content of this method is always
* regenerated by the Form Editor.
*/
@SuppressWarnings("unchecked")
// <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
private void initComponents() {
setMinimumSize(new java.awt.Dimension(50, 30));
setPreferredSize(null);
setLayout(new java.awt.GridBagLayout());
}// </editor-fold>//GEN-END:initComponents
// Variables declaration - do not modify//GEN-BEGIN:variables
// End of variables declaration//GEN-END:variables
}
@@ -1,7 +1,7 @@
/*
* Central Repository
*
* Copyright 2018 Basis Technology Corp.
* Copyright 2018-2019 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -31,11 +31,12 @@ import org.sleuthkit.datamodel.TskDataException;
* Class for populating the Other Occurrences tab
*/
class OtherOccurrenceNodeInstanceData implements OtherOccurrenceNodeData {
// For now hard code the string for the central repo files type, since
// getting it dynamically can fail.
private static final String FILE_TYPE_STR = "Files";
private static final String CSV_ITEM_SEPARATOR = "\",\"";
private final String caseName;
private String deviceID;
private String dataSourceName;
@@ -44,12 +45,13 @@ class OtherOccurrenceNodeInstanceData implements OtherOccurrenceNodeData {
private final String value;
private TskData.FileKnown known;
private String comment;
private AbstractFile originalAbstractFile = null;
private CorrelationAttributeInstance originalCorrelationInstance = null;
/**
* Create a node from a central repo instance.
*
* @param instance The central repo instance
* @param type The type of the instance
* @param value The value of the instance
@@ -63,15 +65,17 @@ class OtherOccurrenceNodeInstanceData implements OtherOccurrenceNodeData {
this.value = value;
known = instance.getKnownStatus();
comment = instance.getComment();
originalCorrelationInstance = instance;
}
/**
* Create a node from an abstract file.
*
* @param newFile The abstract file
* @param autopsyCase The current case
* @throws EamDbException
*
* @throws EamDbException
*/
OtherOccurrenceNodeInstanceData(AbstractFile newFile, Case autopsyCase) throws EamDbException {
caseName = autopsyCase.getDisplayName();
@@ -82,115 +86,129 @@ class OtherOccurrenceNodeInstanceData implements OtherOccurrenceNodeData {
} catch (TskDataException | TskCoreException ex) {
throw new EamDbException("Error loading data source for abstract file ID " + newFile.getId(), ex);
}
filePath = newFile.getParentPath() + newFile.getName();
typeStr = FILE_TYPE_STR;
value = newFile.getMd5Hash();
known = newFile.getKnown();
comment = "";
originalAbstractFile = newFile;
}
/**
* Check if this node is a "file" type
*
* @return true if it is a file type
*/
boolean isFileType() {
return FILE_TYPE_STR.equals(typeStr);
}
/**
* Update the known status for this node
*
* @param newKnownStatus The new known status
*/
void updateKnown(TskData.FileKnown newKnownStatus) {
known = newKnownStatus;
}
/**
* Update the comment for this node
*
* @param newComment The new comment
*/
void updateComment(String newComment) {
comment = newComment;
}
/**
* Check if this is a central repo node.
* @return true if this node was created from a central repo instance, false otherwise
*
* @return true if this node was created from a central repo instance, false
* otherwise
*/
boolean isCentralRepoNode() {
return (originalCorrelationInstance != null);
}
}
/**
* Get the case name
*
* @return the case name
*/
String getCaseName() {
return caseName;
}
/**
* Get the device ID
*
* @return the device ID
*/
String getDeviceID() {
return deviceID;
}
/**
* Get the data source name
*
* @return the data source name
*/
String getDataSourceName() {
return dataSourceName;
}
/**
* Get the file path
*
* @return the file path
*/
String getFilePath() {
return filePath;
}
/**
* Get the type (as a string)
*
* @return the type
*/
String getType() {
return typeStr;
}
/**
* Get the value (MD5 hash for files)
*
* @return the value
*/
String getValue() {
return value;
}
/**
* Get the known status
*
* @return the known status
*/
TskData.FileKnown getKnown() {
return known;
}
/**
* Get the comment
*
* @return the comment
*/
String getComment() {
return comment;
}
/**
* Get the backing abstract file.
* Should only be called if isCentralRepoNode() is false
* Get the backing abstract file. Should only be called if
* isCentralRepoNode() is false
*
* @return the original abstract file
*/
AbstractFile getAbstractFile() throws EamDbException {
@@ -199,12 +217,14 @@ class OtherOccurrenceNodeInstanceData implements OtherOccurrenceNodeData {
}
return originalAbstractFile;
}
/**
* Get the backing CorrelationAttributeInstance.
* Should only be called if isCentralRepoNode() is true
* Get the backing CorrelationAttributeInstance. Should only be called if
* isCentralRepoNode() is true
*
* @return the original CorrelationAttributeInstance
* @throws EamDbException
*
* @throws EamDbException
*/
CorrelationAttributeInstance getCorrelationAttributeInstance() throws EamDbException {
if (originalCorrelationInstance == null) {
@@ -212,4 +232,33 @@ class OtherOccurrenceNodeInstanceData implements OtherOccurrenceNodeData {
}
return originalCorrelationInstance;
}
/**
* Get the string to append between elements when writing the node instance
* data to a CSV
*
* @return the CSV_ITEM_SEPARATOR string
*/
static String getCsvItemSeparator() {
return CSV_ITEM_SEPARATOR;
}
/**
* Create a string representation of the node's data comma separated with a
* line separator ending
*
* @return a comma separated string representation of the node's data
*/
String toCsvString() {
StringBuilder line = new StringBuilder("\"");
line.append(getCaseName()).append(CSV_ITEM_SEPARATOR)
.append(getDataSourceName()).append(CSV_ITEM_SEPARATOR)
.append(getType()).append(CSV_ITEM_SEPARATOR)
.append(getValue()).append(CSV_ITEM_SEPARATOR)
.append(getKnown().toString()).append(CSV_ITEM_SEPARATOR)
.append(getFilePath()).append(CSV_ITEM_SEPARATOR)
.append(getComment()).append('"')
.append(System.getProperty("line.separator"));
return line.toString();
}
}
@@ -22,6 +22,7 @@ import java.util.ArrayList;
import java.util.List;
import javax.swing.table.AbstractTableModel;
import org.openide.util.NbBundle.Messages;
import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationCase;
/**
* Model for cells in the cases section of the other occurrences data content
@@ -32,26 +33,16 @@ public class OtherOccurrencesCasesTableModel extends AbstractTableModel {
private static final long serialVersionUID = 1L;
private final List<CorrelationCaseWrapper> correlationCaseList = new ArrayList<>();
/**
* Create a table model for displaying case names
*/
OtherOccurrencesCasesTableModel() {
// This constructor is intentionally empty.
}
@Override
public int getColumnCount() {
return TableColumns.values().length;
}
/**
* Get the preferred width that has been configured for this column.
*
* A value of 0 means that no preferred width has been defined for this
* column.
*
* @param colIdx Column index
*
* @return preferred column width >= 0
*/
public int getColumnPreferredWidth(int colIdx) {
return TableColumns.values()[colIdx].columnWidth();
return 1;
}
@Override
@@ -59,45 +50,43 @@ public class OtherOccurrencesCasesTableModel extends AbstractTableModel {
return correlationCaseList.size();
}
@Messages({"OtherOccurrencesCasesTableModel.case=Case",})
@Override
public String getColumnName(int colIdx) {
return TableColumns.values()[colIdx].columnName();
return Bundle.OtherOccurrencesCasesTableModel_case();
}
@Messages({"OtherOccurrencesCasesTableModel.noData=No Data."})
@Override
public Object getValueAt(int rowIdx, int colIdx) {
if (0 == correlationCaseList.size()) {
//if anything would prevent this from working we will consider it no data for the sake of simplicity
if (correlationCaseList.isEmpty() || rowIdx < 0
|| rowIdx >= correlationCaseList.size()
|| correlationCaseList.get(rowIdx) == null
|| correlationCaseList.get(rowIdx).getMessage() == null
|| correlationCaseList.get(rowIdx).getMessage().isEmpty()) {
return Bundle.OtherOccurrencesCasesTableModel_noData();
}
CorrelationCaseWrapper caseWrapper = correlationCaseList.get(rowIdx);
TableColumns columnId = TableColumns.values()[colIdx];
return mapCorrelationCase(caseWrapper, columnId);
return correlationCaseList.get(rowIdx).getMessage();
}
/**
* Map a column ID to the value in that cell for correlation case wrapper.
* Get a correlation case for the selected index. Does not query the Central
* Repository so CorrelationCase will be partial missing CR case ID and
* other information that is stored in the CR.
*
* @param correlationCaseWrapper The correlation case wrapper
* @param columnId The ID of the cell column.
* @param rowIdx the row from the table model which corresponds to the case
*
* @return The value in the cell.
* @return CorrelationCase for the table item specified or null if no
* correlation could be found for any reason
*/
@Messages({"OtherOccurrencesCasesTableModel.noData=No Data."})
private Object mapCorrelationCase(CorrelationCaseWrapper correlationCaseWrapper, TableColumns columnId) {
String value = Bundle.OtherOccurrencesCasesTableModel_noData();
switch (columnId) {
case CASE_NAME:
value = correlationCaseWrapper.getMessage();
break;
default: //Use default "No data" value.
break;
CorrelationCase getCorrelationCase(int rowIdx) {
//if anything would prevent this from working we will return null
if (correlationCaseList.isEmpty() || rowIdx < 0
|| rowIdx >= correlationCaseList.size()
|| correlationCaseList.get(rowIdx) == null) {
return null;
}
return value;
}
Object getCorrelationCase(int rowIdx) {
return correlationCaseList.get(rowIdx).getCorrelationCase();
}
@@ -107,7 +96,7 @@ public class OtherOccurrencesCasesTableModel extends AbstractTableModel {
}
/**
* Add one correlated instance object to the table
* Add one correlation case wrapper object to the table
*
* @param newCorrelationCaseWrapper data to add to the table
*/
@@ -123,27 +112,4 @@ public class OtherOccurrencesCasesTableModel extends AbstractTableModel {
correlationCaseList.clear();
fireTableDataChanged();
}
@Messages({"OtherOccurrencesCasesTableModel.case=Case",})
enum TableColumns {
// Ordering here determines displayed column order in Content Viewer.
// If order is changed, update the CellRenderer to ensure correct row coloring.
CASE_NAME(Bundle.OtherOccurrencesCasesTableModel_case(), 100);
private final String columnName;
private final int columnWidth;
TableColumns(String columnName, int columnWidth) {
this.columnName = columnName;
this.columnWidth = columnWidth;
}
public String columnName() {
return columnName;
}
public int columnWidth() {
return columnWidth;
}
}
}
@@ -0,0 +1,206 @@
/*
* Central Repository
*
* Copyright 2019 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.centralrepository.contentviewer;
import java.util.LinkedHashSet;
import java.util.Objects;
import java.util.Set;
import javax.swing.table.AbstractTableModel;
import org.openide.util.NbBundle;
/**
* Model for cells in the data sources section of the other occurrences data
* content viewer
*/
final class OtherOccurrencesDataSourcesTableModel extends AbstractTableModel {
private static final long serialVersionUID = 1L;
private final Set<DataSourceColumnItem> dataSourceSet = new LinkedHashSet<>();
/**
* Create a table model for displaying data source names
*/
OtherOccurrencesDataSourcesTableModel() {
// This constructor is intentionally empty.
}
@Override
public int getColumnCount() {
return 1;
}
@Override
public int getRowCount() {
return dataSourceSet.size();
}
@NbBundle.Messages({"OtherOccurrencesDataSourcesTableModel.dataSourceName=Data Source Name",
"OtherOccurrencesDataSourcesTableModel.noData=No Data."})
@Override
public String getColumnName(int colIdx) {
return Bundle.OtherOccurrencesDataSourcesTableModel_dataSourceName();
}
@Override
public Object getValueAt(int rowIdx, int colIdx) {
//if anything would prevent this from working we will consider it no data for the sake of simplicity
if (dataSourceSet.isEmpty() || rowIdx < 0
|| rowIdx >= dataSourceSet.size()
|| !(dataSourceSet.toArray()[rowIdx] instanceof DataSourceColumnItem)) {
return Bundle.OtherOccurrencesDataSourcesTableModel_noData();
}
return ((DataSourceColumnItem) dataSourceSet.toArray()[rowIdx]).getDataSourceName();
}
/**
* Get the device id of the data source shown at the specified row index
*
* @param rowIdx the row index of the data source you want the device id for
*
* @return the device id of the specified data source or an empty string if
* a device id could not be retrieved
*/
String getDeviceIdForRow(int rowIdx) {
//if anything would prevent this from working we will return an empty string
if (dataSourceSet.isEmpty() || rowIdx < 0
|| rowIdx >= dataSourceSet.size()
|| !(dataSourceSet.toArray()[rowIdx] instanceof DataSourceColumnItem)) {
return "";
}
return ((DataSourceColumnItem) dataSourceSet.toArray()[rowIdx]).getDeviceId();
}
/**
* Get the case name of the data source shown at the specified row index
*
* @param rowIdx the row index of the data source you want the case name for
*
* @return the case name of the specified data source or an empty string if
* a case name could not be retrieved
*/
String getCaseNameForRow(int rowIdx) {
//if anything would prevent this from working we will return an empty string
if (dataSourceSet.isEmpty() || rowIdx < 0
|| rowIdx >= dataSourceSet.size()
|| !(dataSourceSet.toArray()[rowIdx] instanceof DataSourceColumnItem)) {
return "";
}
return ((DataSourceColumnItem) dataSourceSet.toArray()[rowIdx]).getCaseName();
}
@Override
public Class<String> getColumnClass(int colIdx) {
return String.class;
}
/**
* Add data source information to the table of unique data sources
*
* @param newNodeData data to add to the table
*/
void addNodeData(OtherOccurrenceNodeData newNodeData) {
dataSourceSet.add(new DataSourceColumnItem((OtherOccurrenceNodeInstanceData) newNodeData));
fireTableDataChanged();
}
/**
* Clear the node data table.
*/
void clearTable() {
dataSourceSet.clear();
fireTableDataChanged();
}
/**
* Private class for storing data source information in a way that
* facilitates de-duping.
*/
private final class DataSourceColumnItem {
private final String caseName;
private final String deviceId;
private final String dataSourceName;
/**
* Create a DataSourceColumnItem given an
* OtherOccurrenceNodeInstanceData object
*
* @param nodeData the OtherOccurrenceNodeInstanceData which contains
* the data source information
*/
private DataSourceColumnItem(OtherOccurrenceNodeInstanceData nodeData) {
this(nodeData.getCaseName(), nodeData.getDeviceID(), nodeData.getDataSourceName());
}
/**
* Create a DataSourceColumnItem given a case name, device id, and data
* source name
*
* @param caseName the name of the case the data source exists in
* @param deviceId the name of the device id for the data source
* @param dataSourceName the name of the data source
*/
private DataSourceColumnItem(String caseName, String deviceId, String dataSourceName) {
this.caseName = caseName;
this.deviceId = deviceId;
this.dataSourceName = dataSourceName;
}
/**
* Get the device id
*
* @return the data source's device id
*/
private String getDeviceId() {
return deviceId;
}
/**
* Get the data source name
*
* @return the data source's name
*/
private String getDataSourceName() {
return dataSourceName;
}
/**
* Get the name of the case the data source exists in
*
* @return the name of the case the data source is in
*/
private String getCaseName() {
return caseName;
}
@Override
public boolean equals(Object other) {
return other instanceof DataSourceColumnItem
&& caseName.equals(((DataSourceColumnItem) other).getCaseName())
&& dataSourceName.equals(((DataSourceColumnItem) other).getDataSourceName())
&& deviceId.equals(((DataSourceColumnItem) other).getDeviceId());
}
@Override
public int hashCode() {
return Objects.hash(caseName, deviceId, dataSourceName);
}
}
}
@@ -1,69 +0,0 @@
/*
* Central Repository
*
* Copyright 2015-2019 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.centralrepository.contentviewer;
import java.awt.Color;
import java.awt.Component;
import javax.swing.JComponent;
import javax.swing.JTable;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.TableCellRenderer;
import org.sleuthkit.datamodel.TskData;
/**
* Renderer for cells in the files section of the other occurrences data content viewer
*/
public class OtherOccurrencesFilesTableCellRenderer implements TableCellRenderer {
public static final DefaultTableCellRenderer DEFAULT_RENDERER = new DefaultTableCellRenderer();
@Override
public Component getTableCellRendererComponent(
JTable table,
Object value,
boolean isSelected,
boolean hasFocus,
int row,
int column) {
Component renderer = DEFAULT_RENDERER.getTableCellRendererComponent(
table, value, isSelected, hasFocus, row, column);
((JComponent) renderer).setOpaque(true);
Color foreground, background;
if (isSelected) {
foreground = Color.WHITE;
background = new Color(51,153,255);
} else {
String known_status = (String) table.getModel().getValueAt(table.convertRowIndexToModel(row),
table.getColumn(OtherOccurrencesFilesTableModel.TableColumns.KNOWN.columnName()).getModelIndex());
if (known_status.equals(TskData.FileKnown.BAD.getName())) {
foreground = Color.WHITE;
background = Color.RED;
} else if (known_status.equals(TskData.FileKnown.UNKNOWN.getName())) {
foreground = Color.BLACK;
background = Color.WHITE;
} else {
foreground = Color.BLACK;
background = Color.WHITE;
}
}
renderer.setForeground(foreground);
renderer.setBackground(background);
return renderer;
}
}
@@ -19,150 +19,76 @@
package org.sleuthkit.autopsy.centralrepository.contentviewer;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.swing.table.AbstractTableModel;
import org.openide.util.NbBundle.Messages;
import org.apache.commons.io.FilenameUtils;
/**
* Model for cells in the files section of the other occurrences data content viewer
* Model for cells in the files section of the other occurrences data content
* viewer
*/
public class OtherOccurrencesFilesTableModel extends AbstractTableModel {
private static final long serialVersionUID = 1L;
private final List<String> nodeKeys = new ArrayList<>();
private final Map<String, List<OtherOccurrenceNodeData>> nodeMap = new HashMap<>();
@Messages({"OtherOccurrencesFilesTableModel.device=Device",
"OtherOccurrencesFilesTableModel.dataSource=Data Source",
"OtherOccurrencesFilesTableModel.path=Path",
"OtherOccurrencesFilesTableModel.attribute=Matched Attribute",
"OtherOccurrencesFilesTableModel.value=Attribute Value",
"OtherOccurrencesFilesTableModel.known=Known",
"OtherOccurrencesFilesTableModel.comment=Comment",
"OtherOccurrencesFilesTableModel.noData=No Data.",})
enum TableColumns {
// Ordering here determines displayed column order in Content Viewer.
// If order is changed, update the CellRenderer to ensure correct row coloring.
ATTRIBUTE(Bundle.OtherOccurrencesFilesTableModel_attribute(), 75),
VALUE(Bundle.OtherOccurrencesFilesTableModel_value(), 190),
KNOWN(Bundle.OtherOccurrencesFilesTableModel_known(), 25),
FILE_PATH(Bundle.OtherOccurrencesFilesTableModel_path(), 470),
COMMENT(Bundle.OtherOccurrencesFilesTableModel_comment(), 190);
private final String columnName;
private final int columnWidth;
TableColumns(String columnName, int columnWidth) {
this.columnName = columnName;
this.columnWidth = columnWidth;
}
public String columnName() {
return columnName;
}
public int columnWidth() {
return columnWidth;
}
};
private final List<OtherOccurrenceNodeData> nodeDataList = new ArrayList<>();
/**
* Create a table model for displaying file names
*/
OtherOccurrencesFilesTableModel() {
// This constructor is intentionally empty.
}
@Override
public int getColumnCount() {
return TableColumns.values().length;
}
/**
* Get the preferred width that has been configured for this column.
*
* A value of 0 means that no preferred width has been defined for this
* column.
*
* @param colIdx Column index
*
* @return preferred column width >= 0
*/
public int getColumnPreferredWidth(int colIdx) {
return TableColumns.values()[colIdx].columnWidth();
return 1;
}
@Override
public int getRowCount() {
return nodeDataList.size();
return nodeKeys.size();
}
@Messages({"OtherOccurrencesFilesTableModel.fileName=File Name",
"OtherOccurrencesFilesTableModel.noData=No Data."})
@Override
public String getColumnName(int colIdx) {
return TableColumns.values()[colIdx].columnName();
return Bundle.OtherOccurrencesFilesTableModel_fileName();
}
@Override
public Object getValueAt(int rowIdx, int colIdx) {
if (0 == nodeDataList.size()) {
//if anything would prevent this from working we will consider it no data for the sake of simplicity
if (nodeMap.isEmpty() || nodeKeys.isEmpty() || rowIdx < 0
|| rowIdx >= nodeKeys.size() || nodeKeys.get(rowIdx) == null
|| nodeMap.get(nodeKeys.get(rowIdx)) == null
|| nodeMap.get(nodeKeys.get(rowIdx)).isEmpty()) {
return Bundle.OtherOccurrencesFilesTableModel_noData();
}
OtherOccurrenceNodeData nodeData = nodeDataList.get(rowIdx);
TableColumns columnId = TableColumns.values()[colIdx];
if (nodeData instanceof OtherOccurrenceNodeMessageData) {
return mapNodeMessageData((OtherOccurrenceNodeMessageData) nodeData, columnId);
}
return mapNodeInstanceData((OtherOccurrenceNodeInstanceData) nodeData, columnId);
return FilenameUtils.getName(((OtherOccurrenceNodeInstanceData) nodeMap.get(nodeKeys.get(rowIdx)).get(0)).getFilePath());
}
/**
* Map a column ID to the value in that cell for node message data.
* Get a list of OtherOccurrenceNodeData that exist for the file which
* corresponds to the Index
*
* @param nodeData The node message data.
* @param columnId The ID of the cell column.
* @param rowIdx the index of the file to get data for
*
* @return The value in the cell.
* @return a list of OtherOccurrenceNodeData for the specified index or an
* empty list if no data was found
*/
private Object mapNodeMessageData(OtherOccurrenceNodeMessageData nodeData, TableColumns columnId) {
if (columnId == TableColumns.ATTRIBUTE) {
return nodeData.getDisplayMessage();
List<OtherOccurrenceNodeData> getListOfNodesForFile(int rowIdx) {
//if anything would prevent this from working return an empty list
if (nodeMap.isEmpty() || nodeKeys.isEmpty() || rowIdx < 0
|| rowIdx >= nodeKeys.size() || nodeKeys.get(rowIdx) == null
|| nodeMap.get(nodeKeys.get(rowIdx)) == null) {
return new ArrayList<>();
}
return "";
}
/**
* Map a column ID to the value in that cell for node instance data.
*
* @param nodeData The node instance data.
* @param columnId The ID of the cell column.
*
* @return The value in the cell.
*/
private Object mapNodeInstanceData(OtherOccurrenceNodeInstanceData nodeData, TableColumns columnId) {
String value = Bundle.OtherOccurrencesFilesTableModel_noData();
switch (columnId) {
case FILE_PATH:
value = nodeData.getFilePath();
break;
case ATTRIBUTE:
value = nodeData.getType();
break;
case VALUE:
value = nodeData.getValue();
break;
case KNOWN:
value = nodeData.getKnown().getName();
break;
case COMMENT:
value = nodeData.getComment();
break;
default: //Use default "No data" value.
break;
}
return value;
}
Object getRow(int rowIdx) {
return nodeDataList.get(rowIdx);
return nodeMap.get(nodeKeys.get(rowIdx));
}
@Override
@@ -176,15 +102,27 @@ public class OtherOccurrencesFilesTableModel extends AbstractTableModel {
* @param newNodeData data to add to the table
*/
void addNodeData(OtherOccurrenceNodeData newNodeData) {
nodeDataList.add(newNodeData);
String newNodeKey = createNodeKey((OtherOccurrenceNodeInstanceData) newNodeData);//FilenameUtils.getName(((OtherOccurrenceNodeInstanceData)newNodeData).getFilePath());
List<OtherOccurrenceNodeData> nodeList = nodeMap.get(newNodeKey);
if (nodeList == null) {
nodeKeys.add(newNodeKey);
nodeList = new ArrayList<>();
}
nodeList.add(newNodeData);
nodeMap.put(newNodeKey, nodeList);
fireTableDataChanged();
}
private String createNodeKey(OtherOccurrenceNodeInstanceData nodeData) {
return nodeData.getCaseName() + nodeData.getDataSourceName() + nodeData.getDeviceID() + nodeData.getFilePath();
}
/**
* Clear the node data table.
*/
void clearTable() {
nodeDataList.clear();
nodeKeys.clear();
nodeMap.clear();
fireTableDataChanged();
}
@@ -170,13 +170,13 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie
"GstVideoPanel.cannotProcFile.err");
//Video playback components
private PlayBin gstPlayBin;
private volatile PlayBin gstPlayBin;
private JavaFxAppSink fxAppSink;
private JFXPanel fxPanel;
private volatile boolean livePlayBin;
private volatile boolean hasError;
private Bus.ERROR errorListener;
private Bus.STATE_CHANGED stateChangeListener;
private Bus.EOS endOfStreamListener;
//When a video is playing, update the UI every 75 ms
//Update progress bar and time label during video playback
private final Timer timer = new Timer(75, new VideoPanelUpdater());
private static final int PROGRESS_SLIDER_SIZE = 2000;
@@ -196,7 +196,6 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie
progressSlider.setMinimum(0);
progressSlider.setMaximum(PROGRESS_SLIDER_SIZE);
progressSlider.setValue(0);
//Manage the gstreamer video position when a user is dragging the slider in the panel.
progressSlider.addChangeListener(new ChangeListener() {
@Override
@@ -212,24 +211,49 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie
}
}
});
//Manage the audio level when the user is adjusting the volumn slider
audioSlider.addChangeListener((ChangeEvent event) -> {
if (audioSlider.getValueIsAdjusting()) {
int audioPercent = audioSlider.getValue() * 2;
gstPlayBin.setVolumePercent(audioPercent);
double audioPercent = (audioSlider.getValue() * 2.0) / 100.0;
gstPlayBin.setVolume(audioPercent);
}
});
videoPanel.setLayout(new BoxLayout(videoPanel, BoxLayout.Y_AXIS));
fxPanel = new JFXPanel();
videoPanel.add(fxPanel);//add jfx ui to JPanel
errorListener = new Bus.ERROR() {
@Override
public void errorMessage(GstObject go, int i, String string) {
enableComponents(false);
infoLabel.setText(String.format(
"<html><font color='red'>%s</font></html>",
MEDIA_PLAYER_ERROR_STRING));
timer.stop();
}
};
stateChangeListener = new Bus.STATE_CHANGED() {
@Override
public void stateChanged(GstObject go, State oldState, State currentState, State pendingState) {
if (State.PLAYING.equals(currentState)) {
playButton.setText("||");
} else {
playButton.setText("►");
}
}
};
endOfStreamListener = new Bus.EOS() {
@Override
public void endOfStream(GstObject go) {
gstPlayBin.seek(ClockTime.ZERO);
progressSlider.setValue(0);
/**
* Keep the video from automatically playing
*/
Gst.getExecutor().submit(() -> gstPlayBin.pause());
}
};
}
private void initGst() throws GstException, UnsatisfiedLinkError {
logger.log(Level.INFO, "Attempting initializing of gstreamer for video/audio viewing"); //NON-NLS
Gst.init();
gstPlayBin = new PlayBin("VideoPlayer");
}
/**
@@ -241,8 +265,6 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie
@NbBundle.Messages({"GstVideoPanel.noOpenCase.errMsg=No open case available."})
void loadFile(final AbstractFile file) {
//Ensure everything is back in the initial state
reset();
infoLabel.setText("");
if (file.isDirNameFlagSet(TskData.TSK_FS_NAME_FLAG_ENUM.UNALLOC)) {
infoLabel.setText(NbBundle.getMessage(this.getClass(), "GstVideoPanel.setupVideo.infoLabel.text"));
@@ -252,7 +274,7 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie
try {
//Pushing off initialization to the background
extractMediaWorker = new ExtractMedia(file, VideoUtils.getVideoFileInTempDir(file));
extractMediaWorker.execute();
extractMediaWorker.execute();
} catch (NoCurrentCaseException ex) {
logger.log(Level.SEVERE, "Exception while getting open case.", ex); //NON-NLS
infoLabel.setText(String.format("<html><font color='red'>%s</font></html>", Bundle.GstVideoPanel_noOpenCase_errMsg()));
@@ -261,14 +283,14 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie
}
/**
* Assume no support on a fresh reset until we begin loading the file
* for play.
* Assume no support on a fresh reset until we begin loading the file for
* play.
*/
@NbBundle.Messages({
"MediaPlayerPanel.noSupport=File not supported."
})
void resetComponents() {
progressLabel.setText(String.format("%s/%s", Bundle.MediaPlayerPanel_unknownTime(),
progressLabel.setText(String.format("%s/%s", Bundle.MediaPlayerPanel_unknownTime(),
Bundle.MediaPlayerPanel_unknownTime()));
infoLabel.setText(Bundle.MediaPlayerPanel_noSupport());
progressSlider.setValue(0);
@@ -278,39 +300,24 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie
* Return this panel to its initial state.
*/
void reset() {
timer.stop();
if(livePlayBin && !hasError) {
gstPlayBin.stop();
}
hasError = false;
livePlayBin = false;
gstPlayBin.dispose();
if (fxAppSink != null) {
fxAppSink.clear();
}
videoPanel.removeAll();
if (extractMediaWorker != null) {
extractMediaWorker.cancel(true);
}
timer.stop();
if (gstPlayBin != null) {
gstPlayBin.stop();
gstPlayBin.getBus().disconnect(endOfStreamListener);
gstPlayBin.getBus().disconnect(endOfStreamListener);
gstPlayBin.getBus().disconnect(endOfStreamListener);
gstPlayBin.dispose();
fxAppSink.clear();
gstPlayBin = null;
}
videoPanel.removeAll();
resetComponents();
enableComponents(false);
}
/**
* If the node has been reset but messages from the previous PlayBin are
* still firing, ignore them.
*/
synchronized void setLabelText(String msg) {
if (livePlayBin) {
infoLabel.setText(msg);
}
}
private void enableComponents(boolean isEnabled) {
playButton.setEnabled(isEnabled);
progressSlider.setEnabled(isEnabled);
@@ -442,31 +449,37 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie
protected void done() {
try {
super.get();
//Video is ready for playback. Clean up previous components and create new ones
if(this.isCancelled()) {
return;
}
//Video is ready for playback. Create new components
gstPlayBin = new PlayBin("VideoPlayer", tempFile.toURI());
//Create a custom AppSink that hooks into JavaFx panels for video display
fxPanel = new JFXPanel();
//Configure event handling
Bus playBinBus = gstPlayBin.getBus();
playBinBus.connect(endOfStreamListener);
playBinBus.connect(stateChangeListener);
playBinBus.connect(errorListener);
if(this.isCancelled()) {
return;
}
JFXPanel fxPanel = new JFXPanel();
videoPanel.removeAll();
videoPanel.setLayout(new BoxLayout(videoPanel, BoxLayout.Y_AXIS));
videoPanel.add(fxPanel);
fxAppSink = new JavaFxAppSink("JavaFxAppSink", fxPanel);
gstPlayBin.setVideoSink(fxAppSink);
videoPanel.setLayout(new BoxLayout(videoPanel, BoxLayout.Y_AXIS));
videoPanel.add(fxPanel);//add jfx ui to JPanel
if(this.isCancelled()) {
return;
}
//Configure event handling
attachEOSListener(gstPlayBin); //Handle end of video events
attachStateListener(gstPlayBin); //Handle syncing play/pause button to the stream state
attachErrorListener(gstPlayBin); //Handle errors gracefully when they are encountered
//Customize components
gstPlayBin.setVolumePercent(audioSlider.getValue() * 2);
/**
* Prepare the PlayBin for playback.
*/
gstPlayBin.ready();
livePlayBin = true;
//Customize components
gstPlayBin.setVolume((audioSlider.getValue() * 2.0) / 100.0);
gstPlayBin.pause();
timer.start();
enableComponents(true);
} catch (CancellationException ex) {
logger.log(Level.INFO, "Media buffering was canceled."); //NON-NLS
@@ -476,69 +489,6 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie
logger.log(Level.SEVERE, "Fatal error during media buffering.", ex); //NON-NLS
}
}
/**
* Listens for the end of stream event, in which case we conveniently
* reset the video for the user.
*/
private void attachEOSListener(PlayBin gstPlayBin) {
gstPlayBin.getBus().connect(new Bus.EOS() {
@Override
public void endOfStream(GstObject go) {
gstPlayBin.seek(ClockTime.ZERO);
progressSlider.setValue(0);
/**
* Keep the video from automatically playing
*/
Gst.getExecutorService().submit(() -> gstPlayBin.pause());
}
});
}
/**
* Listen for state changes and update the play/pause button
* accordingly. In addition, handle the state transition from
* READY -> PAUSED.
*/
private void attachStateListener(PlayBin gstPlayBin) {
gstPlayBin.getBus().connect(new Bus.STATE_CHANGED() {
@Override
public void stateChanged(GstObject go, State oldState, State currentState, State pendingState) {
/**
* If we are ready, it is safe to transition to the pause state
* to initiate data-flow for pre-roll frame and duration
* information.
*/
if (State.READY.equals(currentState)) {
Gst.getExecutorService().submit(() -> gstPlayBin.pause());
timer.start();
}
if (State.PLAYING.equals(currentState)) {
playButton.setText("||");
} else {
playButton.setText("►");
}
}
});
}
/**
* On error messages disable the UI and show the user an error was
* encountered.
*/
private void attachErrorListener(PlayBin gstPlayBin) {
gstPlayBin.getBus().connect(new Bus.ERROR() {
@Override
public void errorMessage(GstObject go, int i, String string) {
enableComponents(false);
setLabelText(String.format("<html><font color='red'>%s</font></html>",
MEDIA_PLAYER_ERROR_STRING));
timer.stop();
hasError = true;
}
});
}
}
/**
@@ -549,21 +499,19 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie
@Override
public void actionPerformed(ActionEvent e) {
if (!progressSlider.getValueIsAdjusting()) {
if(livePlayBin) {
long position = gstPlayBin.queryPosition(TimeUnit.NANOSECONDS);
long duration = gstPlayBin.queryDuration(TimeUnit.NANOSECONDS);
/**
* Duration may not be known until there is video data in the
* pipeline. We start this updater when data-flow has just been
* initiated so buffering may still be in progress.
*/
if (duration != -1) {
double relativePosition = (double) position / duration;
progressSlider.setValue((int) (relativePosition * PROGRESS_SLIDER_SIZE));
}
updateTimeLabel(position, duration);
long position = gstPlayBin.queryPosition(TimeUnit.NANOSECONDS);
long duration = gstPlayBin.queryDuration(TimeUnit.NANOSECONDS);
/**
* Duration may not be known until there is video data in the
* pipeline. We start this updater when data-flow has just been
* initiated so buffering may still be in progress.
*/
if (duration != -1) {
double relativePosition = (double) position / duration;
progressSlider.setValue((int) (relativePosition * PROGRESS_SLIDER_SIZE));
}
updateTimeLabel(position, duration);
}
}
}
@@ -681,7 +629,7 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie
}// </editor-fold>//GEN-END:initComponents
private void playButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_playButtonActionPerformed
if(gstPlayBin.isPlaying()) {
if (gstPlayBin.isPlaying()) {
gstPlayBin.pause();
} else {
gstPlayBin.play();
@@ -59,7 +59,7 @@
</Property>
</Properties>
<AuxValues>
<AuxValue name="JavaCodeGenerator_CreateCodeCustom" type="java.lang.String" value="new BeanTreeView()"/>
<AuxValue name="JavaCodeGenerator_CreateCodeCustom" type="java.lang.String" value="new ExpansionBeanTreeView()"/>
</AuxValues>
<Layout class="org.netbeans.modules.form.compat2.layouts.support.JScrollPaneSupportLayout"/>
@@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2011-2019 Basis Technology Corp.
* Copyright 2012-2019 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -26,7 +26,6 @@ import java.beans.PropertyVetoException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.EnumSet;
import java.util.LinkedList;
import java.util.List;
@@ -47,6 +46,7 @@ import javax.swing.tree.TreeSelectionModel;
import org.apache.commons.lang3.StringUtils;
import org.openide.explorer.ExplorerManager;
import org.openide.explorer.ExplorerUtils;
import org.openide.explorer.view.BeanTreeView;
import org.openide.explorer.view.Visualizer;
import org.openide.nodes.AbstractNode;
import org.openide.nodes.Children;
@@ -128,7 +128,7 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat
getTree().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
//Hook into the JTree and pre-expand the Views Node and Results node when a user
//expands an item in the tree that makes these nodes visible.
getTree().addTreeExpansionListener(new TreeExpansionListener() {
((ExpansionBeanTreeView )getTree()).addTreeExpansionListener(new TreeExpansionListener() {
@Override
public void treeExpanded(TreeExpansionEvent event) {
//Bail immediately if we are not in the Group By view.
@@ -192,7 +192,7 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat
* @param rootChildren Children node containing Results node and Views node.
*/
private void preExpandNodes(Children rootChildren) {
ExpansionBeanTreeView tree = getTree();
BeanTreeView tree = getTree();
Node results = rootChildren.findChild(ResultsNode.NAME);
if (!Objects.isNull(results)) {
@@ -930,8 +930,8 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat
*
* @return tree the BeanTreeView
*/
public ExpansionBeanTreeView getTree() {
return (ExpansionBeanTreeView) this.treeView;
BeanTreeView getTree() {
return (BeanTreeView) this.treeView;
}
/**
@@ -24,7 +24,7 @@ import org.openide.explorer.view.BeanTreeView;
/**
* Adds the ability to listen to user-driven JTree expansion events.
*/
class ExpansionBeanTreeView extends BeanTreeView {
final class ExpansionBeanTreeView extends BeanTreeView {
public void addTreeExpansionListener(TreeExpansionListener listener) {
this.tree.addTreeExpansionListener(listener);
}
@@ -58,7 +58,7 @@ public class HealthMonitorDashboard {
private final static Logger logger = Logger.getLogger(HealthMonitorDashboard.class.getName());
private final static String ADMIN_ACCESS_FILE_NAME = "_aiaa"; // NON-NLS
private final static String ADMIN_ACCESS_FILE_NAME = "admin"; // NON-NLS
private final static String ADMIN_ACCESS_FILE_PATH = Paths.get(PlatformUtil.getUserConfigDirectory(), ADMIN_ACCESS_FILE_NAME).toString();
Map<String, List<HealthMonitor.DatabaseTimingResult>> timingData;
@@ -52,6 +52,7 @@ final class EncryptionDetectionDataSourceIngestModule implements DataSourceInges
private Blackboard blackboard;
private double calculatedEntropy;
private final double minimumEntropy;
private IngestJobContext context;
/**
* Create an EncryptionDetectionDataSourceIngestModule object that will
@@ -67,6 +68,7 @@ final class EncryptionDetectionDataSourceIngestModule implements DataSourceInges
public void startUp(IngestJobContext context) throws IngestModule.IngestModuleException {
validateSettings();
blackboard = Case.getCurrentCase().getServices().getBlackboard();
this.context = context;
}
@Messages({
@@ -77,8 +79,6 @@ final class EncryptionDetectionDataSourceIngestModule implements DataSourceInges
@Override
public ProcessResult process(Content dataSource, DataSourceIngestModuleProgress progressBar) {
try {
if (dataSource instanceof Image) {
@@ -92,10 +92,23 @@ final class EncryptionDetectionDataSourceIngestModule implements DataSourceInges
int numVolSystemsChecked = 0;
progressBar.progress(Bundle.EncryptionDetectionDataSourceIngestModule_processing_message(), 0);
for (VolumeSystem volumeSystem : volumeSystems) {
if (context.dataSourceIngestIsCancelled()) {
return ProcessResult.OK;
}
for (Volume volume : volumeSystem.getVolumes()) {
if (context.dataSourceIngestIsCancelled()) {
return ProcessResult.OK;
}
if (BitlockerDetection.isBitlockerVolume(volume)) {
return flagVolume(volume, BlackboardArtifact.ARTIFACT_TYPE.TSK_ENCRYPTION_DETECTED, Bundle.EncryptionDetectionDataSourceIngestModule_artifactComment_bitlocker());
}
if (context.dataSourceIngestIsCancelled()) {
return ProcessResult.OK;
}
if (isVolumeEncrypted(volume)) {
return flagVolume(volume, BlackboardArtifact.ARTIFACT_TYPE.TSK_ENCRYPTION_SUSPECTED, String.format(Bundle.EncryptionDetectionDataSourceIngestModule_artifactComment_suspected(), calculatedEntropy));
}
@@ -139,6 +152,11 @@ final class EncryptionDetectionDataSourceIngestModule implements DataSourceInges
* there was a problem.
*/
private IngestModule.ProcessResult flagVolume(Volume volume, BlackboardArtifact.ARTIFACT_TYPE artifactType, String comment) {
if (context.dataSourceIngestIsCancelled()) {
return ProcessResult.OK;
}
try {
BlackboardArtifact artifact = volume.newArtifact(artifactType);
artifact.addAttribute(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_COMMENT, EncryptionDetectionModuleFactory.getModuleName(), comment));
@@ -198,7 +216,7 @@ final class EncryptionDetectionDataSourceIngestModule implements DataSourceInges
* http://www.forensicswiki.org/wiki/TrueCrypt#Detection
*/
if (volume.getFileSystems().isEmpty()) {
calculatedEntropy = EncryptionDetectionTools.calculateEntropy(volume);
calculatedEntropy = EncryptionDetectionTools.calculateEntropy(volume, context);
if (calculatedEntropy >= minimumEntropy) {
return true;
}
@@ -82,6 +82,7 @@ final class EncryptionDetectionFileIngestModule extends FileIngestModuleAdapter
private final Logger logger = services.getLogger(EncryptionDetectionModuleFactory.getModuleName());
private FileTypeDetector fileTypeDetector;
private Blackboard blackboard;
private IngestJobContext context;
private double calculatedEntropy;
private final double minimumEntropy;
@@ -107,6 +108,7 @@ final class EncryptionDetectionFileIngestModule extends FileIngestModuleAdapter
public void startUp(IngestJobContext context) throws IngestModule.IngestModuleException {
try {
validateSettings();
this.context = context;
blackboard = Case.getCurrentCaseThrows().getServices().getBlackboard();
fileTypeDetector = new FileTypeDetector();
} catch (FileTypeDetector.FileTypeDetectorInitException ex) {
@@ -194,6 +196,10 @@ final class EncryptionDetectionFileIngestModule extends FileIngestModuleAdapter
*/
private IngestModule.ProcessResult flagFile(AbstractFile file, BlackboardArtifact.ARTIFACT_TYPE artifactType, String comment) {
try {
if (context.fileIngestIsCancelled()) {
return IngestModule.ProcessResult.OK;
}
BlackboardArtifact artifact = file.newArtifact(artifactType);
artifact.addAttribute(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_COMMENT,
EncryptionDetectionModuleFactory.getModuleName(), comment));
@@ -397,7 +403,7 @@ final class EncryptionDetectionFileIngestModule extends FileIngestModuleAdapter
/*
* Qualify the entropy.
*/
calculatedEntropy = EncryptionDetectionTools.calculateEntropy(file);
calculatedEntropy = EncryptionDetectionTools.calculateEntropy(file, context);
if (calculatedEntropy >= minimumEntropy) {
possiblyEncrypted = true;
}
@@ -22,6 +22,7 @@ import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.ingest.IngestJobContext;
import org.sleuthkit.autopsy.ingest.IngestModule;
import org.sleuthkit.datamodel.ReadContentInputStream;
import org.sleuthkit.datamodel.Content;
@@ -69,6 +70,7 @@ final class EncryptionDetectionTools {
* content as possibly encrypted.
*
* @param content The content to be calculated against.
* @param context The ingest job context for cancellation checks
*
* @return The entropy of the content.
*
@@ -77,7 +79,7 @@ final class EncryptionDetectionTools {
* @throws IOException If there is a failure closing or
* reading from the InputStream.
*/
static double calculateEntropy(Content content) throws ReadContentInputStream.ReadContentInputStreamException, IOException {
static double calculateEntropy(Content content, IngestJobContext context) throws ReadContentInputStream.ReadContentInputStreamException, IOException {
/*
* Logic in this method is based on
* https://github.com/willjasen/entropy/blob/master/entropy.java
@@ -95,8 +97,17 @@ final class EncryptionDetectionTools {
*/
int[] byteOccurences = new int[BYTE_OCCURENCES_BUFFER_SIZE];
int readByte;
long bytesRead = 0;
while ((readByte = bin.read()) != -1) {
byteOccurences[readByte]++;
// Do a cancellation check every 10,000 bytes
bytesRead++;
if (bytesRead % 10000 == 0) {
if (context.dataSourceIngestIsCancelled() || context.fileIngestIsCancelled()) {
return 0;
}
}
}
/*
@@ -37,7 +37,7 @@ FileReportDataTypes.knownStatus.text=Known Status
FileReportDataTypes.perms.text=Permissions
FileReportDataTypes.path.text=Full Path
FileReportText.getName.text=Files - Text
FileReportText.getDesc.text=A tab delimited text file containing information about individual files in the case.
FileReportText.getDesc.text=A delimited text file containing information about individual files in the case.
ReportBodyFile.progress.querying=Querying files...
ReportBodyFile.ingestWarning.text=Warning, this report was run before ingest services completed\!
ReportBodyFile.progress.loading=Loading files...
@@ -258,3 +258,5 @@ CreatePortableCasePanel.outputFolderTextField.text=jTextField1
CreatePortableCasePanel.chooseOutputFolderButton.text=Choose folder
CreatePortableCasePanel.jLabel1.text=Export files tagged as:
CreatePortableCasePanel.jLabel2.text=Select output folder:
ReportFileTextConfigurationPanel.tabDelimitedButton.text=Tab delimited
ReportFileTextConfigurationPanel.commaDelimitedButton.text=Comma delimited
@@ -85,7 +85,7 @@ FileReportDataTypes.knownStatus.text=Known Status
FileReportDataTypes.perms.text=Permissions
FileReportDataTypes.path.text=Full Path
FileReportText.getName.text=Files - Text
FileReportText.getDesc.text=A tab delimited text file containing information about individual files in the case.
FileReportText.getDesc.text=A delimited text file containing information about individual files in the case.
ReportBodyFile.progress.querying=Querying files...
ReportBodyFile.ingestWarning.text=Warning, this report was run before ingest services completed\!
ReportBodyFile.progress.loading=Loading files...
@@ -306,4 +306,6 @@ CreatePortableCasePanel.outputFolderTextField.text=jTextField1
CreatePortableCasePanel.chooseOutputFolderButton.text=Choose folder
CreatePortableCasePanel.jLabel1.text=Export files tagged as:
CreatePortableCasePanel.jLabel2.text=Select output folder:
ReportFileTextConfigurationPanel.tabDelimitedButton.text=Tab delimited
ReportFileTextConfigurationPanel.commaDelimitedButton.text=Comma delimited
TableReportGenerator.StatusColumn.Header=Review Status
@@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2013 - 2018 Basis Technology Corp.
* Copyright 2013 - 2019 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -19,6 +19,7 @@
package org.sleuthkit.autopsy.report;
import java.io.BufferedWriter;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
@@ -27,6 +28,7 @@ import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.logging.Level;
import javax.swing.JPanel;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.openide.util.NbBundle;
@@ -36,18 +38,18 @@ import org.sleuthkit.datamodel.AbstractFile;
import org.sleuthkit.datamodel.TskCoreException;
/**
* A Tab-delimited text report of the files in the case.
* A delimited text report of the files in the case.
*
* @author jwallace
*/
class FileReportText implements FileReportModule {
private static final Logger logger = Logger.getLogger(FileReportText.class.getName());
private static final String FILE_NAME = "file-report.txt"; //NON-NLS
private static FileReportText instance;
private String reportPath;
private Writer out;
private static final String FILE_NAME = "file-report.txt"; //NON-NLS
private static FileReportText instance;
private ReportFileTextConfigurationPanel configPanel;
// Get the default implementation of this report
public static synchronized FileReportText getDefault() {
@@ -62,7 +64,7 @@ class FileReportText implements FileReportModule {
this.reportPath = baseReportDir + FILE_NAME;
try {
out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(this.reportPath)));
} catch (IOException ex) {
} catch (FileNotFoundException ex) {
logger.log(Level.WARNING, "Failed to create report text file", ex); //NON-NLS
}
}
@@ -85,11 +87,12 @@ class FileReportText implements FileReportModule {
}
}
private String getTabDelimitedList(List<String> list) {
StringBuilder output = new StringBuilder();
private String getDelimitedList(List<String> list, String delimiter) {
StringBuilder output;
output = new StringBuilder();
Iterator<String> it = list.iterator();
while (it.hasNext()) {
output.append(it.next()).append((it.hasNext() ? "\t" : System.lineSeparator()));
output.append('"').append(it.next()).append('"').append((it.hasNext() ? delimiter : System.lineSeparator()));
}
return output.toString();
}
@@ -101,7 +104,7 @@ class FileReportText implements FileReportModule {
titles.add(col.getName());
}
try {
out.write(getTabDelimitedList(titles));
out.write(getDelimitedList(titles, configPanel.getDelimiter()));
} catch (IOException ex) {
logger.log(Level.WARNING, "Error when writing headers to report file: {0}", ex); //NON-NLS
}
@@ -114,7 +117,7 @@ class FileReportText implements FileReportModule {
cells.add(type.getValue(toAdd));
}
try {
out.write(getTabDelimitedList(cells));
out.write(getDelimitedList(cells, configPanel.getDelimiter()));
} catch (IOException ex) {
logger.log(Level.WARNING, "Error when writing row to report file: {0}", ex); //NON-NLS
}
@@ -143,4 +146,12 @@ class FileReportText implements FileReportModule {
public String getRelativeFilePath() {
return FILE_NAME;
}
@Override
public JPanel getConfigurationPanel() {
if (configPanel == null) {
configPanel = new ReportFileTextConfigurationPanel();
}
return configPanel;
}
}
@@ -0,0 +1,68 @@
<?xml version="1.0" encoding="UTF-8" ?>
<Form version="1.5" maxVersion="1.9" type="org.netbeans.modules.form.forminfo.JPanelFormInfo">
<NonVisualComponents>
<Component class="javax.swing.ButtonGroup" name="delimiterGroup">
</Component>
</NonVisualComponents>
<AuxValues>
<AuxValue name="FormSettings_autoResourcing" type="java.lang.Integer" value="1"/>
<AuxValue name="FormSettings_autoSetComponentName" type="java.lang.Boolean" value="false"/>
<AuxValue name="FormSettings_generateFQN" type="java.lang.Boolean" value="true"/>
<AuxValue name="FormSettings_generateMnemonicsCode" type="java.lang.Boolean" value="true"/>
<AuxValue name="FormSettings_i18nAutoMode" type="java.lang.Boolean" value="true"/>
<AuxValue name="FormSettings_layoutCodeTarget" type="java.lang.Integer" value="1"/>
<AuxValue name="FormSettings_listenerGenerationStyle" type="java.lang.Integer" value="0"/>
<AuxValue name="FormSettings_variablesLocal" type="java.lang.Boolean" value="false"/>
<AuxValue name="FormSettings_variablesModifier" type="java.lang.Integer" value="2"/>
</AuxValues>
<Layout>
<DimensionLayout dim="0">
<Group type="103" groupAlignment="0" attributes="0">
<Group type="102" alignment="0" attributes="0">
<EmptySpace min="-2" max="-2" attributes="0"/>
<Component id="tabDelimitedButton" min="-2" pref="116" max="-2" attributes="0"/>
<EmptySpace type="unrelated" max="-2" attributes="0"/>
<Component id="commaDelimitedButton" min="-2" pref="133" max="-2" attributes="0"/>
<EmptySpace pref="166" max="32767" attributes="0"/>
</Group>
</Group>
</DimensionLayout>
<DimensionLayout dim="1">
<Group type="103" groupAlignment="0" attributes="0">
<Group type="102" alignment="0" attributes="0">
<EmptySpace max="-2" attributes="0"/>
<Group type="103" groupAlignment="3" attributes="0">
<Component id="tabDelimitedButton" alignment="3" min="-2" max="-2" attributes="0"/>
<Component id="commaDelimitedButton" alignment="3" min="-2" max="-2" attributes="0"/>
</Group>
<EmptySpace pref="78" max="32767" attributes="0"/>
</Group>
</Group>
</DimensionLayout>
</Layout>
<SubComponents>
<Component class="javax.swing.JRadioButton" name="tabDelimitedButton">
<Properties>
<Property name="buttonGroup" type="javax.swing.ButtonGroup" editor="org.netbeans.modules.form.RADComponent$ButtonGroupPropertyEditor">
<ComponentRef name="delimiterGroup"/>
</Property>
<Property name="selected" type="boolean" value="true"/>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/report/Bundle.properties" key="ReportFileTextConfigurationPanel.tabDelimitedButton.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
</Component>
<Component class="javax.swing.JRadioButton" name="commaDelimitedButton">
<Properties>
<Property name="buttonGroup" type="javax.swing.ButtonGroup" editor="org.netbeans.modules.form.RADComponent$ButtonGroupPropertyEditor">
<ComponentRef name="delimiterGroup"/>
</Property>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/report/Bundle.properties" key="ReportFileTextConfigurationPanel.commaDelimitedButton.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
</Component>
</SubComponents>
</Form>
@@ -0,0 +1,99 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2019 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.report;
/**
* Panel for configuring settings for the file text report
*/
class ReportFileTextConfigurationPanel extends javax.swing.JPanel {
private static final long serialVersionUID = 1L;
private static final String TAB_DELIMITER = "\t"; //NON-NLS
private static final String COMMA_DELIMITER = ","; //NON-NLS
/**
* Creates new form ReportFileTextConfigurationPanel
*/
ReportFileTextConfigurationPanel() {
initComponents();
}
/**
* Get the delimiter that was selected on this panel
*
* @return the selected delimiter
*/
String getDelimiter() {
if (commaDelimitedButton.isSelected()) {
return COMMA_DELIMITER;
} else {
//if the comma button is not selected default to tab since it was previously the only option
return TAB_DELIMITER;
}
}
/**
* This method is called from within the constructor to initialize the form.
* WARNING: Do NOT modify this code. The content of this method is always
* regenerated by the Form Editor.
*/
@SuppressWarnings("unchecked")
// <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
private void initComponents() {
delimiterGroup = new javax.swing.ButtonGroup();
tabDelimitedButton = new javax.swing.JRadioButton();
commaDelimitedButton = new javax.swing.JRadioButton();
delimiterGroup.add(tabDelimitedButton);
tabDelimitedButton.setSelected(true);
org.openide.awt.Mnemonics.setLocalizedText(tabDelimitedButton, org.openide.util.NbBundle.getMessage(ReportFileTextConfigurationPanel.class, "ReportFileTextConfigurationPanel.tabDelimitedButton.text")); // NOI18N
delimiterGroup.add(commaDelimitedButton);
org.openide.awt.Mnemonics.setLocalizedText(commaDelimitedButton, org.openide.util.NbBundle.getMessage(ReportFileTextConfigurationPanel.class, "ReportFileTextConfigurationPanel.commaDelimitedButton.text")); // NOI18N
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
this.setLayout(layout);
layout.setHorizontalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup()
.addContainerGap()
.addComponent(tabDelimitedButton, javax.swing.GroupLayout.PREFERRED_SIZE, 116, javax.swing.GroupLayout.PREFERRED_SIZE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
.addComponent(commaDelimitedButton, javax.swing.GroupLayout.PREFERRED_SIZE, 133, javax.swing.GroupLayout.PREFERRED_SIZE)
.addContainerGap(166, Short.MAX_VALUE))
);
layout.setVerticalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup()
.addContainerGap()
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
.addComponent(tabDelimitedButton)
.addComponent(commaDelimitedButton))
.addContainerGap(78, Short.MAX_VALUE))
);
}// </editor-fold>//GEN-END:initComponents
// Variables declaration - do not modify//GEN-BEGIN:variables
private javax.swing.JRadioButton commaDelimitedButton;
private javax.swing.ButtonGroup delimiterGroup;
private javax.swing.JRadioButton tabDelimitedButton;
// End of variables declaration//GEN-END:variables
}
@@ -72,6 +72,7 @@ import org.sleuthkit.autopsy.timeline.zooming.ZoomParams;
import org.sleuthkit.datamodel.AbstractFile;
import org.sleuthkit.datamodel.BlackboardArtifact;
import org.sleuthkit.datamodel.BlackboardArtifactTag;
import org.sleuthkit.datamodel.Content;
import org.sleuthkit.datamodel.ContentTag;
import org.sleuthkit.datamodel.SleuthkitCase;
import org.sleuthkit.datamodel.Tag;
@@ -711,10 +712,10 @@ public class EventsRepository {
// if the time is legitimate ( greater than zero ) insert it into the db
if (eventDescription != null && eventDescription.getTime() > 0) {
long objectID = bbart.getObjectID();
AbstractFile f = skCase.getAbstractFileById(objectID);
long datasourceID = f.getDataSource().getId();
Content content = skCase.getContentById(objectID);
long datasourceID = content.getDataSource().getId();
long artifactID = bbart.getArtifactID();
Set<String> hashSets = f.getHashSetNames();
Set<String> hashSets = content.getHashSetNames();
List<BlackboardArtifactTag> tags = tagsManager.getBlackboardArtifactTagsByArtifact(bbart);
String fullDescription = eventDescription.getFullDescription();
String medDescription = eventDescription.getMedDescription();