Autopsy 4.22.1
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 */
19package org.sleuthkit.autopsy.timeline;
20
21import java.io.IOException;
22import java.net.URL;
23import java.time.Duration;
24import java.time.Instant;
25import java.time.temporal.ChronoField;
26import java.time.temporal.ChronoUnit;
27import java.util.Arrays;
28import java.util.Collection;
29import java.util.Collections;
30import java.util.HashSet;
31import java.util.List;
32import java.util.Set;
33import java.util.logging.Level;
34import javafx.beans.binding.Bindings;
35import javafx.beans.property.SimpleObjectProperty;
36import javafx.fxml.FXML;
37import javafx.fxml.FXMLLoader;
38import javafx.scene.control.ButtonBar;
39import javafx.scene.control.ButtonType;
40import javafx.scene.control.ComboBox;
41import javafx.scene.control.Dialog;
42import javafx.scene.control.DialogPane;
43import javafx.scene.control.Label;
44import javafx.scene.control.ListCell;
45import javafx.scene.control.Spinner;
46import javafx.scene.control.SpinnerValueFactory;
47import javafx.scene.control.TableCell;
48import javafx.scene.control.TableColumn;
49import javafx.scene.control.TableView;
50import javafx.scene.image.ImageView;
51import javafx.scene.layout.VBox;
52import javafx.stage.Modality;
53import javafx.util.converter.IntegerStringConverter;
54import org.apache.commons.lang3.StringUtils;
55import org.apache.commons.lang3.math.NumberUtils;
56import org.apache.commons.text.WordUtils;
57import org.controlsfx.validation.ValidationMessage;
58import org.controlsfx.validation.ValidationSupport;
59import org.controlsfx.validation.Validator;
60import org.joda.time.Interval;
61import org.openide.util.NbBundle;
62import org.sleuthkit.autopsy.coreutils.Logger;
63import org.sleuthkit.autopsy.timeline.events.ViewInTimelineRequestedEvent;
64import org.sleuthkit.autopsy.timeline.ui.EventTypeUtils;
65import org.sleuthkit.autopsy.timeline.utils.IntervalUtils;
66import org.sleuthkit.datamodel.AbstractFile;
67import org.sleuthkit.datamodel.BlackboardArtifact;
68import org.sleuthkit.datamodel.Content;
69import org.sleuthkit.datamodel.TskCoreException;
70import org.sleuthkit.datamodel.TimelineEventType;
71import org.sleuthkit.datamodel.TimelineEvent;
72
78@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives
79final 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:/" + ShowInTimelineDialog.class.getPackage().getName().replace(".", "/") + "/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
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}
synchronized static Logger getLogger(String name)
Definition Logger.java:124
static String getImagePath(TimelineEventType type)
static Interval getIntervalAround(DateTime aroundInstant, ReadablePeriod period)

Copyright © 2012-2024 Sleuth Kit Labs. Generated on:
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.