Autopsy  4.1
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-16 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.ImmutableSet;
22 import com.google.common.collect.Iterables;
23 import com.google.common.collect.Lists;
24 import java.util.Arrays;
25 import java.util.Collections;
26 import java.util.List;
27 import static java.util.Objects.nonNull;
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;
60 import static org.sleuthkit.autopsy.timeline.ui.detailview.EventNodeBase.configureActionButton;
64 
68 final class EventClusterNode extends MultiEventNodeBase<EventCluster, EventStripe, EventStripeNode> {
69 
70  private static final Logger LOGGER = Logger.getLogger(EventClusterNode.class.getName());
71 
75  private static final BorderWidths CLUSTER_BORDER_WIDTHS = new BorderWidths(2, 1, 2, 1);
76 
81  private final Border clusterBorder = new Border(new BorderStroke(evtColor.deriveColor(0, 1, 1, .4), BorderStrokeStyle.SOLID, CORNER_RADII_1, CLUSTER_BORDER_WIDTHS));
82 
86  private Button plusButton;
90  private Button minusButton;
91 
99  EventClusterNode(DetailsChartLane<?> chartLane, EventCluster eventCluster, EventStripeNode parentNode) {
100  super(chartLane, eventCluster, parentNode);
101 
102  subNodePane.setBorder(clusterBorder);
103  subNodePane.setBackground(defaultBackground);
104  subNodePane.setMinWidth(1);
105  subNodePane.setMaxWidth(USE_PREF_SIZE);
106  setMinHeight(24);
107  setAlignment(Pos.CENTER_LEFT);
108 
109  setCursor(Cursor.HAND);
110  getChildren().addAll(subNodePane, infoHBox);
111 
112  if (parentNode == null) {
113  setDescriptionVisibility(DescriptionVisibility.SHOWN);
114  }
115  }
116 
122  Button getNewExpandButton() {
123  return ActionUtils.createButton(new ExpandClusterAction(this), ActionUtils.ActionTextBehavior.HIDE);
124  }
125 
131  Button getNewCollapseButton() {
132  return ActionUtils.createButton(new CollapseClusterAction(this), ActionUtils.ActionTextBehavior.HIDE);
133  }
134 
135  @Override
136  void installActionButtons() {
137  super.installActionButtons();
138  if (plusButton == null) {
139  plusButton = getNewExpandButton();
140  minusButton = getNewCollapseButton();
141  controlsHBox.getChildren().addAll(minusButton, plusButton);
142 
143  configureActionButton(plusButton);
144  configureActionButton(minusButton);
145  }
146  }
147 
148  @Override
149  void showFullDescription(final int size) {
150  if (getParentNode().isPresent()) {
151  showCountOnly(size);
152  } else {
153  super.showFullDescription(size);
154  }
155  }
156 
163  @NbBundle.Messages(value = "EventClusterNode.loggedTask.name=Load sub events")
164  @ThreadConfined(type = ThreadConfined.ThreadType.JFX)
165  private synchronized void loadSubStripes(DescriptionLoD.RelativeDetail relativeDetail) {
166  getChartLane().setCursor(Cursor.WAIT);
167 
168  /*
169  * make new ZoomParams to query with
170  *
171  * We need to extend end time for the query by one second, because it is
172  * treated as an open interval but we want to include events at exactly
173  * the time of the last event in this cluster. Restrict the sub stripes
174  * to the type and description of this cluster by intersecting a new
175  * filter with the existing root filter.
176  */
177  final RootFilter subClusterFilter = eventsModel.filterProperty().get().copyOf();
178  subClusterFilter.getSubFilters().addAll(
179  new DescriptionFilter(getEvent().getDescriptionLoD(), getDescription(), DescriptionFilter.FilterMode.INCLUDE),
180  new TypeFilter(getEventType()));
181  final Interval subClusterSpan = new Interval(getStartMillis(), getEndMillis() + 1000);
182  final EventTypeZoomLevel eventTypeZoomLevel = eventsModel.eventTypeZoomProperty().get();
183  final ZoomParams zoomParams = new ZoomParams(subClusterSpan, eventTypeZoomLevel, subClusterFilter, getDescriptionLoD());
184 
185  /*
186  * task to load sub-stripes in a background thread
187  */
188  Task<List<EventStripe>> loggedTask;
189  loggedTask = new LoggedTask<List<EventStripe>>(Bundle.EventClusterNode_loggedTask_name(), false) {
190 
191  private volatile DescriptionLoD loadedDescriptionLoD = getDescriptionLoD().withRelativeDetail(relativeDetail);
192 
193  @Override
194  protected List<EventStripe> call() throws Exception {
195  //newly loaded substripes
196  List<EventStripe> stripes;
197  //next LoD in diraction of given relativeDetail
198  DescriptionLoD next = loadedDescriptionLoD;
199  do {
200 
201  loadedDescriptionLoD = next;
202  if (loadedDescriptionLoD == getEvent().getDescriptionLoD()) {
203  //if we are back at the level of detail of the original cluster, return empty list to inidicate.
204  return Collections.emptyList();
205  }
206 
207  //query for stripes at the desired level of detail
208  stripes = eventsModel.getEventStripes(zoomParams.withDescrLOD(loadedDescriptionLoD));
209  //setup next for subsequent go through the "do" loop
210  next = loadedDescriptionLoD.withRelativeDetail(relativeDetail);
211  } 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.
212 
213  // return list of EventStripes with parents set to this cluster
214  return stripes.stream()
215  .map(eventStripe -> eventStripe.withParent(getEvent()))
216  .collect(Collectors.toList());
217  }
218 
219  @Override
220  protected void succeeded() {
221  ObservableList<TimeLineEvent> chartNestedEvents = getChartLane().getParentChart().getAllNestedEvents();
222 
223  //clear the existing subnodes/events
224  chartNestedEvents.removeAll(StripeFlattener.flatten(subNodes));
225  subNodes.clear();
226 
227  try {
228  setDescriptionLOD(loadedDescriptionLoD);
229  List<EventStripe> newSubStripes = get();
230  if (newSubStripes.isEmpty()) {
231  //restore original display
232  getChildren().setAll(subNodePane, infoHBox);
233  } else {
234  //display new sub stripes
235  subNodes.addAll(Lists.transform(newSubStripes, EventClusterNode.this::createChildNode)); //map stripes to nodes
236  chartNestedEvents.addAll(StripeFlattener.flatten(subNodes));
237  getChildren().setAll(new VBox(infoHBox, subNodePane));
238  }
239  } catch (InterruptedException | ExecutionException ex) {
240  LOGGER.log(Level.SEVERE, "Error loading subnodes", ex); //NON-NLS
241  }
242 
243  getChartLane().requestChartLayout();
244  getChartLane().setCursor(null);
245  }
246  };
247 
248  //start task
249  new Thread(loggedTask).start();
250  getChartLane().getController().monitorTask(loggedTask);
251  }
252 
253  @Override
254  EventNodeBase<?> createChildNode(EventStripe stripe) {
255  ImmutableSet<Long> eventIDs = stripe.getEventIDs();
256  if (eventIDs.size() == 1) {
257  //If the stripe is a single event, make a single event node rather than a stripe node.
258  SingleEvent singleEvent = getController().getEventsModel().getEventById(Iterables.getOnlyElement(eventIDs)).withParent(stripe);
259  return new SingleEventNode(getChartLane(), singleEvent, this);
260  } else {
261  return new EventStripeNode(getChartLane(), stripe, this);
262  }
263  }
264 
265  @Override
266  protected void layoutChildren() {
267  double chartX = getChartLane().getXAxis().getDisplayPosition(new DateTime(getStartMillis()));
268  double w = getChartLane().getXAxis().getDisplayPosition(new DateTime(getEndMillis())) - chartX;
269  subNodePane.setPrefWidth(Math.max(1, w));
270  super.layoutChildren();
271  }
272 
273  @Override
274  Iterable<? extends Action> getActions() {
275  return Iterables.concat(
276  super.getActions(),
277  Arrays.asList(new ExpandClusterAction(this), new CollapseClusterAction(this))
278  );
279  }
280 
281  @Override
282  EventHandler<MouseEvent> getDoubleClickHandler() {
283  return mouseEvent -> new ExpandClusterAction(this).handle(null);
284  }
285 
290  static private class ExpandClusterAction extends Action {
291 
292  private static final Image PLUS = new Image("/org/sleuthkit/autopsy/timeline/images/plus-button.png"); // NON-NLS //NOI18N
293 
294  @NbBundle.Messages({"ExpandClusterAction.text=Expand"})
295  ExpandClusterAction(EventClusterNode node) {
296  super(Bundle.ExpandClusterAction_text());
297  setGraphic(new ImageView(PLUS));
298 
299  setEventHandler(actionEvent -> {
300  if (node.getDescriptionLoD().moreDetailed() != null) {
301  node.loadSubStripes(DescriptionLoD.RelativeDetail.MORE);
302  }
303  });
304 
305  //disabled if the given node is already at full description level of detail
306  disabledProperty().bind(node.descriptionLoDProperty().isEqualTo(DescriptionLoD.FULL));
307  }
308  }
309 
314  static private class CollapseClusterAction extends Action {
315 
316  private static final Image MINUS = new Image("/org/sleuthkit/autopsy/timeline/images/minus-button.png"); // NON-NLS //NOI18N
317 
318  @NbBundle.Messages({"CollapseClusterAction.text=Collapse"})
319  CollapseClusterAction(EventClusterNode node) {
320  super(Bundle.CollapseClusterAction_text());
321  setGraphic(new ImageView(MINUS));
322 
323  setEventHandler(actionEvent -> {
324  if (node.getDescriptionLoD().lessDetailed() != null) {
325  node.loadSubStripes(DescriptionLoD.RelativeDetail.LESS);
326  }
327  });
328 
329  //disabled if node is at clusters level of detail
330  disabledProperty().bind(node.descriptionLoDProperty().isEqualTo(node.getEvent().getDescriptionLoD()));
331  }
332  }
333 }
DescriptionLoD withRelativeDetail(RelativeDetail relativeDetail)

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