Merge pull request #5890 from raman-bt/6314-artifact-content-viewer

6382: Define new interface ArtifactContentViewer, and refactor DataContentViewerArtfact
This commit is contained in:
Richard Cordovano 2020-05-15 14:11:51 -04:00 committed by GitHub
commit 61b7dbe491
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 767 additions and 459 deletions

View File

@ -0,0 +1,56 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2020 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.contentviewers;
import java.awt.Component;
import org.sleuthkit.datamodel.BlackboardArtifact;
/**
* Common interface implemented by artifact viewers.
*
* An artifact viewer displays the artifact in a custom
* layout panel suitable for the artifact type.
*
*/
public interface ArtifactContentViewer {
/**
* Called to display the contents of the given artifact.
*
* @param artifact the artifact to display.
*/
void setArtifact(BlackboardArtifact artifact);
/**
* Returns the panel.
*
* @return display panel.
*/
Component getComponent();
/**
* Checks whether the given artifact is supported by the viewer.
*
* @param artifact Artifact to check.
*
* @return True if the artifact can be displayed by the viewer, false otherwise.
*/
boolean isSupported(BlackboardArtifact artifact);
}

View File

@ -954,3 +954,5 @@ manager.properties.lafError =\
manager.properties.brokenProperty = Broken default property {0} value: {1}
manager.properties.missingProperty = Missing default property {0} value: {1}
DefaultArtifactContentViewer.copyMenuItem.text=Copy
DefaultArtifactContentViewer.selectAllMenuItem.text=Select All

View File

@ -164,3 +164,5 @@ MediaViewImagePanel.tagsMenu.text_1=\u30bf\u30b0\u30e1\u30cb\u30e5\u30fc
SQLiteViewer.readTable.errorText=\u6b21\u306e\u30c6\u30fc\u30d6\u30eb\u306e\u884c\u3092\u53d6\u5f97\u4e2d\u306b\u30a8\u30e9\u30fc\u304c\u767a\u751f\u3057\u307e\u3057\u305f: {0}
# {0} - tableName
SQLiteViewer.selectTable.errorText=\u6b21\u306e\u30c6\u30fc\u30d6\u30eb\u306e\u884c\u30ab\u30a6\u30f3\u30c8\u3092\u53d6\u5f97\u4e2d\u306b\u30a8\u30e9\u30fc\u304c\u767a\u751f\u3057\u307e\u3057\u305f: {0}
DefaultArtifactContentViewer.selectAllMenuItem.text=\u3059\u3079\u3066\u3092\u9078\u629e
DefaultArtifactContentViewer.copyMenuItem.text=\u30b3\u30d4\u30fc

View File

@ -0,0 +1,73 @@
<?xml version="1.0" encoding="UTF-8" ?>
<Form version="1.5" maxVersion="1.7" type="org.netbeans.modules.form.forminfo.JPanelFormInfo">
<NonVisualComponents>
<Container class="javax.swing.JPopupMenu" name="rightClickMenu">
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignAbsoluteLayout">
<Property name="useNullLayout" type="boolean" value="true"/>
</Layout>
<SubComponents>
<MenuItem class="javax.swing.JMenuItem" name="copyMenuItem">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/contentviewers/Bundle.properties" key="DefaultArtifactContentViewer.copyMenuItem.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
</MenuItem>
<MenuItem class="javax.swing.JMenuItem" name="selectAllMenuItem">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/contentviewers/Bundle.properties" key="DefaultArtifactContentViewer.selectAllMenuItem.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
</MenuItem>
</SubComponents>
</Container>
</NonVisualComponents>
<Properties>
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[100, 58]"/>
</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="false"/>
<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">
<Component id="resultsTableScrollPane" pref="100" max="32767" attributes="0"/>
</Group>
</DimensionLayout>
<DimensionLayout dim="1">
<Group type="103" groupAlignment="0" attributes="0">
<Component id="resultsTableScrollPane" alignment="1" pref="58" max="32767" attributes="0"/>
</Group>
</DimensionLayout>
</Layout>
<SubComponents>
<Container class="javax.swing.JScrollPane" name="resultsTableScrollPane">
<Properties>
<Property name="horizontalScrollBarPolicy" type="int" value="32"/>
<Property name="verticalScrollBarPolicy" type="int" value="22"/>
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[620, 34]"/>
</Property>
</Properties>
<AuxValues>
<AuxValue name="autoScrollPane" type="java.lang.Boolean" value="true"/>
</AuxValues>
<Layout class="org.netbeans.modules.form.compat2.layouts.support.JScrollPaneSupportLayout"/>
</Container>
</SubComponents>
</Form>

View File

