org.sleuthkit.autopsy.timeline.actions.SaveSnapshotAsReport.java Source code

Java tutorial

Introduction

Here is the source code for org.sleuthkit.autopsy.timeline.actions.SaveSnapshotAsReport.java

Source

/*
 * Autopsy Forensic Browser
 *
 * Copyright 2014-16 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.timeline.actions;

import java.awt.Desktop;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.function.Supplier;
import java.util.logging.Level;
import javafx.embed.swing.SwingFXUtils;
import javafx.scene.Node;
import javafx.scene.control.Alert;
import javafx.scene.control.ButtonBar;
import javafx.scene.control.ButtonType;
import javafx.scene.control.Control;
import javafx.scene.control.TextInputDialog;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javax.swing.JOptionPane;
import org.apache.commons.lang3.StringUtils;
import org.controlsfx.control.HyperlinkLabel;
import org.controlsfx.control.action.Action;
import org.controlsfx.validation.ValidationResult;
import org.controlsfx.validation.ValidationSupport;
import org.controlsfx.validation.Validator;
import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.coreutils.FileUtil;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.timeline.PromptDialogManager;
import org.sleuthkit.autopsy.timeline.TimeLineController;
import org.sleuthkit.autopsy.timeline.snapshot.SnapShotReportWriter;
import org.sleuthkit.datamodel.TskCoreException;

/**
 * Action that saves a snapshot of the given node as an autopsy report.
 * Delegates to SnapsHotReportWrite to actually generate and write the report.
 */
public class SaveSnapshotAsReport extends Action {

    private static final Logger LOGGER = Logger.getLogger(SaveSnapshotAsReport.class.getName());
    private static final Image SNAP_SHOT = new Image("org/sleuthkit/autopsy/timeline/images/image.png", 16, 16,
            true, true); //NON_NLS
    private static final ButtonType OPEN = new ButtonType(Bundle.OpenReportAction_DisplayName(),
            ButtonBar.ButtonData.NO);
    private static final ButtonType OK = new ButtonType(ButtonType.OK.getText(), ButtonBar.ButtonData.CANCEL_CLOSE);

    private final TimeLineController controller;
    private final Case currentCase;

