Autopsy  4.19.3
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-2019 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.Collection;
29 import java.util.Collections;
30 import java.util.HashSet;
31 import java.util.List;
32 import java.util.Set;
33 import java.util.logging.Level;
34 import javafx.beans.binding.Bindings;
35 import javafx.beans.property.SimpleObjectProperty;
36 import javafx.fxml.FXML;
37 import javafx.fxml.FXMLLoader;
38 import javafx.scene.control.ButtonBar;
39 import javafx.scene.control.ButtonType;
40 import javafx.scene.control.ComboBox;
41 import javafx.scene.control.Dialog;
42 import javafx.scene.control.DialogPane;
43 import javafx.scene.control.Label;
44 import javafx.scene.control.ListCell;
45 import javafx.scene.control.Spinner;
46 import javafx.scene.control.SpinnerValueFactory;
47 import javafx.scene.control.TableCell;
48 import javafx.scene.control.TableColumn;
49 import javafx.scene.control.TableView;
50 import javafx.scene.image.ImageView;
51 import javafx.scene.layout.VBox;
52 import javafx.stage.Modality;
53 import javafx.util.converter.IntegerStringConverter;
54 import org.apache.commons.lang3.StringUtils;
55 import org.apache.commons.lang3.math.NumberUtils;
56 import org.apache.commons.text.WordUtils;
57 import org.controlsfx.validation.ValidationMessage;
58 import org.controlsfx.validation.ValidationSupport;
59 import org.controlsfx.validation.Validator;
60 import org.joda.time.Interval;
61 import org.openide.util.NbBundle;
66 import org.sleuthkit.datamodel.AbstractFile;
67 import org.sleuthkit.datamodel.BlackboardArtifact;
68 import org.sleuthkit.datamodel.Content;
69 import org.sleuthkit.datamodel.TskCoreException;
70 import org.sleuthkit.datamodel.TimelineEventType;
71 import org.sleuthkit.datamodel.TimelineEvent;
72 
78 @SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives
79 final class ShowInTimelineDialog extends Dialog<ViewInTimelineRequestedEvent> {
80 
81  private static final Logger LOGGER = Logger.getLogger(ShowInTimelineDialog.class.getName());
82 
83  @NbBundle.Messages({"ShowInTimelineDialog.showTimelineButtonType.text=Show Timeline"})
84  private static final ButtonType SHOW = new ButtonType(Bundle.ShowInTimelineDialog_showTimelineButtonType_text(), ButtonBar.ButtonData.OK_DONE);
85 
90  private static final List<ChronoField> SCROLL_BY_UNITS = Arrays.asList(
91  ChronoField.YEAR,
92  ChronoField.MONTH_OF_YEAR,
93  ChronoField.DAY_OF_MONTH,
94  ChronoField.HOUR_OF_DAY,
95  ChronoField.MINUTE_OF_HOUR,
96  ChronoField.SECOND_OF_MINUTE);
97 
98  @FXML
99  private TableView<TimelineEvent> eventTable;
100 
101  @FXML
102  private TableColumn<TimelineEvent, TimelineEventType> typeColumn;
103 
104  @FXML
105  private TableColumn<TimelineEvent, Long> dateTimeColumn;
106 
107  @FXML
108  private Spinner<Integer> amountSpinner;
109 
110  @FXML
111  private ComboBox<ChronoField> unitComboBox;
112 
113  @FXML
114  private Label chooseEventLabel;
115 
116  private final VBox contentRoot = new VBox();
117 
118  private final ValidationSupport validationSupport = new ValidationSupport();
119 
127  @NbBundle.Messages({
128  "ShowInTimelineDialog.amountValidator.message=The entered amount must only contain digits."})
129  private ShowInTimelineDialog(TimeLineController controller, Collection<Long> eventIDS) throws TskCoreException {
130 
131  //load dialog content fxml
132  final String name = "nbres:/" + StringUtils.replace(ShowInTimelineDialog.class.getPackage().getName(), ".", "/") + "/ShowInTimelineDialog.fxml"; // NON-NLS
133  try {
134  FXMLLoader fxmlLoader = new FXMLLoader(new URL(name));
135  fxmlLoader.setRoot(contentRoot);
136  fxmlLoader.setController(this);
137 
138  fxmlLoader.load();
139  } catch (IOException ex) {
140  LOGGER.log(Level.SEVERE, "Unable to load FXML, node initialization may not be complete.", ex); //NON-NLS
141  }
142  //assert that fxml loading happened correctly
143  assert eventTable != null : "fx:id=\"eventTable\" was not injected: check your FXML file 'ShowInTimelineDialog.fxml'.";
144  assert typeColumn != null : "fx:id=\"typeColumn\" was not injected: check your FXML file 'ShowInTimelineDialog.fxml'.";
145  assert dateTimeColumn != null : "fx:id=\"dateTimeColumn\" was not injected: check your FXML file 'ShowInTimelineDialog.fxml'.";
146  assert amountSpinner != null : "fx:id=\"amountsSpinner\" was not injected: check your FXML file 'ShowInTimelineDialog.fxml'.";
147  assert unitComboBox != null : "fx:id=\"unitChoiceBox\" was not injected: check your FXML file 'ShowInTimelineDialog.fxml'.";
148 
149  //validat that spinner has a integer in the text field.
150  validationSupport.registerValidator(amountSpinner.getEditor(), false,
151  Validator.createPredicateValidator(NumberUtils::isDigits, Bundle.ShowInTimelineDialog_amountValidator_message()));
152 
153  //configure dialog properties
154  PromptDialogManager.setDialogIcons(this);
155  initModality(Modality.APPLICATION_MODAL);
156 
157  //add scenegraph loaded from fxml to this dialog.
158  DialogPane dialogPane = getDialogPane();
159  dialogPane.setContent(contentRoot);
160  //add buttons to dialog
161  dialogPane.getButtonTypes().setAll(SHOW, ButtonType.CANCEL);
162 
164  amountSpinner.setValueFactory(new SpinnerValueFactory.IntegerSpinnerValueFactory(1, 1000));
165  amountSpinner.getValueFactory().setConverter(new IntegerStringConverter() {
175  @Override
176  public Integer fromString(String string) {
177  try {
178  return super.fromString(string);
179  } catch (NumberFormatException ex) {
180  return amountSpinner.getValue();
181  }
182  }
183  });
184 
185  unitComboBox.setButtonCell(new ChronoFieldListCell());
186  unitComboBox.setCellFactory(comboBox -> new ChronoFieldListCell());
187  unitComboBox.getItems().setAll(SCROLL_BY_UNITS);
188  unitComboBox.getSelectionModel().select(ChronoField.MINUTE_OF_HOUR);
189 
190  typeColumn.setCellValueFactory(param -> new SimpleObjectProperty<>(param.getValue().getEventType()));
191  typeColumn.setCellFactory(param -> new TypeTableCell<>());
192 
193  dateTimeColumn.setCellValueFactory(param -> new SimpleObjectProperty<>(param.getValue().getEventTimeInMs()));
194  dateTimeColumn.setCellFactory(param -> new DateTimeTableCell<>());
195 
196  //add events to table
197  Set<TimelineEvent> events = new HashSet<>();
198  EventsModel eventsModel = controller.getEventsModel();
199  for (Long eventID : eventIDS) {
200  try {
201  events.add(eventsModel.getEventById(eventID));
202  } catch (TskCoreException ex) {
203  throw new TskCoreException("Error getting event by id.", ex);
204  }
205  }
206  eventTable.getItems().setAll(events);
207  eventTable.setPrefHeight(Math.min(200, 24 * eventTable.getItems().size() + 28));
208  }
209 
217  @NbBundle.Messages({"ShowInTimelineDialog.artifactTitle=View Result in Timeline."})
218  ShowInTimelineDialog(TimeLineController controller, BlackboardArtifact artifact) throws TskCoreException {
219  //get events IDs from artifact
220  this(controller, controller.getEventsModel().getEventIDsForArtifact(artifact));
221 
222  //hide instructional label and autoselect first(and only) event.
223  chooseEventLabel.setVisible(false);
224  chooseEventLabel.setManaged(false);
225  eventTable.getSelectionModel().select(0);
226 
227  //require validation of ammount spinner to enable show button
228  getDialogPane().lookupButton(SHOW).disableProperty().bind(validationSupport.invalidProperty());
229 
230  //set result converter that does not require selection.
231  setResultConverter(buttonType -> (buttonType == SHOW)
232  ? makeEventInTimeRange(eventTable.getItems().get(0))
233  : null
234  );
235  setTitle(Bundle.ShowInTimelineDialog_artifactTitle());
236  }
237 
245  @NbBundle.Messages({"# {0} - file path",
246  "ShowInTimelineDialog.fileTitle=View {0} in timeline.",
247  "ShowInTimelineDialog.eventSelectionValidator.message=You must select an event."})
248  ShowInTimelineDialog(TimeLineController controller, AbstractFile file) throws TskCoreException {
249  this(controller, controller.getEventsModel().getEventIDsForFile(file, false));
250 
251  /*
252  * since ValidationSupport does not support list selection, we will
253  * manually apply and remove decoration in response to selection
254  * property changes.
255  */
256  eventTable.getSelectionModel().selectedItemProperty().isNull().addListener((selectedItemNullProperty, wasNull, isNull) -> {
257  if (isNull) {
258  validationSupport.getValidationDecorator().applyValidationDecoration(
259  ValidationMessage.error(eventTable, Bundle.ShowInTimelineDialog_eventSelectionValidator_message()));
260  } else {
261  validationSupport.getValidationDecorator().removeDecorations(eventTable);
262  }
263  });
264 
265  //require selection and validation of ammount spinner to enable show button
266  getDialogPane().lookupButton(SHOW).disableProperty().bind(Bindings.or(
267  validationSupport.invalidProperty(),
268  eventTable.getSelectionModel().selectedItemProperty().isNull()
269  ));
270 
271  //set result converter that uses selection.
272  setResultConverter(buttonType -> (buttonType == SHOW)
273  ? makeEventInTimeRange(eventTable.getSelectionModel().getSelectedItem())
274  : null
275  );
276 
277  setTitle(Bundle.ShowInTimelineDialog_fileTitle(StringUtils.abbreviateMiddle(getContentPathSafe(file), " ... ", 50)));
278  }
279 
291  static String getContentPathSafe(Content content) {
292  try {
293  return content.getUniquePath();
294  } catch (TskCoreException tskCoreException) {
295  String contentName = content.getName();
296  LOGGER.log(Level.SEVERE, "Failed to get unique path for " + contentName, tskCoreException); //NON-NLS
297  return contentName;
298  }
299  }
300 
308  private ViewInTimelineRequestedEvent makeEventInTimeRange(TimelineEvent selectedEvent) {
309  Duration selectedDuration = unitComboBox.getSelectionModel().getSelectedItem().getBaseUnit().getDuration().multipliedBy(amountSpinner.getValue());
310  Interval range = IntervalUtils.getIntervalAround(Instant.ofEpochMilli(selectedEvent.getEventTimeInMs()), selectedDuration);
311  return new ViewInTimelineRequestedEvent(Collections.singleton(selectedEvent.getEventID()), range);
312  }
313 
317  static private class ChronoUnitListCell extends ListCell<ChronoUnit> {
318 
319  @Override
320  protected void updateItem(ChronoUnit item, boolean empty) {
321  super.updateItem(item, empty);
322 
323  if (empty || item == null) {
324  setText(null);
325  } else {
326  setText(WordUtils.capitalizeFully(item.toString()));
327  }
328  }
329  }
330 
337  static private class DateTimeTableCell<X> extends TableCell<X, Long> {
338 
339  @Override
340  protected void updateItem(Long item, boolean empty) {
341  super.updateItem(item, empty);
342 
343  if (item == null || empty) {
344  setText(null);
345  } else {
346  setText(TimeLineController.getZonedFormatter().print(item));
347  }
348  }
349  }
350 
356  static private class TypeTableCell<X> extends TableCell<X, TimelineEventType> {
357 
358  @Override
359  protected void updateItem(TimelineEventType item, boolean empty) {
360  super.updateItem(item, empty);
361 
362  if (item == null || empty) {
363  setText(null);
364  setGraphic(null);
365  } else {
366  setText(item.getDisplayName());
367  setGraphic(new ImageView(EventTypeUtils.getImagePath(item)));
368  }
369  }
370  }
371 }
static String getImagePath(TimelineEventType type)

Copyright © 2012-2022 Basis Technology. Generated on: Tue Jun 27 2023
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.