@ -0,0 +1,524 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2011-2020 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.contentviewers;
import java.awt.Component;
import java.awt.Cursor;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.datatransfer.StringSelection;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.logging.Level;
import javax.swing.JMenuItem;
import javax.swing.JTextArea;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.TableColumnModelEvent;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableColumn;
import javax.swing.event.TableColumnModelListener;
import javax.swing.text.View;
import org.apache.commons.lang.StringUtils;
import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.datamodel.ContentUtils;
import org.sleuthkit.datamodel.BlackboardArtifact;
import org.sleuthkit.datamodel.BlackboardAttribute;
import org.sleuthkit.datamodel.Content;
import org.sleuthkit.datamodel.TskCoreException;
import org.netbeans.swing.etable.ETable;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.google.gson.JsonArray;
import java.util.Locale;
import java.util.Map;
import javax.swing.SwingUtilities;
/**
* This class displays a Blackboard artifact as a table listing all it's
* attributes names and values.
*/
@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives
public class DefaultArtifactContentViewer extends javax.swing.JPanel implements ArtifactContentViewer {
@NbBundle.Messages({
"DefaultArtifactContentViewer.attrsTableHeader.type=Type",
"DefaultArtifactContentViewer.attrsTableHeader.value=Value",
"DefaultArtifactContentViewer.attrsTableHeader.sources=Source(s)",
"DataContentViewerArtifact.failedToGetSourcePath.message=Failed to get source file path from case database",
"DataContentViewerArtifact.failedToGetAttributes.message=Failed to get some or all attributes from case database"
})
private final static Logger logger = Logger.getLogger(DefaultArtifactContentViewer.class.getName());
private static final long serialVersionUID = 1L;
private static final String[] COLUMN_HEADERS = {
Bundle.DefaultArtifactContentViewer_attrsTableHeader_type(),
Bundle.DefaultArtifactContentViewer_attrsTableHeader_value(),
Bundle.DefaultArtifactContentViewer_attrsTableHeader_sources()};
private static final int[] COLUMN_WIDTHS = {100, 800, 100};
private static final int CELL_BOTTOM_MARGIN = 5;
private static final int CELL_RIGHT_MARGIN = 1;
public DefaultArtifactContentViewer() {
initResultsTable();
initComponents();
resultsTableScrollPane.setViewportView(resultsTable);
customizeComponents();
resetComponents();
resultsTable.setDefaultRenderer(Object.class, new MultiLineTableCellRenderer());
}
private void initResultsTable() {
resultsTable = new ETable();
resultsTable.setModel(new javax.swing.table.DefaultTableModel() {
private static final long serialVersionUID = 1L;
@Override
public boolean isCellEditable(int rowIndex, int columnIndex) {
return false;
}
});
resultsTable.setCellSelectionEnabled(true);
resultsTable.getTableHeader().setReorderingAllowed(false);
resultsTable.setColumnHidingAllowed(false);
resultsTable.getColumnModel().getSelectionModel().setSelectionMode(javax.swing.ListSelectionModel.SINGLE_INTERVAL_SELECTION);
resultsTable.getColumnModel().addColumnModelListener(new TableColumnModelListener() {
@Override
public void columnAdded(TableColumnModelEvent e) {
// do nothing
}
@Override
public void columnRemoved(TableColumnModelEvent e) {
// do nothing
}
@Override
public void columnMoved(TableColumnModelEvent e) {
// do nothing
}
@Override
public void columnMarginChanged(ChangeEvent e) {
updateRowHeights(); //When the user changes column width we may need to resize row height
}
@Override
public void columnSelectionChanged(ListSelectionEvent e) {
// do nothing
}
});
resultsTable.setAutoResizeMode(javax.swing.JTable.AUTO_RESIZE_NEXT_COLUMN);
}
/**
* Sets the row heights to the heights of the content in their Value column.
*/
private void updateRowHeights() {
int valueColIndex = -1;
for (int col = 0; col < resultsTable.getColumnCount(); col++) {
if (resultsTable.getColumnName(col).equals(COLUMN_HEADERS[1])) {
valueColIndex = col;
}
}
if (valueColIndex != -1) {
for (int row = 0; row < resultsTable.getRowCount(); row++) {
Component comp = resultsTable.prepareRenderer(
resultsTable.getCellRenderer(row, valueColIndex), row, valueColIndex);
final int rowHeight;
if (comp instanceof JTextArea) {
final JTextArea tc = (JTextArea) comp;
final View rootView = tc.getUI().getRootView(tc);
java.awt.Insets i = tc.getInsets();
rootView.setSize(resultsTable.getColumnModel().getColumn(valueColIndex)
.getWidth() - (i.left + i.right +CELL_RIGHT_MARGIN), //current width minus borders
Integer.MAX_VALUE);
rowHeight = (int) rootView.getPreferredSpan(View.Y_AXIS);
} else {
rowHeight = comp.getPreferredSize().height;
}
if (rowHeight > 0) {
resultsTable.setRowHeight(row, rowHeight + CELL_BOTTOM_MARGIN);
}
}
}
}
/**
* Update the column widths so that the Value column has most of the space.
*/
private void updateColumnSizes() {
Enumeration<TableColumn> columns = resultsTable.getColumnModel().getColumns();
while (columns.hasMoreElements()) {
TableColumn col = columns.nextElement();
if (col.getHeaderValue().equals(COLUMN_HEADERS[0])) {
col.setPreferredWidth(COLUMN_WIDTHS[0]);
} else if (col.getHeaderValue().equals(COLUMN_HEADERS[1])) {
col.setPreferredWidth(COLUMN_WIDTHS[1]);
} else if (col.getHeaderValue().equals(COLUMN_HEADERS[2])) {
col.setPreferredWidth(COLUMN_WIDTHS[2]);
}
}
}
/**
* 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() {
rightClickMenu = new javax.swing.JPopupMenu();
copyMenuItem = new javax.swing.JMenuItem();
selectAllMenuItem = new javax.swing.JMenuItem();
resultsTableScrollPane = new javax.swing.JScrollPane();
copyMenuItem.setText(org.openide.util.NbBundle.getMessage(DefaultArtifactContentViewer.class, "DefaultArtifactContentViewer.copyMenuItem.text")); // NOI18N
rightClickMenu.add(copyMenuItem);
selectAllMenuItem.setText(org.openide.util.NbBundle.getMessage(DefaultArtifactContentViewer.class, "DefaultArtifactContentViewer.selectAllMenuItem.text")); // NOI18N
rightClickMenu.add(selectAllMenuItem);
setPreferredSize(new java.awt.Dimension(100, 58));
resultsTableScrollPane.setHorizontalScrollBarPolicy(javax.swing.ScrollPaneConstants.HORIZONTAL_SCROLLBAR_ALWAYS);
resultsTableScrollPane.setVerticalScrollBarPolicy(javax.swing.ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);
resultsTableScrollPane.setPreferredSize(new java.awt.Dimension(620, 34));
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
this.setLayout(layout);
layout.setHorizontalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(resultsTableScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 100, Short.MAX_VALUE)
);
layout.setVerticalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(resultsTableScrollPane, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.DEFAULT_SIZE, 58, Short.MAX_VALUE)
);
}// </editor-fold>//GEN-END:initComponents
// Variables declaration - do not modify//GEN-BEGIN:variables
private javax.swing.JMenuItem copyMenuItem;
private javax.swing.JScrollPane resultsTableScrollPane;
private javax.swing.JPopupMenu rightClickMenu;
private javax.swing.JMenuItem selectAllMenuItem;
// End of variables declaration//GEN-END:variables
private ETable resultsTable;
private void customizeComponents() {
resultsTable.setComponentPopupMenu(rightClickMenu);
ActionListener actList = new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
JMenuItem jmi = (JMenuItem) e.getSource();
if (jmi.equals(copyMenuItem)) {
StringBuilder selectedText = new StringBuilder(512);
for (int row : resultsTable.getSelectedRows()) {
for (int col : resultsTable.getSelectedColumns()) {
selectedText.append((String) resultsTable.getValueAt(row, col));
selectedText.append('\t');
}
//if its the last row selected don't add a new line
if (row != resultsTable.getSelectedRows()[resultsTable.getSelectedRows().length - 1]) {
selectedText.append(System.lineSeparator());
}
}
Toolkit.getDefaultToolkit().getSystemClipboard().setContents(new StringSelection(selectedText.toString()), null);
} else if (jmi.equals(selectAllMenuItem)) {
resultsTable.selectAll();
}
}
};
copyMenuItem.addActionListener(actList);
selectAllMenuItem.addActionListener(actList);
}
/**
* Resets the components to an empty view state.
*/
private void resetComponents() {
((DefaultTableModel) resultsTable.getModel()).setRowCount(0);
}
@Override
public Component getComponent() {
return this;
}
@Override
public void setArtifact(BlackboardArtifact artifact) {
try {
ResultsTableArtifact resultsTableArtifact = new ResultsTableArtifact(artifact, artifact.getParent());
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
updateView(resultsTableArtifact);
}
});
} catch (TskCoreException ex) {
logger.log(Level.SEVERE, String.format("Error getting parent content for artifact (artifact_id=%d, obj_id=%d)", artifact.getArtifactID(), artifact.getObjectID()), ex);
}
}
@Override
public boolean isSupported(BlackboardArtifact artifact) {
// This viewer supports all artifacts.
return true;
}
/**
* This class is a container to hold the data necessary for the artifact
* being viewed.
*/
private class ResultsTableArtifact {
private final SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.US);
private String[][] rowData = null;
private final String artifactDisplayName;
private final Content content;
ResultsTableArtifact(BlackboardArtifact artifact, Content content) {
artifactDisplayName = artifact.getDisplayName();
this.content = content;
addRows(artifact);
}
ResultsTableArtifact(String errorMsg) {
artifactDisplayName = errorMsg;
rowData = new String[1][3];
rowData[0] = new String[]{"", errorMsg, ""};
content = null;
}
private String[][] getRows() {
return rowData;
}
private void addRows(BlackboardArtifact artifact) {
List<String[]> rowsToAdd = new ArrayList<>();
try {
/*
* Add rows for each attribute.
*/
for (BlackboardAttribute attr : artifact.getAttributes()) {
/*
* Attribute value column.
*/
String value;
switch (attr.getAttributeType().getValueType()) {
// Use Autopsy date formatting settings, not TSK defaults
case DATETIME:
value = epochTimeToString(attr.getValueLong());
break;
case JSON:
// Get the attribute's JSON value and convert to indented multiline display string
String jsonVal = attr.getValueString();
JsonParser parser = new JsonParser();
JsonObject json = parser.parse(jsonVal).getAsJsonObject();
value = toJsonDisplayString(json, "");
break;
case STRING:
case INTEGER:
case LONG:
case DOUBLE:
case BYTE:
default:
value = attr.getDisplayString();
break;
}
/*
* Attribute sources column.
*/
String sources = StringUtils.join(attr.getSources(), ", ");
rowsToAdd.add(new String[]{attr.getAttributeType().getDisplayName(), value, sources});
}
/*
* Add a row for the source content path.
*/
String path = "";
try {
if (null != content) {
path = content.getUniquePath();
}
} catch (TskCoreException ex) {
logger.log(Level.SEVERE, String.format("Error getting source content path for artifact (artifact_id=%d, obj_id=%d)", artifact.getArtifactID(), artifact.getObjectID()), ex);
path = Bundle.DataContentViewerArtifact_failedToGetSourcePath_message();
}
rowsToAdd.add(new String[]{"Source File Path", path, ""});
/*
* Add a row for the artifact id.
*/
rowsToAdd.add(new String[]{"Artifact ID", Long.toString(artifact.getArtifactID()), ""});
} catch (TskCoreException ex) {
rowsToAdd.add(new String[]{"", Bundle.DataContentViewerArtifact_failedToGetAttributes_message(), ""});
}
rowData = rowsToAdd.toArray(new String[0][0]);
}
/**
* @return the artifactDisplayName
*/
String getArtifactDisplayName() {
return artifactDisplayName;
}
private static final String INDENT_RIGHT = " ";
private static final String NEW_LINE = "\n";
/**
* Recursively converts a JSON element into an indented multi-line
* display string.
*
* @param element JSON element to convert
* @param startIndent Starting indentation for the element.
*
* @return A multi-line display string.
*/
private String toJsonDisplayString(JsonElement element, String startIndent) {
StringBuilder sb = new StringBuilder("");
JsonObject obj = element.getAsJsonObject();
for (Map.Entry<String, JsonElement> entry : obj.entrySet()) {
appendJsonElementToString(entry.getKey(), entry.getValue(), startIndent, sb );
}
String returnString = sb.toString();
if (startIndent.length() == 0 && returnString.startsWith(NEW_LINE)) {
returnString = returnString.substring(NEW_LINE.length());
}
return returnString;
}
/**
* Converts the given JSON element into string and appends to the given string builder.
*
* @param jsonKey
* @param jsonElement
* @param startIndent Starting indentation for the element.
* @param sb String builder to append to.
*/
private void appendJsonElementToString(String jsonKey, JsonElement jsonElement, String startIndent, StringBuilder sb) {
if (jsonElement.isJsonArray()) {
JsonArray jsonArray = jsonElement.getAsJsonArray();
if (jsonArray.size() > 0) {
int count = 1;
sb.append(NEW_LINE).append(String.format("%s%s", startIndent, jsonKey));
for (JsonElement arrayMember : jsonArray) {
sb.append(NEW_LINE).append(String.format("%s%d", startIndent.concat(INDENT_RIGHT), count));
sb.append(toJsonDisplayString(arrayMember, startIndent.concat(INDENT_RIGHT).concat(INDENT_RIGHT)));
count++;
}
}
} else if (jsonElement.isJsonObject()) {
sb.append(NEW_LINE).append(String.format("%s%s %s", startIndent, jsonKey, toJsonDisplayString(jsonElement.getAsJsonObject(), startIndent + INDENT_RIGHT)));
} else if (jsonElement.isJsonPrimitive()) {
String attributeName = jsonKey;
String attributeValue;
if (attributeName.toUpperCase().contains("DATETIME")) {
attributeValue = epochTimeToString(Long.parseLong(jsonElement.getAsString()));
} else {
attributeValue = jsonElement.getAsString();
}
sb.append(NEW_LINE).append(String.format("%s%s = %s", startIndent, attributeName, attributeValue));
} else if (jsonElement.isJsonNull()) {
sb.append(NEW_LINE).append(String.format("%s%s = null", startIndent, jsonKey));
}
}
/**
* Converts epoch time to readable string.
*
* @param epochTime epoch time value to be converted to string.
* @return String with human readable time.
*/
private String epochTimeToString(long epochTime) {
String dateTimeString = "0000-00-00 00:00:00";
if (null != content && 0 != epochTime) {
dateFormatter.setTimeZone(ContentUtils.getTimeZone(content));
dateTimeString = dateFormatter.format(new java.util.Date(epochTime * 1000));
}
return dateTimeString;
}
}
/**
* Updates the table view with the given artifact data.
*
* It should be called on EDT.
*
* @param resultsTableArtifact Artifact data to display in the view.
*/
private void updateView(ResultsTableArtifact resultsTableArtifact) {
this.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
DefaultTableModel tModel = ((DefaultTableModel) resultsTable.getModel());
tModel.setDataVector(resultsTableArtifact.getRows(), COLUMN_HEADERS);
updateColumnSizes();
updateRowHeights();
resultsTable.clearSelection();
this.setCursor(null);
}
/**
* TableCellRenderer for displaying multiline text.
*/
private class MultiLineTableCellRenderer implements javax.swing.table.TableCellRenderer {
@Override
public Component getTableCellRendererComponent(javax.swing.JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
javax.swing.JTextArea jtex = new javax.swing.JTextArea();
if (value instanceof String) {
jtex.setText((String) value);
jtex.setLineWrap(true);
jtex.setWrapStyleWord(false);
}
//cell backgroud color when selected
if (isSelected) {
jtex.setBackground(javax.swing.UIManager.getColor("Table.selectionBackground"));
} else {
jtex.setBackground(javax.swing.UIManager.getColor("Table.background"));
}
return jtex;
}
}
}

