19 package org.sleuthkit.autopsy.communications;
21 import com.github.mustachejava.DefaultMustacheFactory;
22 import com.github.mustachejava.Mustache;
23 import com.google.common.collect.Multimap;
24 import com.google.common.collect.MultimapBuilder;
25 import com.mxgraph.model.mxCell;
26 import com.mxgraph.model.mxICell;
27 import com.mxgraph.util.mxConstants;
28 import com.mxgraph.view.mxGraph;
29 import com.mxgraph.view.mxStylesheet;
30 import java.io.InputStream;
31 import java.io.InputStreamReader;
32 import java.io.StringWriter;
34 import java.util.HashMap;
35 import java.util.HashSet;
36 import java.util.List;
39 import java.util.concurrent.CancellationException;
40 import java.util.concurrent.ExecutionException;
41 import java.util.logging.Level;
42 import javax.swing.SwingWorker;
56 final class CommunicationsGraph
extends mxGraph {
58 private static final Logger logger = Logger.getLogger(CommunicationsGraph.class.getName());
59 private static final URL MARKER_PIN_URL = CommunicationsGraph.class.getResource(
"/org/sleuthkit/autopsy/communications/images/marker--pin.png");
60 private static final URL LOCK_URL = CommunicationsGraph.class.getResource(
"/org/sleuthkit/autopsy/communications/images/lock_large_locked.png");
63 private final static Mustache labelMustache;
66 final InputStream templateStream = CommunicationsGraph.class.getResourceAsStream(
"/org/sleuthkit/autopsy/communications/Vertex_Label_template.html");
67 labelMustache =
new DefaultMustacheFactory().compile(
new InputStreamReader(templateStream),
"Vertex_Label");
72 static final private mxStylesheet mxStylesheet =
new mxStylesheet();
76 mxStylesheet.getDefaultVertexStyle().put(mxConstants.STYLE_SHAPE, mxConstants.SHAPE_ELLIPSE);
77 mxStylesheet.getDefaultVertexStyle().put(mxConstants.STYLE_PERIMETER, mxConstants.PERIMETER_ELLIPSE);
78 mxStylesheet.getDefaultVertexStyle().put(mxConstants.STYLE_FONTCOLOR,
"000000");
81 mxStylesheet.getDefaultEdgeStyle().put(mxConstants.STYLE_NOLABEL,
true);
82 mxStylesheet.getDefaultEdgeStyle().put(mxConstants.STYLE_PERIMETER_SPACING, 0);
83 mxStylesheet.getDefaultEdgeStyle().put(mxConstants.STYLE_ENDARROW, mxConstants.NONE);
84 mxStylesheet.getDefaultEdgeStyle().put(mxConstants.STYLE_STARTARROW, mxConstants.NONE);
88 private final Map<String, mxCell> nodeMap =
new HashMap<>();
91 private final Multimap<Content, mxCell> edgeMap = MultimapBuilder.hashKeys().hashSetValues().build();
92 private final LockedVertexModel lockedVertexModel;
94 private final PinnedAccountModel pinnedAccountModel;
96 CommunicationsGraph(PinnedAccountModel pinnedAccountModel, LockedVertexModel lockedVertexModel) {
98 this.pinnedAccountModel = pinnedAccountModel;
99 this.lockedVertexModel = lockedVertexModel;
101 setAutoSizeCells(
true);
102 setCellsCloneable(
false);
103 setDropEnabled(
false);
104 setCellsCloneable(
false);
105 setCellsEditable(
false);
106 setCellsResizable(
false);
107 setCellsMovable(
true);
108 setCellsDisconnectable(
false);
109 setConnectableEdges(
false);
110 setDisconnectOnMove(
false);
111 setEdgeLabelsMovable(
false);
112 setVertexLabelsMovable(
false);
113 setAllowDanglingEdges(
false);
114 setCellsBendable(
true);
115 setKeepEdgesInBackground(
true);
116 setResetEdgesOnMove(
true);
125 LockedVertexModel getLockedVertexModel() {
126 return lockedVertexModel;
129 PinnedAccountModel getPinnedAccountModel() {
130 return pinnedAccountModel;
136 removeCells(getChildVertices(getDefaultParent()));
140 public String convertValueToString(Object cell) {
141 final StringWriter stringWriter =
new StringWriter();
142 HashMap<String, Object> scopes =
new HashMap<>();
144 Object value = getModel().getValue(cell);
145 if (value instanceof AccountDeviceInstanceKey) {
146 final AccountDeviceInstanceKey adiKey = (AccountDeviceInstanceKey) value;
148 scopes.put(
"accountName", adiKey.getAccountDeviceInstance().getAccount().getTypeSpecificID());
149 scopes.put(
"size", Math.round(Math.log(adiKey.getMessageCount()) + 5));
150 scopes.put(
"iconFileName", CommunicationsGraph.class.getResource(Utils.getIconFilePath(adiKey.getAccountDeviceInstance().getAccount().getAccountType())));
151 scopes.put(
"pinned", pinnedAccountModel.isAccountPinned(adiKey.getAccountDeviceInstance()));
152 scopes.put(
"MARKER_PIN_URL", MARKER_PIN_URL);
153 scopes.put(
"locked", lockedVertexModel.isVertexLocked((mxCell) cell));
154 scopes.put(
"LOCK_URL", LOCK_URL);
156 labelMustache.execute(stringWriter, scopes);
158 return stringWriter.toString();
165 public String getToolTipForCell(Object cell) {
166 final StringWriter stringWriter =
new StringWriter();
167 HashMap<String, Object> scopes =
new HashMap<>();
169 Object value = getModel().getValue(cell);
170 if (value instanceof AccountDeviceInstanceKey) {
171 final AccountDeviceInstanceKey adiKey = (AccountDeviceInstanceKey) value;
173 scopes.put(
"accountName", adiKey.getAccountDeviceInstance().getAccount().getTypeSpecificID());
174 scopes.put(
"relationships", 12);
175 scopes.put(
"iconFileName", CommunicationsGraph.class.getResource(Utils.getIconFilePath(adiKey.getAccountDeviceInstance().getAccount().getAccountType())));
176 scopes.put(
"pinned", pinnedAccountModel.isAccountPinned(adiKey.getAccountDeviceInstance()));
177 scopes.put(
"MARKER_PIN_URL", MARKER_PIN_URL);
178 scopes.put(
"locked", lockedVertexModel.isVertexLocked((mxCell) cell));
179 scopes.put(
"LOCK_URL", LOCK_URL);
180 scopes.put(
"device_id", adiKey.getAccountDeviceInstance().getDeviceId());
182 labelMustache.execute(stringWriter, scopes);
184 return stringWriter.toString();
186 final mxICell edge = (mxICell) cell;
187 final long count = (long) edge.getValue();
188 return "<html>" + edge.getId() +
"<br>" + count + (count == 1 ?
" relationship" :
" relationships") +
"</html>";
192 SwingWorker<?, ?> rebuild(ProgressIndicator progress, CommunicationsManager commsManager, CommunicationsFilter currentFilter) {
193 return new RebuildWorker(progress, commsManager, currentFilter);
198 getView().setScale(1);
199 pinnedAccountModel.clear();
200 lockedVertexModel.clear();
203 private mxCell getOrCreateVertex(AccountDeviceInstance adi, CommunicationsManager commsManager, CommunicationsFilter currentFilter) {
204 final AccountDeviceInstance accountDeviceInstance = adi;
205 final String name = accountDeviceInstance.getAccount().getTypeSpecificID();
207 final mxCell vertex = nodeMap.computeIfAbsent(name + accountDeviceInstance.getDeviceId(), vertexName -> {
208 long adiRelationshipsCount = 1;
210 adiRelationshipsCount = commsManager.getRelationshipSourcesCount(accountDeviceInstance, currentFilter);
211 }
catch (TskCoreException tskCoreException) {
212 logger.log(Level.SEVERE,
"There was an error fetching relationships for the node: " + accountDeviceInstance, tskCoreException);
215 double size = Math.sqrt(adiRelationshipsCount) + 10;
216 AccountDeviceInstanceKey adiKey =
new AccountDeviceInstanceKey(adi, currentFilter, adiRelationshipsCount);
218 mxCell newVertex = (mxCell) insertVertex(
230 @SuppressWarnings(
"unchecked")
231 private mxCell addOrUpdateEdge(
long relSources,
232 AccountDeviceInstance account1, AccountDeviceInstance account2,
233 CommunicationsManager commsManager, CommunicationsFilter currentFilter) {
234 mxCell vertex1 = getOrCreateVertex(account1, commsManager, currentFilter);
235 mxCell vertex2 = getOrCreateVertex(account2, commsManager, currentFilter);
236 Object[] edgesBetween = getEdgesBetween(vertex1, vertex2);
238 if (edgesBetween.length == 0) {
239 final String edgeName = vertex1.getId() +
" - " + vertex2.getId();
240 edge = (mxCell) insertEdge(getDefaultParent(), edgeName, relSources, vertex1, vertex2,
241 "strokeWidth=" + (Math.log(relSources) + 1));
243 edge = (mxCell) edgesBetween[0];
244 edge.setStyle(
"strokeWidth=" + (Math.log(relSources) + 1));
260 this.progressIndicator = progress;
268 progressIndicator.
start(
"Loading accounts");
269 int progressCounter = 0;
274 final Set<AccountDeviceInstance> relatedAccounts =
new HashSet<>();
275 for (
final AccountDeviceInstance adi : pinnedAccountModel.getPinnedAccounts()) {
280 final List<AccountDeviceInstance> relatedAccountDeviceInstances
281 = commsManager.getRelatedAccountDeviceInstances(adi, currentFilter);
282 relatedAccounts.add(adi);
283 getOrCreateVertex(adi, commsManager, currentFilter);
285 for (
final AccountDeviceInstance relatedADI : relatedAccountDeviceInstances)
286 relatedAccounts.add(relatedADI);
288 progressIndicator.
progress(++progressCounter);
291 Map<AccountPair, Long> relationshipCounts = commsManager.getRelationshipCountsPairwise(relatedAccounts, currentFilter);
293 int total = relationshipCounts.size();
295 String progressText =
"";
297 for (Map.Entry<AccountPair, Long> entry : relationshipCounts.entrySet()) {
298 Long count = entry.getValue();
299 AccountPair relationshipKey = entry.getKey();
300 AccountDeviceInstance account1 = relationshipKey.getFirst();
301 AccountDeviceInstance account2 = relationshipKey.getSecond();
303 if (pinnedAccountModel.isAccountPinned(account1)
304 || pinnedAccountModel.isAccountPinned(account2)) {
305 mxCell addEdge = addOrUpdateEdge(count, account1, account2, commsManager, currentFilter);
306 progressText = addEdge.getId();
308 progressIndicator.
progress(progressText, progress++);
310 }
catch (TskCoreException tskCoreException) {
311 logger.log(Level.SEVERE,
"Error", tskCoreException);
322 }
catch (InterruptedException | ExecutionException ex) {
323 logger.log(Level.SEVERE,
"Error building graph visualization. ", ex);
324 }
catch (CancellationException ex) {
325 logger.log(Level.INFO,
"Graph visualization cancelled");
327 progressIndicator.
finish();
void start(String message, int totalWorkUnits)
final ProgressIndicator progressIndicator
final CommunicationsManager commsManager
final CommunicationsFilter currentFilter
void switchToDeterminate(String message, int workUnitsCompleted, int totalWorkUnits)
void progress(String message)