Autopsy  4.19.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;
61 import org.sleuthkit.datamodel.AbstractFile;
62 import org.sleuthkit.datamodel.BlackboardArtifact;
63 import org.sleuthkit.datamodel.Content;
64 import org.sleuthkit.datamodel.DataSource;
65 import org.sleuthkit.datamodel.TskCoreException;
66 
70 @Messages({"BaseDataSourceSummaryPanel_goToArtifact=View Source Result",
71  "BaseDataSourceSummaryPanel_goToFile=View Source File in Directory"})
72 abstract class BaseDataSourceSummaryPanel extends JPanel {
73 
74  private static final long serialVersionUID = 1L;
75 
76  private static final Logger logger = Logger.getLogger(BaseDataSourceSummaryPanel.class.getName());
77 
78  private final SwingWorkerSequentialExecutor executor = new SwingWorkerSequentialExecutor();
79  private final EventUpdateHandler updateHandler;
80  private final List<UpdateGovernor> governors;
81 
82  private Runnable notifyParentClose = null;
83 
84  private DataSource dataSource;
85 
92  private final UpdateGovernor updateGovernor = new UpdateGovernor() {
101  private boolean isInDataSource(BlackboardArtifact art, DataSource ds) {
102  try {
103 
104  return (art.getDataSource() != null && art.getDataSource().getId() == ds.getId());
105  } catch (TskCoreException ex) {
106  logger.log(Level.WARNING, "There was an error fetching datasource for artifact.", ex);
107  return false;
108  }
109  }
110 
111  @Override
112  public boolean isRefreshRequired(ModuleDataEvent evt) {
113  DataSource ds = getDataSource();
114  // make sure there is an event.
115  if (ds == null || evt == null) {
116  return false;
117  }
118 
119  //if there are no artifacts with matching datasource, return
120  // if no artifacts are present, pass it on just in case there was something wrong with ModuleDataEvent
121  if (evt.getArtifacts() != null
122  && !evt.getArtifacts().isEmpty()
123  && !evt.getArtifacts().stream().anyMatch((art) -> isInDataSource(art, ds))) {
124  return false;
125  }
126 
127  // otherwise, see if there is something that wants updates
128  for (UpdateGovernor governor : governors) {
129  if (governor.isRefreshRequired(evt)) {
130  return true;
131  }
132  }
133 
134  return false;
135  }
136 
137  @Override
138  public boolean isRefreshRequired(ModuleContentEvent evt) {
139  DataSource ds = getDataSource();
140  // make sure there is an event.
141  if (ds == null || evt == null) {
142  return false;
143  }
144 
145  try {
146  // if the underlying content has a datasource and that datasource != the
147  // current datasource, return false
148  if (evt.getSource() instanceof Content
149  && ((Content) evt.getSource()).getDataSource() != null
150  && ((Content) evt.getSource()).getDataSource().getId() != ds.getId()) {
151  return false;
152  }
153  } catch (TskCoreException ex) {
154  // on an exception, keep going for tolerance sake
155  logger.log(Level.WARNING, "There was an error fetching datasource for content.", ex);
156  }
157 
158  for (UpdateGovernor governor : governors) {
159  if (governor.isRefreshRequired(evt)) {
160  return true;
161  }
162  }
163 
164  return false;
165  }
166 
167  @Override
168  public boolean isRefreshRequired(AbstractFile file) {
169  DataSource currentDataSource = getDataSource();
170  if (currentDataSource == null || file == null) {
171  return false;
172  }
173 
174  // make sure the file is for the current data source
175  Long fileDsId = null;
176  try {
177  Content fileDataSource = file.getDataSource();
178  fileDsId = fileDataSource.getId();
179  } catch (TskCoreException ex) {
180  logger.log(Level.WARNING, "Unable to get the datasource for newly added file", ex);
181  }
182 
183  if (fileDsId != null && currentDataSource.getId() == fileDsId) {
184  for (UpdateGovernor governor : governors) {
185  if (governor.isRefreshRequired(file)) {
186  return true;
187  }
188  }
189  }
190 
191  return false;
192  }
193 
194  @Override
195  public boolean isRefreshRequired(IngestJobEvent evt) {
196  for (UpdateGovernor governor : governors) {
197  if (governor.isRefreshRequired(evt)) {
198  return true;
199  }
200  }
201 
202  return false;
203  }
204 
205  @Override
206  public boolean isRefreshRequiredForCaseEvent(PropertyChangeEvent evt) {
207  for (UpdateGovernor governor : governors) {
208  if (governor.isRefreshRequiredForCaseEvent(evt)) {
209  return true;
210  }
211  }
212 
213  return false;
214  }
215 
216  @Override
217  public Set<Case.Events> getCaseEventUpdates() {
218  // return the union of all case events sets from delegates.
219  return governors.stream()
220  .filter(governor -> governor.getCaseEventUpdates() != null)
221  .flatMap(governor -> governor.getCaseEventUpdates().stream())
222  .collect(Collectors.toSet());
223  }
224 
225  @Override
226  public Set<IngestJobEvent> getIngestJobEventUpdates() {
227  // return the union of all case events sets from delegates.
228  return governors.stream()
229  .filter(governor -> governor.getIngestJobEventUpdates() != null)
230  .flatMap(governor -> governor.getIngestJobEventUpdates().stream())
231  .collect(Collectors.toSet());
232  }
233  };
234 
241  protected BaseDataSourceSummaryPanel(UpdateGovernor... governors) {
242  this.governors = (governors == null) ? Collections.emptyList() : Arrays.asList(governors);
243  this.updateHandler = new EventUpdateHandler(this::onRefresh, updateGovernor);
244  }
245 
249  void init() {
250  this.updateHandler.register();
251  }
252 
260  protected MenuItem getArtifactNavigateItem(BlackboardArtifact artifact) {
261  if (artifact == null) {
262  return null;
263  }
264 
265  return new DefaultMenuItem(
266  Bundle.BaseDataSourceSummaryPanel_goToArtifact(),
267  () -> {
268  final DirectoryTreeTopComponent dtc = DirectoryTreeTopComponent.findInstance();
269 
270  // Navigate to the source context artifact.
271  if (dtc != null && artifact != null) {
272  dtc.viewArtifact(artifact);
273  }
274 
275  notifyParentClose();
276  });
277  }
278 
279  private static final Pattern windowsDrivePattern = Pattern.compile("^[A-Za-z]\\:(.*)$");
280 
288  private String normalizePath(String path) {
289  if (path == null) {
290  return null;
291  }
292 
293  String trimmed = path.trim();
294  Matcher match = windowsDrivePattern.matcher(trimmed);
295  if (match.find()) {
296  return FilenameUtils.normalize(match.group(1), true);
297  } else {
298  return FilenameUtils.normalize(trimmed, true);
299  }
300  }
301 
308  protected MenuItem getFileNavigateItem(String path) {
309  if (StringUtils.isNotBlank(path)) {
310  Path p = Paths.get(path);
311  String fileName = normalizePath(p.getFileName().toString());
312  String directory = normalizePath(p.getParent().toString());
313 
314  if (fileName != null && directory != null) {
315  try {
316  List<AbstractFile> files = Case.getCurrentCaseThrows().getSleuthkitCase().findFiles(getDataSource(), fileName, directory);
317  if (CollectionUtils.isNotEmpty(files)) {
318  return getFileNavigateItem(files.get(0));
319  }
320  } catch (TskCoreException | NoCurrentCaseException ex) {
321  logger.log(Level.WARNING, "There was an error fetching file for path: " + path, ex);
322  }
323  }
324  }
325 
326  return null;
327  }
328 
336  protected MenuItem getFileNavigateItem(AbstractFile file) {
337  if (file == null) {
338  return null;
339  }
340 
341  return new DefaultMenuItem(
342  Bundle.BaseDataSourceSummaryPanel_goToFile(),
343  () -> {
344  new ViewContextAction(Bundle.BaseDataSourceSummaryPanel_goToFile(), file)
345  .actionPerformed(null);
346 
347  notifyParentClose();
348  });
349  }
350 
354  public void close() {
355  executor.cancelRunning();
356  updateHandler.unregister();
357  }
358 
364  synchronized void setDataSource(DataSource dataSource) {
365  this.dataSource = dataSource;
366  this.executor.cancelRunning();
367  onNewDataSource(this.dataSource);
368  }
369 
373  protected void notifyParentClose() {
374  if (notifyParentClose != null) {
375  notifyParentClose.run();
376  }
377  }
378 
384  void setParentCloseListener(Runnable action) {
385  notifyParentClose = action;
386  }
387 
391  protected synchronized DataSource getDataSource() {
392  return this.dataSource;
393  }
394 
401  protected void submit(List<? extends SwingWorker<?, ?>> workers) {
402  executor.submit(workers);
403  }
404 
410  synchronized void onRefresh() {
411  // trigger on new data source with the current data source
412  fetchInformation(this.dataSource);
413  }
414 
421  protected abstract void fetchInformation(DataSource dataSource);
422 
431  protected void fetchInformation(List<DataFetchComponents<DataSource, ?>> dataFetchComponents, DataSource dataSource) {
432  if (dataSource == null || !Case.isCaseOpen()) {
433  dataFetchComponents.forEach((item) -> item.getResultHandler()
434  .accept(DataFetchResult.getSuccessResult(null)));
435  } else {
436  // create swing workers to run for each loadable item
437  List<DataFetchWorker<?, ?>> workers = dataFetchComponents
438  .stream()
439  .map((components) -> new DataFetchWorker<>(components, dataSource))
440  .collect(Collectors.toList());
441 
442  // submit swing workers to run
443  if (!workers.isEmpty()) {
444  submit(workers);
445  }
446  }
447  }
448 
454  protected abstract void onNewDataSource(DataSource dataSource);
455 
462  abstract List<ExcelSheetExport> getExports(DataSource dataSource);
463 
473  protected static <T> T getFetchResult(
474  DataFetcher<DataSource, T> dataFetcher,
475  String sheetName, DataSource ds) {
476 
477  try {
478  return dataFetcher.runQuery(ds);
479  } catch (Exception ex) {
480  logger.log(Level.WARNING,
481  String.format("There was an error while acquiring data for exporting worksheet(s): '%s' for dataSource: %s",
482  sheetName == null ? "<null>" : sheetName,
483  ds == null || ds.getName() == null ? "<null>" : ds.getName()), ex);
484  return null;
485  }
486  }
487 
491  protected interface ExcelExportFunction<T> {
492 
500  ExcelSheetExport convert(T data) throws ExcelExportException;
501  }
502 
513  protected static <T> ExcelSheetExport convertToExcel(ExcelExportFunction<T> excelConverter, T data, String sheetName) {
514  if (data == null) {
515  return null;
516  }
517 
518  try {
519  return excelConverter.convert(data);
520  } catch (ExcelExportException ex) {
521  logger.log(Level.WARNING,
522  String.format("There was an error while preparing export of worksheet(s): '%s'",
523  sheetName == null ? "<null>" : sheetName), ex);
524  return null;
525  }
526  }
527 
538  protected static <T> ExcelSheetExport getExport(
539  DataFetcher<DataSource, T> dataFetcher, ExcelExportFunction<T> excelConverter,
540  String sheetName, DataSource ds) {
541 
542  T data = getFetchResult(dataFetcher, sheetName, ds);
543  return convertToExcel(excelConverter, data, sheetName);
544  }
545 
554  protected static <T, C extends ExcelCellModel> ExcelSheetExport getTableExport(List<ColumnModel<T, C>> columnsModel,
555  String sheetName, List<T> data) {
556 
557  return convertToExcel((dataList) -> new ExcelTableExport<T, C>(sheetName, columnsModel, dataList),
558  data,
559  sheetName);
560  }
561 
572  protected static <T, C extends ExcelCellModel> ExcelSheetExport getTableExport(
573  DataFetcher<DataSource, List<T>> dataFetcher, List<ColumnModel<T, C>> columnsModel,
574  String sheetName, DataSource ds) {
575 
576  return getExport(dataFetcher,
577  (dataList) -> new ExcelTableExport<T, C>(sheetName, columnsModel, dataList),
578  sheetName,
579  ds);
580  }
581 
591  protected void onNewDataSource(
592  List<DataFetchComponents<DataSource, ?>> dataFetchComponents,
593  List<? extends LoadableComponent<?>> loadableComponents,
594  DataSource dataSource) {
595  // if no data source is present or the case is not open,
596  // set results for tables to null.
597  if (dataSource == null || !Case.isCaseOpen()) {
598  dataFetchComponents.forEach((item) -> item.getResultHandler()
599  .accept(DataFetchResult.getSuccessResult(null)));
600 
601  } else {
602  // set tables to display loading screen
603  loadableComponents.forEach((table) -> table.showDefaultLoadingMessage());
604 
605  fetchInformation(dataSource);
606  }
607  }
608 }

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