Autopsy  4.15.0
Graphical digital forensics platform for The Sleuth Kit and other tools.
GeoFilterPanel.java
Go to the documentation of this file.
1 /*
2  * Autopsy Forensic Browser
3  *
4  * Copyright 2019 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.geolocation;
20 
22 import java.awt.Color;
23 import java.awt.Graphics;
24 import java.awt.GridBagConstraints;
25 import java.awt.event.ActionListener;
26 import java.awt.image.BufferedImage;
27 import java.sql.ResultSet;
28 import java.sql.SQLException;
29 import java.util.ArrayList;
30 import java.util.Collections;
31 import java.util.HashMap;
32 import java.util.List;
33 import java.util.Map;
34 import java.util.concurrent.ExecutionException;
35 import java.util.logging.Level;
36 import javafx.util.Pair;
37 import javax.swing.Icon;
38 import javax.swing.ImageIcon;
39 import javax.swing.SpinnerNumberModel;
40 import javax.swing.SwingWorker;
41 import org.openide.util.NbBundle.Messages;
44 import org.sleuthkit.datamodel.BlackboardArtifact;
45 import org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE;
46 import org.sleuthkit.datamodel.BlackboardAttribute;
47 import org.sleuthkit.datamodel.DataSource;
48 import org.sleuthkit.datamodel.SleuthkitCase;
49 import org.sleuthkit.datamodel.TskCoreException;
50 
55 class GeoFilterPanel extends javax.swing.JPanel {
56 
57  final static String INITPROPERTY = "FilterPanelInitCompleted";
58 
59  private static final long serialVersionUID = 1L;
60  private static final Logger logger = Logger.getLogger(GeoFilterPanel.class.getName());
61 
62  private final SpinnerNumberModel numberModel;
63  private final CheckBoxListPanel<DataSource> dsCheckboxPanel;
64  private final CheckBoxListPanel<ARTIFACT_TYPE> atCheckboxPanel;
65 
66  // Make sure to update if other GPS artifacts are added
67  @SuppressWarnings("deprecation")
68  private static final ARTIFACT_TYPE[] GPS_ARTIFACT_TYPES = {
69  ARTIFACT_TYPE.TSK_GPS_BOOKMARK,
70  ARTIFACT_TYPE.TSK_GPS_LAST_KNOWN_LOCATION,
71  ARTIFACT_TYPE.TSK_GPS_ROUTE,
72  ARTIFACT_TYPE.TSK_GPS_SEARCH,
73  ARTIFACT_TYPE.TSK_GPS_TRACK,
74  ARTIFACT_TYPE.TSK_GPS_TRACKPOINT,
75  ARTIFACT_TYPE.TSK_METADATA_EXIF
76  };
77 
81  @Messages({
82  "GeoFilterPanel_DataSource_List_Title=Data Sources",
83  "GeoFilterPanel_ArtifactType_List_Title=Types"
84  })
85  GeoFilterPanel() {
86  // numberModel is used in initComponents
87  numberModel = new SpinnerNumberModel(10, 1, Integer.MAX_VALUE, 1);
88 
89  initComponents();
90 
91  // The gui builder cannot handle using CheckBoxListPanel due to its
92  // use of generics so we will initalize it here.
93  dsCheckboxPanel = new CheckBoxListPanel<>();
94  dsCheckboxPanel.setPanelTitle(Bundle.GeoFilterPanel_DataSource_List_Title());
95  dsCheckboxPanel.setPanelTitleIcon(new ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/images/image.png")));
96  dsCheckboxPanel.setSetAllSelected(true);
97 
98  atCheckboxPanel = new CheckBoxListPanel<>();
99  atCheckboxPanel.setPanelTitle(Bundle.GeoFilterPanel_ArtifactType_List_Title());
100  atCheckboxPanel.setPanelTitleIcon(new ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/images/extracted_content.png")));
101  atCheckboxPanel.setSetAllSelected(true);
102 
103  GridBagConstraints gridBagConstraints = new GridBagConstraints();
104  gridBagConstraints.gridx = 0;
105  gridBagConstraints.gridy = 3;
106  gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
107  gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
108  gridBagConstraints.weightx = 1.0;
109  gridBagConstraints.weighty = 1.0;
110  gridBagConstraints.insets = new java.awt.Insets(0, 15, 0, 15);
111  add(dsCheckboxPanel, gridBagConstraints);
112 
113  gridBagConstraints = new GridBagConstraints();
114  gridBagConstraints.gridx = 0;
115  gridBagConstraints.gridy = 4;
116  gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
117  gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
118  gridBagConstraints.weightx = 1.0;
119  gridBagConstraints.weighty = 1.0;
120  gridBagConstraints.insets = new java.awt.Insets(0, 15, 0, 15);
121  add(atCheckboxPanel, gridBagConstraints);
122  }
123 
124  @Override
125  public void setEnabled(boolean enabled) {
126  applyButton.setEnabled(enabled);
127  mostRecentButton.setEnabled(enabled);
128  allButton.setEnabled(enabled);
129  showWaypointsWOTSCheckBox.setEnabled(enabled && mostRecentButton.isSelected());
130  dsCheckboxPanel.setEnabled(enabled);
131  atCheckboxPanel.setEnabled(enabled);
132  daysLabel.setEnabled(enabled);
133  daysSpinner.setEnabled(enabled);
134  }
135 
139  void updateDataSourceList() {
140  DataSourceUpdater updater = new DataSourceUpdater();
141  updater.execute();
142  }
143 
147  void clearDataSourceList() {
148  dsCheckboxPanel.clearList();
149  atCheckboxPanel.clearList();
150  }
151 
152  boolean hasDataSources() {
153  return !dsCheckboxPanel.isEmpty();
154  }
155 
161  void addActionListener(ActionListener listener) {
162  applyButton.addActionListener(listener);
163  }
164 
172  @Messages({
173  "GeoFilterPanel_empty_dataSource=Unable to apply filter, please select one or more data sources.",
174  "GeoFilterPanel_empty_artifactType=Unable to apply filter, please select one or more artifact types."
175  })
176  GeoFilter getFilterState() throws GeoLocationUIException {
177  List<DataSource> dataSources = dsCheckboxPanel.getSelectedElements();
178  if (dataSources.isEmpty()) {
179  throw new GeoLocationUIException(Bundle.GeoFilterPanel_empty_dataSource());
180  }
181 
182  List<ARTIFACT_TYPE> artifactTypes = atCheckboxPanel.getSelectedElements();
183  if (artifactTypes.isEmpty()) {
184  throw new GeoLocationUIException(Bundle.GeoFilterPanel_empty_artifactType());
185  }
186  return new GeoFilter(allButton.isSelected(),
187  showWaypointsWOTSCheckBox.isSelected(),
188  numberModel.getNumber().intValue(),
189  dataSources,
190  artifactTypes);
191  }
192 
197  private void updateWaypointOptions() {
198  boolean selected = mostRecentButton.isSelected();
199  showWaypointsWOTSCheckBox.setEnabled(selected);
200  daysSpinner.setEnabled(selected);
201  }
202 
208  @SuppressWarnings("unchecked")
209  // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
210  private void initComponents() {
211  java.awt.GridBagConstraints gridBagConstraints;
212 
213  javax.swing.ButtonGroup buttonGroup = new javax.swing.ButtonGroup();
214  javax.swing.JPanel waypointSettings = new javax.swing.JPanel();
215  allButton = new javax.swing.JRadioButton();
216  mostRecentButton = new javax.swing.JRadioButton();
217  showWaypointsWOTSCheckBox = new javax.swing.JCheckBox();
218  daysSpinner = new javax.swing.JSpinner(numberModel);
219  daysLabel = new javax.swing.JLabel();
220  showLabel = new javax.swing.JLabel();
221  javax.swing.JPanel buttonPanel = new javax.swing.JPanel();
222  applyButton = new javax.swing.JButton();
223  javax.swing.JLabel optionsLabel = new javax.swing.JLabel();
224 
225  setLayout(new java.awt.GridBagLayout());
226 
227  waypointSettings.setBorder(javax.swing.BorderFactory.createTitledBorder(org.openide.util.NbBundle.getMessage(GeoFilterPanel.class, "GeoFilterPanel.waypointSettings.border.title"))); // NOI18N
228  waypointSettings.setLayout(new java.awt.GridBagLayout());
229 
230  buttonGroup.add(allButton);
231  allButton.setSelected(true);
232  org.openide.awt.Mnemonics.setLocalizedText(allButton, org.openide.util.NbBundle.getMessage(GeoFilterPanel.class, "GeoFilterPanel.allButton.text")); // NOI18N
233  allButton.addActionListener(new java.awt.event.ActionListener() {
234  public void actionPerformed(java.awt.event.ActionEvent evt) {
235  allButtonActionPerformed(evt);
236  }
237  });
238  gridBagConstraints = new java.awt.GridBagConstraints();
239  gridBagConstraints.gridx = 0;
240  gridBagConstraints.gridy = 1;
241  gridBagConstraints.gridwidth = 4;
242  gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
243  gridBagConstraints.weightx = 1.0;
244  waypointSettings.add(allButton, gridBagConstraints);
245 
246  buttonGroup.add(mostRecentButton);
247  org.openide.awt.Mnemonics.setLocalizedText(mostRecentButton, org.openide.util.NbBundle.getMessage(GeoFilterPanel.class, "GeoFilterPanel.mostRecentButton.text")); // NOI18N
248  mostRecentButton.addActionListener(new java.awt.event.ActionListener() {
249  public void actionPerformed(java.awt.event.ActionEvent evt) {
250  mostRecentButtonActionPerformed(evt);
251  }
252  });
253  gridBagConstraints = new java.awt.GridBagConstraints();
254  gridBagConstraints.gridx = 0;
255  gridBagConstraints.gridy = 2;
256  gridBagConstraints.gridwidth = 2;
257  gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
258  waypointSettings.add(mostRecentButton, gridBagConstraints);
259 
260  org.openide.awt.Mnemonics.setLocalizedText(showWaypointsWOTSCheckBox, org.openide.util.NbBundle.getMessage(GeoFilterPanel.class, "GeoFilterPanel.showWaypointsWOTSCheckBox.text")); // NOI18N
261  showWaypointsWOTSCheckBox.setEnabled(false);
262  gridBagConstraints = new java.awt.GridBagConstraints();
263  gridBagConstraints.gridx = 1;
264  gridBagConstraints.gridy = 3;
265  gridBagConstraints.gridwidth = 3;
266  gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
267  gridBagConstraints.insets = new java.awt.Insets(0, -20, 0, 5);
268  waypointSettings.add(showWaypointsWOTSCheckBox, gridBagConstraints);
269 
270  daysSpinner.setEnabled(false);
271  daysSpinner.setPreferredSize(new java.awt.Dimension(75, 26));
272  gridBagConstraints = new java.awt.GridBagConstraints();
273  gridBagConstraints.gridx = 2;
274  gridBagConstraints.gridy = 2;
275  gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
276  waypointSettings.add(daysSpinner, gridBagConstraints);
277 
278  org.openide.awt.Mnemonics.setLocalizedText(daysLabel, org.openide.util.NbBundle.getMessage(GeoFilterPanel.class, "GeoFilterPanel.daysLabel.text")); // NOI18N
279  gridBagConstraints = new java.awt.GridBagConstraints();
280  gridBagConstraints.gridx = 3;
281  gridBagConstraints.gridy = 2;
282  gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST;
283  gridBagConstraints.weightx = 1.0;
284  gridBagConstraints.insets = new java.awt.Insets(0, 5, 0, 5);
285  waypointSettings.add(daysLabel, gridBagConstraints);
286 
287  org.openide.awt.Mnemonics.setLocalizedText(showLabel, org.openide.util.NbBundle.getMessage(GeoFilterPanel.class, "GeoFilterPanel.showLabel.text")); // NOI18N
288  showLabel.setToolTipText(org.openide.util.NbBundle.getMessage(GeoFilterPanel.class, "GeoFilterPanel.showLabel.toolTipText")); // NOI18N
289  gridBagConstraints = new java.awt.GridBagConstraints();
290  gridBagConstraints.gridx = 0;
291  gridBagConstraints.gridy = 0;
292  gridBagConstraints.insets = new java.awt.Insets(0, 5, 0, 0);
293  waypointSettings.add(showLabel, gridBagConstraints);
294 
295  gridBagConstraints = new java.awt.GridBagConstraints();
296  gridBagConstraints.gridx = 0;
297  gridBagConstraints.gridy = 2;
298  gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
299  gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
300  gridBagConstraints.weightx = 1.0;
301  gridBagConstraints.insets = new java.awt.Insets(5, 15, 9, 15);
302  add(waypointSettings, gridBagConstraints);
303 
304  buttonPanel.setLayout(new java.awt.GridBagLayout());
305 
306  applyButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/images/tick.png"))); // NOI18N
307  org.openide.awt.Mnemonics.setLocalizedText(applyButton, org.openide.util.NbBundle.getMessage(GeoFilterPanel.class, "GeoFilterPanel.applyButton.text")); // NOI18N
308  gridBagConstraints = new java.awt.GridBagConstraints();
309  gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHEAST;
310  gridBagConstraints.weightx = 1.0;
311  buttonPanel.add(applyButton, gridBagConstraints);
312 
313  gridBagConstraints = new java.awt.GridBagConstraints();
314  gridBagConstraints.gridx = 0;
315  gridBagConstraints.gridy = 0;
316  gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
317  gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
318  gridBagConstraints.weightx = 1.0;
319  gridBagConstraints.insets = new java.awt.Insets(9, 15, 0, 15);
320  add(buttonPanel, gridBagConstraints);
321 
322  optionsLabel.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/images/blueGeo16.png"))); // NOI18N
323  org.openide.awt.Mnemonics.setLocalizedText(optionsLabel, org.openide.util.NbBundle.getMessage(GeoFilterPanel.class, "GeoFilterPanel.optionsLabel.text")); // NOI18N
324  gridBagConstraints = new java.awt.GridBagConstraints();
325  gridBagConstraints.gridx = 0;
326  gridBagConstraints.gridy = 1;
327  gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST;
328  gridBagConstraints.insets = new java.awt.Insets(0, 15, 0, 0);
329  add(optionsLabel, gridBagConstraints);
330  }// </editor-fold>//GEN-END:initComponents
331 
332  private void allButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_allButtonActionPerformed
333  updateWaypointOptions();
334  }//GEN-LAST:event_allButtonActionPerformed
335 
336  private void mostRecentButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_mostRecentButtonActionPerformed
337  updateWaypointOptions();
338  }//GEN-LAST:event_mostRecentButtonActionPerformed
339 
340 
341  // Variables declaration - do not modify//GEN-BEGIN:variables
342  private javax.swing.JRadioButton allButton;
343  private javax.swing.JButton applyButton;
344  private javax.swing.JLabel daysLabel;
345  private javax.swing.JSpinner daysSpinner;
346  private javax.swing.JRadioButton mostRecentButton;
347  private javax.swing.JLabel showLabel;
348  private javax.swing.JCheckBox showWaypointsWOTSCheckBox;
349  // End of variables declaration//GEN-END:variables
350 
354  final class GeoFilter {
355 
356  private final boolean showAll;
357  private final boolean showWithoutTimeStamp;
358  private final int mostRecentNumDays;
359  private final List<DataSource> dataSources;
360  private final List<ARTIFACT_TYPE> artifactTypes;
361 
380  GeoFilter(boolean showAll, boolean withoutTimeStamp,
381  int mostRecentNumDays, List<DataSource> dataSources,
382  List<ARTIFACT_TYPE> artifactTypes) {
383  this.showAll = showAll;
384  this.showWithoutTimeStamp = withoutTimeStamp;
385  this.mostRecentNumDays = mostRecentNumDays;
386  this.dataSources = dataSources;
387  this.artifactTypes = artifactTypes;
388  }
389 
395  boolean showAllWaypoints() {
396  return showAll;
397  }
398 
406  boolean showWaypointsWithoutTimeStamp() {
407  return showWithoutTimeStamp;
408  }
409 
416  int getMostRecentNumDays() {
417  return mostRecentNumDays;
418  }
419 
427  List<DataSource> getDataSources() {
428  return Collections.unmodifiableList(dataSources);
429  }
430 
438  List<ARTIFACT_TYPE> getArtifactTypes() {
439  return Collections.unmodifiableList(artifactTypes);
440  }
441  }
442 
447  final private class Sources {
448 
449  final List<Pair<String, DataSource>> dataSources;
450  final Map<ARTIFACT_TYPE, Long> artifactTypes;
451 
452  private Sources(List<Pair<String, DataSource>> dataSources,
453  Map<ARTIFACT_TYPE, Long> artifactTypes) {
454  this.dataSources = dataSources;
455  this.artifactTypes = artifactTypes;
456  }
457  }
458 
465  final private class DataSourceUpdater extends SwingWorker<Sources, Void> {
466 
467  @Override
468  protected Sources doInBackground() throws Exception {
469  SleuthkitCase sleuthkitCase = Case.getCurrentCase().getSleuthkitCase();
470  List<Pair<String, DataSource>> validSources = new ArrayList<>();
471  HashMap<ARTIFACT_TYPE, Long> atCountsTotal = new HashMap<>();
472 
473  for (DataSource dataSource : sleuthkitCase.getDataSources()) {
474  Map<ARTIFACT_TYPE, Long> atCounts = getGPSDataSources(sleuthkitCase, dataSource);
475  if (!atCounts.isEmpty()) {
476  for (Map.Entry<ARTIFACT_TYPE, Long> entry : atCounts.entrySet()) {
477  atCountsTotal.putIfAbsent(entry.getKey(), 0L);
478  atCountsTotal.put(entry.getKey(), atCountsTotal.get(entry.getKey()) + entry.getValue());
479  }
480  String dsName = sleuthkitCase.getContentById(dataSource.getId()).getName();
481  Pair<String, DataSource> pair = new Pair<>(dsName, dataSource);
482  validSources.add(pair);
483  }
484  }
485  return new Sources(validSources, atCountsTotal);
486  }
487 
500  private long getGPSDataCount(SleuthkitCase sleuthkitCase,
501  DataSource dataSource, BlackboardArtifact.ARTIFACT_TYPE artifactType) throws TskCoreException {
502  long count = 0;
503  String queryStr
504  = "SELECT count(DISTINCT artifact_id) AS count FROM"
505  + " ("
506  + " SELECT * FROM blackboard_artifacts as arts"
507  + " INNER JOIN blackboard_attributes as attrs"
508  + " ON attrs.artifact_id = arts.artifact_id"
509  + " WHERE arts.artifact_type_id = " + artifactType.getTypeID()
510  + " AND arts.data_source_obj_id = " + dataSource.getId()
511  + " AND arts.review_status_id != " + BlackboardArtifact.ReviewStatus.REJECTED.getID()
512  + " AND"
513  + " ("
514  + "attrs.attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE.getTypeID()
515  + " or attrs.attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE.getTypeID()
516  + " or attrs.attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_TRACKPOINTS.getTypeID()
517  + " or attrs.attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_WAYPOINTS.getTypeID()
518  + " )"
519  + " )";
520  try (SleuthkitCase.CaseDbQuery queryResult = sleuthkitCase.executeQuery(queryStr);
521  ResultSet resultSet = queryResult.getResultSet()) {
522  if (resultSet.next()) {
523  count = resultSet.getLong("count");
524  }
525  } catch (SQLException ex) {
526  Throwable cause = ex.getCause();
527  if (cause != null) {
528  logger.log(Level.SEVERE, cause.getMessage(), cause);
529  } else {
530  logger.log(Level.SEVERE, ex.getMessage(), ex);
531  }
532  }
533  return count;
534  }
535 
547  private Map<ARTIFACT_TYPE, Long> getGPSDataSources(SleuthkitCase sleuthkitCase, DataSource dataSource) throws TskCoreException {
548  HashMap<ARTIFACT_TYPE, Long> ret = new HashMap<>();
549  for (BlackboardArtifact.ARTIFACT_TYPE type : GPS_ARTIFACT_TYPES) {
550  long count = getGPSDataCount(sleuthkitCase, dataSource, type);
551  if (count > 0) {
552  ret.put(type, count);
553  }
554  }
555  return ret;
556  }
557 
566  private ImageIcon getImageIcon(int artifactTypeId) {
567  Color color = MapWaypoint.getColor(artifactTypeId);
568  BufferedImage img = new BufferedImage(16, 16, BufferedImage.TYPE_INT_ARGB);
569 
570  Graphics g = img.createGraphics();
571  g.setColor(color);
572  g.fillRect(0, 0, 16, 16);
573  g.dispose();
574 
575  return new ImageIcon(img);
576  }
577 
578  @Override
579  public void done() {
580  Sources sources = null;
581  try {
582  sources = get();
583  } catch (InterruptedException | ExecutionException ex) {
584  Throwable cause = ex.getCause();
585  if (cause != null) {
586  logger.log(Level.SEVERE, cause.getMessage(), cause);
587  } else {
588  logger.log(Level.SEVERE, ex.getMessage(), ex);
589  }
590  }
591 
592  if (sources != null) {
593  for (Pair<String, DataSource> source : sources.dataSources) {
594  dsCheckboxPanel.addElement(source.getKey(), null, source.getValue());
595  }
596  for (Map.Entry<ARTIFACT_TYPE, Long> entry : sources.artifactTypes.entrySet()) {
597  String dispName = entry.getKey().getDisplayName() + " (" + entry.getValue() + ")";
598  Icon icon = getImageIcon(entry.getKey().getTypeID());
599  atCheckboxPanel.addElement(dispName, icon, entry.getKey());
600  }
601  }
602 
603  GeoFilterPanel.this.firePropertyChange(INITPROPERTY, false, true);
604  }
605 
606  }
607 
608 }
Map< ARTIFACT_TYPE, Long > getGPSDataSources(SleuthkitCase sleuthkitCase, DataSource dataSource)
long getGPSDataCount(SleuthkitCase sleuthkitCase, DataSource dataSource, BlackboardArtifact.ARTIFACT_TYPE artifactType)
Sources(List< Pair< String, DataSource >> dataSources, Map< ARTIFACT_TYPE, Long > artifactTypes)

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