Autopsy  4.16.0
Graphical digital forensics platform for The Sleuth Kit and other tools.
CaseEventListener.java
Go to the documentation of this file.
1 /*
2  * Central Repository
3  *
4  * Copyright 2017-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.centralrepository.eventlisteners;
20 
21 import com.google.common.util.concurrent.ThreadFactoryBuilder;
22 import java.beans.PropertyChangeEvent;
23 import java.beans.PropertyChangeListener;
24 import java.util.EnumSet;
25 import java.util.List;
26 import java.util.Set;
27 import java.util.concurrent.ExecutorService;
28 import java.util.concurrent.Executors;
29 import java.util.logging.Level;
30 import org.apache.commons.lang.StringUtils;
31 import org.openide.util.NbBundle.Messages;
48 import org.sleuthkit.datamodel.AbstractFile;
49 import org.sleuthkit.datamodel.BlackboardArtifact;
50 import org.sleuthkit.datamodel.BlackboardArtifactTag;
51 import org.sleuthkit.datamodel.Content;
52 import org.sleuthkit.datamodel.ContentTag;
53 import org.sleuthkit.datamodel.TagName;
54 import org.sleuthkit.datamodel.TskCoreException;
55 import org.sleuthkit.datamodel.TskData;
57 import org.sleuthkit.datamodel.Tag;
59 
64 @Messages({"caseeventlistener.evidencetag=Evidence"})
65 final class CaseEventListener implements PropertyChangeListener {
66 
67  private static final Logger LOGGER = Logger.getLogger(CaseEventListener.class.getName());
68  private final ExecutorService jobProcessingExecutor;
69  private static final String CASE_EVENT_THREAD_NAME = "Case-Event-Listener-%d";
70 
71  private static final Set<Case.Events> CASE_EVENTS_OF_INTEREST = EnumSet.of(
79 
80  CaseEventListener() {
81  jobProcessingExecutor = Executors.newSingleThreadExecutor(new ThreadFactoryBuilder().setNameFormat(CASE_EVENT_THREAD_NAME).build());
82  }
83 
84  void shutdown() {
85  ThreadUtils.shutDownTaskExecutor(jobProcessingExecutor);
86  }
87 
88  @Override
89  public void propertyChange(PropertyChangeEvent evt) {
90  if (!(evt instanceof AutopsyEvent) || (((AutopsyEvent) evt).getSourceType() != AutopsyEvent.SourceType.LOCAL)) {
91  return;
92  }
93 
94  CentralRepository dbManager;
95  try {
96  dbManager = CentralRepository.getInstance();
97  } catch (CentralRepoException ex) {
98  LOGGER.log(Level.SEVERE, "Failed to get instance of db manager.", ex);
99  return;
100  }
101 
102  // If any changes are made to which event types are handled the change
103  // must also be made to CASE_EVENTS_OF_INTEREST.
104  switch (Case.Events.valueOf(evt.getPropertyName())) {
105  case CONTENT_TAG_ADDED:
106  case CONTENT_TAG_DELETED: {
107  jobProcessingExecutor.submit(new ContentTagTask(dbManager, evt));
108  }
109  break;
110 
111  case BLACKBOARD_ARTIFACT_TAG_DELETED:
112  case BLACKBOARD_ARTIFACT_TAG_ADDED: {
113  jobProcessingExecutor.submit(new BlackboardTagTask(dbManager, evt));
114  }
115  break;
116 
117  case DATA_SOURCE_ADDED: {
118  jobProcessingExecutor.submit(new DataSourceAddedTask(dbManager, evt));
119  }
120  break;
121  case TAG_DEFINITION_CHANGED: {
122  jobProcessingExecutor.submit(new TagDefinitionChangeTask(evt));
123  }
124  break;
125  case CURRENT_CASE: {
126  jobProcessingExecutor.submit(new CurrentCaseTask(dbManager, evt));
127  }
128  break;
129  case DATA_SOURCE_NAME_CHANGED: {
130  jobProcessingExecutor.submit(new DataSourceNameChangedTask(dbManager, evt));
131  }
132  break;
133  }
134  }
135 
136  /*
137  * Add all of our Case Event Listeners to the case.
138  */
139  void installListeners() {
140  Case.addEventTypeSubscriber(CASE_EVENTS_OF_INTEREST, this);
141  }
142 
143  /*
144  * Remove all of our Case Event Listeners from the case.
145  */
146  void uninstallListeners() {
147  Case.removeEventTypeSubscriber(CASE_EVENTS_OF_INTEREST, this);
148  }
149 
157  private static boolean isNotableTag(Tag t) {
158  return (t != null && isNotableTagName(t.getName()));
159  }
160 
168  private static boolean isNotableTagName(TagName t) {
169  return (t != null && TagsManager.getNotableTagDisplayNames().contains(t.getDisplayName()));
170  }
171 
179  private static boolean hasNotableTag(List<? extends Tag> tags) {
180  if (tags == null) {
181  return false;
182  }
183 
184  return tags.stream()
185  .filter(CaseEventListener::isNotableTag)
186  .findFirst()
187  .isPresent();
188  }
189 
190  private final class ContentTagTask implements Runnable {
191 
193  private final PropertyChangeEvent event;
194 
195  private ContentTagTask(CentralRepository db, PropertyChangeEvent evt) {
196  dbManager = db;
197  event = evt;
198  }
199 
200  @Override
201  public void run() {
202  if (!CentralRepository.isEnabled()) {
203  return;
204  }
205 
206  Case.Events curEventType = Case.Events.valueOf(event.getPropertyName());
207  if (curEventType == Case.Events.CONTENT_TAG_ADDED && event instanceof ContentTagAddedEvent) {
208  handleTagAdded((ContentTagAddedEvent) event);
209  } else if (curEventType == Case.Events.CONTENT_TAG_DELETED && event instanceof ContentTagDeletedEvent) {
210  handleTagDeleted((ContentTagDeletedEvent) event);
211  } else {
212  LOGGER.log(Level.SEVERE,
213  String.format("Received an event %s of type %s and was expecting either CONTENT_TAG_ADDED or CONTENT_TAG_DELETED.",
214  event, curEventType));
215  }
216  }
217 
219  // ensure tag deleted event has a valid content id
220  if (evt.getDeletedTagInfo() == null) {
221  LOGGER.log(Level.SEVERE, "ContentTagDeletedEvent did not have valid content to provide a content id.");
222  return;
223  }
224 
225  try {
226  // obtain content
227  Content content = Case.getCurrentCaseThrows().getSleuthkitCase().getContentById(evt.getDeletedTagInfo().getContentID());
228  if (content == null) {
229  LOGGER.log(Level.WARNING,
230  String.format("Unable to get content for item with content id: %d.", evt.getDeletedTagInfo().getContentID()));
231  return;
232  }
233 
234  // then handle the event
235  handleTagChange(content);
236  } catch (NoCurrentCaseException | TskCoreException ex) {
237  LOGGER.log(Level.WARNING, "Error updating non-file object: " + evt.getDeletedTagInfo().getContentID(), ex);
238  }
239  }
240 
242  // ensure tag added event has a valid content id
243  if (evt.getAddedTag() == null || evt.getAddedTag().getContent() == null) {
244  LOGGER.log(Level.SEVERE, "ContentTagAddedEvent did not have valid content to provide a content id.");
245  return;
246  }
247 
248  // then handle the event
249  handleTagChange(evt.getAddedTag().getContent());
250  }
251 
259  private void handleTagChange(Content content) {
260  AbstractFile af = null;
261  try {
262  af = Case.getCurrentCaseThrows().getSleuthkitCase().getAbstractFileById(content.getId());
263  } catch (NoCurrentCaseException | TskCoreException ex) {
264  Long contentID = (content != null) ? content.getId() : null;
265  LOGGER.log(Level.WARNING, "Error updating non-file object: " + contentID, ex);
266  }
267 
268  if (af == null) {
269  return;
270  }
271 
272  try {
273  // Get the tags on the content object
275 
276  if (hasNotableTag(tagsManager.getContentTagsByContent(content))) {
277  // if there is a notable tag on the object, set content known status to bad
278  setContentKnownStatus(af, TskData.FileKnown.BAD);
279  } else {
280  // otherwise, set to unknown
281  setContentKnownStatus(af, TskData.FileKnown.UNKNOWN);
282  }
283  } catch (TskCoreException | NoCurrentCaseException ex) {
284  LOGGER.log(Level.SEVERE, "Failed to obtain tags manager for case.", ex);
285  }
286  }
287 
297  private void setContentKnownStatus(AbstractFile af, TskData.FileKnown knownStatus) {
299 
300  if (eamArtifact != null) {
301  // send update to Central Repository db
302  try {
303  dbManager.setAttributeInstanceKnownStatus(eamArtifact, knownStatus);
304  } catch (CentralRepoException ex) {
305  LOGGER.log(Level.SEVERE, "Error connecting to Central Repository database while setting artifact known status.", ex); //NON-NLS
306  }
307  }
308  }
309  }
310 
311  private final class BlackboardTagTask implements Runnable {
312 
314  private final PropertyChangeEvent event;
315 
316  private BlackboardTagTask(CentralRepository db, PropertyChangeEvent evt) {
317  dbManager = db;
318  event = evt;
319  }
320 
321  @Override
322  public void run() {
323  if (!CentralRepository.isEnabled()) {
324  return;
325  }
326 
327  Case.Events curEventType = Case.Events.valueOf(event.getPropertyName());
328  if (curEventType == Case.Events.BLACKBOARD_ARTIFACT_TAG_ADDED && event instanceof BlackBoardArtifactTagAddedEvent) {
329  handleTagAdded((BlackBoardArtifactTagAddedEvent) event);
330  } else if (curEventType == Case.Events.BLACKBOARD_ARTIFACT_TAG_DELETED && event instanceof BlackBoardArtifactTagDeletedEvent) {
331  handleTagDeleted((BlackBoardArtifactTagDeletedEvent) event);
332  } else {
333  LOGGER.log(Level.WARNING,
334  String.format("Received an event %s of type %s and was expecting either CONTENT_TAG_ADDED or CONTENT_TAG_DELETED.",
335  event, curEventType));
336  }
337  }
338 
340  // ensure tag deleted event has a valid content id
341  if (evt.getDeletedTagInfo() == null) {
342  LOGGER.log(Level.SEVERE, "BlackBoardArtifactTagDeletedEvent did not have valid content to provide a content id.");
343  return;
344  }
345 
346  try {
347  Case openCase = Case.getCurrentCaseThrows();
348 
349  // obtain content
350  Content content = openCase.getSleuthkitCase().getContentById(evt.getDeletedTagInfo().getContentID());
351  if (content == null) {
352  LOGGER.log(Level.WARNING,
353  String.format("Unable to get content for item with content id: %d.", evt.getDeletedTagInfo().getContentID()));
354  return;
355  }
356 
357  // obtain blackboard artifact
358  BlackboardArtifact bbArtifact = openCase.getSleuthkitCase().getBlackboardArtifact(evt.getDeletedTagInfo().getArtifactID());
359  if (bbArtifact == null) {
360  LOGGER.log(Level.WARNING,
361  String.format("Unable to get blackboard artifact for item with artifact id: %d.", evt.getDeletedTagInfo().getArtifactID()));
362  return;
363  }
364 
365  // then handle the event
366  handleTagChange(content, bbArtifact);
367  } catch (NoCurrentCaseException | TskCoreException ex) {
368  LOGGER.log(Level.WARNING, "Error updating non-file object.", ex);
369  }
370  }
371 
373  // ensure tag added event has a valid content id
374  if (evt.getAddedTag() == null || evt.getAddedTag().getContent() == null || evt.getAddedTag().getArtifact() == null) {
375  LOGGER.log(Level.SEVERE, "BlackBoardArtifactTagAddedEvent did not have valid content to provide a content id.");
376  return;
377  }
378 
379  // then handle the event
380  handleTagChange(evt.getAddedTag().getContent(), evt.getAddedTag().getArtifact());
381  }
382 
391  private void handleTagChange(Content content, BlackboardArtifact bbArtifact) {
392  Case openCase;
393  try {
394  openCase = Case.getCurrentCaseThrows();
395  } catch (NoCurrentCaseException ex) {
396  LOGGER.log(Level.SEVERE, "Exception while getting open case.", ex);
397  return;
398  }
399 
400  try {
401  if (isKnownFile(content)) {
402  return;
403  }
404 
405  TagsManager tagsManager = openCase.getServices().getTagsManager();
406  List<BlackboardArtifactTag> tags = tagsManager.getBlackboardArtifactTagsByArtifact(bbArtifact);
407  if (hasNotableTag(tags)) {
408  setArtifactKnownStatus(bbArtifact, TskData.FileKnown.BAD);
409  } else {
410  setArtifactKnownStatus(bbArtifact, TskData.FileKnown.UNKNOWN);
411  }
412  } catch (TskCoreException ex) {
413  LOGGER.log(Level.SEVERE, "Failed to obtain tags manager for case.", ex);
414  return;
415  }
416  }
417 
425  private boolean isKnownFile(Content content) {
426  return ((content instanceof AbstractFile) && (((AbstractFile) content).getKnown() == TskData.FileKnown.KNOWN));
427  }
428 
436  private void setArtifactKnownStatus(BlackboardArtifact bbArtifact, TskData.FileKnown knownStatus) {
437  List<CorrelationAttributeInstance> convertedArtifacts = CorrelationAttributeUtil.makeCorrAttrsForCorrelation(bbArtifact);
438  for (CorrelationAttributeInstance eamArtifact : convertedArtifacts) {
439  try {
440  dbManager.setAttributeInstanceKnownStatus(eamArtifact, knownStatus);
441  } catch (CentralRepoException ex) {
442  LOGGER.log(Level.SEVERE, "Error connecting to Central Repository database while setting artifact known status.", ex); //NON-NLS
443  }
444  }
445  }
446 
447  }
448 
449  private final class TagDefinitionChangeTask implements Runnable {
450 
451  private final PropertyChangeEvent event;
452 
453  private TagDefinitionChangeTask(PropertyChangeEvent evt) {
454  event = evt;
455  }
456 
457  @Override
458  public void run() {
459  if (!CentralRepository.isEnabled()) {
460  return;
461  }
462  //get the display name of the tag that has had it's definition modified
463  String modifiedTagName = (String) event.getOldValue();
464 
465  /*
466  * Set knownBad status for all files/artifacts in the given case
467  * that are tagged with the given tag name.
468  */
469  try {
470  TagName tagName = Case.getCurrentCaseThrows().getServices().getTagsManager().getDisplayNamesToTagNamesMap().get(modifiedTagName);
471  //First update the artifacts
472  //Get all BlackboardArtifactTags with this tag name
473  List<BlackboardArtifactTag> artifactTags = Case.getCurrentCaseThrows().getSleuthkitCase().getBlackboardArtifactTagsByTagName(tagName);
474  for (BlackboardArtifactTag bbTag : artifactTags) {
475  //start with assumption that none of the other tags applied to this Correlation Attribute will prevent it's status from being changed
476  boolean hasTagWithConflictingKnownStatus = false;
477  // if the status of the tag has been changed to TskData.FileKnown.UNKNOWN
478  // we need to check the status of all other tags on this correlation attribute before changing
479  // the status of the correlation attribute in the central repository
480  if (tagName.getKnownStatus() == TskData.FileKnown.UNKNOWN) {
481  Content content = bbTag.getContent();
482  // If the content which this Blackboard Artifact Tag is linked to is an AbstractFile with KNOWN status then
483  // it's status in the central reporsitory should not be changed to UNKNOWN
484  if ((content instanceof AbstractFile) && (((AbstractFile) content).getKnown() == TskData.FileKnown.KNOWN)) {
485  continue;
486  }
487  //Get the BlackboardArtifact which this BlackboardArtifactTag has been applied to.
488  BlackboardArtifact bbArtifact = bbTag.getArtifact();
490  List<BlackboardArtifactTag> tags = tagsManager.getBlackboardArtifactTagsByArtifact(bbArtifact);
491  //get all tags which are on this blackboard artifact
492  for (BlackboardArtifactTag t : tags) {
493  //All instances of the modified tag name will be changed, they can not conflict with each other
494  if (t.getName().equals(tagName)) {
495  continue;
496  }
497  //if any other tags on this artifact are Notable in status then this artifact can not have its status changed
498  if (TskData.FileKnown.BAD == t.getName().getKnownStatus()) {
499  //a tag with a conflicting status has been found, the status of this correlation attribute can not be modified
500  hasTagWithConflictingKnownStatus = true;
501  break;
502  }
503  }
504  }
505  //if the Correlation Attribute will have no tags with a status which would prevent the current status from being changed
506  if (!hasTagWithConflictingKnownStatus) {
507  //Get the correlation atttributes that correspond to the current BlackboardArtifactTag if their status should be changed
508  //with the initial set of correlation attributes this should be a single correlation attribute
509  List<CorrelationAttributeInstance> convertedArtifacts = CorrelationAttributeUtil.makeCorrAttrsForCorrelation(bbTag.getArtifact());
510  for (CorrelationAttributeInstance eamArtifact : convertedArtifacts) {
511  CentralRepository.getInstance().setAttributeInstanceKnownStatus(eamArtifact, tagName.getKnownStatus());
512  }
513  }
514  }
515  // Next update the files
516 
517  List<ContentTag> fileTags = Case.getCurrentCaseThrows().getSleuthkitCase().getContentTagsByTagName(tagName);
518  //Get all ContentTags with this tag name
519  for (ContentTag contentTag : fileTags) {
520  //start with assumption that none of the other tags applied to this ContentTag will prevent it's status from being changed
521  boolean hasTagWithConflictingKnownStatus = false;
522  // if the status of the tag has been changed to TskData.FileKnown.UNKNOWN
523  // we need to check the status of all other tags on this file before changing
524  // the status of the file in the central repository
525  if (tagName.getKnownStatus() == TskData.FileKnown.UNKNOWN) {
526  Content content = contentTag.getContent();
528  List<ContentTag> tags = tagsManager.getContentTagsByContent(content);
529  //get all tags which are on this file
530  for (ContentTag t : tags) {
531  //All instances of the modified tag name will be changed, they can not conflict with each other
532  if (t.getName().equals(tagName)) {
533  continue;
534  }
535  //if any other tags on this file are Notable in status then this file can not have its status changed
536  if (TskData.FileKnown.BAD == t.getName().getKnownStatus()) {
537  //a tag with a conflicting status has been found, the status of this file can not be modified
538  hasTagWithConflictingKnownStatus = true;
539  break;
540  }
541  }
542  }
543  //if the file will have no tags with a status which would prevent the current status from being changed
544  if (!hasTagWithConflictingKnownStatus) {
545  Content taggedContent = contentTag.getContent();
546  if (taggedContent instanceof AbstractFile) {
547  final CorrelationAttributeInstance eamArtifact = CorrelationAttributeUtil.makeCorrAttrFromFile((AbstractFile) taggedContent);
548  if (eamArtifact != null) {
550  }
551  }
552  }
553  }
554  } catch (TskCoreException ex) {
555  LOGGER.log(Level.SEVERE, "Cannot update known status in central repository for tag: " + modifiedTagName, ex); //NON-NLS
556  } catch (CentralRepoException ex) {
557  LOGGER.log(Level.SEVERE, "Cannot get central repository for tag: " + modifiedTagName, ex); //NON-NLS
558  } catch (NoCurrentCaseException ex) {
559  LOGGER.log(Level.SEVERE, "Exception while getting open case.", ex); //NON-NLS
560  }
561  } //TAG_STATUS_CHANGED
562  }
563 
564  private final class DataSourceAddedTask implements Runnable {
565 
567  private final PropertyChangeEvent event;
568 
569  private DataSourceAddedTask(CentralRepository db, PropertyChangeEvent evt) {
570  dbManager = db;
571  event = evt;
572  }
573 
574  @Override
575  public void run() {
576  if (!CentralRepository.isEnabled()) {
577  return;
578  }
579  Case openCase;
580  try {
581  openCase = Case.getCurrentCaseThrows();
582  } catch (NoCurrentCaseException ex) {
583  LOGGER.log(Level.SEVERE, "Exception while getting open case.", ex);
584  return;
585  }
586 
587  final DataSourceAddedEvent dataSourceAddedEvent = (DataSourceAddedEvent) event;
588  Content newDataSource = dataSourceAddedEvent.getDataSource();
589 
590  try {
591  CorrelationCase correlationCase = dbManager.getCase(openCase);
592  if (null == dbManager.getDataSource(correlationCase, newDataSource.getId())) {
593  CorrelationDataSource.fromTSKDataSource(correlationCase, newDataSource);
594  }
595  } catch (CentralRepoException ex) {
596  LOGGER.log(Level.SEVERE, "Error adding new data source to the central repository", ex); //NON-NLS
597  }
598  } // DATA_SOURCE_ADDED
599  }
600 
601  private final class CurrentCaseTask implements Runnable {
602 
604  private final PropertyChangeEvent event;
605 
606  private CurrentCaseTask(CentralRepository db, PropertyChangeEvent evt) {
607  dbManager = db;
608  event = evt;
609  }
610 
611  @Override
612  public void run() {
613  /*
614  * A case has been opened if evt.getOldValue() is null and
615  * evt.getNewValue() is a valid Case.
616  */
617  if ((null == event.getOldValue()) && (event.getNewValue() instanceof Case)) {
618  Case curCase = (Case) event.getNewValue();
619  IngestEventsListener.resetCeModuleInstanceCount();
620 
621  if (!CentralRepository.isEnabled()) {
622  return;
623  }
624 
625  try {
626  // NOTE: Cannot determine if the opened case is a new case or a reopened case,
627  // so check for existing name in DB and insert if missing.
628  if (dbManager.getCase(curCase) == null) {
629  dbManager.newCase(curCase);
630  }
631  } catch (CentralRepoException ex) {
632  LOGGER.log(Level.SEVERE, "Error connecting to Central Repository database.", ex); //NON-NLS
633  }
634  }
635  } // CURRENT_CASE
636  }
637 
638  private final class DataSourceNameChangedTask implements Runnable {
639 
641  private final PropertyChangeEvent event;
642 
643  private DataSourceNameChangedTask(CentralRepository db, PropertyChangeEvent evt) {
644  dbManager = db;
645  event = evt;
646  }
647 
648  @Override
649  public void run() {
650 
651  final DataSourceNameChangedEvent dataSourceNameChangedEvent = (DataSourceNameChangedEvent) event;
652  Content dataSource = dataSourceNameChangedEvent.getDataSource();
653  String newName = (String) event.getNewValue();
654 
655  if (!StringUtils.isEmpty(newName)) {
656 
657  if (!CentralRepository.isEnabled()) {
658  return;
659  }
660 
661  try {
662  CorrelationCase correlationCase = dbManager.getCase(Case.getCurrentCaseThrows());
663  CorrelationDataSource existingEamDataSource = dbManager.getDataSource(correlationCase, dataSource.getId());
664  dbManager.updateDataSourceName(existingEamDataSource, newName);
665  } catch (CentralRepoException ex) {
666  LOGGER.log(Level.SEVERE, "Error updating data source with ID " + dataSource.getId() + " to " + newName, ex); //NON-NLS
667  } catch (NoCurrentCaseException ex) {
668  LOGGER.log(Level.SEVERE, "No open case", ex);
669  }
670  }
671  } // DATA_SOURCE_NAME_CHANGED
672  }
673 }
static CorrelationDataSource fromTSKDataSource(CorrelationCase correlationCase, Content dataSource)
List< ContentTag > getContentTagsByContent(Content content)
static CorrelationAttributeInstance makeCorrAttrFromFile(AbstractFile file)
void setAttributeInstanceKnownStatus(CorrelationAttributeInstance eamArtifact, TskData.FileKnown knownStatus)
static List< CorrelationAttributeInstance > makeCorrAttrsForCorrelation(BlackboardArtifact artifact)
static void shutDownTaskExecutor(ExecutorService executor)
void updateDataSourceName(CorrelationDataSource eamDataSource, String newName)
void setArtifactKnownStatus(BlackboardArtifact bbArtifact, TskData.FileKnown knownStatus)
CorrelationDataSource getDataSource(CorrelationCase correlationCase, Long caseDbDataSourceId)
synchronized static Logger getLogger(String name)
Definition: Logger.java:124
static void addEventTypeSubscriber(Set< Events > eventTypes, PropertyChangeListener subscriber)
Definition: Case.java:491
static void removeEventTypeSubscriber(Set< Events > eventTypes, PropertyChangeListener subscriber)
Definition: Case.java:536
List< BlackboardArtifactTag > getBlackboardArtifactTagsByArtifact(BlackboardArtifact artifact)

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