Autopsy  4.11.0
Graphical digital forensics platform for The Sleuth Kit and other tools.
ShowInTimelineDialog.java
Go to the documentation of this file.
1 /*
2  * Autopsy Forensic Browser
3  *
4  * Copyright 2011-2018 Basis Technology Corp.
5  * Contact: carrier <at> sleuthkit <dot> org
6  *
7  * Licensed under the Apache License, Version 2.0 (the "License");
8  * you may not use this file except in compliance with the License.
9  * You may obtain a copy of the License at
10  *
11  * http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing, software
14  * distributed under the License is distributed on an "AS IS" BASIS,
15  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16  * See the License for the specific language governing permissions and
17  * limitations under the License.
18  */
19 package org.sleuthkit.autopsy.timeline;
20 
21 import java.io.IOException;
22 import java.net.URL;
23 import java.time.Duration;
24 import java.time.Instant;
25 import java.time.temporal.ChronoField;
26 import java.time.temporal.ChronoUnit;
27 import java.util.Arrays;
28 import java.util.Collections;
29 import java.util.List;
30 import java.util.logging.Level;
31 import java.util.stream.Collectors;
32 import javafx.beans.binding.Bindings;
33 import javafx.beans.property.SimpleObjectProperty;
34 import javafx.fxml.FXML;
35 import javafx.fxml.FXMLLoader;
36 import javafx.scene.control.ButtonBar;
37 import javafx.scene.control.ButtonType;
38 import javafx.scene.control.ComboBox;
39 import javafx.scene.control.Dialog;
40 import javafx.scene.control.DialogPane;
41 import javafx.scene.control.Label;
42 import javafx.scene.control.ListCell;
43 import javafx.scene.control.Spinner;
44 import javafx.scene.control.SpinnerValueFactory;
45 import javafx.scene.control.TableCell;
46 import javafx.scene.control.TableColumn;
47 import javafx.scene.control.TableView;
48 import javafx.scene.image.ImageView;
49 import javafx.scene.layout.VBox;
50 import javafx.stage.Modality;
51 import javafx.util.converter.IntegerStringConverter;
52 import org.apache.commons.lang3.StringUtils;
53 import org.apache.commons.lang3.math.NumberUtils;
54 import org.apache.commons.lang3.text.WordUtils;
55 import org.controlsfx.validation.ValidationMessage;
56 import org.controlsfx.validation.ValidationSupport;
57 import org.controlsfx.validation.Validator;
58 import org.joda.time.Interval;
59 import org.openide.util.NbBundle;
65 import org.sleuthkit.datamodel.AbstractFile;
66 import org.sleuthkit.datamodel.BlackboardArtifact;
67 import org.sleuthkit.datamodel.Content;
68 import org.sleuthkit.datamodel.TskCoreException;
69 
75 @SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives
76 final class ShowInTimelineDialog extends Dialog<ViewInTimelineRequestedEvent> {
77 
78  private static final Logger LOGGER = Logger.getLogger(ShowInTimelineDialog.class.getName());
79 
80  @NbBundle.Messages({"ShowInTimelineDialog.showTimelineButtonType.text=Show Timeline"})
81  private static final ButtonType SHOW = new ButtonType(Bundle.ShowInTimelineDialog_showTimelineButtonType_text(), ButtonBar.ButtonData.OK_DONE);
82 
87  private static final List<ChronoField> SCROLL_BY_UNITS = Arrays.asList(
88  ChronoField.YEAR,
89  ChronoField.MONTH_OF_YEAR,
90  ChronoField.DAY_OF_MONTH,
91  ChronoField.HOUR_OF_DAY,
92  ChronoField.MINUTE_OF_HOUR,
93  ChronoField.SECOND_OF_MINUTE);
94 
95  @FXML
96  private TableView<SingleEvent> eventTable;
97 
98  @FXML
99  private TableColumn<SingleEvent, EventType> typeColumn;
100 
101  @FXML
102  private TableColumn<SingleEvent, Long> dateTimeColumn;
103 
104  @FXML
105  private Spinner<Integer> amountSpinner;
106 
107  @FXML
108  private ComboBox<ChronoField> unitComboBox;
109 
110  @FXML
111  private Label chooseEventLabel;
112 
113  private final VBox contentRoot = new VBox();
114 
115  private final TimeLineController controller;
116 
117  private final ValidationSupport validationSupport = new ValidationSupport();
118 
126  @NbBundle.Messages({
127  "ShowInTimelineDialog.amountValidator.message=The entered amount must only contain digits."
128  })
129  private ShowInTimelineDialog(TimeLineController controller, List<Long> eventIDS) {
130  this.controller = controller;
131 
132  //load dialog content fxml
133  final String name = "nbres:/" + StringUtils.replace(ShowInTimelineDialog.class.getPackage().getName(), ".", "/") + "/ShowInTimelineDialog.fxml"; // NON-NLS
134  try {
135  FXMLLoader fxmlLoader = new FXMLLoader(new URL(name));
136  fxmlLoader.setRoot(contentRoot);
137  fxmlLoader.setController(this);
138 
139  fxmlLoader.load();
140  } catch (IOException ex) {
141  LOGGER.log(Level.SEVERE, "Unable to load FXML, node initialization may not be complete.", ex); //NON-NLS
142  }
143  //assert that fxml loading happened correctly
144  assert eventTable != null : "fx:id=\"eventTable\" was not injected: check your FXML file 'ShowInTimelineDialog.fxml'.";
145  assert typeColumn != null : "fx:id=\"typeColumn\" was not injected: check your FXML file 'ShowInTimelineDialog.fxml'.";
146  assert dateTimeColumn != null : "fx:id=\"dateTimeColumn\" was not injected: check your FXML file 'ShowInTimelineDialog.fxml'.";
147  assert amountSpinner != null : "fx:id=\"amountsSpinner\" was not injected: check your FXML file 'ShowInTimelineDialog.fxml'.";
148  assert unitComboBox != null : "fx:id=\"unitChoiceBox\" was not injected: check your FXML file 'ShowInTimelineDialog.fxml'.";
149 
150  //validat that spinner has a integer in the text field.
151  validationSupport.registerValidator(amountSpinner.getEditor(), false,
152  Validator.createPredicateValidator(NumberUtils::isDigits, Bundle.ShowInTimelineDialog_amountValidator_message()));
153 
154  //configure dialog properties
155  PromptDialogManager.setDialogIcons(this);
156  initModality(Modality.APPLICATION_MODAL);
157 
158  //add scenegraph loaded from fxml to this dialog.
159  DialogPane dialogPane = getDialogPane();
160  dialogPane.setContent(contentRoot);
161  //add buttons to dialog
162  dialogPane.getButtonTypes().setAll(SHOW, ButtonType.CANCEL);
163 
165  amountSpinner.setValueFactory(new SpinnerValueFactory.IntegerSpinnerValueFactory(1, 1000));
166  amountSpinner.getValueFactory().setConverter(new IntegerStringConverter() {
176  @Override
177  public Integer fromString(String string) {
178  try {
179  return super.fromString(string);
180  } catch (NumberFormatException ex) {
181  return amountSpinner.getValue();
182  }
183  }
184  });
185 
186  unitComboBox.setButtonCell(new ChronoFieldListCell());
187  unitComboBox.setCellFactory(comboBox -> new ChronoFieldListCell());
188  unitComboBox.getItems().setAll(SCROLL_BY_UNITS);
189  unitComboBox.getSelectionModel().select(ChronoField.MINUTE_OF_HOUR);
190 
191  typeColumn.setCellValueFactory(param -> new SimpleObjectProperty<>(param.getValue().getEventType()));
192  typeColumn.setCellFactory(param -> new TypeTableCell<>());
193 
194  dateTimeColumn.setCellValueFactory(param -> new SimpleObjectProperty<>(param.getValue().getStartMillis()));
195  dateTimeColumn.setCellFactory(param -> new DateTimeTableCell<>());
196 
197  //add events to table
198  eventTable.getItems().setAll(eventIDS.stream().map(controller.getEventsModel()::getEventById).collect(Collectors.toSet()));
199  eventTable.setPrefHeight(Math.min(200, 24 * eventTable.getItems().size() + 28));
200  }
201 
209  @NbBundle.Messages({"ShowInTimelineDialog.artifactTitle=View Result in Timeline."})
210  ShowInTimelineDialog(TimeLineController controller, BlackboardArtifact artifact) {
211  //get events IDs from artifact
212  this(controller, controller.getEventsModel().getEventIDsForArtifact(artifact));
213 
214  //hide instructional label and autoselect first(and only) event.
215  chooseEventLabel.setVisible(false);
216  chooseEventLabel.setManaged(false);
217  eventTable.getSelectionModel().select(0);
218 
219  //require validation of ammount spinner to enable show button
220  getDialogPane().lookupButton(SHOW).disableProperty().bind(validationSupport.invalidProperty());
221 
222  //set result converter that does not require selection.
223  setResultConverter(buttonType -> (buttonType == SHOW)
224  ? makeEventInTimeRange(eventTable.getItems().get(0))
225  : null
226  );
227  setTitle(Bundle.ShowInTimelineDialog_artifactTitle());
228  }
229 
237  @NbBundle.Messages({"# {0} - file path",
238  "ShowInTimelineDialog.fileTitle=View {0} in timeline.",
239  "ShowInTimelineDialog.eventSelectionValidator.message=You must select an event."})
240  ShowInTimelineDialog(TimeLineController controller, AbstractFile file) {
241  this(controller, controller.getEventsModel().getEventIDsForFile(file, false));
242 
243  /*
244  * since ValidationSupport does not support list selection, we will
245  * manually apply and remove decoration in response to selection
246  * property changes.
247  */
248  eventTable.getSelectionModel().selectedItemProperty().isNull().addListener((selectedItemNullProperty, wasNull, isNull) -> {
249  if (isNull) {
250  validationSupport.getValidationDecorator().applyValidationDecoration(
251  ValidationMessage.error(eventTable, Bundle.ShowInTimelineDialog_eventSelectionValidator_message()));
252  } else {
253  validationSupport.getValidationDecorator().removeDecorations(eventTable);
254  }
255  });
256 
257  //require selection and validation of ammount spinner to enable show button
258  getDialogPane().lookupButton(SHOW).disableProperty().bind(Bindings.or(
259  validationSupport.invalidProperty(),
260  eventTable.getSelectionModel().selectedItemProperty().isNull()
261  ));
262 
263  //set result converter that uses selection.
264  setResultConverter(buttonType -> (buttonType == SHOW)
265  ? makeEventInTimeRange(eventTable.getSelectionModel().getSelectedItem())
266  : null
267  );
268 
269  setTitle(Bundle.ShowInTimelineDialog_fileTitle(StringUtils.abbreviateMiddle(getContentPathSafe(file), " ... ", 50)));
270  }
271 
283  static String getContentPathSafe(Content content) {
284  try {
285  return content.getUniquePath();
286  } catch (TskCoreException tskCoreException) {
287  String contentName = content.getName();
288  LOGGER.log(Level.SEVERE, "Failed to get unique path for " + contentName, tskCoreException); //NON-NLS
289  return contentName;
290  }
291  }
292 
300  private ViewInTimelineRequestedEvent makeEventInTimeRange(SingleEvent selectedEvent) {
301  Duration selectedDuration = unitComboBox.getSelectionModel().getSelectedItem().getBaseUnit().getDuration().multipliedBy(amountSpinner.getValue());
302  Interval range = IntervalUtils.getIntervalAround(Instant.ofEpochMilli(selectedEvent.getStartMillis()), selectedDuration);
303  return new ViewInTimelineRequestedEvent(Collections.singleton(selectedEvent.getEventID()), range);
304  }
305 
309  static private class ChronoUnitListCell extends ListCell<ChronoUnit> {
310 
311  @Override
312  protected void updateItem(ChronoUnit item, boolean empty) {
313  super.updateItem(item, empty);
314 
315  if (empty || item == null) {
316  setText(null);
317  } else {
318  setText(WordUtils.capitalizeFully(item.toString()));
319  }
320  }
321  }
322 
329  static private class DateTimeTableCell<X> extends TableCell<X, Long> {
330 
331  @Override
332  protected void updateItem(Long item, boolean empty) {
333  super.updateItem(item, empty);
334 
335  if (item == null || empty) {
336  setText(null);
337  } else {
338  setText(TimeLineController.getZonedFormatter().print(item));
339  }
340  }
341  }
342 
348  static private class TypeTableCell<X> extends TableCell<X, EventType> {
349 
350  @Override
351  protected void updateItem(EventType item, boolean empty) {
352  super.updateItem(item, empty);
353 
354  if (item == null || empty) {
355  setText(null);
356  setGraphic(null);
357  } else {
358  setText(item.getDisplayName());
359  setGraphic(new ImageView(item.getFXImage()));
360  }
361  }
362  }
363 }

Copyright © 2012-2018 Basis Technology. Generated on: Fri Jun 21 2019
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.