Autopsy  4.20.0
Graphical digital forensics platform for The Sleuth Kit and other tools.
EventClusterNode.java
Go to the documentation of this file.
1 /*
2  * Autopsy Forensic Browser
3  *
4  * Copyright 2013-18 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.ui.detailview;
20 
21 import com.google.common.collect.Iterables;
22 import java.util.ArrayList;
23 import java.util.Arrays;
24 import java.util.Collections;
25 import java.util.List;
26 import static java.util.Objects.nonNull;
27 import java.util.Set;
28 import java.util.concurrent.ExecutionException;
29 import java.util.logging.Level;
30 import java.util.stream.Collectors;
31 import javafx.collections.ObservableList;
32 import javafx.concurrent.Task;
33 import javafx.event.EventHandler;
34 import javafx.geometry.Pos;
35 import javafx.scene.Cursor;
36 import javafx.scene.control.Button;
37 import javafx.scene.image.Image;
38 import javafx.scene.image.ImageView;
39 import javafx.scene.input.MouseEvent;
40 import javafx.scene.layout.Border;
41 import javafx.scene.layout.BorderStroke;
42 import javafx.scene.layout.BorderStrokeStyle;
43 import javafx.scene.layout.BorderWidths;
44 import javafx.scene.layout.VBox;
45 import org.controlsfx.control.action.Action;
46 import org.controlsfx.control.action.ActionUtils;
47 import org.joda.time.DateTime;
48 import org.joda.time.Interval;
49 import org.openide.util.NbBundle;
53 import static org.sleuthkit.autopsy.timeline.ui.detailview.EventNodeBase.configureActionButton;
62 import org.sleuthkit.datamodel.TimelineLevelOfDetail;
63 import org.sleuthkit.datamodel.TskCoreException;
64 import org.sleuthkit.datamodel.TimelineEventType;
65 import org.sleuthkit.datamodel.TimelineEvent;
66 import org.sleuthkit.datamodel.TimelineFilter.EventTypeFilter;
67 
71 final class EventClusterNode extends MultiEventNodeBase<EventCluster, EventStripe, EventStripeNode> {
72 
73  private static final Logger LOGGER = Logger.getLogger(EventClusterNode.class.getName());
74 
78  private static final BorderWidths CLUSTER_BORDER_WIDTHS = new BorderWidths(2, 1, 2, 1);
79 
84  private final Border clusterBorder = new Border(new BorderStroke(evtColor.deriveColor(0, 1, 1, .4), BorderStrokeStyle.SOLID, CORNER_RADII_1, CLUSTER_BORDER_WIDTHS));
85 
89  private Button plusButton;
93  private Button minusButton;
94 
102  EventClusterNode(DetailsChartLane<?> chartLane, EventCluster eventCluster, EventStripeNode parentNode) {
103  super(chartLane, eventCluster, parentNode);
104 
105  subNodePane.setBorder(clusterBorder);
106  subNodePane.setBackground(defaultBackground);
107  subNodePane.setMinWidth(1);
108  subNodePane.setMaxWidth(USE_PREF_SIZE);
109  setMinHeight(24);
110  setAlignment(Pos.CENTER_LEFT);
111 
112  setCursor(Cursor.HAND);
113  getChildren().addAll(subNodePane, infoHBox);
114 
115  if (parentNode == null) {
116  setDescriptionVisibility(DescriptionVisibility.SHOWN);
117  }
118  }
119 
125  Button getNewExpandButton() {
126  return ActionUtils.createButton(new ExpandClusterAction(this), ActionUtils.ActionTextBehavior.HIDE);
127  }
128 
134  Button getNewCollapseButton() {
135  return ActionUtils.createButton(new CollapseClusterAction(this), ActionUtils.ActionTextBehavior.HIDE);
136  }
137 
138  @Override
139  void installActionButtons() {
140  super.installActionButtons();
141  if (plusButton == null) {
142  plusButton = getNewExpandButton();
143  minusButton = getNewCollapseButton();
144  controlsHBox.getChildren().addAll(minusButton, plusButton);
145 
146  configureActionButton(plusButton);
147  configureActionButton(minusButton);
148  }
149  }
150 
151  @Override
152  void showFullDescription(final int size) {
153  if (getParentNode().isPresent()) {
154  showCountOnly(size);
155  } else {
156  super.showFullDescription(size);
157  }
158  }
159 
166  @NbBundle.Messages(value = "EventClusterNode.loggedTask.name=Load sub events")
167  @ThreadConfined(type = ThreadConfined.ThreadType.JFX)
168  private synchronized void loadSubStripes(RelativeDetail relativeDetail) {
169  getChartLane().setCursor(Cursor.WAIT);
170 
171  /*
172  * Make new ZoomState to query with:
173  *
174  * We need to extend end time for the query by one second, because it is
175  * treated as an open interval but we want to include events at exactly
176  * the time of the last event in this cluster. Restrict the sub stripes
177  * to the type and description of this cluster by intersecting a new
178  * filter with the existing root filter.
179  */
180  RootFilterState subClusterFilter = eventsModel.getEventFilterState()
181  .intersect(new SqlFilterState<>(
182  new EventTypeFilter(getEventType()), true));
183  final Interval subClusterSpan = new Interval(getStartMillis(), getEndMillis() + 1000);
184  final TimelineEventType.HierarchyLevel eventTypeZoomLevel = eventsModel.getEventTypeZoom();
185  final EventsModelParams zoom = new EventsModelParams(subClusterSpan, eventTypeZoomLevel, subClusterFilter, getDescriptionLevel());
186 
187  DescriptionFilter descriptionFilter = new DescriptionFilter(getEvent().getDescriptionLevel(), getDescription());
188  /*
189  * task to load sub-stripes in a background thread
190  */
191  Task<List<EventStripe>> loggedTask;
192  loggedTask = new LoggedTask<List<EventStripe>>(Bundle.EventClusterNode_loggedTask_name(), false) {
193 
194  private volatile TimelineLevelOfDetail loadedDescriptionLevel = withRelativeDetail(getDescriptionLevel(), relativeDetail);
195 
196  @Override
197  protected List<EventStripe> call() throws Exception {
198  //newly loaded substripes
199  List<EventStripe> stripes;
200  //next LoD in diraction of given relativeDetail
201  TimelineLevelOfDetail next = loadedDescriptionLevel;
202  do {
203  loadedDescriptionLevel = next;
204  if (loadedDescriptionLevel == getEvent().getDescriptionLevel()) {
205  //if we are back at the level of detail of the original cluster, return empty list to inidicate.
206  return Collections.emptyList();
207  }
208 
209  //query for stripes at the desired level of detail
210  stripes = chartLane.getParentChart().getDetailsViewModel().getEventStripes(descriptionFilter, zoom.withDescrLOD(loadedDescriptionLevel));
211  //setup next for subsequent go through the "do" loop
212  next = withRelativeDetail(loadedDescriptionLevel, relativeDetail);
213  } while (stripes.size() == 1 && nonNull(next)); //keep going while there was only on stripe and we havne't reached the end of the LoD continuum.
214 
215  // return list of EventStripes with parents set to this cluster
216  return stripes.stream()
217  .map(eventStripe -> eventStripe.withParent(getEvent()))
218  .collect(Collectors.toList());
219  }
220 
221  @Override
222  protected void succeeded() {
223  ObservableList<DetailViewEvent> chartNestedEvents = getChartLane().getParentChart().getAllNestedEvents();
224 
225  //clear the existing subnodes/events
226  chartNestedEvents.removeAll(StripeFlattener.flatten(subNodes));
227  subNodes.clear();
228 
229  try {
230  setDescriptionLOD(loadedDescriptionLevel);
231  List<EventStripe> newSubStripes = get();
232  if (newSubStripes.isEmpty()) {
233  //restore original display
234  getChildren().setAll(subNodePane, infoHBox);
235  } else {
236  //display new sub stripes
237  List<EventNodeBase<?>> newSubNodes = new ArrayList<>();
238  for (EventStripe subStripe : newSubStripes) {//map stripes to nodes
239  newSubNodes.add(createChildNode(subStripe));
240  }
241  subNodes.addAll(newSubNodes);
242  chartNestedEvents.addAll(StripeFlattener.flatten(subNodes));
243  getChildren().setAll(new VBox(infoHBox, subNodePane));
244  }
245  } catch (TskCoreException | InterruptedException | ExecutionException ex) {
246  LOGGER.log(Level.SEVERE, "Error loading subnodes", ex); //NON-NLS
247 
248  }
249 
250  getChartLane().requestChartLayout();
251  getChartLane().setCursor(null);
252  }
253  };
254 
255  //start task
256  new Thread(loggedTask).start();
257  getChartLane().getController().monitorTask(loggedTask);
258  }
259 
260  @Override
261  EventNodeBase<?> createChildNode(EventStripe stripe) throws TskCoreException {
262  Set<Long> eventIDs = stripe.getEventIDs();
263  if (eventIDs.size() == 1) {
264  //If the stripe is a single event, make a single event node rather than a stripe node.
265  TimelineEvent singleEvent = getController().getEventsModel().getEventById(Iterables.getOnlyElement(eventIDs));
266  SingleDetailsViewEvent singleDetailsEvent = new SingleDetailsViewEvent(singleEvent).withParent(stripe);
267  return new SingleEventNode(getChartLane(), singleDetailsEvent, this);
268  } else {
269  return new EventStripeNode(getChartLane(), stripe, this);
270  }
271  }
272 
273  @Override
274  protected void layoutChildren() {
275  double chartX = getChartLane().getXAxis().getDisplayPosition(new DateTime(getStartMillis()));
276  double width = getChartLane().getXAxis().getDisplayPosition(new DateTime(getEndMillis())) - chartX;
277  subNodePane.setPrefWidth(Math.max(1, width));
278  super.layoutChildren();
279  }
280 
281  @Override
282  Iterable<? extends Action> getActions() {
283  return Iterables.concat(
284  super.getActions(),
285  Arrays.asList(new ExpandClusterAction(this), new CollapseClusterAction(this))
286  );
287  }
288 
289  @Override
290  EventHandler<MouseEvent> getDoubleClickHandler() {
291  return mouseEvent -> new ExpandClusterAction(this).handle(null);
292  }
293 
298  static private class ExpandClusterAction extends Action {
299 
300  private static final Image PLUS = new Image("/org/sleuthkit/autopsy/timeline/images/plus-button.png"); // NON-NLS //NOI18N
301 
302  @NbBundle.Messages({"ExpandClusterAction.text=Expand"})
303  ExpandClusterAction(EventClusterNode node) {
304  super(Bundle.ExpandClusterAction_text());
305  setGraphic(new ImageView(PLUS));
306 
307  setEventHandler(actionEvent -> {
308  if (node.getDescriptionLevel().moreDetailed() != null) {
309  node.loadSubStripes(RelativeDetail.MORE);
310  }
311  });
312 
313  //disabled if the given node is already at full description level of detail
314  disabledProperty().bind(node.descriptionLoDProperty().isEqualTo(TimelineLevelOfDetail.HIGH));
315  }
316  }
317 
322  static private class CollapseClusterAction extends Action {
323 
324  private static final Image MINUS = new Image("/org/sleuthkit/autopsy/timeline/images/minus-button.png"); // NON-NLS //NOI18N
325 
326  @NbBundle.Messages({"CollapseClusterAction.text=Collapse"})
327  CollapseClusterAction(EventClusterNode node) {
328  super(Bundle.CollapseClusterAction_text());
329  setGraphic(new ImageView(MINUS));
330 
331  setEventHandler(actionEvent -> {
332  if (node.getDescriptionLevel().lessDetailed() != null) {
333  node.loadSubStripes(RelativeDetail.LESS);
334  }
335  });
336 
337  //disabled if node is at clusters level of detail
338  disabledProperty().bind(node.descriptionLoDProperty().isEqualTo(node.getEvent().getDescriptionLevel()));
339  }
340  }
341 
342  private enum RelativeDetail {
343 
347  }
348 
349  private static TimelineLevelOfDetail withRelativeDetail(TimelineLevelOfDetail LoD, RelativeDetail relativeDetail) {
350  switch (relativeDetail) {
351  case EQUAL:
352  return LoD;
353  case MORE:
354  return LoD.moreDetailed();
355  case LESS:
356  return LoD.lessDetailed();
357  default:
358  throw new IllegalArgumentException("Unknown RelativeDetail value " + relativeDetail);
359  }
360  }
361 
362 }
synchronized RootFilterState getEventFilterState()
synchronized TimelineEventType.HierarchyLevel getEventTypeZoom()
RootFilterState intersect(FilterState< ?extends TimelineFilter > other)
TimelineEvent getEventById(Long eventID)

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