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.List;
38 import java.util.concurrent.CancellationException;
39 import java.util.concurrent.ExecutionException;
40 import java.util.logging.Level;
41 import javax.swing.SwingWorker;
55 final class CommunicationsGraph
extends mxGraph {
57 private static final Logger logger = Logger.getLogger(CommunicationsGraph.class.getName());
58 private static final URL MARKER_PIN_URL = CommunicationsGraph.class.getResource(
"/org/sleuthkit/autopsy/communications/images/marker--pin.png");
59 private static final URL LOCK_URL = CommunicationsGraph.class.getResource(
"/org/sleuthkit/autopsy/communications/images/lock_large_locked.png");
62 private final static Mustache labelMustache;
65 final InputStream templateStream = CommunicationsGraph.class.getResourceAsStream(
"/org/sleuthkit/autopsy/communications/Vertex_Label_template.html");
66 labelMustache =
new DefaultMustacheFactory().compile(
new InputStreamReader(templateStream),
"Vertex_Label");
71 static final private mxStylesheet mxStylesheet =
new mxStylesheet();
75 mxStylesheet.getDefaultVertexStyle().put(mxConstants.STYLE_SHAPE, mxConstants.SHAPE_ELLIPSE);
76 mxStylesheet.getDefaultVertexStyle().put(mxConstants.STYLE_PERIMETER, mxConstants.PERIMETER_ELLIPSE);
77 mxStylesheet.getDefaultVertexStyle().put(mxConstants.STYLE_FONTCOLOR,
"000000");
80 mxStylesheet.getDefaultEdgeStyle().put(mxConstants.STYLE_NOLABEL,
true);
81 mxStylesheet.getDefaultEdgeStyle().put(mxConstants.STYLE_PERIMETER_SPACING, 0);
82 mxStylesheet.getDefaultEdgeStyle().put(mxConstants.STYLE_ENDARROW, mxConstants.NONE);
83 mxStylesheet.getDefaultEdgeStyle().put(mxConstants.STYLE_STARTARROW, mxConstants.NONE);
87 private final Map<String, mxCell> nodeMap =
new HashMap<>();
90 private final Multimap<Content, mxCell> edgeMap = MultimapBuilder.hashKeys().hashSetValues().build();
91 private final LockedVertexModel lockedVertexModel;
93 private final PinnedAccountModel pinnedAccountModel;
95 CommunicationsGraph(PinnedAccountModel pinnedAccountModel, LockedVertexModel lockedVertexModel) {
97 this.pinnedAccountModel = pinnedAccountModel;
98 this.lockedVertexModel = lockedVertexModel;
100 setAutoSizeCells(
true);
101 setCellsCloneable(
false);
102 setDropEnabled(
false);
103 setCellsCloneable(
false);
104 setCellsEditable(
false);
105 setCellsResizable(
false);
106 setCellsMovable(
true);
107 setCellsDisconnectable(
false);
108 setConnectableEdges(
false);
109 setDisconnectOnMove(
false);
110 setEdgeLabelsMovable(
false);
111 setVertexLabelsMovable(
false);
112 setAllowDanglingEdges(
false);
113 setCellsBendable(
true);
114 setKeepEdgesInBackground(
true);
115 setResetEdgesOnMove(
true);
124 LockedVertexModel getLockedVertexModel() {
125 return lockedVertexModel;
128 PinnedAccountModel getPinnedAccountModel() {
129 return pinnedAccountModel;
135 removeCells(getChildVertices(getDefaultParent()));
139 public String convertValueToString(Object cell) {
140 final StringWriter stringWriter =
new StringWriter();
141 HashMap<String, Object> scopes =
new HashMap<>();
143 Object value = getModel().getValue(cell);
144 if (value instanceof AccountDeviceInstanceKey) {
145 final AccountDeviceInstanceKey adiKey = (AccountDeviceInstanceKey) value;
147 scopes.put(
"accountName", adiKey.getAccountDeviceInstance().getAccount().getTypeSpecificID());
148 scopes.put(
"size", Math.round(Math.log(adiKey.getMessageCount()) + 5));
149 scopes.put(
"iconFileName", CommunicationsGraph.class.getResource(Utils.getIconFilePath(adiKey.getAccountDeviceInstance().getAccount().getAccountType())));
150 scopes.put(
"pinned", pinnedAccountModel.isAccountPinned(adiKey));
151 scopes.put(
"MARKER_PIN_URL", MARKER_PIN_URL);
152 scopes.put(
"locked", lockedVertexModel.isVertexLocked((mxCell) cell));
153 scopes.put(
"LOCK_URL", LOCK_URL);
155 labelMustache.execute(stringWriter, scopes);
157 return stringWriter.toString();
164 public String getToolTipForCell(Object cell) {
165 final StringWriter stringWriter =
new StringWriter();
166 HashMap<String, Object> scopes =
new HashMap<>();
168 Object value = getModel().getValue(cell);
169 if (value instanceof AccountDeviceInstanceKey) {
170 final AccountDeviceInstanceKey adiKey = (AccountDeviceInstanceKey) value;
172 scopes.put(
"accountName", adiKey.getAccountDeviceInstance().getAccount().getTypeSpecificID());
173 scopes.put(
"relationships", 12);
174 scopes.put(
"iconFileName", CommunicationsGraph.class.getResource(Utils.getIconFilePath(adiKey.getAccountDeviceInstance().getAccount().getAccountType())));
175 scopes.put(
"pinned", pinnedAccountModel.isAccountPinned(adiKey));
176 scopes.put(
"MARKER_PIN_URL", MARKER_PIN_URL);
177 scopes.put(
"locked", lockedVertexModel.isVertexLocked((mxCell) cell));
178 scopes.put(
"LOCK_URL", LOCK_URL);
179 scopes.put(
"device_id", adiKey.getAccountDeviceInstance().getDeviceId());
181 labelMustache.execute(stringWriter, scopes);
183 return stringWriter.toString();
185 final mxICell edge = (mxICell) cell;
186 final long count = (long) edge.getValue();
187 return "<html>" + edge.getId() +
"<br>" + count + (count == 1 ?
" relationship" :
" relationships") +
"</html>";
191 SwingWorker<?, ?> rebuild(ProgressIndicator progress, CommunicationsManager commsManager, CommunicationsFilter currentFilter) {
192 return new RebuildWorker(progress, commsManager, currentFilter);
197 getView().setScale(1);
198 pinnedAccountModel.clear();
199 lockedVertexModel.clear();
202 private mxCell getOrCreateVertex(AccountDeviceInstanceKey accountDeviceInstanceKey) {
203 final AccountDeviceInstance accountDeviceInstance = accountDeviceInstanceKey.getAccountDeviceInstance();
204 final String name = accountDeviceInstance.getAccount().getTypeSpecificID();
206 final mxCell vertex = nodeMap.computeIfAbsent(name + accountDeviceInstance.getDeviceId(), vertexName -> {
207 double size = Math.sqrt(accountDeviceInstanceKey.getMessageCount()) + 10;
209 mxCell newVertex = (mxCell) insertVertex(
211 name, accountDeviceInstanceKey,
221 @SuppressWarnings(
"unchecked")
222 private mxCell addOrUpdateEdge(
long relSources, AccountDeviceInstanceKey account1, AccountDeviceInstanceKey account2) {
223 mxCell vertex1 = getOrCreateVertex(account1);
224 mxCell vertex2 = getOrCreateVertex(account2);
225 Object[] edgesBetween = getEdgesBetween(vertex1, vertex2);
227 if (edgesBetween.length == 0) {
228 final String edgeName = vertex1.getId() +
" - " + vertex2.getId();
229 edge = (mxCell) insertEdge(getDefaultParent(), edgeName, relSources, vertex1, vertex2,
230 "strokeWidth=" + (Math.log(relSources) + 1));
232 edge = (mxCell) edgesBetween[0];
233 edge.setStyle(
"strokeWidth=" + (Math.log(relSources) + 1));
249 this.progressIndicator = progress;
257 progressIndicator.
start(
"Loading accounts");
258 int progressCounter = 0;
263 final Map<AccountDeviceInstance, AccountDeviceInstanceKey> relatedAccounts =
new HashMap<>();
264 for (
final AccountDeviceInstanceKey adiKey : pinnedAccountModel.getPinnedAccounts()) {
269 final List<AccountDeviceInstance> relatedAccountDeviceInstances
270 = commsManager.getRelatedAccountDeviceInstances(adiKey.getAccountDeviceInstance(),
currentFilter);
271 relatedAccounts.put(adiKey.getAccountDeviceInstance(), adiKey);
272 getOrCreateVertex(adiKey);
274 for (
final AccountDeviceInstance relatedADI : relatedAccountDeviceInstances) {
275 final long adiRelationshipsCount = commsManager.getRelationshipSourcesCount(relatedADI, currentFilter);
276 final AccountDeviceInstanceKey relatedADIKey =
new AccountDeviceInstanceKey(relatedADI, currentFilter, adiRelationshipsCount);
277 relatedAccounts.put(relatedADI, relatedADIKey);
279 progressIndicator.
progress(++progressCounter);
282 Set<AccountDeviceInstance> accounts = relatedAccounts.keySet();
284 Map<AccountPair, Long> relationshipCounts = commsManager.getRelationshipCountsPairwise(accounts, currentFilter);
286 int total = relationshipCounts.size();
288 String progressText =
"";
290 for (Map.Entry<AccountPair, Long> entry : relationshipCounts.entrySet()) {
291 Long count = entry.getValue();
292 AccountPair relationshipKey = entry.getKey();
293 AccountDeviceInstanceKey account1 = relatedAccounts.get(relationshipKey.getFirst());
294 AccountDeviceInstanceKey account2 = relatedAccounts.get(relationshipKey.getSecond());
296 if (pinnedAccountModel.isAccountPinned(account1)
297 || pinnedAccountModel.isAccountPinned(account2)) {
298 mxCell addEdge = addOrUpdateEdge(count, account1, account2);
299 progressText = addEdge.getId();
301 progressIndicator.
progress(progressText, progress++);
303 }
catch (TskCoreException tskCoreException) {
304 logger.log(Level.SEVERE,
"Error", tskCoreException);
315 }
catch (InterruptedException | ExecutionException ex) {
316 logger.log(Level.SEVERE,
"Error building graph visualization. ", ex);
317 }
catch (CancellationException ex) {
318 logger.log(Level.INFO,
"Graph visualization cancelled");
320 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)