View File

@ -30,8 +30,6 @@ DataContentViewerArtifact.pageLabel2.text=Result
DataContentViewerArtifact.nextPageButton.text=
DataContentViewerArtifact.currentPageLabel.text=1
DataContentViewerArtifact.ofLabel.text=of
DataContentViewerArtifact.copyMenuItem.text=Copy
DataContentViewerArtifact.selectAllMenuItem.text=Select All
DataContentViewerArtifact.pageLabel.text=Result:
AdvancedConfigurationDialog.applyButton.text=OK
DataContentViewerHex.goToPageTextField.text=

View File

@ -82,8 +82,6 @@ DataContentViewerArtifact.pageLabel2.text=\u7d50\u679c
DataContentViewerArtifact.nextPageButton.text=
DataContentViewerArtifact.currentPageLabel.text=1
DataContentViewerArtifact.ofLabel.text=/
DataContentViewerArtifact.copyMenuItem.text=\u30b3\u30d4\u30fc
DataContentViewerArtifact.selectAllMenuItem.text=\u3059\u3079\u3066\u3092\u9078\u629e
DataContentViewerArtifact.pageLabel.text=\u7d50\u679c:
AdvancedConfigurationDialog.applyButton.text=OK
DataContentViewerHex.goToPageTextField.text=

