Autopsy  4.17.0
Graphical digital forensics platform for The Sleuth Kit and other tools.
BaseDataSourceSummaryPanel.java
Go to the documentation of this file.
1 /*
2  * Autopsy Forensic Browser
3  *
4  * Copyright 2020 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.datasourcesummary.ui;
20 
21 import java.beans.PropertyChangeEvent;
22 import java.nio.file.Path;
23 import java.nio.file.Paths;
24 import java.util.Arrays;
25 import java.util.Collections;
26 import java.util.List;
27 import java.util.Set;
28 import java.util.logging.Level;
29 import java.util.regex.Matcher;
30 import java.util.regex.Pattern;
31 import java.util.stream.Collectors;
32 import javax.swing.JPanel;
33 import javax.swing.SwingWorker;
34 import org.apache.commons.collections.CollectionUtils;
35 import org.apache.commons.io.FilenameUtils;
36 import org.apache.commons.lang3.StringUtils;
37 import org.openide.util.NbBundle.Messages;
54 import org.sleuthkit.datamodel.AbstractFile;
55 import org.sleuthkit.datamodel.BlackboardArtifact;
56 import org.sleuthkit.datamodel.Content;
57 import org.sleuthkit.datamodel.DataSource;
58 import org.sleuthkit.datamodel.TskCoreException;
59 
63 @Messages({"BaseDataSourceSummaryPanel_goToArtifact=View Source Result",
64  "BaseDataSourceSummaryPanel_goToFile=View Source File in Directory"})
65 abstract class BaseDataSourceSummaryPanel extends JPanel {
66 
67  private static final long serialVersionUID = 1L;
68 
69  private static final Logger logger = Logger.getLogger(BaseDataSourceSummaryPanel.class.getName());
70 
71  private final SwingWorkerSequentialExecutor executor = new SwingWorkerSequentialExecutor();
72  private final EventUpdateHandler updateHandler;
73  private final List<UpdateGovernor> governors;
74 
75  private Runnable notifyParentClose = null;
76 
77  private DataSource dataSource;
78 
85  private final UpdateGovernor updateGovernor = new UpdateGovernor() {
94  private boolean isInDataSource(BlackboardArtifact art, DataSource ds) {
95  try {
96 
97  return (art.getDataSource() != null && art.getDataSource().getId() == ds.getId());
98  } catch (TskCoreException ex) {
99  logger.log(Level.WARNING, "There was an error fetching datasource for artifact.", ex);
100  return false;
101  }
102  }
103 
104  @Override
105  public boolean isRefreshRequired(ModuleDataEvent evt) {
106  DataSource ds = getDataSource();
107  // make sure there is an event.
108  if (ds == null || evt == null) {
109  return false;
110  }
111 
112  //if there are no artifacts with matching datasource, return
113  // if no artifacts are present, pass it on just in case there was something wrong with ModuleDataEvent
114  if (evt.getArtifacts() != null
115  && !evt.getArtifacts().isEmpty()
116  && !evt.getArtifacts().stream().anyMatch((art) -> isInDataSource(art, ds))) {
117  return false;
118  }
119 
120  // otherwise, see if there is something that wants updates
121  for (UpdateGovernor governor : governors) {
122  if (governor.isRefreshRequired(evt)) {
123  return true;
124  }
125  }
126 
127  return false;
128  }
129 
130  @Override
131  public boolean isRefreshRequired(ModuleContentEvent evt) {
132  DataSource ds = getDataSource();
133  // make sure there is an event.
134  if (ds == null || evt == null) {
135  return false;
136  }
137 
138  try {
139  // if the underlying content has a datasource and that datasource != the
140  // current datasource, return false
141  if (evt.getSource() instanceof Content
142  && ((Content) evt.getSource()).getDataSource() != null
143  && ((Content) evt.getSource()).getDataSource().getId() != ds.getId()) {
144  return false;
145  }
146  } catch (TskCoreException ex) {
147  // on an exception, keep going for tolerance sake
148  logger.log(Level.WARNING, "There was an error fetching datasource for content.", ex);
149  }
150 
151  for (UpdateGovernor governor : governors) {
152  if (governor.isRefreshRequired(evt)) {
153  return true;
154  }
155  }
156 
157  return false;
158  }
159 
160  @Override
161  public boolean isRefreshRequired(AbstractFile file) {
162  DataSource currentDataSource = getDataSource();
163  if (currentDataSource == null || file == null) {
164  return false;
165  }
166 
167  // make sure the file is for the current data source
168  Long fileDsId = null;
169  try {
170  Content fileDataSource = file.getDataSource();
171  fileDsId = fileDataSource.getId();
172  } catch (TskCoreException ex) {
173  logger.log(Level.WARNING, "Unable to get the datasource for newly added file", ex);
174  }
175 
176  if (fileDsId != null && currentDataSource.getId() == fileDsId) {
177  for (UpdateGovernor governor : governors) {
178  if (governor.isRefreshRequired(file)) {
179  return true;
180  }
181  }
182  }
183 
184  return false;
185  }
186 
187  @Override
188  public boolean isRefreshRequired(IngestJobEvent evt) {
189  for (UpdateGovernor governor : governors) {
190  if (governor.isRefreshRequired(evt)) {
191  return true;
192  }
193  }
194 
195  return false;
196  }
197 
198  @Override
199  public boolean isRefreshRequiredForCaseEvent(PropertyChangeEvent evt) {
200  for (UpdateGovernor governor : governors) {
201  if (governor.isRefreshRequiredForCaseEvent(evt)) {
202  return true;
203  }
204  }
205 
206  return false;
207  }
208 
209  @Override
210  public Set<Case.Events> getCaseEventUpdates() {
211  // return the union of all case events sets from delegates.
212  return governors.stream()
213  .filter(governor -> governor.getCaseEventUpdates() != null)
214  .flatMap(governor -> governor.getCaseEventUpdates().stream())
215  .collect(Collectors.toSet());
216  }
217 
218  @Override
219  public Set<IngestJobEvent> getIngestJobEventUpdates() {
220  // return the union of all case events sets from delegates.
221  return governors.stream()
222  .filter(governor -> governor.getIngestJobEventUpdates() != null)
223  .flatMap(governor -> governor.getIngestJobEventUpdates().stream())
224  .collect(Collectors.toSet());
225  }
226  };
227 
234  protected BaseDataSourceSummaryPanel(UpdateGovernor... governors) {
235  this.governors = (governors == null) ? Collections.emptyList() : Arrays.asList(governors);
236  this.updateHandler = new EventUpdateHandler(this::onRefresh, updateGovernor);
237  this.updateHandler.register();
238  }
239 
247  protected CellModelTableCellRenderer.MenuItem getArtifactNavigateItem(BlackboardArtifact artifact) {
248  if (artifact == null) {
249  return null;
250  }
251 
252  return new CellModelTableCellRenderer.DefaultMenuItem(
253  Bundle.BaseDataSourceSummaryPanel_goToArtifact(),
254  () -> {
255  final DirectoryTreeTopComponent dtc = DirectoryTreeTopComponent.findInstance();
256 
257  // Navigate to the source context artifact.
258  if (dtc != null && artifact != null) {
259  dtc.viewArtifact(artifact);
260  }
261 
262  notifyParentClose();
263  });
264  }
265 
266  private static final Pattern windowsDrivePattern = Pattern.compile("^[A-Za-z]\\:(.*)$");
267 
275  private String normalizePath(String path) {
276  if (path == null) {
277  return null;
278  }
279 
280  String trimmed = path.trim();
281  Matcher match = windowsDrivePattern.matcher(trimmed);
282  if (match.find()) {
283  return FilenameUtils.normalize(match.group(1), true);
284  } else {
285  return FilenameUtils.normalize(trimmed, true);
286  }
287  }
288 
295  protected CellModelTableCellRenderer.MenuItem getFileNavigateItem(String path) {
296  if (StringUtils.isNotBlank(path)) {
297  Path p = Paths.get(path);
298  String fileName = normalizePath(p.getFileName().toString());
299  String directory = normalizePath(p.getParent().toString());
300 
301  if (fileName != null && directory != null) {
302  try {
303  List<AbstractFile> files = Case.getCurrentCaseThrows().getSleuthkitCase().findFiles(getDataSource(), fileName, directory);
304  if (CollectionUtils.isNotEmpty(files)) {
305  return getFileNavigateItem(files.get(0));
306  }
307  } catch (TskCoreException | NoCurrentCaseException ex) {
308  logger.log(Level.WARNING, "There was an error fetching file for path: " + path, ex);
309  }
310  }
311  }
312 
313  return null;
314  }
315 
323  protected CellModelTableCellRenderer.MenuItem getFileNavigateItem(AbstractFile file) {
324  if (file == null) {
325  return null;
326  }
327 
328  return new CellModelTableCellRenderer.DefaultMenuItem(
329  Bundle.BaseDataSourceSummaryPanel_goToFile(),
330  () -> {
331  new ViewContextAction(Bundle.BaseDataSourceSummaryPanel_goToFile(), file)
332  .actionPerformed(null);
333 
334  notifyParentClose();
335  });
336  }
337 
341  public void close() {
342  executor.cancelRunning();
343  updateHandler.unregister();
344  }
345 
351  synchronized void setDataSource(DataSource dataSource) {
352  this.dataSource = dataSource;
353  this.executor.cancelRunning();
354  onNewDataSource(this.dataSource);
355  }
356 
360  protected void notifyParentClose() {
361  if (notifyParentClose != null) {
362  notifyParentClose.run();
363  }
364  }
365 
371  void setParentCloseListener(Runnable action) {
372  notifyParentClose = action;
373  }
374 
378  protected synchronized DataSource getDataSource() {
379  return this.dataSource;
380  }
381 
388  protected void submit(List<? extends SwingWorker<?, ?>> workers) {
389  executor.submit(workers);
390  }
391 
397  synchronized void onRefresh() {
398  // trigger on new data source with the current data source
399  fetchInformation(this.dataSource);
400  }
401 
408  protected abstract void fetchInformation(DataSource dataSource);
409 
418  protected void fetchInformation(List<DataFetchComponents<DataSource, ?>> dataFetchComponents, DataSource dataSource) {
419  if (dataSource == null || !Case.isCaseOpen()) {
420  dataFetchComponents.forEach((item) -> item.getResultHandler()
421  .accept(DataFetchResult.getSuccessResult(null)));
422  } else {
423  // create swing workers to run for each loadable item
424  List<DataFetchWorker<?, ?>> workers = dataFetchComponents
425  .stream()
426  .map((components) -> new DataFetchWorker<>(components, dataSource))
427  .collect(Collectors.toList());
428 
429  // submit swing workers to run
430  if (!workers.isEmpty()) {
431  submit(workers);
432  }
433  }
434  }
435 
441  protected abstract void onNewDataSource(DataSource dataSource);
442 
452  protected void onNewDataSource(
453  List<DataFetchComponents<DataSource, ?>> dataFetchComponents,
454  List<? extends LoadableComponent<?>> loadableComponents,
455  DataSource dataSource) {
456  // if no data source is present or the case is not open,
457  // set results for tables to null.
458  if (dataSource == null || !Case.isCaseOpen()) {
459  dataFetchComponents.forEach((item) -> item.getResultHandler()
460  .accept(DataFetchResult.getSuccessResult(null)));
461 
462  } else {
463  // set tables to display loading screen
464  loadableComponents.forEach((table) -> table.showDefaultLoadingMessage());
465 
466  fetchInformation(dataSource);
467  }
468  }
469 }

Copyright © 2012-2021 Basis Technology. Generated on: Tue Jan 19 2021
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.