mirror of
https://github.com/overcuriousity/autopsy-flatpak.git
synced 2025-07-12 16:06:15 +00:00
Merge pull request #7245 from gdicristofaro/7944-interestingItemChanges
7944 interesting item tree structure changes
This commit is contained in:
commit
b38a79d3e0
@ -250,18 +250,16 @@ public class Artifacts {
|
|||||||
KeywordHits.RootNode keywordsNode = new KeywordHits(skCase, dsObjId).new RootNode();
|
KeywordHits.RootNode keywordsNode = new KeywordHits(skCase, dsObjId).new RootNode();
|
||||||
return new TypeNodeKey(keywordsNode, TSK_KEYWORD_HIT);
|
return new TypeNodeKey(keywordsNode, TSK_KEYWORD_HIT);
|
||||||
|
|
||||||
} else if (TSK_INTERESTING_ARTIFACT_HIT.getTypeID() == typeId
|
} else if (TSK_INTERESTING_ARTIFACT_HIT.getTypeID() == typeId) {
|
||||||
|| TSK_INTERESTING_FILE_HIT.getTypeID() == typeId) {
|
InterestingHits.RootNode interestingHitsNode = new InterestingHits(skCase, TSK_INTERESTING_ARTIFACT_HIT, dsObjId).new RootNode();
|
||||||
|
return new TypeNodeKey(interestingHitsNode, TSK_INTERESTING_ARTIFACT_HIT);
|
||||||
InterestingHits.RootNode interestingHitsNode = new InterestingHits(skCase, dsObjId).new RootNode();
|
} else if (TSK_INTERESTING_FILE_HIT.getTypeID() == typeId) {
|
||||||
return new TypeNodeKey(interestingHitsNode,
|
InterestingHits.RootNode interestingHitsNode = new InterestingHits(skCase, TSK_INTERESTING_FILE_HIT, dsObjId).new RootNode();
|
||||||
TSK_INTERESTING_ARTIFACT_HIT,
|
return new TypeNodeKey(interestingHitsNode, TSK_INTERESTING_FILE_HIT);
|
||||||
TSK_INTERESTING_FILE_HIT);
|
|
||||||
|
|
||||||
} else if (TSK_HASHSET_HIT.getTypeID() == typeId) {
|
} else if (TSK_HASHSET_HIT.getTypeID() == typeId) {
|
||||||
HashsetHits.RootNode hashsetHits = new HashsetHits(skCase, dsObjId).new RootNode();
|
HashsetHits.RootNode hashsetHits = new HashsetHits(skCase, dsObjId).new RootNode();
|
||||||
return new TypeNodeKey(hashsetHits, TSK_HASHSET_HIT);
|
return new TypeNodeKey(hashsetHits, TSK_HASHSET_HIT);
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
return new TypeNodeKey(type, dsObjId);
|
return new TypeNodeKey(type, dsObjId);
|
||||||
}
|
}
|
||||||
@ -278,7 +276,7 @@ public class Artifacts {
|
|||||||
*/
|
*/
|
||||||
private final RefreshThrottler refreshThrottler = new RefreshThrottler(this);
|
private final RefreshThrottler refreshThrottler = new RefreshThrottler(this);
|
||||||
private final Category category;
|
private final Category category;
|
||||||
|
|
||||||
private final PropertyChangeListener weakPcl;
|
private final PropertyChangeListener weakPcl;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -293,7 +291,7 @@ public class Artifacts {
|
|||||||
super();
|
super();
|
||||||
this.filteringDSObjId = filteringDSObjId;
|
this.filteringDSObjId = filteringDSObjId;
|
||||||
this.category = category;
|
this.category = category;
|
||||||
|
|
||||||
PropertyChangeListener pcl = (PropertyChangeEvent evt) -> {
|
PropertyChangeListener pcl = (PropertyChangeEvent evt) -> {
|
||||||
String eventType = evt.getPropertyName();
|
String eventType = evt.getPropertyName();
|
||||||
if (eventType.equals(Case.Events.CURRENT_CASE.toString())) {
|
if (eventType.equals(Case.Events.CURRENT_CASE.toString())) {
|
||||||
@ -322,9 +320,9 @@ public class Artifacts {
|
|||||||
|
|
||||||
weakPcl = WeakListeners.propertyChange(pcl, null);
|
weakPcl = WeakListeners.propertyChange(pcl, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void addNotify() {
|
protected void addNotify() {
|
||||||
super.addNotify();
|
super.addNotify();
|
||||||
refreshThrottler.registerForIngestModuleEvents();
|
refreshThrottler.registerForIngestModuleEvents();
|
||||||
IngestManager.getInstance().addIngestJobEventListener(INGEST_JOB_EVENTS_OF_INTEREST, weakPcl);
|
IngestManager.getInstance().addIngestJobEventListener(INGEST_JOB_EVENTS_OF_INTEREST, weakPcl);
|
||||||
@ -632,7 +630,7 @@ public class Artifacts {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
private final PropertyChangeListener weakPcl = WeakListeners.propertyChange(pcl, null);
|
private final PropertyChangeListener weakPcl = WeakListeners.propertyChange(pcl, null);
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -643,7 +641,7 @@ public class Artifacts {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onRemove() {
|
protected void onRemove() {
|
||||||
if(refreshThrottler != null) {
|
if (refreshThrottler != null) {
|
||||||
refreshThrottler.unregisterEventListener();
|
refreshThrottler.unregisterEventListener();
|
||||||
}
|
}
|
||||||
IngestManager.getInstance().removeIngestJobEventListener(weakPcl);
|
IngestManager.getInstance().removeIngestJobEventListener(weakPcl);
|
||||||
@ -663,14 +661,14 @@ public class Artifacts {
|
|||||||
|
|
||||||
case ANALYSIS_RESULT:
|
case ANALYSIS_RESULT:
|
||||||
arts = (filteringDSObjId > 0)
|
arts = (filteringDSObjId > 0)
|
||||||
? blackboard.getAnalysisResultsByType(type.getTypeID(), filteringDSObjId)
|
? blackboard.getAnalysisResultsByType(type.getTypeID(), filteringDSObjId)
|
||||||
: blackboard.getAnalysisResultsByType(type.getTypeID());
|
: blackboard.getAnalysisResultsByType(type.getTypeID());
|
||||||
break;
|
break;
|
||||||
case DATA_ARTIFACT:
|
case DATA_ARTIFACT:
|
||||||
default:
|
default:
|
||||||
arts = (filteringDSObjId > 0)
|
arts = (filteringDSObjId > 0)
|
||||||
? blackboard.getDataArtifacts(type.getTypeID(), filteringDSObjId)
|
? blackboard.getDataArtifacts(type.getTypeID(), filteringDSObjId)
|
||||||
: blackboard.getDataArtifacts(type.getTypeID());
|
: blackboard.getDataArtifacts(type.getTypeID());
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -679,9 +677,9 @@ public class Artifacts {
|
|||||||
//See JIRA-5969
|
//See JIRA-5969
|
||||||
art.getAttributes();
|
art.getAttributes();
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
List<BlackboardArtifact> toRet = (List<BlackboardArtifact>)(List<?>)arts;
|
List<BlackboardArtifact> toRet = (List<BlackboardArtifact>) (List<?>) arts;
|
||||||
return toRet;
|
return toRet;
|
||||||
} catch (NoCurrentCaseException ex) {
|
} catch (NoCurrentCaseException ex) {
|
||||||
logger.log(Level.WARNING, "Trying to access case when no case is open.", ex); //NON-NLS
|
logger.log(Level.WARNING, "Trying to access case when no case is open.", ex); //NON-NLS
|
||||||
|
@ -181,15 +181,13 @@ public interface DisplayableItemNodeVisitor<T> {
|
|||||||
|
|
||||||
T visit(EmptyNode.MessageNode emptyNode);
|
T visit(EmptyNode.MessageNode emptyNode);
|
||||||
|
|
||||||
T visit(InterestingHits.InterestingItemTypeNode aThis);
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Attachments
|
* Attachments
|
||||||
*/
|
*/
|
||||||
T visit(AttachmentNode node);
|
T visit(AttachmentNode node);
|
||||||
|
|
||||||
T visit(OsAccounts.OsAccountNode node);
|
T visit(OsAccounts.OsAccountNode node);
|
||||||
|
|
||||||
T visit(OsAccounts.OsAccountListNode node);
|
T visit(OsAccounts.OsAccountListNode node);
|
||||||
|
|
||||||
T visit(PersonNode node);
|
T visit(PersonNode node);
|
||||||
@ -197,12 +195,12 @@ public interface DisplayableItemNodeVisitor<T> {
|
|||||||
T visit(HostNode node);
|
T visit(HostNode node);
|
||||||
|
|
||||||
T visit(DataSourcesNode node);
|
T visit(DataSourcesNode node);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Unsupported node
|
* Unsupported node
|
||||||
*/
|
*/
|
||||||
T visit(UnsupportedContentNode ucn);
|
T visit(UnsupportedContentNode ucn);
|
||||||
|
|
||||||
T visit(LocalFilesDataSourceNode lfdsn);
|
T visit(LocalFilesDataSourceNode lfdsn);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -332,11 +330,6 @@ public interface DisplayableItemNodeVisitor<T> {
|
|||||||
return defaultVisit(ftByMimeTypeEmptyNode);
|
return defaultVisit(ftByMimeTypeEmptyNode);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public T visit(InterestingHits.InterestingItemTypeNode interestingItemTypeNode) {
|
|
||||||
return defaultVisit(interestingItemTypeNode);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public T visit(DeletedContentNode dcn) {
|
public T visit(DeletedContentNode dcn) {
|
||||||
return defaultVisit(dcn);
|
return defaultVisit(dcn);
|
||||||
@ -546,12 +539,12 @@ public interface DisplayableItemNodeVisitor<T> {
|
|||||||
public T visit(AttachmentNode node) {
|
public T visit(AttachmentNode node) {
|
||||||
return defaultVisit(node);
|
return defaultVisit(node);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public T visit(OsAccounts.OsAccountNode node) {
|
public T visit(OsAccounts.OsAccountNode node) {
|
||||||
return defaultVisit(node);
|
return defaultVisit(node);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public T visit(OsAccounts.OsAccountListNode node) {
|
public T visit(OsAccounts.OsAccountListNode node) {
|
||||||
return defaultVisit(node);
|
return defaultVisit(node);
|
||||||
@ -571,12 +564,12 @@ public interface DisplayableItemNodeVisitor<T> {
|
|||||||
public T visit(PersonNode node) {
|
public T visit(PersonNode node) {
|
||||||
return defaultVisit(node);
|
return defaultVisit(node);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public T visit(UnsupportedContentNode node) {
|
public T visit(UnsupportedContentNode node) {
|
||||||
return defaultVisit(node);
|
return defaultVisit(node);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public T visit(LocalFilesDataSourceNode node) {
|
public T visit(LocalFilesDataSourceNode node) {
|
||||||
return defaultVisit(node);
|
return defaultVisit(node);
|
||||||
|
@ -51,70 +51,95 @@ import org.sleuthkit.datamodel.BlackboardAttribute;
|
|||||||
import org.sleuthkit.datamodel.SleuthkitCase;
|
import org.sleuthkit.datamodel.SleuthkitCase;
|
||||||
import org.sleuthkit.datamodel.SleuthkitCase.CaseDbQuery;
|
import org.sleuthkit.datamodel.SleuthkitCase.CaseDbQuery;
|
||||||
import org.sleuthkit.datamodel.TskCoreException;
|
import org.sleuthkit.datamodel.TskCoreException;
|
||||||
import org.sleuthkit.autopsy.datamodel.Artifacts.UpdatableCountTypeNode;
|
|
||||||
import org.sleuthkit.datamodel.AnalysisResult;
|
import org.sleuthkit.datamodel.AnalysisResult;
|
||||||
|
import org.sleuthkit.autopsy.datamodel.Artifacts.UpdatableCountTypeNode;
|
||||||
|
|
||||||
public class InterestingHits implements AutopsyVisitableItem {
|
public class InterestingHits implements AutopsyVisitableItem {
|
||||||
|
|
||||||
private static final String INTERESTING_ITEMS = NbBundle
|
|
||||||
.getMessage(InterestingHits.class, "InterestingHits.interestingItems.text");
|
|
||||||
private static final String DISPLAY_NAME = NbBundle.getMessage(InterestingHits.class, "InterestingHits.displayName.text");
|
|
||||||
private static final Logger logger = Logger.getLogger(InterestingHits.class.getName());
|
private static final Logger logger = Logger.getLogger(InterestingHits.class.getName());
|
||||||
private static final Set<IngestManager.IngestJobEvent> INGEST_JOB_EVENTS_OF_INTEREST = EnumSet.of(IngestManager.IngestJobEvent.COMPLETED, IngestManager.IngestJobEvent.CANCELLED);
|
private static final Set<IngestManager.IngestJobEvent> INGEST_JOB_EVENTS_OF_INTEREST = EnumSet.of(IngestManager.IngestJobEvent.COMPLETED, IngestManager.IngestJobEvent.CANCELLED);
|
||||||
private static final Set<IngestManager.IngestModuleEvent> INGEST_MODULE_EVENTS_OF_INTEREST = EnumSet.of(IngestManager.IngestModuleEvent.DATA_ADDED);
|
private static final Set<IngestManager.IngestModuleEvent> INGEST_MODULE_EVENTS_OF_INTEREST = EnumSet.of(IngestManager.IngestModuleEvent.DATA_ADDED);
|
||||||
|
|
||||||
private SleuthkitCase skCase;
|
private SleuthkitCase skCase;
|
||||||
private final InterestingResults interestingResults = new InterestingResults();
|
private final InterestingResults interestingResults = new InterestingResults();
|
||||||
private final long filteringDSObjId; // 0 if not filtering/grouping by data source
|
private final long filteringDSObjId; // 0 if not filtering/grouping by data source
|
||||||
|
private final BlackboardArtifact.Type artifactType;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor
|
* Constructor
|
||||||
*
|
*
|
||||||
* @param skCase Case DB
|
* @param skCase Case DB
|
||||||
|
* @param artifactType The artifact type (either interesting file or
|
||||||
|
* artifact).
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
public InterestingHits(SleuthkitCase skCase) {
|
public InterestingHits(SleuthkitCase skCase, BlackboardArtifact.Type artifactType) {
|
||||||
this(skCase, 0);
|
this(skCase, artifactType, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor
|
* Constructor
|
||||||
*
|
*
|
||||||
* @param skCase Case DB
|
* @param skCase Case DB
|
||||||
* @param objId Object id of the data source
|
* @param artifactType The artifact type (either interesting file or
|
||||||
|
* artifact).
|
||||||
|
* @param objId Object id of the data source
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
public InterestingHits(SleuthkitCase skCase, long objId) {
|
public InterestingHits(SleuthkitCase skCase, BlackboardArtifact.Type artifactType, long objId) {
|
||||||
this.skCase = skCase;
|
this.skCase = skCase;
|
||||||
|
this.artifactType = artifactType;
|
||||||
this.filteringDSObjId = objId;
|
this.filteringDSObjId = objId;
|
||||||
interestingResults.update();
|
interestingResults.update();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cache of result ids mapped by artifact type -> set name -> artifact id.
|
||||||
|
*/
|
||||||
private class InterestingResults extends Observable {
|
private class InterestingResults extends Observable {
|
||||||
|
|
||||||
// NOTE: the map can be accessed by multiple worker threads and needs to be synchronized
|
// NOTE: the map can be accessed by multiple worker threads and needs to be synchronized
|
||||||
private final Map<String, Map<String, Set<Long>>> interestingItemsMap = new LinkedHashMap<>();
|
private final Map<String, Set<Long>> interestingItemsMap = new LinkedHashMap<>();
|
||||||
|
|
||||||
public List<String> getSetNames() {
|
/**
|
||||||
|
* Returns all the set names for a given interesting item type.
|
||||||
|
*
|
||||||
|
* @param type The interesting item type.
|
||||||
|
*
|
||||||
|
* @return The set names.
|
||||||
|
*/
|
||||||
|
List<String> getSetNames() {
|
||||||
List<String> setNames;
|
List<String> setNames;
|
||||||
synchronized (interestingItemsMap) {
|
synchronized (interestingItemsMap) {
|
||||||
setNames = new ArrayList<>(interestingItemsMap.keySet());
|
setNames = new ArrayList<>(interestingItemsMap.keySet());
|
||||||
}
|
}
|
||||||
Collections.sort(setNames);
|
Collections.sort(setNames, (a, b) -> a.compareToIgnoreCase(b));
|
||||||
return setNames;
|
return setNames;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Set<Long> getArtifactIds(String setName, String typeName) {
|
/**
|
||||||
|
* Returns all artifact ids belonging to the specified interesting item
|
||||||
|
* type and set name.
|
||||||
|
*
|
||||||
|
* @param type The interesting item type.
|
||||||
|
* @param setName The set name.
|
||||||
|
*
|
||||||
|
* @return The artifact ids in that set name and type.
|
||||||
|
*/
|
||||||
|
Set<Long> getArtifactIds(String setName) {
|
||||||
synchronized (interestingItemsMap) {
|
synchronized (interestingItemsMap) {
|
||||||
return interestingItemsMap.get(setName).get(typeName);
|
return new HashSet<>(interestingItemsMap.getOrDefault(setName, Collections.emptySet()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void update() {
|
/**
|
||||||
|
* Triggers a fetch from the database to update this cache.
|
||||||
|
*/
|
||||||
|
void update() {
|
||||||
synchronized (interestingItemsMap) {
|
synchronized (interestingItemsMap) {
|
||||||
interestingItemsMap.clear();
|
interestingItemsMap.clear();
|
||||||
}
|
}
|
||||||
loadArtifacts(BlackboardArtifact.Type.TSK_INTERESTING_FILE_HIT);
|
loadArtifacts();
|
||||||
loadArtifacts(BlackboardArtifact.Type.TSK_INTERESTING_ARTIFACT_HIT);
|
|
||||||
setChanged();
|
setChanged();
|
||||||
notifyObservers();
|
notifyObservers();
|
||||||
}
|
}
|
||||||
@ -124,18 +149,18 @@ public class InterestingHits implements AutopsyVisitableItem {
|
|||||||
* the interestingItemsMap
|
* the interestingItemsMap
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("deprecation")
|
@SuppressWarnings("deprecation")
|
||||||
private void loadArtifacts(BlackboardArtifact.Type artType) {
|
private void loadArtifacts() {
|
||||||
if (skCase == null) {
|
if (skCase == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
int setNameId = BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SET_NAME.getTypeID();
|
int setNameId = BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SET_NAME.getTypeID();
|
||||||
int artId = artType.getTypeID();
|
|
||||||
String query = "SELECT value_text,blackboard_artifacts.artifact_obj_id,attribute_type_id " //NON-NLS
|
String query = "SELECT value_text, blackboard_artifacts.artifact_obj_id " //NON-NLS
|
||||||
+ "FROM blackboard_attributes,blackboard_artifacts WHERE " //NON-NLS
|
+ "FROM blackboard_attributes,blackboard_artifacts WHERE " //NON-NLS
|
||||||
+ "attribute_type_id=" + setNameId //NON-NLS
|
+ "attribute_type_id=" + setNameId //NON-NLS
|
||||||
+ " AND blackboard_attributes.artifact_id=blackboard_artifacts.artifact_id" //NON-NLS
|
+ " AND blackboard_attributes.artifact_id=blackboard_artifacts.artifact_id" //NON-NLS
|
||||||
+ " AND blackboard_artifacts.artifact_type_id=" + artId; //NON-NLS
|
+ " AND blackboard_artifacts.artifact_type_id = " + artifactType.getTypeID(); //NON-NLS
|
||||||
if (filteringDSObjId > 0) {
|
if (filteringDSObjId > 0) {
|
||||||
query += " AND blackboard_artifacts.data_source_obj_id = " + filteringDSObjId;
|
query += " AND blackboard_artifacts.data_source_obj_id = " + filteringDSObjId;
|
||||||
}
|
}
|
||||||
@ -146,12 +171,9 @@ public class InterestingHits implements AutopsyVisitableItem {
|
|||||||
while (resultSet.next()) {
|
while (resultSet.next()) {
|
||||||
String value = resultSet.getString("value_text"); //NON-NLS
|
String value = resultSet.getString("value_text"); //NON-NLS
|
||||||
long artifactObjId = resultSet.getLong("artifact_obj_id"); //NON-NLS
|
long artifactObjId = resultSet.getLong("artifact_obj_id"); //NON-NLS
|
||||||
if (!interestingItemsMap.containsKey(value)) {
|
interestingItemsMap
|
||||||
interestingItemsMap.put(value, new LinkedHashMap<>());
|
.computeIfAbsent(value, (k) -> new HashSet<>())
|
||||||
interestingItemsMap.get(value).put(BlackboardArtifact.Type.TSK_INTERESTING_FILE_HIT.getDisplayName(), new HashSet<>());
|
.add(artifactObjId);
|
||||||
interestingItemsMap.get(value).put(BlackboardArtifact.Type.TSK_INTERESTING_ARTIFACT_HIT.getDisplayName(), new HashSet<>());
|
|
||||||
}
|
|
||||||
interestingItemsMap.get(value).get(artType.getDisplayName()).add(artifactObjId);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (TskCoreException | SQLException ex) {
|
} catch (TskCoreException | SQLException ex) {
|
||||||
@ -166,54 +188,8 @@ public class InterestingHits implements AutopsyVisitableItem {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Node for the interesting items
|
* Creates nodes for all sets for a specified interesting item type.
|
||||||
*/
|
*/
|
||||||
public class RootNode extends UpdatableCountTypeNode {
|
|
||||||
|
|
||||||
public RootNode() {
|
|
||||||
super(Children.create(new SetNameFactory(), true),
|
|
||||||
Lookups.singleton(DISPLAY_NAME),
|
|
||||||
DISPLAY_NAME,
|
|
||||||
filteringDSObjId,
|
|
||||||
BlackboardArtifact.Type.TSK_INTERESTING_ARTIFACT_HIT,
|
|
||||||
BlackboardArtifact.Type.TSK_INTERESTING_FILE_HIT);
|
|
||||||
super.setName(INTERESTING_ITEMS);
|
|
||||||
this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/interesting_item.png"); //NON-NLS
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isLeafTypeNode() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public <T> T accept(DisplayableItemNodeVisitor<T> visitor) {
|
|
||||||
return visitor.visit(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected Sheet createSheet() {
|
|
||||||
Sheet sheet = super.createSheet();
|
|
||||||
Sheet.Set sheetSet = sheet.get(Sheet.PROPERTIES);
|
|
||||||
if (sheetSet == null) {
|
|
||||||
sheetSet = Sheet.createPropertiesSet();
|
|
||||||
sheet.put(sheetSet);
|
|
||||||
}
|
|
||||||
|
|
||||||
sheetSet.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "InterestingHits.createSheet.name.name"),
|
|
||||||
NbBundle.getMessage(this.getClass(), "InterestingHits.createSheet.name.displayName"),
|
|
||||||
NbBundle.getMessage(this.getClass(), "InterestingHits.createSheet.name.desc"),
|
|
||||||
getName()));
|
|
||||||
|
|
||||||
return sheet;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getItemType() {
|
|
||||||
return getClass().getName();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class SetNameFactory extends ChildFactory.Detachable<String> implements Observer {
|
private class SetNameFactory extends ChildFactory.Detachable<String> implements Observer {
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -239,8 +215,7 @@ public class InterestingHits implements AutopsyVisitableItem {
|
|||||||
* event to have a null oldValue.
|
* event to have a null oldValue.
|
||||||
*/
|
*/
|
||||||
ModuleDataEvent eventData = (ModuleDataEvent) evt.getOldValue();
|
ModuleDataEvent eventData = (ModuleDataEvent) evt.getOldValue();
|
||||||
if (null != eventData && (eventData.getBlackboardArtifactType().getTypeID() == BlackboardArtifact.Type.TSK_INTERESTING_ARTIFACT_HIT.getTypeID()
|
if (null != eventData && (eventData.getBlackboardArtifactType().getTypeID() == artifactType.getTypeID())) {
|
||||||
|| eventData.getBlackboardArtifactType().getTypeID() == BlackboardArtifact.Type.TSK_INTERESTING_FILE_HIT.getTypeID())) {
|
|
||||||
interestingResults.update();
|
interestingResults.update();
|
||||||
}
|
}
|
||||||
} catch (NoCurrentCaseException notUsed) {
|
} catch (NoCurrentCaseException notUsed) {
|
||||||
@ -272,26 +247,8 @@ public class InterestingHits implements AutopsyVisitableItem {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
private final PropertyChangeListener weakPcl = WeakListeners.propertyChange(pcl, null);
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void addNotify() {
|
|
||||||
IngestManager.getInstance().addIngestJobEventListener(INGEST_JOB_EVENTS_OF_INTEREST, weakPcl);
|
|
||||||
IngestManager.getInstance().addIngestModuleEventListener(INGEST_MODULE_EVENTS_OF_INTEREST, weakPcl);
|
|
||||||
Case.addEventTypeSubscriber(EnumSet.of(Case.Events.CURRENT_CASE), weakPcl);
|
|
||||||
interestingResults.update();
|
|
||||||
interestingResults.addObserver(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
private final PropertyChangeListener weakPcl = WeakListeners.propertyChange(pcl, null);
|
||||||
protected void finalize() throws Throwable {
|
|
||||||
super.finalize();
|
|
||||||
IngestManager.getInstance().removeIngestJobEventListener(weakPcl);
|
|
||||||
IngestManager.getInstance().removeIngestModuleEventListener(weakPcl);
|
|
||||||
Case.removeEventTypeSubscriber(EnumSet.of(Case.Events.CURRENT_CASE), weakPcl);
|
|
||||||
interestingResults.deleteObserver(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean createKeys(List<String> list) {
|
protected boolean createKeys(List<String> list) {
|
||||||
@ -308,14 +265,35 @@ public class InterestingHits implements AutopsyVisitableItem {
|
|||||||
public void update(Observable o, Object arg) {
|
public void update(Observable o, Object arg) {
|
||||||
refresh(true);
|
refresh(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void addNotify() {
|
||||||
|
IngestManager.getInstance().addIngestJobEventListener(INGEST_JOB_EVENTS_OF_INTEREST, weakPcl);
|
||||||
|
IngestManager.getInstance().addIngestModuleEventListener(INGEST_MODULE_EVENTS_OF_INTEREST, weakPcl);
|
||||||
|
Case.addEventTypeSubscriber(EnumSet.of(Case.Events.CURRENT_CASE), weakPcl);
|
||||||
|
interestingResults.addObserver(this);
|
||||||
|
interestingResults.update();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void finalize() throws Throwable {
|
||||||
|
super.finalize();
|
||||||
|
IngestManager.getInstance().removeIngestJobEventListener(weakPcl);
|
||||||
|
IngestManager.getInstance().removeIngestModuleEventListener(weakPcl);
|
||||||
|
Case.removeEventTypeSubscriber(EnumSet.of(Case.Events.CURRENT_CASE), weakPcl);
|
||||||
|
interestingResults.deleteObserver(this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A node for a set to be displayed in the tree.
|
||||||
|
*/
|
||||||
public class SetNameNode extends DisplayableItemNode implements Observer {
|
public class SetNameNode extends DisplayableItemNode implements Observer {
|
||||||
|
|
||||||
private final String setName;
|
private final String setName;
|
||||||
|
|
||||||
public SetNameNode(String setName) {//, Set<Long> children) {
|
public SetNameNode(String setName) {//, Set<Long> children) {
|
||||||
super(Children.create(new HitTypeFactory(setName), true), Lookups.singleton(setName));
|
super(Children.create(new HitFactory(setName), true), Lookups.singleton(setName));
|
||||||
this.setName = setName;
|
this.setName = setName;
|
||||||
super.setName(setName);
|
super.setName(setName);
|
||||||
updateDisplayName();
|
updateDisplayName();
|
||||||
@ -324,111 +302,81 @@ public class InterestingHits implements AutopsyVisitableItem {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void updateDisplayName() {
|
private void updateDisplayName() {
|
||||||
int sizeOfSet = interestingResults.getArtifactIds(setName, BlackboardArtifact.Type.TSK_INTERESTING_ARTIFACT_HIT.getDisplayName()).size()
|
int sizeOfSet = interestingResults.getArtifactIds(setName).size();
|
||||||
+ interestingResults.getArtifactIds(setName, BlackboardArtifact.Type.TSK_INTERESTING_FILE_HIT.getDisplayName()).size();
|
|
||||||
super.setDisplayName(setName + " (" + sizeOfSet + ")");
|
super.setDisplayName(setName + " (" + sizeOfSet + ")");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isLeafTypeNode() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Sheet createSheet() {
|
||||||
|
Sheet sheet = super.createSheet();
|
||||||
|
Sheet.Set sheetSet = sheet.get(Sheet.PROPERTIES);
|
||||||
|
if (sheetSet == null) {
|
||||||
|
sheetSet = Sheet.createPropertiesSet();
|
||||||
|
sheet.put(sheetSet);
|
||||||
|
}
|
||||||
|
|
||||||
|
sheetSet.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "InterestingHits.createSheet.name.name"),
|
||||||
|
NbBundle.getMessage(this.getClass(), "InterestingHits.createSheet.name.name"),
|
||||||
|
NbBundle.getMessage(this.getClass(), "InterestingHits.createSheet.name.desc"),
|
||||||
|
getName()));
|
||||||
|
|
||||||
|
return sheet;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T> T accept(DisplayableItemNodeVisitor<T> visitor) {
|
||||||
|
return visitor.visit(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void update(Observable o, Object arg) {
|
||||||
|
updateDisplayName();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getItemType() {
|
||||||
|
/**
|
||||||
|
* For custom settings for each rule set, return
|
||||||
|
* getClass().getName() + setName instead.
|
||||||
|
*/
|
||||||
|
return getClass().getName();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parent node for interesting item type that shows child set nodes.
|
||||||
|
*/
|
||||||
|
public class RootNode extends UpdatableCountTypeNode {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Main constructor.
|
||||||
|
*/
|
||||||
|
public RootNode() {
|
||||||
|
super(Children.create(new SetNameFactory(), true),
|
||||||
|
Lookups.singleton(artifactType),
|
||||||
|
artifactType.getDisplayName(),
|
||||||
|
filteringDSObjId,
|
||||||
|
artifactType);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* We use the combination of setName and typeName as the name of the
|
||||||
|
* node to ensure that nodes have a unique name. This comes into
|
||||||
|
* play when associating paging state with the node.
|
||||||
|
*/
|
||||||
|
setName(artifactType.getDisplayName());
|
||||||
|
this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/interesting_item.png"); //NON-NLS
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isLeafTypeNode() {
|
public boolean isLeafTypeNode() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
protected Sheet createSheet() {
|
|
||||||
Sheet sheet = super.createSheet();
|
|
||||||
Sheet.Set sheetSet = sheet.get(Sheet.PROPERTIES);
|
|
||||||
if (sheetSet == null) {
|
|
||||||
sheetSet = Sheet.createPropertiesSet();
|
|
||||||
sheet.put(sheetSet);
|
|
||||||
}
|
|
||||||
|
|
||||||
sheetSet.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "InterestingHits.createSheet.name.name"),
|
|
||||||
NbBundle.getMessage(this.getClass(), "InterestingHits.createSheet.name.name"),
|
|
||||||
NbBundle.getMessage(this.getClass(), "InterestingHits.createSheet.name.desc"),
|
|
||||||
getName()));
|
|
||||||
|
|
||||||
return sheet;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public <T> T accept(DisplayableItemNodeVisitor<T> visitor) {
|
|
||||||
return visitor.visit(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void update(Observable o, Object arg) {
|
|
||||||
updateDisplayName();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getItemType() {
|
|
||||||
/**
|
|
||||||
* For custom settings for each rule set, return
|
|
||||||
* getClass().getName() + setName instead.
|
|
||||||
*/
|
|
||||||
return getClass().getName();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class HitTypeFactory extends ChildFactory<String> implements Observer {
|
|
||||||
|
|
||||||
private final String setName;
|
|
||||||
private final Map<Long, BlackboardArtifact> artifactHits = new HashMap<>();
|
|
||||||
|
|
||||||
private HitTypeFactory(String setName) {
|
|
||||||
super();
|
|
||||||
this.setName = setName;
|
|
||||||
interestingResults.addObserver(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected boolean createKeys(List<String> list) {
|
|
||||||
list.add(BlackboardArtifact.Type.TSK_INTERESTING_FILE_HIT.getDisplayName());
|
|
||||||
list.add(BlackboardArtifact.Type.TSK_INTERESTING_ARTIFACT_HIT.getDisplayName());
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected Node createNodeForKey(String key) {
|
|
||||||
return new InterestingItemTypeNode(setName, key);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void update(Observable o, Object arg) {
|
|
||||||
refresh(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class InterestingItemTypeNode extends DisplayableItemNode implements Observer {
|
|
||||||
|
|
||||||
private final String typeName;
|
|
||||||
private final String setName;
|
|
||||||
|
|
||||||
private InterestingItemTypeNode(String setName, String typeName) {
|
|
||||||
super(Children.create(new HitFactory(setName, typeName), true), Lookups.singleton(setName));
|
|
||||||
this.typeName = typeName;
|
|
||||||
this.setName = setName;
|
|
||||||
/**
|
|
||||||
* We use the combination of setName and typeName as the name of the
|
|
||||||
* node to ensure that nodes have a unique name. This comes into
|
|
||||||
* play when associating paging state with the node.
|
|
||||||
*/
|
|
||||||
super.setName(setName + "_" + typeName);
|
|
||||||
updateDisplayName();
|
|
||||||
this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/interesting_item.png"); //NON-NLS
|
|
||||||
interestingResults.addObserver(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateDisplayName() {
|
|
||||||
super.setDisplayName(typeName + " (" + interestingResults.getArtifactIds(setName, typeName).size() + ")");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isLeafTypeNode() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Sheet createSheet() {
|
protected Sheet createSheet() {
|
||||||
Sheet sheet = super.createSheet();
|
Sheet sheet = super.createSheet();
|
||||||
@ -449,11 +397,6 @@ public class InterestingHits implements AutopsyVisitableItem {
|
|||||||
return visitor.visit(this);
|
return visitor.visit(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void update(Observable o, Object arg) {
|
|
||||||
updateDisplayName();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getItemType() {
|
public String getItemType() {
|
||||||
/**
|
/**
|
||||||
@ -464,21 +407,27 @@ public class InterestingHits implements AutopsyVisitableItem {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Factory for creating individual interesting item BlackboardArtifactNodes.
|
||||||
|
*/
|
||||||
private class HitFactory extends BaseChildFactory<AnalysisResult> implements Observer {
|
private class HitFactory extends BaseChildFactory<AnalysisResult> implements Observer {
|
||||||
|
|
||||||
private final String setName;
|
private final String setName;
|
||||||
private final String typeName;
|
|
||||||
private final Map<Long, AnalysisResult> artifactHits = new HashMap<>();
|
private final Map<Long, AnalysisResult> artifactHits = new HashMap<>();
|
||||||
|
|
||||||
private HitFactory(String setName, String typeName) {
|
/**
|
||||||
|
* Main constructor.
|
||||||
|
*
|
||||||
|
* @param setName The set name of artifacts to be displayed.
|
||||||
|
*/
|
||||||
|
private HitFactory(String setName) {
|
||||||
/**
|
/**
|
||||||
* The node name passed to the parent constructor must be the same
|
* The node name passed to the parent constructor must be the same
|
||||||
* as the name set in the InterestingItemTypeNode constructor, i.e.
|
* as the name set in the InterestingItemTypeNode constructor, i.e.
|
||||||
* setName underscore typeName
|
* setName underscore typeName
|
||||||
*/
|
*/
|
||||||
super(setName + "_" + typeName);
|
super(setName);
|
||||||
this.setName = setName;
|
this.setName = setName;
|
||||||
this.typeName = typeName;
|
|
||||||
interestingResults.addObserver(this);
|
interestingResults.addObserver(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -486,7 +435,7 @@ public class InterestingHits implements AutopsyVisitableItem {
|
|||||||
protected List<AnalysisResult> makeKeys() {
|
protected List<AnalysisResult> makeKeys() {
|
||||||
|
|
||||||
if (skCase != null) {
|
if (skCase != null) {
|
||||||
interestingResults.getArtifactIds(setName, typeName).forEach((id) -> {
|
interestingResults.getArtifactIds(setName).forEach((id) -> {
|
||||||
try {
|
try {
|
||||||
if (!artifactHits.containsKey(id)) {
|
if (!artifactHits.containsKey(id)) {
|
||||||
AnalysisResult art = skCase.getBlackboard().getAnalysisResultById(id);
|
AnalysisResult art = skCase.getBlackboard().getAnalysisResultById(id);
|
||||||
|
@ -1271,9 +1271,10 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat
|
|||||||
treeNode = getHashsetNode(typesChildren, art);
|
treeNode = getHashsetNode(typesChildren, art);
|
||||||
} else if (typeID == BlackboardArtifact.Type.TSK_KEYWORD_HIT.getTypeID()) {
|
} else if (typeID == BlackboardArtifact.Type.TSK_KEYWORD_HIT.getTypeID()) {
|
||||||
treeNode = getKeywordHitNode(typesChildren, art);
|
treeNode = getKeywordHitNode(typesChildren, art);
|
||||||
} else if (typeID == BlackboardArtifact.Type.TSK_INTERESTING_FILE_HIT.getTypeID()
|
} else if (typeID == BlackboardArtifact.Type.TSK_INTERESTING_FILE_HIT.getTypeID()) {
|
||||||
|| typeID == BlackboardArtifact.Type.TSK_INTERESTING_ARTIFACT_HIT.getTypeID()) {
|
treeNode = getInterestingItemNode(typesChildren, BlackboardArtifact.Type.TSK_INTERESTING_FILE_HIT, art);
|
||||||
treeNode = getInterestingItemNode(typesChildren, art);
|
} else if (typeID == BlackboardArtifact.Type.TSK_INTERESTING_ARTIFACT_HIT.getTypeID()) {
|
||||||
|
treeNode = getInterestingItemNode(typesChildren, BlackboardArtifact.Type.TSK_INTERESTING_ARTIFACT_HIT, art);
|
||||||
} else if (typeID == BlackboardArtifact.Type.TSK_EMAIL_MSG.getTypeID()) {
|
} else if (typeID == BlackboardArtifact.Type.TSK_EMAIL_MSG.getTypeID()) {
|
||||||
treeNode = getEmailNode(typesChildren, art);
|
treeNode = getEmailNode(typesChildren, art);
|
||||||
} else if (typeID == BlackboardArtifact.Type.TSK_ACCOUNT.getTypeID()) {
|
} else if (typeID == BlackboardArtifact.Type.TSK_ACCOUNT.getTypeID()) {
|
||||||
@ -1401,42 +1402,46 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat
|
|||||||
*
|
*
|
||||||
* @param typesChildren The children object of the same category as
|
* @param typesChildren The children object of the same category as
|
||||||
* interesting item.
|
* interesting item.
|
||||||
|
* @param artifactType The type of the artifact (interesting hit or
|
||||||
|
* artifact).
|
||||||
* @param art The artifact.
|
* @param art The artifact.
|
||||||
*
|
*
|
||||||
* @return The interesting item artifact's parent node or null if cannot be
|
* @return The interesting item artifact's parent node or null if cannot be
|
||||||
* found.
|
* found.
|
||||||
*/
|
*/
|
||||||
private Node getInterestingItemNode(Children typesChildren, BlackboardArtifact art) {
|
private Node getInterestingItemNode(Children typesChildren, BlackboardArtifact.Type artifactType, BlackboardArtifact art) {
|
||||||
Node interestingItemsRootNode = typesChildren.findChild(NbBundle
|
Node interestingItemsRootNode = typesChildren.findChild(artifactType.getDisplayName());
|
||||||
.getMessage(InterestingHits.class, "InterestingHits.interestingItems.text"));
|
Children setNodeChildren = (interestingItemsRootNode == null) ? null : interestingItemsRootNode.getChildren();
|
||||||
Children interestingItemsRootChildren = interestingItemsRootNode.getChildren();
|
|
||||||
|
// set node children for type could not be found, so return null.
|
||||||
|
if (setNodeChildren == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
String setName = null;
|
||||||
try {
|
try {
|
||||||
String setName = null;
|
setName = art.getAttributes().stream()
|
||||||
List<BlackboardAttribute> attributes = art.getAttributes();
|
.filter(attr -> attr.getAttributeType().getTypeID() == BlackboardAttribute.Type.TSK_SET_NAME.getTypeID())
|
||||||
for (BlackboardAttribute att : attributes) {
|
.map(attr -> attr.getValueString())
|
||||||
int typeId = att.getAttributeType().getTypeID();
|
.findFirst()
|
||||||
if (typeId == BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SET_NAME.getTypeID()) {
|
.orElse(null);
|
||||||
setName = att.getValueString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Node setNode = interestingItemsRootChildren.findChild(setName);
|
|
||||||
if (setNode == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
Children fileArtifactChildren = setNode.getChildren();
|
|
||||||
Node[] fileArtifactNodes = fileArtifactChildren == null ? null : fileArtifactChildren.getNodes();
|
|
||||||
if (fileArtifactNodes == null || fileArtifactNodes.length != 2) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (art.getArtifactTypeID() == BlackboardArtifact.Type.TSK_INTERESTING_FILE_HIT.getTypeID())
|
|
||||||
? fileArtifactNodes[0]
|
|
||||||
: fileArtifactNodes[1];
|
|
||||||
} catch (TskCoreException ex) {
|
} catch (TskCoreException ex) {
|
||||||
LOGGER.log(Level.WARNING, "Error retrieving attributes", ex); //NON-NLS
|
LOGGER.log(Level.WARNING, "Error retrieving attributes", ex); //NON-NLS
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// if no set name, no set node will be identified.
|
||||||
|
if (setName == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// make sure data is fully loaded
|
||||||
|
final String finalSetName = setName;
|
||||||
|
return Stream.of(setNodeChildren.getNodes(true))
|
||||||
|
.filter(setNode -> finalSetName.equals(setNode.getLookup().lookup(String.class)))
|
||||||
|
.findFirst()
|
||||||
|
.orElse(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
Loading…
x
Reference in New Issue
Block a user