/*
* Autopsy Forensic Browser
*
* Copyright 2011-2015 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sleuthkit.autopsy.datamodel;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Observable;
import java.util.Observer;
import java.util.Set;
import java.util.logging.Level;
import org.openide.nodes.ChildFactory;
import org.openide.nodes.Children;
import org.openide.nodes.Node;
import org.openide.nodes.Sheet;
import org.openide.util.NbBundle;
import org.openide.util.lookup.Lookups;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.ingest.IngestManager;
import org.sleuthkit.autopsy.ingest.ModuleDataEvent;
import org.sleuthkit.datamodel.BlackboardArtifact;
import org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE;
import org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE;
import org.sleuthkit.datamodel.SleuthkitCase;
import org.sleuthkit.datamodel.SleuthkitCase.CaseDbQuery;
import org.sleuthkit.datamodel.TskCoreException;
import org.sleuthkit.datamodel.TskException;
/**
* Hash set hits node support. Inner classes have all of the nodes in the tree.
*/
public class HashsetHits implements AutopsyVisitableItem {
private static final String HASHSET_HITS = BlackboardArtifact.ARTIFACT_TYPE.TSK_HASHSET_HIT.getLabel();
private static final String DISPLAY_NAME = BlackboardArtifact.ARTIFACT_TYPE.TSK_HASHSET_HIT.getDisplayName();
private static final Logger logger = Logger.getLogger(HashsetHits.class.getName());
private SleuthkitCase skCase;
private final HashsetResults hashsetResults;
public HashsetHits(SleuthkitCase skCase) {
this.skCase = skCase;
hashsetResults = new HashsetResults();
}
@Override
public <T> T accept(AutopsyItemVisitor<T> v) {
return v.visit(this);
}
/**
* Stores all of the hashset results in a single class that is observable
* for the child nodes
*/
private class HashsetResults extends Observable {
// maps hashset name to list of artifacts for that set
// NOTE: the map can be accessed by multiple worker threads and needs to be synchronized
private final Map<String, Set<Long>> hashSetHitsMap = new LinkedHashMap<>();
HashsetResults() {
update();
}
List<String> getSetNames() {
List<String> names;
synchronized (hashSetHitsMap) {
names = new ArrayList<>(hashSetHitsMap.keySet());
}
Collections.sort(names);
return names;
}
Set<Long> getArtifactIds(String hashSetName) {
synchronized (hashSetHitsMap) {
return hashSetHitsMap.get(hashSetName);
}
}
@SuppressWarnings("deprecation")
final void update() {
synchronized (hashSetHitsMap) {
hashSetHitsMap.clear();
}
if (skCase == null) {
return;
}
int setNameId = ATTRIBUTE_TYPE.TSK_SET_NAME.getTypeID();
int artId = ARTIFACT_TYPE.TSK_HASHSET_HIT.getTypeID();
String query = "SELECT value_text,blackboard_attributes.artifact_id,attribute_type_id " //NON-NLS
+ "FROM blackboard_attributes,blackboard_artifacts WHERE " //NON-NLS
+ "attribute_type_id=" + setNameId //NON-NLS
+ " AND blackboard_attributes.artifact_id=blackboard_artifacts.artifact_id" //NON-NLS
+ " AND blackboard_artifacts.artifact_type_id=" + artId; //NON-NLS
try (CaseDbQuery dbQuery = skCase.executeQuery(query)) {
ResultSet resultSet = dbQuery.getResultSet();
synchronized (hashSetHitsMap) {
while (resultSet.next()) {
String setName = resultSet.getString("value_text"); //NON-NLS
long artifactId = resultSet.getLong("artifact_id"); //NON-NLS
if (!hashSetHitsMap.containsKey(setName)) {
hashSetHitsMap.put(setName, new HashSet<Long>());
}
hashSetHitsMap.get(setName).add(artifactId);
}
}
} catch (TskCoreException | SQLException ex) {
logger.log(Level.WARNING, "SQL Exception occurred: ", ex); //NON-NLS
}
setChanged();
notifyObservers();
}
}
/**
* Top-level node for all hash sets
*/
public class RootNode extends DisplayableItemNode {
public RootNode() {
super(Children.create(new HashsetNameFactory(), true), Lookups.singleton(DISPLAY_NAME));
super.setName(HASHSET_HITS);
super.setDisplayName(DISPLAY_NAME);
this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/hashset_hits.png"); //NON-NLS
}
@Override
public boolean isLeafTypeNode() {
return false;
}
@Override
public <T> T accept(DisplayableItemNodeVisitor<T> v) {
return v.visit(this);
}
@Override
protected Sheet createSheet() {
Sheet s = super.createSheet();
Sheet.Set ss = s.get(Sheet.PROPERTIES);
if (ss == null) {
ss = Sheet.createPropertiesSet();
s.put(ss);
}
ss.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "HashsetHits.createSheet.name.name"),
NbBundle.getMessage(this.getClass(), "HashsetHits.createSheet.name.displayName"),
NbBundle.getMessage(this.getClass(), "HashsetHits.createSheet.name.desc"),
getName()));
return s;
}
@Override
public String getItemType() {
return getClass().getName();
}
}
/**
* Creates child nodes for each hashset name
*/
private class HashsetNameFactory extends ChildFactory.Detachable<String> implements Observer {
/*
* This should probably be in the HashsetHits class, but the factory has
* nice methods for its startup and shutdown, so it seemed like a
* cleaner place to register the property change listener.
*/
private final PropertyChangeListener pcl = new PropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent evt) {
String eventType = evt.getPropertyName();
if (eventType.equals(IngestManager.IngestModuleEvent.DATA_ADDED.toString())) {
/**
* Checking for a current case is a stop gap measure until a
* different way of handling the closing of cases is worked
* out. Currently, remote events may be received for a case
* that is already closed.
*/
try {
Case.getCurrentCase();
/**
* Due to some unresolved issues with how cases are
* closed, it is possible for the event to have a null
* oldValue if the event is a remote event.
*/
ModuleDataEvent eventData = (ModuleDataEvent) evt.getOldValue();
if (null != eventData && eventData.getBlackboardArtifactType().getTypeID() == ARTIFACT_TYPE.TSK_HASHSET_HIT.getTypeID()) {
hashsetResults.update();
}
} catch (IllegalStateException notUsed) {
/**
* Case is closed, do nothing.
*/
}
} else if (eventType.equals(IngestManager.IngestJobEvent.COMPLETED.toString())
|| eventType.equals(IngestManager.IngestJobEvent.CANCELLED.toString())) {
/**
* Checking for a current case is a stop gap measure until a
* different way of handling the closing of cases is worked
* out. Currently, remote events may be received for a case
* that is already closed.
*/
try {
Case.getCurrentCase();
hashsetResults.update();
} catch (IllegalStateException notUsed) {
/**
* Case is closed, do nothing.
*/
}
} else if (eventType.equals(Case.Events.CURRENT_CASE.toString())) {
// case was closed. Remove listeners so that we don't get called with a stale case handle
if (evt.getNewValue() == null) {
removeNotify();
skCase = null;
}
}
}
};
@Override
protected void addNotify() {
IngestManager.getInstance().addIngestJobEventListener(pcl);
IngestManager.getInstance().addIngestModuleEventListener(pcl);
Case.addPropertyChangeListener(pcl);
hashsetResults.update();
hashsetResults.addObserver(this);
}
@Override
protected void removeNotify() {
IngestManager.getInstance().removeIngestJobEventListener(pcl);
IngestManager.getInstance().removeIngestModuleEventListener(pcl);
Case.removePropertyChangeListener(pcl);
hashsetResults.deleteObserver(this);
}
@Override
protected boolean createKeys(List<String> list) {
list.addAll(hashsetResults.getSetNames());
return true;
}
@Override
protected Node createNodeForKey(String key) {
return new HashsetNameNode(key);
}
@Override
public void update(Observable o, Object arg) {
refresh(true);
}
}
/**
* Node for a hash set name
*/
public class HashsetNameNode extends DisplayableItemNode implements Observer {
private final String hashSetName;
public HashsetNameNode(String hashSetName) {
super(Children.create(new HitFactory(hashSetName), true), Lookups.singleton(hashSetName));
super.setName(hashSetName);
this.hashSetName = hashSetName;
updateDisplayName();
this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/hashset_hits.png"); //NON-NLS
hashsetResults.addObserver(this);
}
/**
* Update the count in the display name
*/
private void updateDisplayName() {
super.setDisplayName(hashSetName + " (" + hashsetResults.getArtifactIds(hashSetName).size() + ")");
}
@Override
public boolean isLeafTypeNode() {
return true;
}
@Override
protected Sheet createSheet() {
Sheet s = super.createSheet();
Sheet.Set ss = s.get(Sheet.PROPERTIES);
if (ss == null) {
ss = Sheet.createPropertiesSet();
s.put(ss);
}
ss.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "HashsetHits.createSheet.name.name"),
NbBundle.getMessage(this.getClass(), "HashsetHits.createSheet.name.displayName"),
NbBundle.getMessage(this.getClass(), "HashsetHits.createSheet.name.desc"),
getName()));
return s;
}
@Override
public <T> T accept(DisplayableItemNodeVisitor<T> v) {
return v.visit(this);
}
@Override
public void update(Observable o, Object arg) {
updateDisplayName();
}
@Override
public String getItemType() {
/**
* For custom settings for each hash set, return
* getClass().getName() + hashSetName instead.
*/
return getClass().getName();
}
}
/**
* Creates the nodes for the hits in a given set.
*/
private class HitFactory extends ChildFactory.Detachable<Long> implements Observer {
private String hashsetName;
private HitFactory(String hashsetName) {
super();
this.hashsetName = hashsetName;
}
@Override
protected void addNotify() {
hashsetResults.addObserver(this);
}
@Override
protected void removeNotify() {
hashsetResults.deleteObserver(this);
}
@Override
protected boolean createKeys(List<Long> list) {
list.addAll(hashsetResults.getArtifactIds(hashsetName));
return true;
}
@Override
protected Node createNodeForKey(Long id) {
if (skCase == null) {
return null;
}
try {
BlackboardArtifact art = skCase.getBlackboardArtifact(id);
return new BlackboardArtifactNode(art);
} catch (TskException ex) {
logger.log(Level.WARNING, "TSK Exception occurred", ex); //NON-NLS
}
return null;
}
@Override
public void update(Observable o, Object arg) {
refresh(true);
}
}
}