/*
* Autopsy Forensic Browser
*
* Copyright 2011-2016 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.datamodel;
import java.awt.Desktop;
import java.awt.event.ActionEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.logging.Level;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.JCheckBox;
import javax.swing.JOptionPane;
import org.openide.nodes.ChildFactory;
import org.openide.nodes.Children;
import org.openide.nodes.Node;
import org.openide.nodes.Sheet;
import org.openide.util.NbBundle;
import org.openide.util.Utilities;
import org.openide.util.lookup.Lookups;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil;
import org.sleuthkit.autopsy.directorytree.ExternalViewerAction;
import org.sleuthkit.datamodel.Report;
import org.sleuthkit.datamodel.TskCoreException;
/**
* Implements the Reports subtree of the Autopsy tree.
*/
public final class Reports implements AutopsyVisitableItem {
private static final SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss z");
@Override
public <T> T accept(AutopsyItemVisitor<T> visitor) {
// CreateAutopsyNodeVisitor.visit() constructs a ReportsListNode.
return visitor.visit(this);
}
/**
* The root node of the Reports subtree of the Autopsy tree.
*/
public static final class ReportsListNode extends DisplayableItemNode {
private static final long serialVersionUID = 1L;
private static final String DISPLAY_NAME = NbBundle.getMessage(ReportsListNode.class, "ReportsListNode.displayName");
private static final String ICON_PATH = "org/sleuthkit/autopsy/images/report_16.png"; //NON-NLS
public ReportsListNode() {
super(Children.create(new ReportNodeFactory(), true));
setName(DISPLAY_NAME);
setDisplayName(DISPLAY_NAME);
this.setIconBaseWithExtension(ICON_PATH);
}
@Override
public boolean isLeafTypeNode() {
return true;
}
@Override
public <T> T accept(DisplayableItemNodeVisitor<T> visitor) {
// - GetPopupActionsDisplayableItemNodeVisitor.visit() returns null.
// - GetPreferredActionsDisplayableItemNodeVisitor.visit() returns null.
// - IsLeafItemVisitor.visit() returns false.
// - ShowItemVisitor.visit() returns true.
return visitor.visit(this);
}
@Override
public String getItemType() {
return getClass().getName();
}
}
/**
* The child node factory that creates ReportNode children for a
* ReportsListNode.
*/
private static final class ReportNodeFactory extends ChildFactory<Report> {
ReportNodeFactory() {
Case.addPropertyChangeListener((PropertyChangeEvent evt) -> {
String eventType = evt.getPropertyName();
if (eventType.equals(Case.Events.REPORT_ADDED.toString()) || eventType.equals(Case.Events.REPORT_DELETED.toString())) {
/**
* Checking for a current case is a stop gap measure until a
* different way of handling the closing of cases is worked
* out. Currently, remote events may be received for a case
* that is already closed.
*/
try {
Case.getCurrentCase();
ReportNodeFactory.this.refresh(true);
} catch (IllegalStateException notUsed) {
/**
* Case is closed, do nothing.
*/
}
}
});
}
@Override
protected boolean createKeys(List<Report> keys) {
try {
keys.addAll(Case.getCurrentCase().getAllReports());
} catch (TskCoreException ex) {
Logger.getLogger(Reports.ReportNodeFactory.class.getName()).log(Level.SEVERE, "Failed to get reports", ex); //NON-NLS
}
return true;
}
@Override
protected Node createNodeForKey(Report key) {
return new ReportNode(key);
}
}
/**
* A leaf node in the Reports subtree of the Autopsy tree, wraps a Report
* object.
*/
public static final class ReportNode extends DisplayableItemNode {
private static final long serialVersionUID = 1L;
private static final String ICON_PATH = "org/sleuthkit/autopsy/images/report_16.png"; //NON-NLS
private final Report report;
ReportNode(Report report) {
super(Children.LEAF, Lookups.fixed(report));
this.report = report;
super.setName(this.report.getSourceModuleName());
super.setDisplayName(this.report.getSourceModuleName());
this.setIconBaseWithExtension(ICON_PATH);
}
@Override
public boolean isLeafTypeNode() {
return true;
}
@Override
public <T> T accept(DisplayableItemNodeVisitor<T> visitor) {
// - GetPopupActionsDisplayableItemNodeVisitor.visit() calls getActions().
// - GetPreferredActionsDisplayableItemNodeVisitor.visit() calls getPreferredAction().
// - IsLeafItemVisitor.visit() returns true.
// - ShowItemVisitor.visit() returns true.
return visitor.visit(this);
}
@Override
protected Sheet createSheet() {
Sheet sheet = super.createSheet();
Sheet.Set propertiesSet = sheet.get(Sheet.PROPERTIES);
if (propertiesSet == null) {
propertiesSet = Sheet.createPropertiesSet();
sheet.put(propertiesSet);
}
propertiesSet.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "ReportNode.sourceModuleNameProperty.name"),
NbBundle.getMessage(this.getClass(), "ReportNode.sourceModuleNameProperty.displayName"),
NbBundle.getMessage(this.getClass(), "ReportNode.sourceModuleNameProperty.desc"),
this.report.getSourceModuleName()));
propertiesSet.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "ReportNode.reportNameProperty.name"),
NbBundle.getMessage(this.getClass(), "ReportNode.reportNameProperty.displayName"),
NbBundle.getMessage(this.getClass(), "ReportNode.reportNameProperty.desc"),
this.report.getReportName()));
propertiesSet.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "ReportNode.createdTimeProperty.name"),
NbBundle.getMessage(this.getClass(), "ReportNode.createdTimeProperty.displayName"),
NbBundle.getMessage(this.getClass(), "ReportNode.createdTimeProperty.desc"),
dateFormatter.format(new java.util.Date(this.report.getCreatedTime() * 1000))));
propertiesSet.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "ReportNode.pathProperty.name"),
NbBundle.getMessage(this.getClass(), "ReportNode.pathProperty.displayName"),
NbBundle.getMessage(this.getClass(), "ReportNode.pathProperty.desc"),
this.report.getPath()));
return sheet;
}
@Override
public Action[] getActions(boolean popup) {
List<Action> actions = new ArrayList<>();
actions.addAll(Arrays.asList(super.getActions(true)));
actions.add(new OpenReportAction());
actions.add(DeleteReportAction.getInstance());
return actions.toArray(new Action[actions.size()]);
}
@Override
public AbstractAction getPreferredAction() {
return new OpenReportAction();
}
@Override
public String getItemType() {
return getClass().getName();
}
private static class DeleteReportAction extends AbstractAction {
private static final long serialVersionUID = 1L;
private static DeleteReportAction instance;
// This class is a singleton to support multi-selection of nodes,
// since org.openide.nodes.NodeOp.findActions(Node[] nodes) will
// only pick up an Action if every node in the array returns a
// reference to the same action object from Node.getActions(boolean).
private static DeleteReportAction getInstance() {
if (instance == null) {
instance = new DeleteReportAction();
}
if (Utilities.actionsGlobalContext().lookupAll(Report.class).size() == 1) {
instance.putValue(Action.NAME, NbBundle.getMessage(Reports.class, "DeleteReportAction.actionDisplayName.singleReport"));
} else {
instance.putValue(Action.NAME, NbBundle.getMessage(Reports.class, "DeleteReportAction.actionDisplayName.multipleReports"));
}
return instance;
}
/**
* Do not instantiate directly. Use
* DeleteReportAction.getInstance(), instead.
*/
private DeleteReportAction() {
}
@NbBundle.Messages({
"DeleteReportAction.showConfirmDialog.single.explanation=The report will remain on disk.",
"DeleteReportAction.showConfirmDialog.multiple.explanation=The reports will remain on disk.",
"DeleteReportAction.showConfirmDialog.errorMsg=An error occurred while deleting the reports."})
@Override
public void actionPerformed(ActionEvent e) {
Collection<? extends Report> selectedReportsCollection = Utilities.actionsGlobalContext().lookupAll(Report.class);
String message = selectedReportsCollection.size() > 1
? NbBundle.getMessage(Reports.class, "DeleteReportAction.actionPerformed.showConfirmDialog.multiple.msg", selectedReportsCollection.size())
: NbBundle.getMessage(Reports.class, "DeleteReportAction.actionPerformed.showConfirmDialog.single.msg");
String explanation = selectedReportsCollection.size() > 1
? Bundle.DeleteReportAction_showConfirmDialog_multiple_explanation()
: Bundle.DeleteReportAction_showConfirmDialog_single_explanation();
Object[] jOptionPaneContent = {message, explanation};
if (JOptionPane.YES_OPTION == JOptionPane.showConfirmDialog(null, jOptionPaneContent,
NbBundle.getMessage(Reports.class, "DeleteReportAction.actionPerformed.showConfirmDialog.title"),
JOptionPane.YES_NO_OPTION)) {
try {
Case.getCurrentCase().deleteReports(selectedReportsCollection);
} catch (TskCoreException | IllegalStateException ex) {
Logger.getLogger(DeleteReportAction.class.getName()).log(Level.SEVERE, "Error deleting reports", ex); // NON-NLS
MessageNotifyUtil.Message.error(Bundle.DeleteReportAction_showConfirmDialog_errorMsg());
}
}
}
}
private final class OpenReportAction extends AbstractAction {
private static final long serialVersionUID = 1L;
private OpenReportAction() {
super(NbBundle.getMessage(OpenReportAction.class, "OpenReportAction.actionDisplayName"));
}
@Override
public void actionPerformed(ActionEvent e) {
String reportPath = ReportNode.this.report.getPath();
String extension = "";
int extPosition = reportPath.lastIndexOf('.');
if (extPosition != -1) {
extension = reportPath.substring(extPosition, reportPath.length()).toLowerCase();
}
File file = new File(reportPath);
ExternalViewerAction.openFile("", extension, file);
}
}
}
}