View File

@ -1,30 +1,6 @@
<?xml version="1.0" encoding="UTF-8" ?>
<Form version="1.5" maxVersion="1.7" type="org.netbeans.modules.form.forminfo.JPanelFormInfo">
<NonVisualComponents>
<Container class="javax.swing.JPopupMenu" name="rightClickMenu">
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignAbsoluteLayout">
<Property name="useNullLayout" type="boolean" value="true"/>
</Layout>
<SubComponents>
<MenuItem class="javax.swing.JMenuItem" name="copyMenuItem">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/corecomponents/Bundle.properties" key="DataContentViewerArtifact.copyMenuItem.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
</MenuItem>
<MenuItem class="javax.swing.JMenuItem" name="selectAllMenuItem">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/corecomponents/Bundle.properties" key="DataContentViewerArtifact.selectAllMenuItem.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
</MenuItem>
</SubComponents>
</Container>
</NonVisualComponents>
<Form version="1.9" maxVersion="1.9" type="org.netbeans.modules.form.forminfo.JPanelFormInfo">
<Properties>
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[100, 58]"/>
@ -45,16 +21,16 @@
<Layout>
<DimensionLayout dim="0">
<Group type="103" groupAlignment="0" attributes="0">
<Component id="jScrollPane1" max="32767" attributes="0"/>
<Component id="resultsTableScrollPane" max="32767" attributes="0"/>
<Component id="jScrollPane1" pref="561" max="32767" attributes="0"/>
<Component id="artifactContentPanel" alignment="0" max="32767" attributes="0"/>
</Group>
</DimensionLayout>
<DimensionLayout dim="1">
<Group type="103" groupAlignment="0" attributes="0">
<Group type="102" alignment="0" attributes="0">
<Component id="jScrollPane1" min="-2" pref="24" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="resultsTableScrollPane" max="32767" attributes="0"/>
<EmptySpace min="-2" max="-2" attributes="0"/>
<Component id="artifactContentPanel" pref="397" max="32767" attributes="0"/>
</Group>
</Group>
</DimensionLayout>
@ -248,19 +224,34 @@
</Container>
</SubComponents>
</Container>
<Container class="javax.swing.JScrollPane" name="resultsTableScrollPane">
<Properties>
<Property name="horizontalScrollBarPolicy" type="int" value="32"/>
<Property name="verticalScrollBarPolicy" type="int" value="22"/>
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[620, 34]"/>
</Property>
</Properties>
<AuxValues>
<AuxValue name="autoScrollPane" type="java.lang.Boolean" value="true"/>
</AuxValues>
<Layout class="org.netbeans.modules.form.compat2.layouts.support.JScrollPaneSupportLayout"/>
<Container class="javax.swing.JPanel" name="artifactContentPanel">
<LayoutCode>
<CodeStatement>
<CodeExpression id="1_artifactContentPanel">
<CodeVariable name="artifactContentPanel" type="8194" declaredType="javax.swing.JPanel"/>
<ExpressionOrigin>
<ExpressionProvider type="ComponentRef">
<ComponentRef name="artifactContentPanel"/>
</ExpressionProvider>
</ExpressionOrigin>
</CodeExpression>
<StatementProvider type="CodeMethod">
<CodeMethod name="setLayout" class="java.awt.Container" parameterTypes="java.awt.LayoutManager"/>
</StatementProvider>
<Parameters>
<CodeExpression id="2">
<ExpressionOrigin>
<ExpressionProvider type="CodeConstructor">
<CodeConstructor class="javax.swing.OverlayLayout" parameterTypes="java.awt.Container"/>
</ExpressionProvider>
<Parameters>
<CodeExpression id="1_artifactContentPanel"/>
</Parameters>
</ExpressionOrigin>
</CodeExpression>
</Parameters>
</CodeStatement>
</LayoutCode>
</Container>
</SubComponents>
</Form>

