Autopsy  4.20.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;
56 import org.sleuthkit.datamodel.AbstractFile;
57 import org.sleuthkit.datamodel.BlackboardArtifact;
58 import org.sleuthkit.datamodel.Content;
59 import org.sleuthkit.datamodel.DataSource;
60 import org.sleuthkit.datamodel.TskCoreException;
61 
65 @Messages({"BaseDataSourceSummaryPanel_goToArtifact=View Source Result",
66  "BaseDataSourceSummaryPanel_goToFile=View Source File in Directory"})
67 abstract class BaseDataSourceSummaryPanel extends JPanel {
68 
69  private static final long serialVersionUID = 1L;
70 
71  private static final Logger logger = Logger.getLogger(BaseDataSourceSummaryPanel.class.getName());
72 
73  private final SwingWorkerSequentialExecutor executor = new SwingWorkerSequentialExecutor();
74  private final EventUpdateHandler updateHandler;
75  private final List<UpdateGovernor> governors;
76 
77  private Runnable notifyParentClose = null;
78 
79  private DataSource dataSource;
80 
87  private final UpdateGovernor updateGovernor = new UpdateGovernor() {
96  private boolean isInDataSource(BlackboardArtifact art, DataSource ds) {
97  try {
98 
99  return (art.getDataSource() != null && art.getDataSource().getId() == ds.getId());
100  } catch (TskCoreException ex) {
101  logger.log(Level.WARNING, "There was an error fetching datasource for artifact.", ex);
102  return false;
103  }
104  }
105 
106  @Override
107  public boolean isRefreshRequired(ModuleDataEvent evt) {
108  DataSource ds = getDataSource();
109  // make sure there is an event.
110  if (ds == null || evt == null) {
111  return false;
112  }
113 
114  //if there are no artifacts with matching datasource, return
115  // if no artifacts are present, pass it on just in case there was something wrong with ModuleDataEvent
116  if (evt.getArtifacts() != null
117  && !evt.getArtifacts().isEmpty()
118  && !evt.getArtifacts().stream().anyMatch((art) -> isInDataSource(art, ds))) {
119  return false;
120  }
121 
122  // otherwise, see if there is something that wants updates
123  for (UpdateGovernor governor : governors) {
124  if (governor.isRefreshRequired(evt)) {
125  return true;
126  }
127  }
128 
129  return false;
130  }
131 
132  @Override
133  public boolean isRefreshRequired(ModuleContentEvent evt) {
134  DataSource ds = getDataSource();
135  // make sure there is an event.
136  if (ds == null || evt == null) {
137  return false;
138  }
139 
140  try {
141  // if the underlying content has a datasource and that datasource != the
142  // current datasource, return false
143  if (evt.getSource() instanceof Content
144  && ((Content) evt.getSource()).getDataSource() != null
145  && ((Content) evt.getSource()).getDataSource().getId() != ds.getId()) {
146  return false;
147  }
148  } catch (TskCoreException ex) {
149  // on an exception, keep going for tolerance sake
150  logger.log(Level.WARNING, "There was an error fetching datasource for content.", ex);
151  }
152 
153  for (UpdateGovernor governor : governors) {
154  if (governor.isRefreshRequired(evt)) {
155  return true;
156  }
157  }
158 
159  return false;
160  }
161 
162  @Override
163  public boolean isRefreshRequired(AbstractFile file) {
164  DataSource currentDataSource = getDataSource();
165  if (currentDataSource == null || file == null) {
166  return false;
167  }
168 
169  // make sure the file is for the current data source
170  Long fileDsId = null;
171  try {
172  Content fileDataSource = file.getDataSource();
173  fileDsId = fileDataSource.getId();
174  } catch (TskCoreException ex) {
175  logger.log(Level.WARNING, "Unable to get the datasource for newly added file", ex);
176  }
177 
178  if (fileDsId != null && currentDataSource.getId() == fileDsId) {
179  for (UpdateGovernor governor : governors) {
180  if (governor.isRefreshRequired(file)) {
181  return true;
182  }
183  }
184  }
185 
186  return false;
187  }
188 
189  @Override
190  public boolean isRefreshRequired(IngestJobEvent evt) {
191  for (UpdateGovernor governor : governors) {
192  if (governor.isRefreshRequired(evt)) {
193  return true;
194  }
195  }
196 
197  return false;
198  }
199 
200  @Override
201  public boolean isRefreshRequiredForCaseEvent(PropertyChangeEvent evt) {
202  for (UpdateGovernor governor : governors) {
203  if (governor.isRefreshRequiredForCaseEvent(evt)) {
204  return true;
205  }
206  }
207 
208  return false;
209  }
210 
211  @Override
212  public Set<Case.Events> getCaseEventUpdates() {
213  // return the union of all case events sets from delegates.
214  return governors.stream()
215  .filter(governor -> governor.getCaseEventUpdates() != null)
216  .flatMap(governor -> governor.getCaseEventUpdates().stream())
217  .collect(Collectors.toSet());
218  }
219 
220  @Override
221  public Set<IngestJobEvent> getIngestJobEventUpdates() {
222  // return the union of all case events sets from delegates.
223  return governors.stream()
224  .filter(governor -> governor.getIngestJobEventUpdates() != null)
225  .flatMap(governor -> governor.getIngestJobEventUpdates().stream())
226  .collect(Collectors.toSet());
227  }
228  };
229 
236  protected BaseDataSourceSummaryPanel(UpdateGovernor... governors) {
237  this.governors = (governors == null) ? Collections.emptyList() : Arrays.asList(governors);
238  this.updateHandler = new EventUpdateHandler(this::onRefresh, updateGovernor);
239  }
240 
244  void init() {
245  this.updateHandler.register();
246  }
247 
255  protected MenuItem getArtifactNavigateItem(BlackboardArtifact artifact) {
256  if (artifact == null) {
257  return null;
258  }
259 
260  return new DefaultMenuItem(
261  Bundle.BaseDataSourceSummaryPanel_goToArtifact(),
262  () -> {
263  final DirectoryTreeTopComponent dtc = DirectoryTreeTopComponent.findInstance();
264 
265  // Navigate to the source context artifact.
266  if (dtc != null && artifact != null) {
267  dtc.viewArtifact(artifact);
268  }
269 
270  notifyParentClose();
271  });
272  }
273 
274  private static final Pattern windowsDrivePattern = Pattern.compile("^[A-Za-z]\\:(.*)$");
275 
283  private String normalizePath(String path) {
284  if (path == null) {
285  return null;
286  }
287 
288  String trimmed = path.trim();
289  Matcher match = windowsDrivePattern.matcher(trimmed);
290  if (match.find()) {
291  return FilenameUtils.normalize(match.group(1), true);
292  } else {
293  return FilenameUtils.normalize(trimmed, true);
294  }
295  }
296 
303  protected MenuItem getFileNavigateItem(String path) {
304  if (StringUtils.isNotBlank(path)) {
305  Path p = Paths.get(path);
306  String fileName = normalizePath(p.getFileName().toString());
307  String directory = normalizePath(p.getParent().toString());
308 
309  if (fileName != null && directory != null) {
310  try {
311  List<AbstractFile> files = Case.getCurrentCaseThrows().getSleuthkitCase().findFiles(getDataSource(), fileName, directory);
312  if (CollectionUtils.isNotEmpty(files)) {
313  return getFileNavigateItem(files.get(0));
314  }
315  } catch (TskCoreException | NoCurrentCaseException ex) {
316  logger.log(Level.WARNING, "There was an error fetching file for path: " + path, ex);
317  }
318  }
319  }
320 
321  return null;
322  }
323 
331  protected MenuItem getFileNavigateItem(AbstractFile file) {
332  if (file == null) {
333  return null;
334  }
335 
336  return new DefaultMenuItem(
337  Bundle.BaseDataSourceSummaryPanel_goToFile(),
338  () -> {
339  new ViewContextAction(Bundle.BaseDataSourceSummaryPanel_goToFile(), file)
340  .actionPerformed(null);
341 
342  notifyParentClose();
343  });
344  }
345 
349  public void close() {
350  executor.cancelRunning();
351  updateHandler.unregister();
352  }
353 
359  synchronized void setDataSource(DataSource dataSource) {
360  this.dataSource = dataSource;
361  this.executor.cancelRunning();
362  onNewDataSource(this.dataSource);
363  }
364 
368  protected void notifyParentClose() {
369  if (notifyParentClose != null) {
370  notifyParentClose.run();
371  }
372  }
373 
379  void setParentCloseListener(Runnable action) {
380  notifyParentClose = action;
381  }
382 
386  protected synchronized DataSource getDataSource() {
387  return this.dataSource;
388  }
389 
396  protected void submit(List<? extends SwingWorker<?, ?>> workers) {
397  executor.submit(workers);
398  }
399 
405  synchronized void onRefresh() {
406  // trigger on new data source with the current data source
407  fetchInformation(this.dataSource);
408  }
409 
416  protected abstract void fetchInformation(DataSource dataSource);
417 
426  protected void fetchInformation(List<DataFetchComponents<DataSource, ?>> dataFetchComponents, DataSource dataSource) {
427  if (dataSource == null || !Case.isCaseOpen()) {
428  dataFetchComponents.forEach((item) -> item.getResultHandler()
429  .accept(DataFetchResult.getSuccessResult(null)));
430  } else {
431  // create swing workers to run for each loadable item
432  List<DataFetchWorker<?, ?>> workers = dataFetchComponents
433  .stream()
434  .map((components) -> new DataFetchWorker<>(components, dataSource))
435  .collect(Collectors.toList());
436 
437  // submit swing workers to run
438  if (!workers.isEmpty()) {
439  submit(workers);
440  }
441  }
442  }
443 
449  protected abstract void onNewDataSource(DataSource dataSource);
450 
460  protected static <T> T getFetchResult(
461  DataFetcher<DataSource, T> dataFetcher,
462  String sheetName, DataSource ds) {
463 
464  try {
465  return dataFetcher.runQuery(ds);
466  } catch (Exception ex) {
467  logger.log(Level.WARNING,
468  String.format("There was an error while acquiring data for exporting worksheet(s): '%s' for dataSource: %s",
469  sheetName == null ? "<null>" : sheetName,
470  ds == null || ds.getName() == null ? "<null>" : ds.getName()), ex);
471  return null;
472  }
473  }
474 
484  protected void onNewDataSource(
485  List<DataFetchComponents<DataSource, ?>> dataFetchComponents,
486  List<? extends LoadableComponent<?>> loadableComponents,
487  DataSource dataSource) {
488  // if no data source is present or the case is not open,
489  // set results for tables to null.
490  if (dataSource == null || !Case.isCaseOpen()) {
491  dataFetchComponents.forEach((item) -> item.getResultHandler()
492  .accept(DataFetchResult.getSuccessResult(null)));
493 
494  } else {
495  // set tables to display loading screen
496  loadableComponents.forEach((table) -> table.showDefaultLoadingMessage());
497 
498  fetchInformation(dataSource);
499  }
500  }
501 }

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.