    /**
     * Constructor
     *
     * @param controller   The controller for this timeline action
     * @param nodeSupplier The Supplier of the node to snapshot.
     */
    @NbBundle.Messages({ "Timeline.ModuleName=Timeline", "SaveSnapShotAsReport.action.dialogs.title=Timeline",
            "SaveSnapShotAsReport.action.name.text=Snapshot Report",
            "SaveSnapShotAsReport.action.longText=Save a screen capture of the current view of the timeline as a report.",
            "# {0} - report file path", "SaveSnapShotAsReport.ReportSavedAt=Report saved at [{0}]",
            "SaveSnapShotAsReport.Success=Success",
            "SaveSnapShotAsReport.FailedToAddReport=Failed to add snaphot to case as a report.",
            "# {0} - report path", "SaveSnapShotAsReport.ErrorWritingReport=Error writing report to disk at {0}.",
            "# {0} - generated default report name",
            "SaveSnapShotAsReport.reportName.prompt=leave empty for default report name: {0}.",
            "SaveSnapShotAsReport.reportName.header=Enter a report name for the Timeline Snapshot Report.",
            "SaveSnapShotAsReport.duplicateReportNameError.text=A report with that name already exists." })
    public SaveSnapshotAsReport(TimeLineController controller, Supplier<Node> nodeSupplier) {
        super(Bundle.SaveSnapShotAsReport_action_name_text());
        setLongText(Bundle.SaveSnapShotAsReport_action_longText());
        setGraphic(new ImageView(SNAP_SHOT));

        this.controller = controller;
        this.currentCase = controller.getAutopsyCase();

        setEventHandler(actionEvent -> {
            //capture generation date and use to make default report name
            Date generationDate = new Date();
            final String defaultReportName = FileUtil.escapeFileName(currentCase.getName() + " "
                    + new SimpleDateFormat("MM-dd-yyyy-HH-mm-ss").format(generationDate)); //NON_NLS
            BufferedImage snapshot = SwingFXUtils.fromFXImage(nodeSupplier.get().snapshot(null, null), null);

            //prompt user to pick report name
            TextInputDialog textInputDialog = new TextInputDialog();
            PromptDialogManager.setDialogIcons(textInputDialog);
            textInputDialog.setTitle(Bundle.SaveSnapShotAsReport_action_dialogs_title());
            textInputDialog.getEditor()
                    .setPromptText(Bundle.SaveSnapShotAsReport_reportName_prompt(defaultReportName));
            textInputDialog.setHeaderText(Bundle.SaveSnapShotAsReport_reportName_header());

            //keep prompt even if text field has focus, until user starts typing.
            textInputDialog.getEditor()
                    .setStyle("-fx-prompt-text-fill: derive(-fx-control-inner-background, -30%);");//NON_NLS 

            /*
             * Create a ValidationSupport to validate that a report with the
             * entered name doesn't exist on disk already. Disable ok button if
             * report name is not validated.
             */
            ValidationSupport validationSupport = new ValidationSupport();
            validationSupport.registerValidator(textInputDialog.getEditor(), false, new Validator<String>() {
                @Override
                public ValidationResult apply(Control textField, String enteredReportName) {
                    String reportName = StringUtils.defaultIfBlank(enteredReportName, defaultReportName);
                    boolean exists = Files.exists(Paths.get(currentCase.getReportDirectory(), reportName));
                    return ValidationResult.fromErrorIf(textField,
                            Bundle.SaveSnapShotAsReport_duplicateReportNameError_text(), exists);
                }
            });
            textInputDialog.getDialogPane().lookupButton(ButtonType.OK).disableProperty()
                    .bind(validationSupport.invalidProperty());

            //show dialog and handle result
            textInputDialog.showAndWait().ifPresent(enteredReportName -> {
                //reportName defaults to case name + timestamp if left blank
                String reportName = StringUtils.defaultIfBlank(enteredReportName, defaultReportName);
                Path reportFolderPath = Paths.get(currentCase.getReportDirectory(), reportName,
                        "Timeline Snapshot"); //NON_NLS
                Path reportMainFilePath;

                try {
                    //generate and write report
                    reportMainFilePath = new SnapShotReportWriter(currentCase, reportFolderPath, reportName,
                            controller.getEventsModel().getZoomParamaters(), generationDate, snapshot)
                                    .writeReport();
                } catch (IOException ex) {
                    LOGGER.log(Level.SEVERE, "Error writing report to disk at " + reportFolderPath, ex); //NON_NLS
                    new Alert(Alert.AlertType.ERROR,
                            Bundle.SaveSnapShotAsReport_ErrorWritingReport(reportFolderPath)).show();
                    return;
                }

                try {
                    //add main file as report to case
                    Case.getCurrentCase().addReport(reportMainFilePath.toString(), Bundle.Timeline_ModuleName(),
                            reportName);
                } catch (TskCoreException ex) {
                    LOGGER.log(Level.WARNING,
                            "Failed to add " + reportMainFilePath.toString() + " to case as a report", ex); //NON_NLS
                    new Alert(Alert.AlertType.ERROR, Bundle.SaveSnapShotAsReport_FailedToAddReport()).show();
                    return;
                }

                //notify user of report location
                final Alert alert = new Alert(Alert.AlertType.INFORMATION, null, OPEN, OK);
                alert.setTitle(Bundle.SaveSnapShotAsReport_action_dialogs_title());
                alert.setHeaderText(Bundle.SaveSnapShotAsReport_Success());

                //make action to open report, and hyperlinklable to invoke action
                final OpenReportAction openReportAction = new OpenReportAction(reportMainFilePath);
                HyperlinkLabel hyperlinkLabel = new HyperlinkLabel(
                        Bundle.SaveSnapShotAsReport_ReportSavedAt(reportMainFilePath.toString()));
                hyperlinkLabel.setOnAction(openReportAction);
                alert.getDialogPane().setContent(hyperlinkLabel);

                alert.showAndWait().filter(OPEN::equals).ifPresent(buttonType -> openReportAction.handle(null));
            });
        });
    }

    /**
     * Action that opens the given Path in the system default application.
     */
    @NbBundle.Messages({ "OpenReportAction.DisplayName=Open Report",
            "OpenReportAction.NoAssociatedEditorMessage=There is no associated editor for reports of this type or the associated application failed to launch.",
            "OpenReportAction.MessageBoxTitle=Open Report Failure",
            "OpenReportAction.NoOpenInEditorSupportMessage=This platform (operating system) does not support opening a file in an editor this way.",
            "OpenReportAction.MissingReportFileMessage=The report file no longer exists.",
            "OpenReportAction.ReportFileOpenPermissionDeniedMessage=Permission to open the report file was denied." })
    private class OpenReportAction extends Action {

        OpenReportAction(Path reportHTMLFIle) {
            super(Bundle.OpenReportAction_DisplayName());
            setEventHandler(actionEvent -> {
                try {
                    Desktop.getDesktop().open(reportHTMLFIle.toFile());
                } catch (IOException ex) {
                    JOptionPane.showMessageDialog(null, Bundle.OpenReportAction_NoAssociatedEditorMessage(),
                            Bundle.OpenReportAction_MessageBoxTitle(), JOptionPane.ERROR_MESSAGE);
                } catch (UnsupportedOperationException ex) {
                    JOptionPane.showMessageDialog(null, Bundle.OpenReportAction_NoOpenInEditorSupportMessage(),
                            Bundle.OpenReportAction_MessageBoxTitle(), JOptionPane.ERROR_MESSAGE);
                } catch (IllegalArgumentException ex) {
                    JOptionPane.showMessageDialog(null, Bundle.OpenReportAction_MissingReportFileMessage(),
                            Bundle.OpenReportAction_MessageBoxTitle(), JOptionPane.ERROR_MESSAGE);
                } catch (SecurityException ex) {
                    JOptionPane.showMessageDialog(null,
                            Bundle.OpenReportAction_ReportFileOpenPermissionDeniedMessage(),
                            Bundle.OpenReportAction_MessageBoxTitle(), JOptionPane.ERROR_MESSAGE);
                }
            });
        }
    }
}