Autopsy  4.6.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-2016 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 final class ShowInTimelineDialog extends Dialog<ViewInTimelineRequestedEvent> {
76 
77  private static final Logger LOGGER = Logger.getLogger(ShowInTimelineDialog.class.getName());
78 
79  @NbBundle.Messages({"ShowInTimelineDialog.showTimelineButtonType.text=Show Timeline"})
80  private static final ButtonType SHOW = new ButtonType(Bundle.ShowInTimelineDialog_showTimelineButtonType_text(), ButtonBar.ButtonData.OK_DONE);
81 
86  private static final List<ChronoField> SCROLL_BY_UNITS = Arrays.asList(
87  ChronoField.YEAR,
88  ChronoField.MONTH_OF_YEAR,
89  ChronoField.DAY_OF_MONTH,
90  ChronoField.HOUR_OF_DAY,
91  ChronoField.MINUTE_OF_HOUR,
92  ChronoField.SECOND_OF_MINUTE);
93 
94  @FXML
95  private TableView<SingleEvent> eventTable;
96 
97  @FXML
98  private TableColumn<SingleEvent, EventType> typeColumn;
99 
100  @FXML
101  private TableColumn<SingleEvent, Long> dateTimeColumn;
102 
103  @FXML
104  private Spinner<Integer> amountSpinner;
105 
106  @FXML
107  private ComboBox<ChronoField> unitComboBox;
108 
109  @FXML
110  private Label chooseEventLabel;
111 
112  private final VBox contentRoot = new VBox();
113 
114  private final TimeLineController controller;
115 
116  private final ValidationSupport validationSupport = new ValidationSupport();
117 
125  @NbBundle.Messages({
126  "ShowInTimelineDialog.amountValidator.message=The entered amount must only contain digits."
127  })
128  private ShowInTimelineDialog(TimeLineController controller, List<Long> eventIDS) {
129  this.controller = controller;
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().getStartMillis()));
194  dateTimeColumn.setCellFactory(param -> new DateTimeTableCell<>());
195 
196  //add events to table
197  eventTable.getItems().setAll(eventIDS.stream().map(controller.getEventsModel()::getEventById).collect(Collectors.toSet()));
198  eventTable.setPrefHeight(Math.min(200, 24 * eventTable.getItems().size() + 28));
199  }
200 
208  @NbBundle.Messages({"ShowInTimelineDialog.artifactTitle=View Result in Timeline."})
209  ShowInTimelineDialog(TimeLineController controller, BlackboardArtifact artifact) {
210  //get events IDs from artifact
211  this(controller, controller.getEventsModel().getEventIDsForArtifact(artifact));
212 
213  //hide instructional label and autoselect first(and only) event.
214  chooseEventLabel.setVisible(false);
215  chooseEventLabel.setManaged(false);
216  eventTable.getSelectionModel().select(0);
217 
218  //require validation of ammount spinner to enable show button
219  getDialogPane().lookupButton(SHOW).disableProperty().bind(validationSupport.invalidProperty());
220 
221  //set result converter that does not require selection.
222  setResultConverter(buttonType -> (buttonType == SHOW)
223  ? makeEventInTimeRange(eventTable.getItems().get(0))
224  : null
225  );
226  setTitle(Bundle.ShowInTimelineDialog_artifactTitle());
227  }
228 
236  @NbBundle.Messages({"# {0} - file path",
237  "ShowInTimelineDialog.fileTitle=View {0} in timeline.",
238  "ShowInTimelineDialog.eventSelectionValidator.message=You must select an event."})
239  ShowInTimelineDialog(TimeLineController controller, AbstractFile file) {
240  this(controller, controller.getEventsModel().getEventIDsForFile(file, false));
241 
242  /*
243  * since ValidationSupport does not support list selection, we will
244  * manually apply and remove decoration in response to selection
245  * property changes.
246  */
247  eventTable.getSelectionModel().selectedItemProperty().isNull().addListener((selectedItemNullProperty, wasNull, isNull) -> {
248  if (isNull) {
249  validationSupport.getValidationDecorator().applyValidationDecoration(
250  ValidationMessage.error(eventTable, Bundle.ShowInTimelineDialog_eventSelectionValidator_message()));
251  } else {
252  validationSupport.getValidationDecorator().removeDecorations(eventTable);
253  }
254  });
255 
256  //require selection and validation of ammount spinner to enable show button
257  getDialogPane().lookupButton(SHOW).disableProperty().bind(Bindings.or(
258  validationSupport.invalidProperty(),
259  eventTable.getSelectionModel().selectedItemProperty().isNull()
260  ));
261 
262  //set result converter that uses selection.
263  setResultConverter(buttonType -> (buttonType == SHOW)
264  ? makeEventInTimeRange(eventTable.getSelectionModel().getSelectedItem())
265  : null
266  );
267 
268  setTitle(Bundle.ShowInTimelineDialog_fileTitle(StringUtils.abbreviateMiddle(getContentPathSafe(file), " ... ", 50)));
269  }
270 
282  static String getContentPathSafe(Content content) {
283  try {
284  return content.getUniquePath();
285  } catch (TskCoreException tskCoreException) {
286  String contentName = content.getName();
287  LOGGER.log(Level.SEVERE, "Failed to get unique path for " + contentName, tskCoreException); //NON-NLS
288  return contentName;
289  }
290  }
291 
299  private ViewInTimelineRequestedEvent makeEventInTimeRange(SingleEvent selectedEvent) {
300  Duration selectedDuration = unitComboBox.getSelectionModel().getSelectedItem().getBaseUnit().getDuration().multipliedBy(amountSpinner.getValue());
301  Interval range = IntervalUtils.getIntervalAround(Instant.ofEpochMilli(selectedEvent.getStartMillis()), selectedDuration);
302  return new ViewInTimelineRequestedEvent(Collections.singleton(selectedEvent.getEventID()), range);
303  }
304 
308  static private class ChronoUnitListCell extends ListCell<ChronoUnit> {
309 
310  @Override
311  protected void updateItem(ChronoUnit item, boolean empty) {
312  super.updateItem(item, empty);
313 
314  if (empty || item == null) {
315  setText(null);
316  } else {
317  setText(WordUtils.capitalizeFully(item.toString()));
318  }
319  }
320  }
321 
328  static private class DateTimeTableCell<X> extends TableCell<X, Long> {
329 
330  @Override
331  protected void updateItem(Long item, boolean empty) {
332  super.updateItem(item, empty);
333 
334  if (item == null || empty) {
335  setText(null);
336  } else {
337  setText(TimeLineController.getZonedFormatter().print(item));
338  }
339  }
340  }
341 
347  static private class TypeTableCell<X> extends TableCell<X, EventType> {
348 
349  @Override
350  protected void updateItem(EventType item, boolean empty) {
351  super.updateItem(item, empty);
352 
353  if (item == null || empty) {
354  setText(null);
355  setGraphic(null);
356  } else {
357  setText(item.getDisplayName());
358  setGraphic(new ImageView(item.getFXImage()));
359  }
360  }
361  }
362 }

Copyright © 2012-2016 Basis Technology. Generated on: Mon May 7 2018
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.