View File

@ -20,178 +20,67 @@ package org.sleuthkit.autopsy.corecomponents;
import java.awt.Component;
import java.awt.Cursor;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.datatransfer.StringSelection;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Enumeration;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.logging.Level;
import javax.swing.JMenuItem;
import javax.swing.JTextArea;
import javax.swing.SwingWorker;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.TableColumnModelEvent;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableColumn;
import javax.swing.event.TableColumnModelListener;
import javax.swing.text.View;
import org.apache.commons.lang.StringUtils;
import org.openide.nodes.Node;
import org.openide.util.Lookup;
import org.openide.util.NbBundle;
import org.openide.util.lookup.ServiceProvider;
import org.sleuthkit.autopsy.corecomponentinterfaces.DataContentViewer;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.datamodel.ContentUtils;
import org.sleuthkit.datamodel.BlackboardArtifact;
import org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE;
import org.sleuthkit.datamodel.BlackboardAttribute;
import org.sleuthkit.datamodel.Content;
import org.sleuthkit.datamodel.TskCoreException;
import org.sleuthkit.datamodel.TskException;
import org.netbeans.swing.etable.ETable;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.google.gson.JsonArray;
import java.util.Map;
import java.util.Arrays;
import java.util.Collections;
import org.sleuthkit.autopsy.contentviewers.ArtifactContentViewer;
import org.sleuthkit.autopsy.contentviewers.DefaultArtifactContentViewer;
/**
* Instances of this class display the BlackboardArtifacts associated with the
* Content represented by a Node. Each BlackboardArtifact is rendered displayed
* in a JTable representation of its BlackboardAttributes.
* Content represented by a Node.
*
* It goes through a list of known ArtifactContentViewer to find a viewer that
* supports a given artifact and then hands it the artifact to display.
*/
@ServiceProvider(service = DataContentViewer.class, position = 7)
@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives
public class DataContentViewerArtifact extends javax.swing.JPanel implements DataContentViewer {
private static final long serialVersionUID = 1L;
@NbBundle.Messages({
"DataContentViewerArtifact.attrsTableHeader.type=Type",
"DataContentViewerArtifact.attrsTableHeader.value=Value",
"DataContentViewerArtifact.attrsTableHeader.sources=Source(s)",
"DataContentViewerArtifact.failedToGetSourcePath.message=Failed to get source file path from case database",
"DataContentViewerArtifact.failedToGetAttributes.message=Failed to get some or all attributes from case database"
})
private final static Logger logger = Logger.getLogger(DataContentViewerArtifact.class.getName());
private final static String WAIT_TEXT = NbBundle.getMessage(DataContentViewerArtifact.class, "DataContentViewerArtifact.waitText");
private final static String ERROR_TEXT = NbBundle.getMessage(DataContentViewerArtifact.class, "DataContentViewerArtifact.errorText");
private Node currentNode; // @@@ Remove this when the redundant setNode() calls problem is fixed.
private int currentPage = 1;
private final Object lock = new Object();
private List<ResultsTableArtifact> artifactTableContents; // Accessed by multiple threads, use getArtifactContents() and setArtifactContents()
SwingWorker<ViewUpdate, Void> currentTask; // Accessed by multiple threads, use startNewTask()
private static final String[] COLUMN_HEADERS = {
Bundle.DataContentViewerArtifact_attrsTableHeader_type(),
Bundle.DataContentViewerArtifact_attrsTableHeader_value(),
Bundle.DataContentViewerArtifact_attrsTableHeader_sources()};
private static final int[] COLUMN_WIDTHS = {100, 800, 100};
private static final int CELL_BOTTOM_MARGIN = 5;
private static final int CELL_RIGHT_MARGIN = 1;
private List<BlackboardArtifact> artifactTableContents; // Accessed by multiple threads, use getArtifactContents() and setArtifactContents()
private SwingWorker<ViewUpdate, Void> currentTask; // Accessed by multiple threads, use startNewTask()
private final Collection<ArtifactContentViewer> KNOWN_ARTIFACT_VIEWERS =
Arrays.asList(
// TBD: populate with custom artifact viewers.
);
public DataContentViewerArtifact() {
initResultsTable();
initComponents();
resultsTableScrollPane.setViewportView(resultsTable);
customizeComponents();
resetComponents();
resultsTable.setDefaultRenderer(Object.class, new MultiLineTableCellRenderer());
}
private void initResultsTable() {
resultsTable = new ETable();
resultsTable.setModel(new javax.swing.table.DefaultTableModel() {
private static final long serialVersionUID = 1L;
public boolean isCellEditable(int rowIndex, int columnIndex) {
return false;
}
});
resultsTable.setCellSelectionEnabled(true);
resultsTable.getTableHeader().setReorderingAllowed(false);
resultsTable.setColumnHidingAllowed(false);
resultsTable.getColumnModel().getSelectionModel().setSelectionMode(javax.swing.ListSelectionModel.SINGLE_INTERVAL_SELECTION);
resultsTable.getColumnModel().addColumnModelListener(new TableColumnModelListener() {
@Override
public void columnAdded(TableColumnModelEvent e) {
}
@Override
public void columnRemoved(TableColumnModelEvent e) {
}
@Override
public void columnMoved(TableColumnModelEvent e) {
}
@Override
public void columnMarginChanged(ChangeEvent e) {
updateRowHeights(); //When the user changes column width we may need to resize row height
}
@Override
public void columnSelectionChanged(ListSelectionEvent e) {
}
});
resultsTable.setAutoResizeMode(javax.swing.JTable.AUTO_RESIZE_NEXT_COLUMN);
}
/**
* Sets the row heights to the heights of the content in their Value column.
*/
private void updateRowHeights() {
int valueColIndex = -1;
for (int col = 0; col < resultsTable.getColumnCount(); col++) {
if (resultsTable.getColumnName(col).equals(COLUMN_HEADERS[1])) {
valueColIndex = col;
}
}
if (valueColIndex != -1) {
for (int row = 0; row < resultsTable.getRowCount(); row++) {
Component comp = resultsTable.prepareRenderer(
resultsTable.getCellRenderer(row, valueColIndex), row, valueColIndex);
final int rowHeight;
if (comp instanceof JTextArea) {
final JTextArea tc = (JTextArea) comp;
final View rootView = tc.getUI().getRootView(tc);
java.awt.Insets i = tc.getInsets();
rootView.setSize(resultsTable.getColumnModel().getColumn(valueColIndex)
.getWidth() - (i.left + i.right +CELL_RIGHT_MARGIN), //current width minus borders
Integer.MAX_VALUE);
rowHeight = (int) rootView.getPreferredSpan(View.Y_AXIS);
} else {
rowHeight = comp.getPreferredSize().height;
}
if (rowHeight > 0) {
resultsTable.setRowHeight(row, rowHeight + CELL_BOTTOM_MARGIN);
}
}
}
}
/**
* Update the column widths so that the Value column has most of the space.
*/
private void updateColumnSizes() {
Enumeration<TableColumn> columns = resultsTable.getColumnModel().getColumns();
while (columns.hasMoreElements()) {
TableColumn col = columns.nextElement();
if (col.getHeaderValue().equals(COLUMN_HEADERS[0])) {
col.setPreferredWidth(COLUMN_WIDTHS[0]);
} else if (col.getHeaderValue().equals(COLUMN_HEADERS[1])) {
col.setPreferredWidth(COLUMN_WIDTHS[1]);
} else if (col.getHeaderValue().equals(COLUMN_HEADERS[2])) {
col.setPreferredWidth(COLUMN_WIDTHS[2]);
}
}
}
/**
@ -204,9 +93,6 @@ public class DataContentViewerArtifact extends javax.swing.JPanel implements Dat
private void initComponents() {
java.awt.GridBagConstraints gridBagConstraints;
rightClickMenu = new javax.swing.JPopupMenu();
copyMenuItem = new javax.swing.JMenuItem();
selectAllMenuItem = new javax.swing.JMenuItem();
jScrollPane1 = new javax.swing.JScrollPane();
jPanel1 = new javax.swing.JPanel();
totalPageLabel = new javax.swing.JLabel();
@ -218,13 +104,7 @@ public class DataContentViewerArtifact extends javax.swing.JPanel implements Dat
prevPageButton = new javax.swing.JButton();
artifactLabel = new javax.swing.JLabel();
filler1 = new javax.swing.Box.Filler(new java.awt.Dimension(0, 0), new java.awt.Dimension(0, 0), new java.awt.Dimension(32767, 0));
resultsTableScrollPane = new javax.swing.JScrollPane();
copyMenuItem.setText(org.openide.util.NbBundle.getMessage(DataContentViewerArtifact.class, "DataContentViewerArtifact.copyMenuItem.text")); // NOI18N
rightClickMenu.add(copyMenuItem);
selectAllMenuItem.setText(org.openide.util.NbBundle.getMessage(DataContentViewerArtifact.class, "DataContentViewerArtifact.selectAllMenuItem.text")); // NOI18N
rightClickMenu.add(selectAllMenuItem);
artifactContentPanel = new javax.swing.JPanel();
setPreferredSize(new java.awt.Dimension(100, 58));
@ -341,43 +221,41 @@ public class DataContentViewerArtifact extends javax.swing.JPanel implements Dat
jScrollPane1.setViewportView(jPanel1);
resultsTableScrollPane.setHorizontalScrollBarPolicy(javax.swing.ScrollPaneConstants.HORIZONTAL_SCROLLBAR_ALWAYS);
resultsTableScrollPane.setVerticalScrollBarPolicy(javax.swing.ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);
resultsTableScrollPane.setPreferredSize(new java.awt.Dimension(620, 34));
artifactContentPanel.setLayout(new javax.swing.OverlayLayout(artifactContentPanel));
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
this.setLayout(layout);
layout.setHorizontalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(jScrollPane1)
.addComponent(resultsTableScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 561, Short.MAX_VALUE)
.addComponent(artifactContentPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
);
layout.setVerticalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup()
.addComponent(jScrollPane1, javax.swing.GroupLayout.PREFERRED_SIZE, 24, javax.swing.GroupLayout.PREFERRED_SIZE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(resultsTableScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
.addComponent(artifactContentPanel, javax.swing.GroupLayout.DEFAULT_SIZE, 397, Short.MAX_VALUE))
);
}// </editor-fold>//GEN-END:initComponents
private void nextPageButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_nextPageButtonActionPerformed
currentPage = currentPage + 1;
currentPage += 1;
currentPageLabel.setText(Integer.toString(currentPage));
artifactLabel.setText(artifactTableContents.get(currentPage - 1).getArtifactDisplayName());
artifactLabel.setText(artifactTableContents.get(currentPage - 1).getDisplayName());
startNewTask(new SelectedArtifactChangedTask(currentPage));
}//GEN-LAST:event_nextPageButtonActionPerformed
private void prevPageButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_prevPageButtonActionPerformed
currentPage = currentPage - 1;
currentPage -= 1;
currentPageLabel.setText(Integer.toString(currentPage));
artifactLabel.setText(artifactTableContents.get(currentPage - 1).getArtifactDisplayName());
artifactLabel.setText(artifactTableContents.get(currentPage - 1).getDisplayName());
startNewTask(new SelectedArtifactChangedTask(currentPage));
}//GEN-LAST:event_prevPageButtonActionPerformed
// Variables declaration - do not modify//GEN-BEGIN:variables
private javax.swing.JPanel artifactContentPanel;
private javax.swing.JLabel artifactLabel;
private javax.swing.JMenuItem copyMenuItem;
private javax.swing.JLabel currentPageLabel;
private javax.swing.Box.Filler filler1;
private javax.swing.JPanel jPanel1;
@ -387,41 +265,9 @@ public class DataContentViewerArtifact extends javax.swing.JPanel implements Dat
private javax.swing.JLabel pageLabel;
private javax.swing.JLabel pageLabel2;
private javax.swing.JButton prevPageButton;
private javax.swing.JScrollPane resultsTableScrollPane;
private javax.swing.JPopupMenu rightClickMenu;
private javax.swing.JMenuItem selectAllMenuItem;
private javax.swing.JLabel totalPageLabel;
// End of variables declaration//GEN-END:variables
private ETable resultsTable;
private void customizeComponents() {
resultsTable.setComponentPopupMenu(rightClickMenu);
ActionListener actList = new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
JMenuItem jmi = (JMenuItem) e.getSource();
if (jmi.equals(copyMenuItem)) {
StringBuilder selectedText = new StringBuilder(512);
for (int row : resultsTable.getSelectedRows()) {
for (int col : resultsTable.getSelectedColumns()) {
selectedText.append((String) resultsTable.getValueAt(row, col));
selectedText.append("\t");
}
//if its the last row selected don't add a new line
if (row != resultsTable.getSelectedRows()[resultsTable.getSelectedRows().length - 1]) {
selectedText.append(System.lineSeparator());
}
}
Toolkit.getDefaultToolkit().getSystemClipboard().setContents(new StringSelection(selectedText.toString()), null);
} else if (jmi.equals(selectAllMenuItem)) {
resultsTable.selectAll();
}
}
};
copyMenuItem.addActionListener(actList);
selectAllMenuItem.addActionListener(actList);
}
/**
* Resets the components to an empty view state.
@ -431,10 +277,12 @@ public class DataContentViewerArtifact extends javax.swing.JPanel implements Dat
currentPageLabel.setText("");
artifactLabel.setText("");
totalPageLabel.setText("");
((DefaultTableModel) resultsTable.getModel()).setRowCount(0);
prevPageButton.setEnabled(false);
nextPageButton.setEnabled(false);
currentNode = null;
artifactContentPanel.removeAll();
}
@Override
@ -521,182 +369,12 @@ public class DataContentViewerArtifact extends javax.swing.JPanel implements Dat
}
}
/**
* This class is a container to hold the data necessary for each of the
* result pages associated with file or artifact beivng viewed.
*/
private class ResultsTableArtifact {
private ArtifactContentViewer getSupportingViewer(BlackboardArtifact artifact) {
private final SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
private String[][] rowData = null;
private final String artifactDisplayName;
private final Content content;
ResultsTableArtifact(BlackboardArtifact artifact, Content content) {
artifactDisplayName = artifact.getDisplayName();
this.content = content;
addRows(artifact);
}
ResultsTableArtifact(String errorMsg) {
artifactDisplayName = errorMsg;
rowData = new String[1][3];
rowData[0] = new String[]{"", errorMsg, ""};
content = null;
}
private String[][] getRows() {
return rowData;
}
private void addRows(BlackboardArtifact artifact) {
List<String[]> rowsToAdd = new ArrayList<>();
try {
/*
* Add rows for each attribute.
*/
for (BlackboardAttribute attr : artifact.getAttributes()) {
/*
* Attribute value column.
*/
String value = "";
switch (attr.getAttributeType().getValueType()) {
case STRING:
case INTEGER:
case LONG:
case DOUBLE:
case BYTE:
default:
value = attr.getDisplayString();
break;
// Use Autopsy date formatting settings, not TSK defaults
case DATETIME:
value = epochTimeToString(attr.getValueLong());
break;
case JSON:
// Get the attribute's JSON value and convert to indented multiline display string
String jsonVal = attr.getValueString();
JsonParser parser = new JsonParser();
JsonObject json = parser.parse(jsonVal).getAsJsonObject();
value = toJsonDisplayString(json, "");
break;
}
/*
* Attribute sources column.
*/
String sources = StringUtils.join(attr.getSources(), ", ");
rowsToAdd.add(new String[]{attr.getAttributeType().getDisplayName(), value, sources});
}
/*
* Add a row for the source content path.
*/
String path = "";
try {
if (null != content) {
path = content.getUniquePath();
}
} catch (TskCoreException ex) {
logger.log(Level.SEVERE, String.format("Error getting source content path for artifact (artifact_id=%d, obj_id=%d)", artifact.getArtifactID(), artifact.getObjectID()), ex);
path = Bundle.DataContentViewerArtifact_failedToGetSourcePath_message();
}
rowsToAdd.add(new String[]{"Source File Path", path, ""});
/*
* Add a row for the artifact id.
*/
rowsToAdd.add(new String[]{"Artifact ID", Long.toString(artifact.getArtifactID()), ""});
} catch (TskCoreException ex) {
rowsToAdd.add(new String[]{"", Bundle.DataContentViewerArtifact_failedToGetAttributes_message(), ""});
}
rowData = rowsToAdd.toArray(new String[0][0]);
}
/**
* @return the artifactDisplayName
*/
String getArtifactDisplayName() {
return artifactDisplayName;
}
private static final String INDENT_RIGHT = " ";
private static final String NEW_LINE = "\n";
/**
* Recursively converts a JSON element into an indented multi-line
* display string.
*
* @param element JSON element to convert
* @param startIndent Starting indentation for the element.
*
* @return A multi-line display string.
*/
private String toJsonDisplayString(JsonElement element, String startIndent) {
StringBuilder sb = new StringBuilder("");
JsonObject obj = element.getAsJsonObject();
for (Map.Entry<String, JsonElement> entry : obj.entrySet()) {
appendJsonElementToString(entry.getKey(), entry.getValue(), startIndent, sb );
}
String returnString = sb.toString();
if (startIndent.length() == 0 && returnString.startsWith(NEW_LINE)) {
returnString = returnString.substring(NEW_LINE.length());
}
return returnString;
}
/**
* Converts the given JSON element into string and appends to the given string builder.
*
* @param jsonKey
* @param jsonElement
* @param startIndent Starting indentation for the element.
* @param sb String builder to append to.
*/
private void appendJsonElementToString(String jsonKey, JsonElement jsonElement, String startIndent, StringBuilder sb) {
if (jsonElement.isJsonArray()) {
JsonArray jsonArray = jsonElement.getAsJsonArray();
if (jsonArray.size() > 0) {
int count = 1;
sb.append(NEW_LINE).append(String.format("%s%s", startIndent, jsonKey));
for (JsonElement arrayMember : jsonArray) {
sb.append(NEW_LINE).append(String.format("%s%d", startIndent.concat(INDENT_RIGHT), count));
sb.append(toJsonDisplayString(arrayMember, startIndent.concat(INDENT_RIGHT).concat(INDENT_RIGHT)));
count++;
}
}
} else if (jsonElement.isJsonObject()) {
sb.append(NEW_LINE).append(String.format("%s%s %s", startIndent, jsonKey, toJsonDisplayString(jsonElement.getAsJsonObject(), startIndent + INDENT_RIGHT)));
} else if (jsonElement.isJsonPrimitive()) {
String attributeName = jsonKey;
String attributeValue;
if (attributeName.toUpperCase().contains("DATETIME")) {
attributeValue = epochTimeToString(Long.parseLong(jsonElement.getAsString()));
} else {
attributeValue = jsonElement.getAsString();
}
sb.append(NEW_LINE).append(String.format("%s%s = %s", startIndent, attributeName, attributeValue));
} else if (jsonElement.isJsonNull()) {
sb.append(NEW_LINE).append(String.format("%s%s = null", startIndent, jsonKey));
}
}
/**
* Converts epoch time to readable string.
*
* @param epochTime epoch time value to be converted to string.
* @return String with human readable time.
*/
private String epochTimeToString(long epochTime) {
String dateTimeString = "0000-00-00 00:00:00";
if (null != content && 0 != epochTime) {
dateFormatter.setTimeZone(ContentUtils.getTimeZone(content));
dateTimeString = dateFormatter.format(new java.util.Date(epochTime * 1000));
}
return dateTimeString;
}
return this.KNOWN_ARTIFACT_VIEWERS.stream()
.filter(knownViewer -> knownViewer.isSupported(artifact))
.findAny()
.orElse(new DefaultArtifactContentViewer());
}
@ -708,18 +386,21 @@ public class DataContentViewerArtifact extends javax.swing.JPanel implements Dat
int numberOfPages;
int currentPage;
ResultsTableArtifact tableContents;
BlackboardArtifact artifact;
String errorMsg;
ViewUpdate(int numberOfPages, int currentPage, ResultsTableArtifact contents) {
ViewUpdate(int numberOfPages, int currentPage, BlackboardArtifact artifact) {
this.currentPage = currentPage;
this.numberOfPages = numberOfPages;
this.tableContents = contents;
this.artifact = artifact;
this.errorMsg = null;
}
ViewUpdate(int numberOfPages, int currentPage, String errorMsg) {
this.currentPage = currentPage;
this.numberOfPages = numberOfPages;
this.tableContents = new ResultsTableArtifact(errorMsg);
this.errorMsg = errorMsg;
this.artifact = null;
}
}
@ -738,14 +419,26 @@ public class DataContentViewerArtifact extends javax.swing.JPanel implements Dat
currentPage = viewUpdate.currentPage;
totalPageLabel.setText(Integer.toString(viewUpdate.numberOfPages));
currentPageLabel.setText(Integer.toString(currentPage));
artifactLabel.setText(viewUpdate.tableContents.getArtifactDisplayName());
DefaultTableModel tModel = ((DefaultTableModel) resultsTable.getModel());
tModel.setDataVector(viewUpdate.tableContents.getRows(), COLUMN_HEADERS);
updateColumnSizes();
updateRowHeights();
resultsTable.clearSelection();
artifactContentPanel.removeAll();
if (viewUpdate.artifact != null) {
artifactLabel.setText(viewUpdate.artifact.getDisplayName());
BlackboardArtifact artifact = viewUpdate.artifact;
ArtifactContentViewer viewer = this.getSupportingViewer(artifact);
viewer.setArtifact(artifact);
artifactContentPanel.add(viewer.getComponent());
} else {
artifactLabel.setText(viewUpdate.errorMsg);
}
artifactContentPanel.revalidate();
this.setCursor(null);
this.revalidate();
}
/**
@ -755,13 +448,7 @@ public class DataContentViewerArtifact extends javax.swing.JPanel implements Dat
* @param task A new SwingWorker object to execute as a background thread.
*/
private synchronized void startNewTask(SwingWorker<ViewUpdate, Void> task) {
String[][] waitRow = new String[1][3];
waitRow[0] = new String[]{"", WAIT_TEXT, ""};
DefaultTableModel tModel = ((DefaultTableModel) resultsTable.getModel());
tModel.setDataVector(waitRow, COLUMN_HEADERS);
updateColumnSizes();
updateRowHeights();
resultsTable.clearSelection();
// The output of the previous task is no longer relevant.
if (currentTask != null) {
// This call sets a cancellation flag. It does not terminate the background thread running the task.
@ -775,12 +462,12 @@ public class DataContentViewerArtifact extends javax.swing.JPanel implements Dat
}
/**
* Populate the cache of artifact represented as ResultsTableArtifacts.
* Populate the cache of artifacts represented as ResultsTableArtifacts.
*
* @param artifactList A list of ResultsTableArtifact representations of
* artifacts.
*/
private void setArtifactContents(List<ResultsTableArtifact> artifactList) {
private void setArtifactContents(List<BlackboardArtifact> artifactList) {
synchronized (lock) {
this.artifactTableContents = artifactList;
}
@ -789,11 +476,11 @@ public class DataContentViewerArtifact extends javax.swing.JPanel implements Dat
/**
* Retrieve the cache of artifact represented as ResultsTableArtifacts.
*
* @return A list of ResultsTableArtifact representations of artifacts.
* @return A list of artifacts.
*/
private List<ResultsTableArtifact> getArtifactContents() {
private List<BlackboardArtifact> getArtifactContents() {
synchronized (lock) {
return artifactTableContents;
return Collections.unmodifiableList(artifactTableContents);
}
}
@ -843,9 +530,9 @@ public class DataContentViewerArtifact extends javax.swing.JPanel implements Dat
}
// Build the new artifact contents cache.
ArrayList<ResultsTableArtifact> artifactContents = new ArrayList<>();
ArrayList<BlackboardArtifact> artifactContents = new ArrayList<>();
for (BlackboardArtifact artifact : artifacts) {
artifactContents.add(new ResultsTableArtifact(artifact, underlyingContent));
artifactContents.add(artifact);
}
// If the node has an underlying blackboard artifact, show it. If not,
@ -932,14 +619,14 @@ public class DataContentViewerArtifact extends javax.swing.JPanel implements Dat
protected ViewUpdate doInBackground() {
// Get the artifact content to display from the cache. Note that one must be subtracted from the
// page index to get the corresponding artifact content index.
List<ResultsTableArtifact> artifactContents = getArtifactContents();
ResultsTableArtifact artifactContent = artifactContents.get(pageIndex - 1);
List<BlackboardArtifact> artifactContents = getArtifactContents();
// It may take a considerable amount of time to fetch the attributes of the selected artifact so check for cancellation.
if (isCancelled()) {
return null;
}
BlackboardArtifact artifactContent = artifactContents.get(pageIndex - 1);
return new ViewUpdate(artifactContents.size(), pageIndex, artifactContent);
}
@ -957,27 +644,4 @@ public class DataContentViewerArtifact extends javax.swing.JPanel implements Dat
}
}
}
/**
* TableCellRenderer for displaying multiline text.
*/
private class MultiLineTableCellRenderer implements javax.swing.table.TableCellRenderer {
@Override
public Component getTableCellRendererComponent(javax.swing.JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
javax.swing.JTextArea jtex = new javax.swing.JTextArea();
if (value instanceof String) {
jtex.setText((String) value);
jtex.setLineWrap(true);
jtex.setWrapStyleWord(false);
}
//cell backgroud color when selected
if (isSelected) {
jtex.setBackground(javax.swing.UIManager.getColor("Table.selectionBackground"));
} else {
jtex.setBackground(javax.swing.UIManager.getColor("Table.background"));
}
return jtex;
}
}
}