Merge branch 'custom-release-may-2018' of https://github.com/sleuthkit/autopsy into 3610-SearchableAutoIngestJobs

This commit is contained in:
William Schaefer 2018-04-11 17:08:40 -04:00
commit 10cc0aceb9
25 changed files with 1579 additions and 396 deletions

View File

@ -327,6 +327,7 @@
<package>org.sleuthkit.autopsy.events</package> <package>org.sleuthkit.autopsy.events</package>
<package>org.sleuthkit.autopsy.filesearch</package> <package>org.sleuthkit.autopsy.filesearch</package>
<package>org.sleuthkit.autopsy.guiutils</package> <package>org.sleuthkit.autopsy.guiutils</package>
<package>org.sleuthkit.autopsy.healthmonitor</package>
<package>org.sleuthkit.autopsy.ingest</package> <package>org.sleuthkit.autopsy.ingest</package>
<package>org.sleuthkit.autopsy.keywordsearchservice</package> <package>org.sleuthkit.autopsy.keywordsearchservice</package>
<package>org.sleuthkit.autopsy.menuactions</package> <package>org.sleuthkit.autopsy.menuactions</package>

View File

@ -0,0 +1,65 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2018 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.communications;
import java.util.Collection;
import javax.swing.AbstractAction;
import javax.swing.ImageIcon;
import javax.swing.JMenuItem;
import org.openide.util.Utilities;
import org.openide.util.actions.Presenter;
/**
* Base class for actions that act on the selected AccountDeviceInstanceKeys.
* getPopupPresenter() provides a JMenuItem that works (i.e., has an icon) in
* custom context menus and also in the Netbeans Explorer views.
*/
abstract class AbstractCVTAction extends AbstractAction implements Presenter.Popup {
/**
* Get the selected accounts that will be acted upon.
*
* @return The selected accounts
*/
Collection<? extends AccountDeviceInstanceKey> getSelectedAccounts() {
return Utilities.actionsGlobalContext().lookupAll(AccountDeviceInstanceKey.class);
}
@Override
public JMenuItem getPopupPresenter() {
JMenuItem presenter = new JMenuItem(this);
presenter.setText(getActionDisplayName());
presenter.setIcon(getIcon());
return presenter;
}
/**
* The name/text of the action as displayed in a menu.
*
* @return The diaplay name of this action
*/
abstract String getActionDisplayName();
/**
* The icon to use for this icon.
*
* @return An ImageIcon used to represent this action.
*/
abstract ImageIcon getIcon();
}

View File

@ -18,17 +18,13 @@
*/ */
package org.sleuthkit.autopsy.communications; package org.sleuthkit.autopsy.communications;
import java.awt.event.ActionEvent;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection;
import javax.swing.AbstractAction;
import javax.swing.Action; import javax.swing.Action;
import org.openide.nodes.AbstractNode; import org.openide.nodes.AbstractNode;
import org.openide.nodes.Children; import org.openide.nodes.Children;
import org.openide.nodes.Sheet; import org.openide.nodes.Sheet;
import org.openide.util.NbBundle; import org.openide.util.NbBundle;
import org.openide.util.Utilities;
import org.openide.util.lookup.Lookups; import org.openide.util.lookup.Lookups;
import org.sleuthkit.autopsy.datamodel.NodeProperty; import org.sleuthkit.autopsy.datamodel.NodeProperty;
import org.sleuthkit.datamodel.Account; import org.sleuthkit.datamodel.Account;
@ -106,54 +102,4 @@ final class AccountDeviceInstanceNode extends AbstractNode {
actions.add(ResetAndPinAccountsAction.getInstance()); actions.add(ResetAndPinAccountsAction.getInstance());
return actions.toArray(new Action[actions.size()]); return actions.toArray(new Action[actions.size()]);
} }
/**
* Action that pins the selected AccountDeviceInstances to the
* visualization.
*/
static private class PinAccountsAction extends AbstractAction {
private static final long serialVersionUID = 1L;
private final static PinAccountsAction instance = new PinAccountsAction();
private static PinAccountsAction getInstance() {
return instance;
}
private PinAccountsAction() {
super("Add Account(s) to Visualization");
}
@Override
public void actionPerformed(ActionEvent e) {
Collection<? extends AccountDeviceInstanceKey> lookupAll =
Utilities.actionsGlobalContext().lookupAll(AccountDeviceInstanceKey.class);
CVTEvents.getCVTEventBus().post(new CVTEvents.PinAccountsEvent(lookupAll, false));
}
}
/**
* Action that pins the selected AccountDeviceInstances to the
* visualization.
*/
static private class ResetAndPinAccountsAction extends AbstractAction {
private static final long serialVersionUID = 1L;
private final static ResetAndPinAccountsAction instance = new ResetAndPinAccountsAction();
private static ResetAndPinAccountsAction getInstance() {
return instance;
}
private ResetAndPinAccountsAction() {
super("Visualize Account(s)");
}
@Override
public void actionPerformed(ActionEvent e) {
Collection<? extends AccountDeviceInstanceKey> lookupAll =
Utilities.actionsGlobalContext().lookupAll(AccountDeviceInstanceKey.class);
CVTEvents.getCVTEventBus().post(new CVTEvents.PinAccountsEvent(lookupAll, true));
}
}
} }

View File

@ -20,9 +20,6 @@ CVTTopComponent.vizPanel.TabConstraints.tabTitle=Visualize
CVTTopComponent.accountsBrowser.TabConstraints.tabTitle_1=Browse CVTTopComponent.accountsBrowser.TabConstraints.tabTitle_1=Browse
CVTTopComponent.browseVisualizeTabPane.AccessibleContext.accessibleName=Visualize CVTTopComponent.browseVisualizeTabPane.AccessibleContext.accessibleName=Visualize
CVTTopComponent.vizPanel.TabConstraints.tabTitle_1=Visualize CVTTopComponent.vizPanel.TabConstraints.tabTitle_1=Visualize
VisualizationPanel.jButton6.text=Hierarchy
VisualizationPanel.jButton7.text=Circle
VisualizationPanel.jButton8.text=Organic
VisualizationPanel.fitGraphButton.text= VisualizationPanel.fitGraphButton.text=
VisualizationPanel.jTextArea1.text=Right-click an account in the Browse Accounts table, and select 'Visualize' to begin. VisualizationPanel.jTextArea1.text=Right-click an account in the Browse Accounts table, and select 'Visualize' to begin.
VisualizationPanel.jLabel1.text=Layouts: VisualizationPanel.jLabel1.text=Layouts:
@ -36,11 +33,8 @@ VisualizationPanel.zoomInButton.toolTipText=Zoom in
VisualizationPanel.zoomInButton.text= VisualizationPanel.zoomInButton.text=
VisualizationPanel.zoomOutButton.toolTipText=Zoom out VisualizationPanel.zoomOutButton.toolTipText=Zoom out
VisualizationPanel.zoomOutButton.text= VisualizationPanel.zoomOutButton.text=
# To change this license header, choose License Headers in Project Properties.
# To change this template file, choose Tools | Templates
# and open the template in the editor.
VisualizationPanel.circleLayoutButton.text=Circle VisualizationPanel.circleLayoutButton.text=Circle
VisualizationPanel.organicLayoutButton.text=Organic VisualizationPanel.organicLayoutButton.text=Organic
VisualizationPanel.fastOrganicLayoutButton.text=Fast Organic VisualizationPanel.fastOrganicLayoutButton.text=Fast Organic
VisualizationPanel.hierarchyLayoutButton.text=Hierarchy VisualizationPanel.hierarchyLayoutButton.text=Hierarchical
VisualizationPanel.clearVizButton.text_1=Clear Viz. VisualizationPanel.clearVizButton.text_1=Clear Viz.

View File

@ -21,7 +21,6 @@ package org.sleuthkit.autopsy.communications;
import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSet;
import com.google.common.eventbus.EventBus; import com.google.common.eventbus.EventBus;
import java.util.Collection; import java.util.Collection;
import java.util.Set;
import org.sleuthkit.datamodel.CommunicationsFilter; import org.sleuthkit.datamodel.CommunicationsFilter;
/** /**
@ -79,7 +78,7 @@ final class CVTEvents {
return accountDeviceInstances; return accountDeviceInstances;
} }
public UnpinAccountsEvent(Set<AccountDeviceInstanceKey> accountDeviceInstances) { UnpinAccountsEvent(Collection<? extends AccountDeviceInstanceKey> accountDeviceInstances) {
this.accountDeviceInstances = ImmutableSet.copyOf(accountDeviceInstances); this.accountDeviceInstances = ImmutableSet.copyOf(accountDeviceInstances);
} }
} }

View File

@ -25,7 +25,6 @@ import com.google.common.collect.MultimapBuilder;
import com.mxgraph.model.mxCell; import com.mxgraph.model.mxCell;
import com.mxgraph.model.mxICell; import com.mxgraph.model.mxICell;
import com.mxgraph.util.mxConstants; import com.mxgraph.util.mxConstants;
import com.mxgraph.view.mxCellState;
import com.mxgraph.view.mxGraph; import com.mxgraph.view.mxGraph;
import com.mxgraph.view.mxStylesheet; import com.mxgraph.view.mxStylesheet;
import java.io.InputStream; import java.io.InputStream;
@ -84,17 +83,19 @@ final class CommunicationsGraph extends mxGraph {
mxStylesheet.getDefaultEdgeStyle().put(mxConstants.STYLE_STARTARROW, mxConstants.NONE); mxStylesheet.getDefaultEdgeStyle().put(mxConstants.STYLE_STARTARROW, mxConstants.NONE);
} }
/* Map from type specific account identifier to mxCell(vertex). */ /** Map from type specific account identifier to mxCell(vertex). */
private final Map<String, mxCell> nodeMap = new HashMap<>(); private final Map<String, mxCell> nodeMap = new HashMap<>();
/* Map from relationship source (Content) to mxCell (edge). */ /** Map from relationship source (Content) to mxCell (edge). */
private final Multimap<Content, mxCell> edgeMap = MultimapBuilder.hashKeys().hashSetValues().build(); private final Multimap<Content, mxCell> edgeMap = MultimapBuilder.hashKeys().hashSetValues().build();
private final LockedVertexModel lockedVertexModel; private final LockedVertexModel lockedVertexModel;
private final PinnedAccountModel pinnedAccountModel; private final PinnedAccountModel pinnedAccountModel;
CommunicationsGraph() { CommunicationsGraph(PinnedAccountModel pinnedAccountModel, LockedVertexModel lockedVertexModel) {
super(mxStylesheet); super(mxStylesheet);
this.pinnedAccountModel = pinnedAccountModel;
this.lockedVertexModel = lockedVertexModel;
//set fixed properties of graph. //set fixed properties of graph.
setAutoSizeCells(true); setAutoSizeCells(true);
setCellsCloneable(false); setCellsCloneable(false);
@ -113,21 +114,6 @@ final class CommunicationsGraph extends mxGraph {
setKeepEdgesInBackground(true); setKeepEdgesInBackground(true);
setResetEdgesOnMove(true); setResetEdgesOnMove(true);
setHtmlLabels(true); setHtmlLabels(true);
lockedVertexModel = new LockedVertexModel();
lockedVertexModel.registerhandler((LockedVertexModel.VertexLockEvent event) -> {
if (event.isVertexLocked()) {
getView().clear(event.getVertex(), true, true);
getView().validate();
} else {
final mxCellState state = getView().getState(event.getVertex(), true);
getView().updateLabel(state);
getView().updateLabelBounds(state);
getView().updateBoundingBox(state);
}
});
pinnedAccountModel = new PinnedAccountModel(this);
} }
/** /**
@ -255,20 +241,20 @@ final class CommunicationsGraph extends mxGraph {
*/ */
private class RebuildWorker extends SwingWorker<Void, Void> { private class RebuildWorker extends SwingWorker<Void, Void> {
private final ProgressIndicator progress; private final ProgressIndicator progressIndicator;
private final CommunicationsManager commsManager; private final CommunicationsManager commsManager;
private final CommunicationsFilter currentFilter; private final CommunicationsFilter currentFilter;
RebuildWorker(ProgressIndicator progress, CommunicationsManager commsManager, CommunicationsFilter currentFilter) { RebuildWorker(ProgressIndicator progress, CommunicationsManager commsManager, CommunicationsFilter currentFilter) {
this.progress = progress; this.progressIndicator = progress;
this.currentFilter = currentFilter; this.currentFilter = currentFilter;
this.commsManager = commsManager; this.commsManager = commsManager;
} }
@Override @Override
protected Void doInBackground() throws Exception { protected Void doInBackground() {
progress.start("Loading accounts"); progressIndicator.start("Loading accounts");
int progressCounter = 0; int progressCounter = 0;
try { try {
/** /**
@ -279,18 +265,18 @@ final class CommunicationsGraph extends mxGraph {
if (isCancelled()) { if (isCancelled()) {
break; break;
} }
//get accounts related to pinned account
final List<AccountDeviceInstance> relatedAccountDeviceInstances final List<AccountDeviceInstance> relatedAccountDeviceInstances
= commsManager.getRelatedAccountDeviceInstances(adiKey.getAccountDeviceInstance(), currentFilter); = commsManager.getRelatedAccountDeviceInstances(adiKey.getAccountDeviceInstance(), currentFilter);
relatedAccounts.put(adiKey.getAccountDeviceInstance(), adiKey); relatedAccounts.put(adiKey.getAccountDeviceInstance(), adiKey);
getOrCreateVertex(adiKey); getOrCreateVertex(adiKey);
//get accounts related to pinned account
for (final AccountDeviceInstance relatedADI : relatedAccountDeviceInstances) { for (final AccountDeviceInstance relatedADI : relatedAccountDeviceInstances) {
final long adiRelationshipsCount = commsManager.getRelationshipSourcesCount(relatedADI, currentFilter); final long adiRelationshipsCount = commsManager.getRelationshipSourcesCount(relatedADI, currentFilter);
final AccountDeviceInstanceKey relatedADIKey = new AccountDeviceInstanceKey(relatedADI, currentFilter, adiRelationshipsCount); final AccountDeviceInstanceKey relatedADIKey = new AccountDeviceInstanceKey(relatedADI, currentFilter, adiRelationshipsCount);
relatedAccounts.put(relatedADI, relatedADIKey); //store related accounts relatedAccounts.put(relatedADI, relatedADIKey); //store related accounts
} }
progress.progress(++progressCounter); progressIndicator.progress(++progressCounter);
} }
Set<AccountDeviceInstance> accounts = relatedAccounts.keySet(); Set<AccountDeviceInstance> accounts = relatedAccounts.keySet();
@ -298,9 +284,9 @@ final class CommunicationsGraph extends mxGraph {
Map<AccountPair, Long> relationshipCounts = commsManager.getRelationshipCountsPairwise(accounts, currentFilter); Map<AccountPair, Long> relationshipCounts = commsManager.getRelationshipCountsPairwise(accounts, currentFilter);
int total = relationshipCounts.size(); int total = relationshipCounts.size();
int k = 0; int progress = 0;
String progressText = ""; String progressText = "";
progress.switchToDeterminate("", 0, total); progressIndicator.switchToDeterminate("", 0, total);
for (Map.Entry<AccountPair, Long> entry : relationshipCounts.entrySet()) { for (Map.Entry<AccountPair, Long> entry : relationshipCounts.entrySet()) {
Long count = entry.getValue(); Long count = entry.getValue();
AccountPair relationshipKey = entry.getKey(); AccountPair relationshipKey = entry.getKey();
@ -312,11 +298,10 @@ final class CommunicationsGraph extends mxGraph {
mxCell addEdge = addOrUpdateEdge(count, account1, account2); mxCell addEdge = addOrUpdateEdge(count, account1, account2);
progressText = addEdge.getId(); progressText = addEdge.getId();
} }
progress.progress(progressText, k++); progressIndicator.progress(progressText, progress++);
} }
} catch (TskCoreException tskCoreException) { } catch (TskCoreException tskCoreException) {
logger.log(Level.SEVERE, "Error", tskCoreException); logger.log(Level.SEVERE, "Error", tskCoreException);
} finally {
} }
return null; return null;
@ -332,7 +317,7 @@ final class CommunicationsGraph extends mxGraph {
} catch (CancellationException ex) { } catch (CancellationException ex) {
logger.log(Level.INFO, "Graph visualization cancelled"); logger.log(Level.INFO, "Graph visualization cancelled");
} finally { } finally {
progress.finish(); progressIndicator.finish();
} }
} }
} }

View File

@ -18,20 +18,19 @@
*/ */
package org.sleuthkit.autopsy.communications; package org.sleuthkit.autopsy.communications;
import com.google.common.collect.ImmutableSet;
import com.google.common.eventbus.EventBus; import com.google.common.eventbus.EventBus;
import com.mxgraph.model.mxCell; import com.mxgraph.model.mxCell;
import java.util.Collection;
import java.util.HashSet; import java.util.HashSet;
import java.util.Set; import java.util.Set;
class LockedVertexModel { /**
* Model of which vertices in a graph are locked ( not moveable by layout
void registerhandler(EventHandler<VertexLockEvent> handler) { * algorithms).
eventBus.register(handler); *
} */
final class LockedVertexModel {
void unregisterhandler(EventHandler<VertexLockEvent> handler) {
eventBus.unregister(handler);
}
private final EventBus eventBus = new EventBus(); private final EventBus eventBus = new EventBus();
@ -42,30 +41,34 @@ class LockedVertexModel {
*/ */
private final Set<mxCell> lockedVertices = new HashSet<>(); private final Set<mxCell> lockedVertices = new HashSet<>();
LockedVertexModel() { void registerhandler(Object handler) {
eventBus.register(handler);
}
void unregisterhandler(Object handler) {
eventBus.unregister(handler);
} }
/** /**
* Lock the given vertex so that applying a layout algorithm doesn't move * Lock the given vertices so that applying a layout algorithm doesn't move
* it. The user can still manually position the vertex. * them. The user can still manually position the vertices.
* *
* @param vertex The vertex to lock. * @param vertex The vertex to lock.
*/ */
void lockVertex(mxCell vertex) { void lock(Collection<mxCell> vertices) {
lockedVertices.add(vertex); lockedVertices.addAll(vertices);
eventBus.post(new VertexLockEvent(vertex, true)); eventBus.post(new VertexLockEvent(true, vertices));
} }
/** /**
* Lock the given vertex so that applying a layout algorithm can move it. * Unlock the given vertices so that applying a layout algorithm can move
* them.
* *
* @param vertex The vertex to unlock. * @param vertex The vertex to unlock.
*/ */
void unlockVertex(mxCell vertex) { void unlock(Collection<mxCell> vertices) {
lockedVertices.remove(vertex); lockedVertices.removeAll(vertices);
eventBus.post(new VertexLockEvent(vertex, false)); eventBus.post(new VertexLockEvent(false, vertices));
} }
boolean isVertexLocked(mxCell vertex) { boolean isVertexLocked(mxCell vertex) {
@ -77,21 +80,36 @@ class LockedVertexModel {
lockedVertices.clear(); lockedVertices.clear();
} }
static class VertexLockEvent { boolean isEmpty() {
return lockedVertices.isEmpty();
private final mxCell vertex;
public mxCell getVertex() {
return vertex;
} }
public boolean isVertexLocked() { /**
* Event that represents a change in the locked state of one or more
* vertices.
*/
final static class VertexLockEvent {
private final boolean locked;
private final Set<mxCell> vertices;
/**
* @return The vertices whose locked state has changed.
*/
public Set<mxCell> getVertices() {
return vertices;
}
/**
* @return True if the vertices are locked, False if the vertices are
* unlocked.
*/
public boolean isLocked() {
return locked; return locked;
} }
private final boolean locked;
VertexLockEvent(mxCell vertex, boolean locked) { VertexLockEvent(boolean locked, Collection< mxCell> vertices) {
this.vertex = vertex; this.vertices = ImmutableSet.copyOf(vertices);
this.locked = locked; this.locked = locked;
} }
} }

View File

@ -35,7 +35,6 @@ import org.sleuthkit.autopsy.corecomponents.DataResultPanel;
import org.sleuthkit.autopsy.corecomponents.DataResultViewerTable; import org.sleuthkit.autopsy.corecomponents.DataResultViewerTable;
import org.sleuthkit.autopsy.corecomponents.TableFilterNode; import org.sleuthkit.autopsy.corecomponents.TableFilterNode;
import org.sleuthkit.autopsy.directorytree.DataResultFilterNode; import org.sleuthkit.autopsy.directorytree.DataResultFilterNode;
import org.sleuthkit.datamodel.AccountDeviceInstance;
/** /**
* The right hand side of the CVT. Has a DataResultPanel to show a listing of * The right hand side of the CVT. Has a DataResultPanel to show a listing of
@ -151,10 +150,10 @@ public final class MessageBrowser extends JPanel implements ExplorerManager.Prov
//Use lookup here? //Use lookup here?
final AccountDeviceInstanceNode adiNode = (AccountDeviceInstanceNode) selectedNodes[0]; final AccountDeviceInstanceNode adiNode = (AccountDeviceInstanceNode) selectedNodes[0];
final Set<AccountDeviceInstance> accountDeviceInstances = new HashSet<>(); final Set<AccountDeviceInstanceKey> accountDeviceInstances = new HashSet<>();
for (final Node n : selectedNodes) { for (final Node n : selectedNodes) {
//Use lookup here? //Use lookup here?
accountDeviceInstances.add(((AccountDeviceInstanceNode) n).getAccountDeviceInstance()); accountDeviceInstances.add(((AccountDeviceInstanceNode) n).getAccountDeviceInstanceKey());
} }
return SelectionNode.createFromAccounts(accountDeviceInstances, adiNode.getFilter(), adiNode.getCommsManager()); return SelectionNode.createFromAccounts(accountDeviceInstances, adiNode.getFilter(), adiNode.getCommsManager());
} }

View File

@ -0,0 +1,59 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2018 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.communications;
import java.awt.event.ActionEvent;
import javax.swing.ImageIcon;
import org.openide.util.ImageUtilities;
import org.openide.util.NbBundle;
/**
* Action that pins the AccountDevicesIntanceKeys in the ActionsGlobalContext to
* the visualizaion
*/
@NbBundle.Messages({"PinAccountsAction.pluralText=Add Selected Accounts to Visualization",
"PinAccountsAction.singularText=Add Selected Account to Visualization"})
final class PinAccountsAction extends AbstractCVTAction {
static private final ImageIcon ICON = ImageUtilities.loadImageIcon(
"/org/sleuthkit/autopsy/communications/images/marker--plus.png", false);
private static final String SINGULAR_TEXT = Bundle.PinAccountsAction_singularText();
private static final String PLURAL_TEXT = Bundle.PinAccountsAction_pluralText();
private static final PinAccountsAction instance = new PinAccountsAction();
static PinAccountsAction getInstance() {
return instance;
}
@Override
public void actionPerformed(ActionEvent event) {
CVTEvents.getCVTEventBus().post(new CVTEvents.PinAccountsEvent(getSelectedAccounts(), false));
}
@Override
protected String getActionDisplayName() {
return getSelectedAccounts().size() > 1 ? PLURAL_TEXT : SINGULAR_TEXT;
}
@Override
ImageIcon getIcon() {
return ICON;
}
}

View File

@ -19,22 +19,31 @@
package org.sleuthkit.autopsy.communications; package org.sleuthkit.autopsy.communications;
import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSet;
import com.google.common.eventbus.EventBus;
import java.util.HashSet; import java.util.HashSet;
import java.util.Set; import java.util.Set;
/**
* Model of what accounts are pinned to a visualization.
*/
class PinnedAccountModel { class PinnedAccountModel {
/** /**
* Set of AccountDeviceInstanceKeys that are 'Pinned' to this graph. Pinned * Set of AccountDeviceInstanceKeys that are 'Pinned' to the graph. Pinned
* accounts are shown regardless of filters, and accounts that are related * accounts are shown regardless of filters, and accounts that are related
* to pinned accounts and pass the filters are show. Pinning accounts is the * to pinned accounts and pass the filters are show. Pinning accounts is the
* primary way to populate the graph. * primary way to populate the graph.
*/ */
private final Set<AccountDeviceInstanceKey> pinnedAccountDevices = new HashSet<>(); private final Set<AccountDeviceInstanceKey> pinnedAccountDevices = new HashSet<>();
private final CommunicationsGraph graph;
PinnedAccountModel(CommunicationsGraph graph) { private final EventBus eventBus = new EventBus();
this.graph = graph;
void registerhandler(Object handler) {
eventBus.register(handler);
}
void unregisterhandler(Object handler) {
eventBus.unregister(handler);
} }
boolean isAccountPinned(AccountDeviceInstanceKey account) { boolean isAccountPinned(AccountDeviceInstanceKey account) {

View File

@ -0,0 +1,59 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2018 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.communications;
import java.awt.event.ActionEvent;
import javax.swing.ImageIcon;
import org.openide.util.ImageUtilities;
import org.openide.util.NbBundle;
/**
* Action that clears any pinned accounts and pins the AcountDevicesInstanceKeys
* in the ActionsGlobalContext to the visualization.
*/
@NbBundle.Messages(value = {"ResetAndPinAccountsAction.singularText=Visualize Only Selected Account",
"ResetAndPinAccountsAction.pluralText=Visualize Only Selected Accounts"})
final class ResetAndPinAccountsAction extends AbstractCVTAction {
private static final ImageIcon ICON = ImageUtilities.loadImageIcon(
"/org/sleuthkit/autopsy/communications/images/marker--pin.png", false);
private static final String SINGULAR_TEXT = Bundle.ResetAndPinAccountsAction_singularText();
private static final String PLURAL_TEXT = Bundle.ResetAndPinAccountsAction_pluralText();
private static final ResetAndPinAccountsAction instance = new ResetAndPinAccountsAction();
static ResetAndPinAccountsAction getInstance() {
return instance;
}
@Override
public void actionPerformed(ActionEvent event) {
CVTEvents.getCVTEventBus().post(new CVTEvents.PinAccountsEvent(getSelectedAccounts(), true));
}
@Override
protected String getActionDisplayName() {
return getSelectedAccounts().size() > 1 ? PLURAL_TEXT : SINGULAR_TEXT;
}
@Override
ImageIcon getIcon() {
return ICON;
}
}

View File

@ -24,10 +24,13 @@ import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.stream.Collectors;
import org.openide.nodes.AbstractNode; import org.openide.nodes.AbstractNode;
import org.openide.nodes.ChildFactory; import org.openide.nodes.ChildFactory;
import org.openide.nodes.Children; import org.openide.nodes.Children;
import org.openide.nodes.Node; import org.openide.nodes.Node;
import org.openide.util.Lookup;
import org.openide.util.lookup.Lookups;
import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.datamodel.AccountDeviceInstance; import org.sleuthkit.datamodel.AccountDeviceInstance;
import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.BlackboardArtifact;
@ -44,23 +47,27 @@ import org.sleuthkit.datamodel.TskCoreException;
*/ */
final class SelectionNode extends AbstractNode { final class SelectionNode extends AbstractNode {
private SelectionNode(Children children) { private SelectionNode(Children children, Lookup lookup) {
super(children); super(children, lookup);
} }
static SelectionNode createFromAccountsAndRelationships( static SelectionNode createFromAccountsAndRelationships(
Set<Content> edgeRelationshipArtifacts, Set<Content> edgeRelationshipArtifacts,
Set<AccountDeviceInstance> accountDeviceInstances, Set<AccountDeviceInstanceKey> accountDeviceInstanceKeys,
CommunicationsFilter filter, CommunicationsFilter filter,
CommunicationsManager commsManager) { CommunicationsManager commsManager) {
Set<AccountDeviceInstance> accountDeviceInstances = accountDeviceInstanceKeys.stream()
.map(AccountDeviceInstanceKey::getAccountDeviceInstance)
.collect(Collectors.toSet());
SelectionNode node = new SelectionNode(Children.create( SelectionNode node = new SelectionNode(Children.create(
new RelationshipChildren( new RelationshipChildren(
edgeRelationshipArtifacts, edgeRelationshipArtifacts,
accountDeviceInstances, accountDeviceInstances,
commsManager, commsManager,
filter), filter),
true)); true), Lookups.fixed(accountDeviceInstanceKeys.toArray()));
//This is not good for internationalization!!! //This is not good for internationalization!!!
String name = ""; String name = "";
@ -82,7 +89,7 @@ final class SelectionNode extends AbstractNode {
} }
static SelectionNode createFromAccounts( static SelectionNode createFromAccounts(
Set<AccountDeviceInstance> accountDeviceInstances, Set<AccountDeviceInstanceKey> accountDeviceInstances,
CommunicationsFilter filter, CommunicationsFilter filter,
CommunicationsManager commsManager) { CommunicationsManager commsManager) {
@ -122,9 +129,9 @@ final class SelectionNode extends AbstractNode {
} }
@Override @Override
protected Node createNodeForKey(Content t) { protected Node createNodeForKey(Content content) {
if (t instanceof BlackboardArtifact) { if (content instanceof BlackboardArtifact) {
return new RelationshipNode((BlackboardArtifact) t); return new RelationshipNode((BlackboardArtifact) content);
} else { } else {
throw new UnsupportedOperationException("Cannot create a RelationshipNode for non BlackboardArtifact content."); throw new UnsupportedOperationException("Cannot create a RelationshipNode for non BlackboardArtifact content.");
} }

View File

@ -0,0 +1,59 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2018 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.communications;
import java.awt.event.ActionEvent;
import javax.swing.ImageIcon;
import org.openide.util.ImageUtilities;
import org.openide.util.NbBundle;
/**
* Action that unpins the AcccountDeviceInstanceKeys in the ActionsGlobalContext
* form the visualization.
*/
@NbBundle.Messages({"UnpinAccountsAction.pluralText=Remove Selected Accounts",
"UnpinAccountsAction.singularText=Remove Selected Account"})
final class UnpinAccountsAction extends AbstractCVTAction {
static final private ImageIcon ICON = ImageUtilities.loadImageIcon(
"/org/sleuthkit/autopsy/communications/images/marker--minus.png", false);
private static final String SINGULAR_TEXT = Bundle.UnpinAccountsAction_singularText();
private static final String PLURAL_TEXT = Bundle.UnpinAccountsAction_pluralText();
private static final UnpinAccountsAction instance = new UnpinAccountsAction();
static UnpinAccountsAction getInstance() {
return instance;
}
@Override
public void actionPerformed(final ActionEvent event) {
CVTEvents.getCVTEventBus().post(new CVTEvents.UnpinAccountsEvent(getSelectedAccounts()));
}
@Override
String getActionDisplayName() {
return getSelectedAccounts().size() > 1 ? PLURAL_TEXT : SINGULAR_TEXT;
}
@Override
ImageIcon getIcon() {
return ICON;
}
}

View File

@ -68,15 +68,15 @@
<SubComponents> <SubComponents>
<Component class="javax.swing.JTextArea" name="jTextArea1"> <Component class="javax.swing.JTextArea" name="jTextArea1">
<Properties> <Properties>
<Property name="background" type="java.awt.Color" editor="org.netbeans.beaninfo.editors.ColorEditor">
<Color blue="f0" green="f0" red="f0" type="rgb"/>
</Property>
<Property name="columns" type="int" value="20"/> <Property name="columns" type="int" value="20"/>
<Property name="lineWrap" type="boolean" value="true"/> <Property name="lineWrap" type="boolean" value="true"/>
<Property name="rows" type="int" value="5"/> <Property name="rows" type="int" value="5"/>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor"> <Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/communications/Bundle.properties" key="VisualizationPanel.jTextArea1.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/> <ResourceString bundle="org/sleuthkit/autopsy/communications/Bundle.properties" key="VisualizationPanel.jTextArea1.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property> </Property>
<Property name="background" type="java.awt.Color" editor="org.netbeans.beaninfo.editors.ColorEditor">
<Color blue="f0" green="f0" red="f0" type="rgb"/>
</Property>
</Properties> </Properties>
</Component> </Component>
</SubComponents> </SubComponents>
@ -166,9 +166,6 @@
<Property name="horizontalTextPosition" type="int" value="0"/> <Property name="horizontalTextPosition" type="int" value="0"/>
<Property name="verticalTextPosition" type="int" value="3"/> <Property name="verticalTextPosition" type="int" value="3"/>
</Properties> </Properties>
<Events>
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="hierarchyLayoutButtonActionPerformed"/>
</Events>
</Component> </Component>
<Component class="javax.swing.JButton" name="fastOrganicLayoutButton"> <Component class="javax.swing.JButton" name="fastOrganicLayoutButton">
<Properties> <Properties>
@ -179,9 +176,6 @@
<Property name="horizontalTextPosition" type="int" value="0"/> <Property name="horizontalTextPosition" type="int" value="0"/>
<Property name="verticalTextPosition" type="int" value="3"/> <Property name="verticalTextPosition" type="int" value="3"/>
</Properties> </Properties>
<Events>
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="fastOrganicLayoutButtonActionPerformed"/>
</Events>
</Component> </Component>
<Component class="javax.swing.JButton" name="organicLayoutButton"> <Component class="javax.swing.JButton" name="organicLayoutButton">
<Properties> <Properties>
@ -192,9 +186,6 @@
<Property name="horizontalTextPosition" type="int" value="0"/> <Property name="horizontalTextPosition" type="int" value="0"/>
<Property name="verticalTextPosition" type="int" value="3"/> <Property name="verticalTextPosition" type="int" value="3"/>
</Properties> </Properties>
<Events>
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="organicLayoutButtonActionPerformed"/>
</Events>
</Component> </Component>
<Component class="javax.swing.JButton" name="circleLayoutButton"> <Component class="javax.swing.JButton" name="circleLayoutButton">
<Properties> <Properties>
@ -205,9 +196,6 @@
<Property name="horizontalTextPosition" type="int" value="0"/> <Property name="horizontalTextPosition" type="int" value="0"/>
<Property name="verticalTextPosition" type="int" value="3"/> <Property name="verticalTextPosition" type="int" value="3"/>
</Properties> </Properties>
<Events>
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="circleLayoutButtonActionPerformed"/>
</Events>
</Component> </Component>
<Component class="javax.swing.JToolBar$Separator" name="jSeparator1"> <Component class="javax.swing.JToolBar$Separator" name="jSeparator1">
<Properties> <Properties>
@ -222,11 +210,11 @@
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor"> <Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/communications/Bundle.properties" key="VisualizationPanel.zoomOutButton.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/> <ResourceString bundle="org/sleuthkit/autopsy/communications/Bundle.properties" key="VisualizationPanel.zoomOutButton.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property> </Property>
<Property name="focusable" type="boolean" value="false"/>
<Property name="horizontalTextPosition" type="int" value="0"/>
<Property name="toolTipText" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor"> <Property name="toolTipText" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/communications/Bundle.properties" key="VisualizationPanel.zoomOutButton.toolTipText" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/> <ResourceString bundle="org/sleuthkit/autopsy/communications/Bundle.properties" key="VisualizationPanel.zoomOutButton.toolTipText" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property> </Property>
<Property name="focusable" type="boolean" value="false"/>
<Property name="horizontalTextPosition" type="int" value="0"/>
<Property name="verticalTextPosition" type="int" value="3"/> <Property name="verticalTextPosition" type="int" value="3"/>
</Properties> </Properties>
<Events> <Events>
@ -241,11 +229,11 @@
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor"> <Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/communications/Bundle.properties" key="VisualizationPanel.zoomInButton.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/> <ResourceString bundle="org/sleuthkit/autopsy/communications/Bundle.properties" key="VisualizationPanel.zoomInButton.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property> </Property>
<Property name="focusable" type="boolean" value="false"/>
<Property name="horizontalTextPosition" type="int" value="0"/>
<Property name="toolTipText" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor"> <Property name="toolTipText" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/communications/Bundle.properties" key="VisualizationPanel.zoomInButton.toolTipText" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/> <ResourceString bundle="org/sleuthkit/autopsy/communications/Bundle.properties" key="VisualizationPanel.zoomInButton.toolTipText" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property> </Property>
<Property name="focusable" type="boolean" value="false"/>
<Property name="horizontalTextPosition" type="int" value="0"/>
<Property name="verticalTextPosition" type="int" value="3"/> <Property name="verticalTextPosition" type="int" value="3"/>
</Properties> </Properties>
<Events> <Events>
@ -260,11 +248,11 @@
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor"> <Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/communications/Bundle.properties" key="VisualizationPanel.zoomActualButton.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/> <ResourceString bundle="org/sleuthkit/autopsy/communications/Bundle.properties" key="VisualizationPanel.zoomActualButton.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property> </Property>
<Property name="focusable" type="boolean" value="false"/>
<Property name="horizontalTextPosition" type="int" value="0"/>
<Property name="toolTipText" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor"> <Property name="toolTipText" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/communications/Bundle.properties" key="VisualizationPanel.zoomActualButton.toolTipText" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/> <ResourceString bundle="org/sleuthkit/autopsy/communications/Bundle.properties" key="VisualizationPanel.zoomActualButton.toolTipText" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property> </Property>
<Property name="focusable" type="boolean" value="false"/>
<Property name="horizontalTextPosition" type="int" value="0"/>
<Property name="verticalTextPosition" type="int" value="3"/> <Property name="verticalTextPosition" type="int" value="3"/>
</Properties> </Properties>
<Events> <Events>
@ -279,11 +267,11 @@
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor"> <Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/communications/Bundle.properties" key="VisualizationPanel.fitZoomButton.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/> <ResourceString bundle="org/sleuthkit/autopsy/communications/Bundle.properties" key="VisualizationPanel.fitZoomButton.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property> </Property>
<Property name="focusable" type="boolean" value="false"/>
<Property name="horizontalTextPosition" type="int" value="0"/>
<Property name="toolTipText" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor"> <Property name="toolTipText" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/communications/Bundle.properties" key="VisualizationPanel.fitZoomButton.toolTipText" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/> <ResourceString bundle="org/sleuthkit/autopsy/communications/Bundle.properties" key="VisualizationPanel.fitZoomButton.toolTipText" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property> </Property>
<Property name="focusable" type="boolean" value="false"/>
<Property name="horizontalTextPosition" type="int" value="0"/>
<Property name="verticalTextPosition" type="int" value="3"/> <Property name="verticalTextPosition" type="int" value="3"/>
</Properties> </Properties>
<Events> <Events>

View File

@ -28,7 +28,6 @@ import com.mxgraph.model.mxCell;
import com.mxgraph.model.mxICell; import com.mxgraph.model.mxICell;
import com.mxgraph.swing.handler.mxRubberband; import com.mxgraph.swing.handler.mxRubberband;
import com.mxgraph.swing.mxGraphComponent; import com.mxgraph.swing.mxGraphComponent;
import com.mxgraph.swing.util.mxMorphing;
import com.mxgraph.util.mxEvent; import com.mxgraph.util.mxEvent;
import com.mxgraph.util.mxEventObject; import com.mxgraph.util.mxEventObject;
import com.mxgraph.util.mxEventSource; import com.mxgraph.util.mxEventSource;
@ -36,11 +35,14 @@ import com.mxgraph.util.mxPoint;
import com.mxgraph.util.mxRectangle; import com.mxgraph.util.mxRectangle;
import com.mxgraph.util.mxUndoManager; import com.mxgraph.util.mxUndoManager;
import com.mxgraph.util.mxUndoableEdit; import com.mxgraph.util.mxUndoableEdit;
import com.mxgraph.view.mxCellState;
import com.mxgraph.view.mxGraph; import com.mxgraph.view.mxGraph;
import com.mxgraph.view.mxGraphView;
import java.awt.BorderLayout; import java.awt.BorderLayout;
import java.awt.Color; import java.awt.Color;
import java.awt.Cursor; import java.awt.Cursor;
import java.awt.Dimension; import java.awt.Dimension;
import java.awt.Font;
import java.awt.Frame; import java.awt.Frame;
import java.awt.event.ActionEvent; import java.awt.event.ActionEvent;
import java.awt.event.ActionListener; import java.awt.event.ActionListener;
@ -51,12 +53,18 @@ import java.beans.PropertyChangeEvent;
import java.beans.PropertyVetoException; import java.beans.PropertyVetoException;
import java.text.DecimalFormat; import java.text.DecimalFormat;
import java.util.Arrays; import java.util.Arrays;
import static java.util.Collections.singleton;
import java.util.EnumSet; import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.Map;
import java.util.concurrent.Future; import java.util.concurrent.Future;
import java.util.function.BiConsumer;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.swing.AbstractAction; import javax.swing.AbstractAction;
import javax.swing.ImageIcon; import javax.swing.ImageIcon;
import javax.swing.JButton; import javax.swing.JButton;
@ -79,12 +87,11 @@ import org.openide.util.Lookup;
import org.openide.util.NbBundle; import org.openide.util.NbBundle;
import org.openide.util.lookup.ProxyLookup; import org.openide.util.lookup.ProxyLookup;
import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.Case;
import static org.sleuthkit.autopsy.casemodule.Case.Events.CURRENT_CASE;
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil;
import org.sleuthkit.autopsy.coreutils.ThreadConfined; import org.sleuthkit.autopsy.coreutils.ThreadConfined;
import org.sleuthkit.autopsy.progress.ModalDialogProgressIndicator; import org.sleuthkit.autopsy.progress.ModalDialogProgressIndicator;
import org.sleuthkit.datamodel.AccountDeviceInstance;
import org.sleuthkit.datamodel.CommunicationsFilter; import org.sleuthkit.datamodel.CommunicationsFilter;
import org.sleuthkit.datamodel.CommunicationsManager; import org.sleuthkit.datamodel.CommunicationsManager;
import org.sleuthkit.datamodel.Content; import org.sleuthkit.datamodel.Content;
@ -100,23 +107,17 @@ import org.sleuthkit.datamodel.TskCoreException;
* CVTTopComponent when this tab is active allowing for context sensitive * CVTTopComponent when this tab is active allowing for context sensitive
* actions to work correctly. * actions to work correctly.
*/ */
@NbBundle.Messages("VisualizationPanel.cancelButton.text=Cancel")
final public class VisualizationPanel extends JPanel implements Lookup.Provider { final public class VisualizationPanel extends JPanel implements Lookup.Provider {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
private static final Logger logger = Logger.getLogger(VisualizationPanel.class.getName()); private static final Logger logger = Logger.getLogger(VisualizationPanel.class.getName());
private static final String BASE_IMAGE_PATH = "/org/sleuthkit/autopsy/communications/images"; private static final String BASE_IMAGE_PATH = "/org/sleuthkit/autopsy/communications/images";
static final private ImageIcon pinIcon
= new ImageIcon(VisualizationPanel.class.getResource(BASE_IMAGE_PATH + "/marker--pin.png"));
static final private ImageIcon addPinIcon
= new ImageIcon(VisualizationPanel.class.getResource(BASE_IMAGE_PATH + "/marker--plus.png"));
static final private ImageIcon unpinIcon
= new ImageIcon(VisualizationPanel.class.getResource(BASE_IMAGE_PATH + "/marker--minus.png"));
static final private ImageIcon unlockIcon static final private ImageIcon unlockIcon
= new ImageIcon(VisualizationPanel.class.getResource(BASE_IMAGE_PATH + "/lock_large_unlocked.png")); = new ImageIcon(VisualizationPanel.class.getResource(BASE_IMAGE_PATH + "/lock_large_unlocked.png"));
static final private ImageIcon lockIcon static final private ImageIcon lockIcon
= new ImageIcon(VisualizationPanel.class.getResource(BASE_IMAGE_PATH + "/lock_large_locked.png")); = new ImageIcon(VisualizationPanel.class.getResource(BASE_IMAGE_PATH + "/lock_large_locked.png"));
@NbBundle.Messages("VisualizationPanel.cancelButton.text=Cancel")
private static final String CANCEL = Bundle.VisualizationPanel_cancelButton_text(); private static final String CANCEL = Bundle.VisualizationPanel_cancelButton_text();
private final ExplorerManager vizEM = new ExplorerManager(); private final ExplorerManager vizEM = new ExplorerManager();
@ -131,27 +132,20 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider
private final CommunicationsGraph graph; private final CommunicationsGraph graph;
private final mxUndoManager undoManager = new mxUndoManager(); private final mxUndoManager undoManager = new mxUndoManager();
private final mxRubberband rubberband; private final mxRubberband rubberband; //NOPMD We keep a referenec as insurance to prevent garbage collection
private final mxFastOrganicLayout fastOrganicLayout;
private final mxCircleLayout circleLayout;
private final mxOrganicLayout organicLayout;
private final mxHierarchicalLayout hierarchicalLayout;
@ThreadConfined(type = ThreadConfined.ThreadType.AWT) @ThreadConfined(type = ThreadConfined.ThreadType.AWT)
private SwingWorker<?, ?> worker; private SwingWorker<?, ?> worker;
private final PinnedAccountModel pinnedAccountModel; private final PinnedAccountModel pinnedAccountModel = new PinnedAccountModel();
private final LockedVertexModel lockedVertexModel; private final LockedVertexModel lockedVertexModel = new LockedVertexModel();
private final Map<NamedGraphLayout, JButton> layoutButtons = new HashMap<>();
private NamedGraphLayout currentLayout;
public VisualizationPanel() { public VisualizationPanel() {
initComponents(); initComponents();
graph = new CommunicationsGraph();
pinnedAccountModel = graph.getPinnedAccountModel();
lockedVertexModel = graph.getLockedVertexModel();
fastOrganicLayout = new mxFastOrganicLayoutImpl(graph); graph = new CommunicationsGraph(pinnedAccountModel, lockedVertexModel);
circleLayout = new mxCircleLayoutImpl(graph);
organicLayout = new mxOrganicLayoutImpl(graph);
hierarchicalLayout = new mxHierarchicalLayoutImpl(graph);
graphComponent = new mxGraphComponent(graph); graphComponent = new mxGraphComponent(graph);
graphComponent.setAutoExtend(true); graphComponent.setAutoExtend(true);
@ -165,123 +159,90 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider
graphComponent.setBackground(Color.WHITE); graphComponent.setBackground(Color.WHITE);
borderLayoutPanel.add(graphComponent, BorderLayout.CENTER); borderLayoutPanel.add(graphComponent, BorderLayout.CENTER);
//install rubber band selection handler //install rubber band other handlers
rubberband = new mxRubberband(graphComponent); rubberband = new mxRubberband(graphComponent);
lockedVertexModel.registerhandler(this);
final mxEventSource.mxIEventListener scaleListener = (Object sender, mxEventObject evt) final mxEventSource.mxIEventListener scaleListener = (Object sender, mxEventObject evt)
-> zoomLabel.setText(DecimalFormat.getPercentInstance().format(graph.getView().getScale())); -> zoomLabel.setText(DecimalFormat.getPercentInstance().format(graph.getView().getScale()));
graph.getView().addListener(mxEvent.SCALE, scaleListener); graph.getView().addListener(mxEvent.SCALE, scaleListener);
graph.getView().addListener(mxEvent.SCALE_AND_TRANSLATE, scaleListener); graph.getView().addListener(mxEvent.SCALE_AND_TRANSLATE, scaleListener);
graphComponent.getGraphControl().addMouseWheelListener(new MouseAdapter() { final GraphMouseListener graphMouseListener = new GraphMouseListener();
/** graphComponent.getGraphControl().addMouseWheelListener(graphMouseListener);
* Translate mouse wheel events into zooming. graphComponent.getGraphControl().addMouseListener(graphMouseListener);
*
* @param event The MouseWheelEvent
*/
@Override
public void mouseWheelMoved(final MouseWheelEvent event) {
super.mouseWheelMoved(event);
if (event.getPreciseWheelRotation() > 0) {
graphComponent.zoomIn();
} else if (event.getPreciseWheelRotation() < 0) {
graphComponent.zoomOut();
}
}
});
graphComponent.getGraphControl().addMouseListener(new MouseAdapter() {
/**
* Right click handler: show context menu.
*
* @param event The MouseEvent
*/
@Override
public void mouseClicked(final MouseEvent event) {
super.mouseClicked(event);
if (SwingUtilities.isRightMouseButton(event)) {
final mxCell cellAt = (mxCell) graphComponent.getCellAt(event.getX(), event.getY());
if (cellAt != null && cellAt.isVertex()) {
final JPopupMenu jPopupMenu = new JPopupMenu();
final AccountDeviceInstanceKey adiKey = (AccountDeviceInstanceKey) cellAt.getValue();
if (lockedVertexModel.isVertexLocked(cellAt)) {
jPopupMenu.add(new JMenuItem(new AbstractAction("UnLock " + cellAt.getId(), unlockIcon) {
@Override
public void actionPerformed(final ActionEvent event) {
lockedVertexModel.unlockVertex(cellAt);
}
}));
} else {
jPopupMenu.add(new JMenuItem(new AbstractAction("Lock " + cellAt.getId(), lockIcon) {
@Override
public void actionPerformed(final ActionEvent event) {
lockedVertexModel.lockVertex(cellAt);
}
}));
}
if (pinnedAccountModel.isAccountPinned(adiKey)) {
jPopupMenu.add(new JMenuItem(new AbstractAction("Unpin " + cellAt.getId(), unpinIcon) {
@Override
public void actionPerformed(final ActionEvent event) {
handleUnPinEvent(new CVTEvents.UnpinAccountsEvent(singleton((AccountDeviceInstanceKey) cellAt.getValue())));
}
}));
} else {
jPopupMenu.add(new JMenuItem(new AbstractAction("Pin " + cellAt.getId(), addPinIcon) {
@Override
public void actionPerformed(final ActionEvent event) {
handlePinEvent(new CVTEvents.PinAccountsEvent(singleton((AccountDeviceInstanceKey) cellAt.getValue()), false));
}
}));
jPopupMenu.add(new JMenuItem(new AbstractAction("Pin only " + cellAt.getId(), pinIcon) {
@Override
public void actionPerformed(final ActionEvent event) {
handlePinEvent(new CVTEvents.PinAccountsEvent(singleton((AccountDeviceInstanceKey) cellAt.getValue()), true));
}
}));
}
jPopupMenu.show(graphComponent.getGraphControl(), event.getX(), event.getY());
}
}
}
});
final MessageBrowser messageBrowser = new MessageBrowser(vizEM, gacEM); final MessageBrowser messageBrowser = new MessageBrowser(vizEM, gacEM);
splitPane.setRightComponent(messageBrowser); splitPane.setRightComponent(messageBrowser);
proxyLookup = new ProxyLookup( proxyLookup = new ProxyLookup(
messageBrowser.getLookup(), ExplorerUtils.createLookup(vizEM, getActionMap()),
ExplorerUtils.createLookup(vizEM, getActionMap())); messageBrowser.getLookup()
);
//feed selection to explorermanager //feed selection to explorermanager
graph.getSelectionModel().addListener(null, new SelectionListener()); graph.getSelectionModel().addListener(mxEvent.CHANGE, new SelectionListener());
final mxEventSource.mxIEventListener undoListener = (Object sender, mxEventObject evt) final mxEventSource.mxIEventListener undoListener = (Object sender, mxEventObject evt)
-> undoManager.undoableEditHappened((mxUndoableEdit) evt.getProperty("edit")); -> undoManager.undoableEditHappened((mxUndoableEdit) evt.getProperty("edit"));
graph.getModel().addListener(mxEvent.UNDO, undoListener); graph.getModel().addListener(mxEvent.UNDO, undoListener);
graph.getView().addListener(mxEvent.UNDO, undoListener); graph.getView().addListener(mxEvent.UNDO, undoListener);
FastOrganicLayoutImpl fastOrganicLayout = new FastOrganicLayoutImpl(graph);
CircleLayoutImpl circleLayout = new CircleLayoutImpl(graph);
OrganicLayoutImpl organicLayout = new OrganicLayoutImpl(graph);
organicLayout.setMaxIterations(10);
HierarchicalLayoutImpl hierarchyLayout = new HierarchicalLayoutImpl(graph);
//local method to configure layout buttons
BiConsumer<JButton, NamedGraphLayout> configure = (layoutButton, layout) -> {
layoutButtons.put(layout, layoutButton);
layoutButton.addActionListener(event -> applyLayout(layout));
};
//configure layout buttons.
configure.accept(circleLayoutButton, circleLayout);
configure.accept(organicLayoutButton, organicLayout);
configure.accept(fastOrganicLayoutButton, fastOrganicLayout);
configure.accept(hierarchyLayoutButton, hierarchyLayout);
applyLayout(fastOrganicLayout);
} }
/**
*
* @param layoutButton the value of layoutButton
* @param layout the value of layout
*/
@Override @Override
public Lookup getLookup() { public Lookup getLookup() {
return proxyLookup; return proxyLookup;
} }
@Subscribe @Subscribe
void handleUnPinEvent(final CVTEvents.UnpinAccountsEvent pinEvent) { void handle(LockedVertexModel.VertexLockEvent event) {
final Set<mxCell> vertices = event.getVertices();
mxGraphView view = graph.getView();
vertices.forEach(vertex -> {
final mxCellState state = view.getState(vertex, true);
view.updateLabel(state);
view.updateLabelBounds(state);
view.updateBoundingBox(state);
graphComponent.redraw(state);
});
}
@Subscribe
void handle(final CVTEvents.UnpinAccountsEvent pinEvent) {
graph.getModel().beginUpdate(); graph.getModel().beginUpdate();
pinnedAccountModel.unpinAccount(pinEvent.getAccountDeviceInstances()); pinnedAccountModel.unpinAccount(pinEvent.getAccountDeviceInstances());
graph.clear(); graph.clear();
rebuildGraph(); rebuildGraph();
// Updates the display // Updates the display
graph.getModel().endUpdate(); graph.getModel().endUpdate();
} }
@Subscribe @Subscribe
void handlePinEvent(final CVTEvents.PinAccountsEvent pinEvent) { void handle(final CVTEvents.PinAccountsEvent pinEvent) {
graph.getModel().beginUpdate(); graph.getModel().beginUpdate();
if (pinEvent.isReplace()) { if (pinEvent.isReplace()) {
graph.resetGraph(); graph.resetGraph();
@ -290,19 +251,16 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider
rebuildGraph(); rebuildGraph();
// Updates the display // Updates the display
graph.getModel().endUpdate(); graph.getModel().endUpdate();
} }
@Subscribe @Subscribe
void handleFilterEvent(final CVTEvents.FilterChangeEvent filterChangeEvent) { void handle(final CVTEvents.FilterChangeEvent filterChangeEvent) {
graph.getModel().beginUpdate(); graph.getModel().beginUpdate();
graph.clear(); graph.clear();
currentFilter = filterChangeEvent.getNewFilter(); currentFilter = filterChangeEvent.getNewFilter();
rebuildGraph(); rebuildGraph();
// Updates the display // Updates the display
graph.getModel().endUpdate(); graph.getModel().endUpdate();
} }
@ThreadConfined(type = ThreadConfined.ThreadType.AWT) @ThreadConfined(type = ThreadConfined.ThreadType.AWT)
@ -327,14 +285,12 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider
if (worker.isCancelled()) { if (worker.isCancelled()) {
graph.resetGraph(); graph.resetGraph();
rebuildGraph(); rebuildGraph();
} else {
morph(fastOrganicLayout);
} }
applyLayout(currentLayout);
} }
}); });
worker.execute(); worker.execute();
} }
} }
@ -351,7 +307,7 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider
logger.log(Level.SEVERE, "Can't get CommunicationsManager when there is no case open.", ex); logger.log(Level.SEVERE, "Can't get CommunicationsManager when there is no case open.", ex);
} }
Case.addEventTypeSubscriber(EnumSet.of(CURRENT_CASE), evt -> { Case.addEventTypeSubscriber(EnumSet.of(Case.Events.CURRENT_CASE), evt -> {
graph.getModel().beginUpdate(); graph.getModel().beginUpdate();
try { try {
graph.resetGraph(); graph.resetGraph();
@ -368,15 +324,9 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider
logger.log(Level.SEVERE, "Error getting CommunicationsManager for the current case.", ex); logger.log(Level.SEVERE, "Error getting CommunicationsManager for the current case.", ex);
} }
} }
}); });
} }
@Override
public void removeNotify() {
super.removeNotify();
}
/** /**
* This method is called from within the constructor to initialize the form. * This method is called from within the constructor to initialize the form.
* WARNING: Do NOT modify this code. The content of this method is always * WARNING: Do NOT modify this code. The content of this method is always
@ -413,11 +363,11 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider
borderLayoutPanel.setLayout(new BorderLayout()); borderLayoutPanel.setLayout(new BorderLayout());
jTextArea1.setBackground(new Color(240, 240, 240));
jTextArea1.setColumns(20); jTextArea1.setColumns(20);
jTextArea1.setLineWrap(true); jTextArea1.setLineWrap(true);
jTextArea1.setRows(5); jTextArea1.setRows(5);
jTextArea1.setText(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.jTextArea1.text")); // NOI18N jTextArea1.setText(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.jTextArea1.text")); // NOI18N
jTextArea1.setBackground(new Color(240, 240, 240));
GroupLayout placeHolderPanelLayout = new GroupLayout(placeHolderPanel); GroupLayout placeHolderPanelLayout = new GroupLayout(placeHolderPanel);
placeHolderPanel.setLayout(placeHolderPanelLayout); placeHolderPanel.setLayout(placeHolderPanelLayout);
@ -442,49 +392,29 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider
hierarchyLayoutButton.setFocusable(false); hierarchyLayoutButton.setFocusable(false);
hierarchyLayoutButton.setHorizontalTextPosition(SwingConstants.CENTER); hierarchyLayoutButton.setHorizontalTextPosition(SwingConstants.CENTER);
hierarchyLayoutButton.setVerticalTextPosition(SwingConstants.BOTTOM); hierarchyLayoutButton.setVerticalTextPosition(SwingConstants.BOTTOM);
hierarchyLayoutButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent evt) {
hierarchyLayoutButtonActionPerformed(evt);
}
});
fastOrganicLayoutButton.setText(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.fastOrganicLayoutButton.text")); // NOI18N fastOrganicLayoutButton.setText(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.fastOrganicLayoutButton.text")); // NOI18N
fastOrganicLayoutButton.setFocusable(false); fastOrganicLayoutButton.setFocusable(false);
fastOrganicLayoutButton.setHorizontalTextPosition(SwingConstants.CENTER); fastOrganicLayoutButton.setHorizontalTextPosition(SwingConstants.CENTER);
fastOrganicLayoutButton.setVerticalTextPosition(SwingConstants.BOTTOM); fastOrganicLayoutButton.setVerticalTextPosition(SwingConstants.BOTTOM);
fastOrganicLayoutButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent evt) {
fastOrganicLayoutButtonActionPerformed(evt);
}
});
organicLayoutButton.setText(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.organicLayoutButton.text")); // NOI18N organicLayoutButton.setText(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.organicLayoutButton.text")); // NOI18N
organicLayoutButton.setFocusable(false); organicLayoutButton.setFocusable(false);
organicLayoutButton.setHorizontalTextPosition(SwingConstants.CENTER); organicLayoutButton.setHorizontalTextPosition(SwingConstants.CENTER);
organicLayoutButton.setVerticalTextPosition(SwingConstants.BOTTOM); organicLayoutButton.setVerticalTextPosition(SwingConstants.BOTTOM);
organicLayoutButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent evt) {
organicLayoutButtonActionPerformed(evt);
}
});
circleLayoutButton.setText(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.circleLayoutButton.text")); // NOI18N circleLayoutButton.setText(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.circleLayoutButton.text")); // NOI18N
circleLayoutButton.setFocusable(false); circleLayoutButton.setFocusable(false);
circleLayoutButton.setHorizontalTextPosition(SwingConstants.CENTER); circleLayoutButton.setHorizontalTextPosition(SwingConstants.CENTER);
circleLayoutButton.setVerticalTextPosition(SwingConstants.BOTTOM); circleLayoutButton.setVerticalTextPosition(SwingConstants.BOTTOM);
circleLayoutButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent evt) {
circleLayoutButtonActionPerformed(evt);
}
});
jSeparator1.setOrientation(SwingConstants.VERTICAL); jSeparator1.setOrientation(SwingConstants.VERTICAL);
zoomOutButton.setIcon(new ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/communications/images/magnifier-zoom-out-red.png"))); // NOI18N zoomOutButton.setIcon(new ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/communications/images/magnifier-zoom-out-red.png"))); // NOI18N
zoomOutButton.setText(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.zoomOutButton.text")); // NOI18N zoomOutButton.setText(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.zoomOutButton.text")); // NOI18N
zoomOutButton.setToolTipText(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.zoomOutButton.toolTipText")); // NOI18N
zoomOutButton.setFocusable(false); zoomOutButton.setFocusable(false);
zoomOutButton.setHorizontalTextPosition(SwingConstants.CENTER); zoomOutButton.setHorizontalTextPosition(SwingConstants.CENTER);
zoomOutButton.setToolTipText(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.zoomOutButton.toolTipText")); // NOI18N
zoomOutButton.setVerticalTextPosition(SwingConstants.BOTTOM); zoomOutButton.setVerticalTextPosition(SwingConstants.BOTTOM);
zoomOutButton.addActionListener(new ActionListener() { zoomOutButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent evt) { public void actionPerformed(ActionEvent evt) {
@ -494,9 +424,9 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider
zoomInButton.setIcon(new ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/communications/images/magnifier-zoom-in-green.png"))); // NOI18N zoomInButton.setIcon(new ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/communications/images/magnifier-zoom-in-green.png"))); // NOI18N
zoomInButton.setText(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.zoomInButton.text")); // NOI18N zoomInButton.setText(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.zoomInButton.text")); // NOI18N
zoomInButton.setToolTipText(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.zoomInButton.toolTipText")); // NOI18N
zoomInButton.setFocusable(false); zoomInButton.setFocusable(false);
zoomInButton.setHorizontalTextPosition(SwingConstants.CENTER); zoomInButton.setHorizontalTextPosition(SwingConstants.CENTER);
zoomInButton.setToolTipText(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.zoomInButton.toolTipText")); // NOI18N
zoomInButton.setVerticalTextPosition(SwingConstants.BOTTOM); zoomInButton.setVerticalTextPosition(SwingConstants.BOTTOM);
zoomInButton.addActionListener(new ActionListener() { zoomInButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent evt) { public void actionPerformed(ActionEvent evt) {
@ -506,9 +436,9 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider
zoomActualButton.setIcon(new ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/communications/images/magnifier-zoom-actual.png"))); // NOI18N zoomActualButton.setIcon(new ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/communications/images/magnifier-zoom-actual.png"))); // NOI18N
zoomActualButton.setText(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.zoomActualButton.text")); // NOI18N zoomActualButton.setText(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.zoomActualButton.text")); // NOI18N
zoomActualButton.setToolTipText(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.zoomActualButton.toolTipText")); // NOI18N
zoomActualButton.setFocusable(false); zoomActualButton.setFocusable(false);
zoomActualButton.setHorizontalTextPosition(SwingConstants.CENTER); zoomActualButton.setHorizontalTextPosition(SwingConstants.CENTER);
zoomActualButton.setToolTipText(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.zoomActualButton.toolTipText")); // NOI18N
zoomActualButton.setVerticalTextPosition(SwingConstants.BOTTOM); zoomActualButton.setVerticalTextPosition(SwingConstants.BOTTOM);
zoomActualButton.addActionListener(new ActionListener() { zoomActualButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent evt) { public void actionPerformed(ActionEvent evt) {
@ -518,9 +448,9 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider
fitZoomButton.setIcon(new ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/communications/images/magnifier-zoom-fit.png"))); // NOI18N fitZoomButton.setIcon(new ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/communications/images/magnifier-zoom-fit.png"))); // NOI18N
fitZoomButton.setText(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.fitZoomButton.text")); // NOI18N fitZoomButton.setText(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.fitZoomButton.text")); // NOI18N
fitZoomButton.setToolTipText(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.fitZoomButton.toolTipText")); // NOI18N
fitZoomButton.setFocusable(false); fitZoomButton.setFocusable(false);
fitZoomButton.setHorizontalTextPosition(SwingConstants.CENTER); fitZoomButton.setHorizontalTextPosition(SwingConstants.CENTER);
fitZoomButton.setToolTipText(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.fitZoomButton.toolTipText")); // NOI18N
fitZoomButton.setVerticalTextPosition(SwingConstants.BOTTOM); fitZoomButton.setVerticalTextPosition(SwingConstants.BOTTOM);
fitZoomButton.addActionListener(new ActionListener() { fitZoomButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent evt) { public void actionPerformed(ActionEvent evt) {
@ -620,21 +550,55 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider
graphComponent.zoomOut(); graphComponent.zoomOut();
}//GEN-LAST:event_zoomOutButtonActionPerformed }//GEN-LAST:event_zoomOutButtonActionPerformed
private void circleLayoutButtonActionPerformed(ActionEvent evt) {//GEN-FIRST:event_circleLayoutButtonActionPerformed /**
morph(circleLayout); * Apply the given layout. The given layout becomes the current layout. The
}//GEN-LAST:event_circleLayoutButtonActionPerformed * layout is computed in the background.
*
* @param layout The layout to apply.
*/
@NbBundle.Messages({"VisualizationPanel.computingLayout=Computing Layout",
"# {0} - layout name",
"VisualizationPanel.layoutFailWithLockedVertices.text={0} layout failed with locked vertices. Unlock some vertices or try a different layout.",
"# {0} - layout name",
"VisualizationPanel.layoutFail.text={0} layout failed. Try a different layout."})
private void applyLayout(NamedGraphLayout layout) {
currentLayout = layout;
layoutButtons.forEach((layoutKey, button)
-> button.setFont(button.getFont().deriveFont(layoutKey == layout ? Font.BOLD : Font.PLAIN)));
private void organicLayoutButtonActionPerformed(ActionEvent evt) {//GEN-FIRST:event_organicLayoutButtonActionPerformed ModalDialogProgressIndicator progressIndicator = new ModalDialogProgressIndicator(windowAncestor, Bundle.VisualizationPanel_computingLayout());
applyOrganicLayout(10); progressIndicator.start(Bundle.VisualizationPanel_computingLayout());
}//GEN-LAST:event_organicLayoutButtonActionPerformed
private void fastOrganicLayoutButtonActionPerformed(ActionEvent evt) {//GEN-FIRST:event_fastOrganicLayoutButtonActionPerformed new SwingWorker<Void, Void>() {
morph(fastOrganicLayout); @Override
}//GEN-LAST:event_fastOrganicLayoutButtonActionPerformed protected Void doInBackground() {
graph.getModel().beginUpdate();
try {
layout.execute(graph.getDefaultParent());
fitGraph();
} finally {
graph.getModel().endUpdate();
progressIndicator.finish();
}
return null;
}
private void hierarchyLayoutButtonActionPerformed(ActionEvent evt) {//GEN-FIRST:event_hierarchyLayoutButtonActionPerformed @Override
morph(hierarchicalLayout); protected void done() {
}//GEN-LAST:event_hierarchyLayoutButtonActionPerformed try {
get();
} catch (InterruptedException | ExecutionException ex) {
logger.log(Level.WARNING, "CVT graph layout failed.", ex);
if (lockedVertexModel.isEmpty()) {
MessageNotifyUtil.Message.error(Bundle.VisualizationPanel_layoutFail_text(layout.getDisplayName()));
} else {
MessageNotifyUtil.Message.error(Bundle.VisualizationPanel_layoutFailWithLockedVertices_text(layout.getDisplayName()));
}
undoManager.undo();
}
}
}.execute();
}
private void clearVizButtonActionPerformed(ActionEvent evt) {//GEN-FIRST:event_clearVizButtonActionPerformed private void clearVizButtonActionPerformed(ActionEvent evt) {//GEN-FIRST:event_clearVizButtonActionPerformed
setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
@ -645,14 +609,8 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider
// Updates the display // Updates the display
graph.getModel().endUpdate(); graph.getModel().endUpdate();
setCursor(Cursor.getDefaultCursor()); setCursor(Cursor.getDefaultCursor());
}//GEN-LAST:event_clearVizButtonActionPerformed }//GEN-LAST:event_clearVizButtonActionPerformed
private void applyOrganicLayout(int iterations) {
organicLayout.setMaxIterations(iterations);
morph(organicLayout);
}
private void fitGraph() { private void fitGraph() {
graphComponent.zoomTo(1, true); graphComponent.zoomTo(1, true);
mxPoint translate = graph.getView().getTranslate(); mxPoint translate = graph.getView().getTranslate();
@ -675,57 +633,11 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider
final Dimension size = graphComponent.getSize(); final Dimension size = graphComponent.getSize();
final double widthFactor = size.getWidth() / boundsForCells.getWidth(); final double widthFactor = size.getWidth() / boundsForCells.getWidth();
final double heightFactor = size.getHeight() / boundsForCells.getHeight();
graphComponent.zoom(widthFactor); graphComponent.zoom((heightFactor + widthFactor) / 2.0);
} }
private void morph(mxIGraphLayout layout) {
// layout using morphing
graph.getModel().beginUpdate();
CancelationListener cancelationListener = new CancelationListener();
ModalDialogProgressIndicator progress = new ModalDialogProgressIndicator(windowAncestor, "Computing layout", new String[]{CANCEL}, CANCEL, cancelationListener);
SwingWorker<Void, Void> morphWorker = new SwingWorker<Void, Void>() {
@Override
protected Void doInBackground() {
progress.start("Computing layout");
layout.execute(graph.getDefaultParent());
if (isCancelled()) {
progress.finish();
return null;
}
mxMorphing morph = new mxMorphing(graphComponent, 20, 1.2, 20) {
@Override
public void updateAnimation() {
fireEvent(new mxEventObject(mxEvent.EXECUTE));
super.updateAnimation(); //To change body of generated methods, choose Tools | Templates.
}
};
morph.addListener(mxEvent.EXECUTE, (Object sender, mxEventObject evt) -> {
if (isCancelled()) {
morph.stopAnimation();
}
});
morph.addListener(mxEvent.DONE, (Object sender, mxEventObject event) -> {
graph.getModel().endUpdate();
if (isCancelled()) {
undoManager.undo();
} else {
fitGraph();
}
progress.finish();
});
morph.startAnimation();
return null;
}
};
cancelationListener.configure(morphWorker, progress);
morphWorker.execute();
}
// Variables declaration - do not modify//GEN-BEGIN:variables // Variables declaration - do not modify//GEN-BEGIN:variables
private JPanel borderLayoutPanel; private JPanel borderLayoutPanel;
@ -749,6 +661,10 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider
private JButton zoomOutButton; private JButton zoomOutButton;
// End of variables declaration//GEN-END:variables // End of variables declaration//GEN-END:variables
/**
* Listens to graph selection model and updates ExplorerManager to reflect
* changes in selection.
*/
final private class SelectionListener implements mxEventSource.mxIEventListener { final private class SelectionListener implements mxEventSource.mxIEventListener {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@ -760,7 +676,7 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider
if (selectionCells.length > 0) { if (selectionCells.length > 0) {
mxICell[] selectedCells = Arrays.asList(selectionCells).toArray(new mxCell[selectionCells.length]); mxICell[] selectedCells = Arrays.asList(selectionCells).toArray(new mxCell[selectionCells.length]);
HashSet<Content> relationshipSources = new HashSet<>(); HashSet<Content> relationshipSources = new HashSet<>();
HashSet<AccountDeviceInstance> adis = new HashSet<>(); HashSet<AccountDeviceInstanceKey> adis = new HashSet<>();
for (mxICell cell : selectedCells) { for (mxICell cell : selectedCells) {
if (cell.isEdge()) { if (cell.isEdge()) {
mxICell source = (mxICell) graph.getModel().getTerminal(cell, true); mxICell source = (mxICell) graph.getModel().getTerminal(cell, true);
@ -777,7 +693,7 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider
logger.log(Level.SEVERE, " Error getting relationsips....", tskCoreException); logger.log(Level.SEVERE, " Error getting relationsips....", tskCoreException);
} }
} else if (cell.isVertex()) { } else if (cell.isVertex()) {
adis.add(((AccountDeviceInstanceKey) cell.getValue()).getAccountDeviceInstance()); adis.add((AccountDeviceInstanceKey) cell.getValue());
} }
} }
@ -793,9 +709,20 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider
} }
} }
final private class mxFastOrganicLayoutImpl extends mxFastOrganicLayout { /**
* Extend mxIGraphLayout with a getDisplayName method,
*/
private interface NamedGraphLayout extends mxIGraphLayout {
mxFastOrganicLayoutImpl(mxGraph graph) { String getDisplayName();
}
/**
* Extension of mxFastOrganicLayout that ignores locked vertices.
*/
final private class FastOrganicLayoutImpl extends mxFastOrganicLayout implements NamedGraphLayout {
FastOrganicLayoutImpl(mxGraph graph) {
super(graph); super(graph);
} }
@ -806,18 +733,26 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider
} }
@Override @Override
public mxRectangle setVertexLocation(Object vertex, double x, double y) { public mxRectangle setVertexLocation(Object vertex, double x, double y) { //NOPMD x, y are standard coordinate names
if (isVertexIgnored(vertex)) { if (isVertexIgnored(vertex)) {
return getVertexBounds(vertex); return getVertexBounds(vertex);
} else { } else {
return super.setVertexLocation(vertex, x, y); return super.setVertexLocation(vertex, x, y);
} }
} }
@Override
public String getDisplayName() {
return "Fast Organic";
}
} }
final private class mxCircleLayoutImpl extends mxCircleLayout { /**
* Extension of mxCircleLayout that ignores locked vertices.
*/
final private class CircleLayoutImpl extends mxCircleLayout implements NamedGraphLayout {
mxCircleLayoutImpl(mxGraph graph) { CircleLayoutImpl(mxGraph graph) {
super(graph); super(graph);
setResetEdges(true); setResetEdges(true);
} }
@ -829,18 +764,26 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider
} }
@Override @Override
public mxRectangle setVertexLocation(Object vertex, double x, double y) { public mxRectangle setVertexLocation(Object vertex, double x, double y) { //NOPMD x, y are standard coordinate names
if (isVertexIgnored(vertex)) { if (isVertexIgnored(vertex)) {
return getVertexBounds(vertex); return getVertexBounds(vertex);
} else { } else {
return super.setVertexLocation(vertex, x, y); return super.setVertexLocation(vertex, x, y);
} }
} }
@Override
public String getDisplayName() {
return "Circle";
}
} }
final private class mxOrganicLayoutImpl extends mxOrganicLayout { /**
* Extension of mxOrganicLayout that ignores locked vertices.
*/
final private class OrganicLayoutImpl extends mxOrganicLayout implements NamedGraphLayout {
mxOrganicLayoutImpl(mxGraph graph) { OrganicLayoutImpl(mxGraph graph) {
super(graph); super(graph);
setResetEdges(true); setResetEdges(true);
} }
@ -852,18 +795,26 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider
} }
@Override @Override
public mxRectangle setVertexLocation(Object vertex, double x, double y) { public mxRectangle setVertexLocation(Object vertex, double x, double y) { //NOPMD x, y are standard coordinate names
if (isVertexIgnored(vertex)) { if (isVertexIgnored(vertex)) {
return getVertexBounds(vertex); return getVertexBounds(vertex);
} else { } else {
return super.setVertexLocation(vertex, x, y); return super.setVertexLocation(vertex, x, y);
} }
} }
@Override
public String getDisplayName() {
return "Organic";
}
} }
final private class mxHierarchicalLayoutImpl extends mxHierarchicalLayout { /**
* Extension of mxHierarchicalLayout that ignores locked vertices.
*/
final private class HierarchicalLayoutImpl extends mxHierarchicalLayout implements NamedGraphLayout {
mxHierarchicalLayoutImpl(mxGraph graph) { HierarchicalLayoutImpl(mxGraph graph) {
super(graph); super(graph);
} }
@ -874,15 +825,24 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider
} }
@Override @Override
public mxRectangle setVertexLocation(Object vertex, double x, double y) { public mxRectangle setVertexLocation(Object vertex, double x, double y) { //NOPMD x, y are standard coordinate names
if (isVertexIgnored(vertex)) { if (isVertexIgnored(vertex)) {
return getVertexBounds(vertex); return getVertexBounds(vertex);
} else { } else {
return super.setVertexLocation(vertex, x, y); return super.setVertexLocation(vertex, x, y);
} }
} }
@Override
public String getDisplayName() {
return "Hierarchical";
}
} }
/**
* Listener that closses the given ModalDialogProgressIndicator and cancels
* the future.
*/
private class CancelationListener implements ActionListener { private class CancelationListener implements ActionListener {
private Future<?> cancellable; private Future<?> cancellable;
@ -899,6 +859,108 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider
cancellable.cancel(true); cancellable.cancel(true);
progress.finish(); progress.finish();
} }
}
/**
* Mouse Adapter for the graphComponent. Handles wheel zooming and context
* menus.
*/
private class GraphMouseListener extends MouseAdapter {
/**
* Translate mouse wheel events into zooming.
*
* @param event The MouseWheelEvent
*/
@Override
public void mouseWheelMoved(final MouseWheelEvent event) {
super.mouseWheelMoved(event);
if (event.getPreciseWheelRotation() < 0) {
graphComponent.zoomIn();
} else if (event.getPreciseWheelRotation() > 0) {
graphComponent.zoomOut();
}
}
/**
* Right click handler: show context menu.
*
* @param event The MouseEvent
*/
@Override
public void mouseClicked(final MouseEvent event) {
super.mouseClicked(event);
if (SwingUtilities.isRightMouseButton(event)) {
final mxCell cellAt = (mxCell) graphComponent.getCellAt(event.getX(), event.getY());
if (cellAt != null && cellAt.isVertex()) {
final JPopupMenu jPopupMenu = new JPopupMenu();
final AccountDeviceInstanceKey adiKey = (AccountDeviceInstanceKey) cellAt.getValue();
Set<mxCell> selectedVertices
= Stream.of(graph.getSelectionModel().getCells())
.map(mxCell.class::cast)
.filter(mxCell::isVertex)
.collect(Collectors.toSet());
if (lockedVertexModel.isVertexLocked(cellAt)) {
jPopupMenu.add(new JMenuItem(new UnlockAction(selectedVertices)));
} else {
jPopupMenu.add(new JMenuItem(new LockAction(selectedVertices)));
}
if (pinnedAccountModel.isAccountPinned(adiKey)) {
jPopupMenu.add(UnpinAccountsAction.getInstance().getPopupPresenter());
} else {
jPopupMenu.add(PinAccountsAction.getInstance().getPopupPresenter());
jPopupMenu.add(ResetAndPinAccountsAction.getInstance().getPopupPresenter());
}
jPopupMenu.show(graphComponent.getGraphControl(), event.getX(), event.getY());
}
}
}
}
/**
* Action that un-locks the selected vertices.
*/
@NbBundle.Messages({
"VisualizationPanel.unlockAction.singularText=Unlock Selected Account",
"VisualizationPanel.unlockAction.pluralText=Unlock Selected Accounts",})
private final class UnlockAction extends AbstractAction {
private final Set<mxCell> selectedVertices;
UnlockAction(Set<mxCell> selectedVertices) {
super(selectedVertices.size() > 1 ? Bundle.VisualizationPanel_unlockAction_pluralText() : Bundle.VisualizationPanel_unlockAction_singularText(),
unlockIcon);
this.selectedVertices = selectedVertices;
}
@Override
public void actionPerformed(final ActionEvent event) {
lockedVertexModel.unlock(selectedVertices);
}
}
/**
* Action that locks the selected vertices.
*/
@NbBundle.Messages({
"VisualizationPanel.lockAction.singularText=Lock Selected Account",
"VisualizationPanel.lockAction.pluralText=Lock Selected Accounts"})
private final class LockAction extends AbstractAction {
private final Set<mxCell> selectedVertices;
LockAction(Set<mxCell> selectedVertices) {
super(selectedVertices.size() > 1 ? Bundle.VisualizationPanel_lockAction_pluralText() : Bundle.VisualizationPanel_lockAction_singularText(),
lockIcon);
this.selectedVertices = selectedVertices;
}
@Override
public void actionPerformed(final ActionEvent event) {
lockedVertexModel.lock(selectedVertices);
}
} }
} }

View File

@ -457,7 +457,8 @@ public final class CoordinationService {
CASES("cases"), CASES("cases"),
MANIFESTS("manifests"), MANIFESTS("manifests"),
CONFIG("config"), CONFIG("config"),
CENTRAL_REPO("centralRepository"); CENTRAL_REPO("centralRepository"),
HEALTH_MONITOR("healthMonitor");
private final String displayName; private final String displayName;

View File

@ -216,6 +216,7 @@ public class Installer extends ModuleInstall {
packageInstallers.add(org.sleuthkit.autopsy.datamodel.Installer.getDefault()); packageInstallers.add(org.sleuthkit.autopsy.datamodel.Installer.getDefault());
packageInstallers.add(org.sleuthkit.autopsy.ingest.Installer.getDefault()); packageInstallers.add(org.sleuthkit.autopsy.ingest.Installer.getDefault());
packageInstallers.add(org.sleuthkit.autopsy.centralrepository.eventlisteners.Installer.getDefault()); packageInstallers.add(org.sleuthkit.autopsy.centralrepository.eventlisteners.Installer.getDefault());
packageInstallers.add(org.sleuthkit.autopsy.healthmonitor.Installer.getDefault());
} }
/** /**

View File

@ -408,6 +408,7 @@ final public class Accounts implements AutopsyVisitableItem {
protected void addNotify() { protected void addNotify() {
IngestManager.getInstance().addIngestJobEventListener(pcl); IngestManager.getInstance().addIngestJobEventListener(pcl);
IngestManager.getInstance().addIngestModuleEventListener(pcl); IngestManager.getInstance().addIngestModuleEventListener(pcl);
Case.addEventTypeSubscriber(EnumSet.of(Case.Events.CURRENT_CASE), pcl);
super.addNotify(); super.addNotify();
} }
@ -415,6 +416,7 @@ final public class Accounts implements AutopsyVisitableItem {
protected void removeNotify() { protected void removeNotify() {
IngestManager.getInstance().removeIngestJobEventListener(pcl); IngestManager.getInstance().removeIngestJobEventListener(pcl);
IngestManager.getInstance().removeIngestModuleEventListener(pcl); IngestManager.getInstance().removeIngestModuleEventListener(pcl);
Case.removeEventTypeSubscriber(EnumSet.of(Case.Events.CURRENT_CASE), pcl);
super.removeNotify(); super.removeNotify();
} }
@ -569,6 +571,7 @@ final public class Accounts implements AutopsyVisitableItem {
protected void addNotify() { protected void addNotify() {
IngestManager.getInstance().addIngestJobEventListener(pcl); IngestManager.getInstance().addIngestJobEventListener(pcl);
IngestManager.getInstance().addIngestModuleEventListener(pcl); IngestManager.getInstance().addIngestModuleEventListener(pcl);
Case.addEventTypeSubscriber(EnumSet.of(Case.Events.CURRENT_CASE), pcl);
super.addNotify(); super.addNotify();
} }
@ -576,6 +579,7 @@ final public class Accounts implements AutopsyVisitableItem {
protected void removeNotify() { protected void removeNotify() {
IngestManager.getInstance().removeIngestJobEventListener(pcl); IngestManager.getInstance().removeIngestJobEventListener(pcl);
IngestManager.getInstance().removeIngestModuleEventListener(pcl); IngestManager.getInstance().removeIngestModuleEventListener(pcl);
Case.removeEventTypeSubscriber(EnumSet.of(Case.Events.CURRENT_CASE), pcl);
super.removeNotify(); super.removeNotify();
} }
@ -691,6 +695,7 @@ final public class Accounts implements AutopsyVisitableItem {
protected void addNotify() { protected void addNotify() {
IngestManager.getInstance().addIngestJobEventListener(pcl); IngestManager.getInstance().addIngestJobEventListener(pcl);
IngestManager.getInstance().addIngestModuleEventListener(pcl); IngestManager.getInstance().addIngestModuleEventListener(pcl);
Case.addEventTypeSubscriber(EnumSet.of(Case.Events.CURRENT_CASE), pcl);
super.addNotify(); super.addNotify();
} }
@ -698,6 +703,7 @@ final public class Accounts implements AutopsyVisitableItem {
protected void removeNotify() { protected void removeNotify() {
IngestManager.getInstance().removeIngestJobEventListener(pcl); IngestManager.getInstance().removeIngestJobEventListener(pcl);
IngestManager.getInstance().removeIngestModuleEventListener(pcl); IngestManager.getInstance().removeIngestModuleEventListener(pcl);
Case.removeEventTypeSubscriber(EnumSet.of(Case.Events.CURRENT_CASE), pcl);
super.removeNotify(); super.removeNotify();
} }
@ -901,6 +907,7 @@ final public class Accounts implements AutopsyVisitableItem {
protected void addNotify() { protected void addNotify() {
IngestManager.getInstance().addIngestJobEventListener(pcl); IngestManager.getInstance().addIngestJobEventListener(pcl);
IngestManager.getInstance().addIngestModuleEventListener(pcl); IngestManager.getInstance().addIngestModuleEventListener(pcl);
Case.addEventTypeSubscriber(EnumSet.of(Case.Events.CURRENT_CASE), pcl);
super.addNotify(); super.addNotify();
} }
@ -908,6 +915,7 @@ final public class Accounts implements AutopsyVisitableItem {
protected void removeNotify() { protected void removeNotify() {
IngestManager.getInstance().removeIngestJobEventListener(pcl); IngestManager.getInstance().removeIngestJobEventListener(pcl);
IngestManager.getInstance().removeIngestModuleEventListener(pcl); IngestManager.getInstance().removeIngestModuleEventListener(pcl);
Case.removeEventTypeSubscriber(EnumSet.of(Case.Events.CURRENT_CASE), pcl);
super.removeNotify(); super.removeNotify();
} }

View File

@ -0,0 +1,785 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2018 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.healthmonitor;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Map;
import java.util.HashMap;
import java.util.UUID;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Level;
import org.apache.commons.dbcp2.BasicDataSource;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.coordinationservice.CoordinationService;
import org.sleuthkit.autopsy.core.UserPreferences;
import org.sleuthkit.autopsy.core.UserPreferencesException;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.coreutils.ModuleSettings;
import org.sleuthkit.autopsy.coreutils.ThreadUtils;
import org.sleuthkit.datamodel.CaseDbConnectionInfo;
import org.sleuthkit.datamodel.CaseDbSchemaVersionNumber;
/**
* Class for recording data on the health of the system.
*
* For timing data:
* Modules will call getTimingMetric() before the code to be timed to get a TimingMetric object
* Modules will call submitTimingMetric() with the obtained TimingMetric object to log it
*/
public final class EnterpriseHealthMonitor implements PropertyChangeListener {
private final static Logger logger = Logger.getLogger(EnterpriseHealthMonitor.class.getName());
private final static String DATABASE_NAME = "EnterpriseHealthMonitor";
private final static String MODULE_NAME = "EnterpriseHealthMonitor";
private final static String IS_ENABLED_KEY = "is_enabled";
private final static long DATABASE_WRITE_INTERVAL = 60; // Minutes
public static final CaseDbSchemaVersionNumber CURRENT_DB_SCHEMA_VERSION
= new CaseDbSchemaVersionNumber(1, 0);
private static final AtomicBoolean isEnabled = new AtomicBoolean(false);
private static EnterpriseHealthMonitor instance;
private final ExecutorService healthMonitorExecutor;
private static final String HEALTH_MONITOR_EVENT_THREAD_NAME = "Health-Monitor-Event-Listener-%d";
private ScheduledThreadPoolExecutor healthMonitorOutputTimer;
private final Map<String, TimingInfo> timingInfoMap;
private static final int CONN_POOL_SIZE = 10;
private BasicDataSource connectionPool = null;
private String hostName;
private EnterpriseHealthMonitor() throws HealthMonitorException {
// Create the map to collect timing metrics. The map will exist regardless
// of whether the monitor is enabled.
timingInfoMap = new HashMap<>();
// Set up the executor to handle case events
healthMonitorExecutor = Executors.newSingleThreadExecutor(new ThreadFactoryBuilder().setNameFormat(HEALTH_MONITOR_EVENT_THREAD_NAME).build());
// Get the host name
try {
hostName = java.net.InetAddress.getLocalHost().getHostName();
} catch (java.net.UnknownHostException ex) {
// Continue on, but log the error and generate a UUID to use for this session
hostName = UUID.randomUUID().toString();
logger.log(Level.SEVERE, "Unable to look up host name - falling back to UUID " + hostName, ex);
}
// Read from module settings to determine if the module is enabled
if (ModuleSettings.settingExists(MODULE_NAME, IS_ENABLED_KEY)) {
if(ModuleSettings.getConfigSetting(MODULE_NAME, IS_ENABLED_KEY).equals("true")){
isEnabled.set(true);
try {
activateMonitor();
} catch (HealthMonitorException ex) {
// If we failed to activate it, then disable the monitor
logger.log(Level.SEVERE, "Health monitor activation failed - disabling health monitor");
setEnabled(false);
throw ex;
}
return;
}
}
isEnabled.set(false);
}
/**
* Get the instance of the EnterpriseHealthMonitor
* @return the instance
* @throws HealthMonitorException
*/
synchronized static EnterpriseHealthMonitor getInstance() throws HealthMonitorException {
if (instance == null) {
instance = new EnterpriseHealthMonitor();
Case.addPropertyChangeListener(instance);
}
return instance;
}
/**
* Activate the health monitor.
* Creates/initialized the database (if needed), clears any existing metrics
* out of the maps, and sets up the timer for writing to the database.
* @throws HealthMonitorException
*/
private synchronized void activateMonitor() throws HealthMonitorException {
logger.log(Level.INFO, "Activating Servies Health Monitor");
if (!UserPreferences.getIsMultiUserModeEnabled()) {
throw new HealthMonitorException("Multi user mode is not enabled - can not activate health monitor");
}
// Set up database (if needed)
try (CoordinationService.Lock lock = getExclusiveDbLock()) {
if(lock == null) {
throw new HealthMonitorException("Error getting database lock");
}
// Check if the database exists
if (! databaseExists()) {
// If not, create a new one
createDatabase();
}
if( ! databaseIsInitialized()) {
initializeDatabaseSchema();
}
} catch (CoordinationService.CoordinationServiceException ex) {
throw new HealthMonitorException("Error releasing database lock", ex);
}
// Clear out any old data
timingInfoMap.clear();
// Start the timer for database writes
startTimer();
}
/**
* Deactivate the health monitor.
* This should only be used when disabling the monitor, not when Autopsy is closing.
* Clears out any metrics that haven't been written, stops the database write timer,
* and shuts down the connection pool.
* @throws HealthMonitorException
*/
private synchronized void deactivateMonitor() throws HealthMonitorException {
logger.log(Level.INFO, "Deactivating Servies Health Monitor");
// Clear out the collected data
timingInfoMap.clear();
// Stop the timer
stopTimer();
// Shut down the connection pool
shutdownConnections();
}
/**
* Start the ScheduledThreadPoolExecutor that will handle the database writes.
*/
private synchronized void startTimer() {
// Make sure the previous executor (if it exists) has been stopped
stopTimer();
healthMonitorOutputTimer = new ScheduledThreadPoolExecutor(1, new ThreadFactoryBuilder().setNameFormat("health_monitor_timer").build());
healthMonitorOutputTimer.scheduleWithFixedDelay(new DatabaseWriteTask(), DATABASE_WRITE_INTERVAL, DATABASE_WRITE_INTERVAL, TimeUnit.MINUTES);
}
/**
* Stop the ScheduledThreadPoolExecutor to prevent further database writes.
*/
private synchronized void stopTimer() {
if(healthMonitorOutputTimer != null) {
ThreadUtils.shutDownTaskExecutor(healthMonitorOutputTimer);
}
}
/**
* Called from the installer to set up the Health Monitor instance at startup.
* @throws HealthMonitorException
*/
static synchronized void startUpIfEnabled() throws HealthMonitorException {
getInstance();
}
/**
* Enabled/disable the health monitor.
* @param enabled true to enable the monitor, false to disable it
* @throws HealthMonitorException
*/
static synchronized void setEnabled(boolean enabled) throws HealthMonitorException {
if(enabled == isEnabled.get()) {
// The setting has not changed, so do nothing
return;
}
if(enabled) {
getInstance().activateMonitor();
// If activateMonitor fails, we won't update either of these
ModuleSettings.setConfigSetting(MODULE_NAME, IS_ENABLED_KEY, "true");
isEnabled.set(true);
} else {
ModuleSettings.setConfigSetting(MODULE_NAME, IS_ENABLED_KEY, "false");
isEnabled.set(false);
getInstance().deactivateMonitor();
}
}
/**
* Get a metric that will measure the time to execute a section of code.
* Call this before the section of code to be timed and then
* submit it afterward using submitTimingMetric().
* This method is safe to call regardless of whether the Enterprise Health
* Monitor is enabled.
* @param name A short but descriptive name describing the code being timed.
* This name will appear in the UI.
* @return The TimingMetric object
*/
public static TimingMetric getTimingMetric(String name) {
if(isEnabled.get()) {
return new TimingMetric(name);
}
return null;
}
/**
* Submit the metric that was previously obtained through getTimingMetric().
* Call this immediately after the section of code being timed.
* This method is safe to call regardless of whether the Enterprise Health
* Monitor is enabled.
* @param metric The TimingMetric object obtained from getTimingMetric()
*/
public static void submitTimingMetric(TimingMetric metric) {
if(isEnabled.get() && (metric != null)) {
metric.stopTiming();
try {
getInstance().addTimingMetric(metric);
} catch (HealthMonitorException ex) {
// We don't want calling methods to have to check for exceptions, so just log it
logger.log(Level.SEVERE, "Error adding timing metric", ex);
}
}
}
/**
* Add the timing metric data to the map.
* @param metric The metric to add. stopTiming() should already have been called.
*/
private void addTimingMetric(TimingMetric metric) throws HealthMonitorException {
// Do as little as possible within the synchronized block to minimize
// blocking with multiple threads.
synchronized(this) {
// There's a small check-then-act situation here where isEnabled
// may have changed before reaching this code. This is fine -
// the map still exists and any extra data added after the monitor
// is disabled will be deleted if the monitor is re-enabled. This
// seems preferable to doing another check on isEnabled within
// the synchronized block.
if(timingInfoMap.containsKey(metric.getName())) {
timingInfoMap.get(metric.getName()).addMetric(metric);
} else {
timingInfoMap.put(metric.getName(), new TimingInfo(metric));
}
}
}
/**
* Write the collected metrics to the database.
* @throws HealthMonitorException
*/
private void writeCurrentStateToDatabase() throws HealthMonitorException {
Map<String, TimingInfo> timingMapCopy;
// Do as little as possible within the synchronized block since it will
// block threads attempting to record metrics.
synchronized(this) {
if(! isEnabled.get()) {
return;
}
// Make a shallow copy of the timing map. The map should be small - one entry
// per metric name.
timingMapCopy = new HashMap<>(timingInfoMap);
timingInfoMap.clear();
}
// Check if there's anything to report (right now we only have the timing map)
if(timingMapCopy.keySet().isEmpty()) {
return;
}
logger.log(Level.INFO, "Writing health monitor metrics to database");
// Write to the database
try (CoordinationService.Lock lock = getSharedDbLock()) {
if(lock == null) {
throw new HealthMonitorException("Error getting database lock");
}
Connection conn = connect();
if(conn == null) {
throw new HealthMonitorException("Error getting database connection");
}
// Add timing metrics to the database
String addTimingInfoSql = "INSERT INTO timing_data (name, host, timestamp, count, average, max, min) VALUES (?, ?, ?, ?, ?, ?, ?)";
try (PreparedStatement statement = conn.prepareStatement(addTimingInfoSql)) {
for(String name:timingMapCopy.keySet()) {
TimingInfo info = timingMapCopy.get(name);
statement.setString(1, name);
statement.setString(2, hostName);
statement.setLong(3, System.currentTimeMillis());
statement.setLong(4, info.getCount());
statement.setLong(5, info.getAverage());
statement.setLong(6, info.getMax());
statement.setLong(7, info.getMin());
statement.execute();
}
} catch (SQLException ex) {
throw new HealthMonitorException("Error saving metric data to database", ex);
} finally {
try {
conn.close();
} catch (SQLException ex) {
logger.log(Level.SEVERE, "Error closing Connection.", ex);
}
}
} catch (CoordinationService.CoordinationServiceException ex) {
throw new HealthMonitorException("Error releasing database lock", ex);
}
}
/**
* Check whether the health monitor database exists.
* Does not check the schema.
* @return true if the database exists, false otherwise
* @throws HealthMonitorException
*/
private boolean databaseExists() throws HealthMonitorException {
try {
// Use the same database settings as the case
CaseDbConnectionInfo db = UserPreferences.getDatabaseConnectionInfo();
Class.forName("org.postgresql.Driver"); //NON-NLS
ResultSet rs = null;
try (Connection connection = DriverManager.getConnection("jdbc:postgresql://" + db.getHost() + ":" + db.getPort() + "/postgres", db.getUserName(), db.getPassword()); //NON-NLS
Statement statement = connection.createStatement();) {
String createCommand = "SELECT 1 AS result FROM pg_database WHERE datname='" + DATABASE_NAME + "'";
rs = statement.executeQuery(createCommand);
if(rs.next()) {
logger.log(Level.INFO, "Existing Enterprise Health Monitor database found");
return true;
}
} finally {
if(rs != null) {
rs.close();
}
}
} catch (UserPreferencesException | ClassNotFoundException | SQLException ex) {
throw new HealthMonitorException("Failed check for health monitor database", ex);
}
return false;
}
/**
* Create a new health monitor database.
* @throws HealthMonitorException
*/
private void createDatabase() throws HealthMonitorException {
try {
// Use the same database settings as the case
CaseDbConnectionInfo db = UserPreferences.getDatabaseConnectionInfo();
Class.forName("org.postgresql.Driver"); //NON-NLS
try (Connection connection = DriverManager.getConnection("jdbc:postgresql://" + db.getHost() + ":" + db.getPort() + "/postgres", db.getUserName(), db.getPassword()); //NON-NLS
Statement statement = connection.createStatement();) {
String createCommand = "CREATE DATABASE \"" + DATABASE_NAME + "\" OWNER \"" + db.getUserName() + "\""; //NON-NLS
statement.execute(createCommand);
}
logger.log(Level.INFO, "Created new health monitor database " + DATABASE_NAME);
} catch (UserPreferencesException | ClassNotFoundException | SQLException ex) {
throw new HealthMonitorException("Failed to delete health monitor database", ex);
}
}
/**
* Setup a connection pool for db connections.
* @throws HealthMonitorException
*/
private void setupConnectionPool() throws HealthMonitorException {
try {
CaseDbConnectionInfo db = UserPreferences.getDatabaseConnectionInfo();
connectionPool = new BasicDataSource();
connectionPool.setDriverClassName("org.postgresql.Driver");
StringBuilder connectionURL = new StringBuilder();
connectionURL.append("jdbc:postgresql://");
connectionURL.append(db.getHost());
connectionURL.append(":");
connectionURL.append(db.getPort());
connectionURL.append("/");
connectionURL.append(DATABASE_NAME);
connectionPool.setUrl(connectionURL.toString());
connectionPool.setUsername(db.getUserName());
connectionPool.setPassword(db.getPassword());
// tweak pool configuration
connectionPool.setInitialSize(3); // start with 3 connections
connectionPool.setMaxIdle(CONN_POOL_SIZE); // max of 10 idle connections
connectionPool.setValidationQuery("SELECT version()");
} catch (UserPreferencesException ex) {
throw new HealthMonitorException("Error loading database configuration", ex);
}
}
/**
* Shut down the connection pool
* @throws HealthMonitorException
*/
private void shutdownConnections() throws HealthMonitorException {
try {
synchronized(this) {
if(connectionPool != null){
connectionPool.close();
connectionPool = null; // force it to be re-created on next connect()
}
}
} catch (SQLException ex) {
throw new HealthMonitorException("Failed to close existing database connections.", ex); // NON-NLS
}
}
/**
* Get a database connection.
* Sets up the connection pool if needed.
* @return The Connection object
* @throws HealthMonitorException
*/
private Connection connect() throws HealthMonitorException {
synchronized (this) {
if (connectionPool == null) {
setupConnectionPool();
}
}
try {
return connectionPool.getConnection();
} catch (SQLException ex) {
throw new HealthMonitorException("Error getting connection from connection pool.", ex); // NON-NLS
}
}
/**
* Test whether the database schema has been initialized.
* We do this by looking for the version number.
* @return True if it has been initialized, false otherwise.
* @throws HealthMonitorException
*/
private boolean databaseIsInitialized() throws HealthMonitorException {
Connection conn = connect();
if(conn == null) {
throw new HealthMonitorException("Error getting database connection");
}
ResultSet resultSet = null;
try (Statement statement = conn.createStatement()) {
resultSet = statement.executeQuery("SELECT value FROM db_info WHERE name='SCHEMA_VERSION'");
return resultSet.next();
} catch (SQLException ex) {
// This likely just means that the db_info table does not exist
return false;
} finally {
if(resultSet != null) {
try {
resultSet.close();
} catch (SQLException ex) {
logger.log(Level.SEVERE, "Error closing result set", ex);
}
}
try {
conn.close();
} catch (SQLException ex) {
logger.log(Level.SEVERE, "Error closing Connection.", ex);
}
}
}
/**
* Get the current schema version
* @return the current schema version
* @throws HealthMonitorException
*/
private CaseDbSchemaVersionNumber getVersion() throws HealthMonitorException {
Connection conn = connect();
if(conn == null) {
throw new HealthMonitorException("Error getting database connection");
}
ResultSet resultSet = null;
try (Statement statement = conn.createStatement()) {
int minorVersion = 0;
int majorVersion = 0;
resultSet = statement.executeQuery("SELECT value FROM db_info WHERE name='SCHEMA_MINOR_VERSION'");
if (resultSet.next()) {
String minorVersionStr = resultSet.getString("value");
try {
minorVersion = Integer.parseInt(minorVersionStr);
} catch (NumberFormatException ex) {
throw new HealthMonitorException("Bad value for schema minor version (" + minorVersionStr + ") - database is corrupt");
}
}
resultSet = statement.executeQuery("SELECT value FROM db_info WHERE name='SCHEMA_VERSION'");
if (resultSet.next()) {
String majorVersionStr = resultSet.getString("value");
try {
majorVersion = Integer.parseInt(majorVersionStr);
} catch (NumberFormatException ex) {
throw new HealthMonitorException("Bad value for schema version (" + majorVersionStr + ") - database is corrupt");
}
}
return new CaseDbSchemaVersionNumber(majorVersion, minorVersion);
} catch (SQLException ex) {
throw new HealthMonitorException("Error initializing database", ex);
} finally {
if(resultSet != null) {
try {
resultSet.close();
} catch (SQLException ex) {
logger.log(Level.SEVERE, "Error closing result set", ex);
}
}
try {
conn.close();
} catch (SQLException ex) {
logger.log(Level.SEVERE, "Error closing Connection.", ex);
}
}
}
/**
* Initialize the database.
* @throws HealthMonitorException
*/
private void initializeDatabaseSchema() throws HealthMonitorException {
Connection conn = connect();
if(conn == null) {
throw new HealthMonitorException("Error getting database connection");
}
try (Statement statement = conn.createStatement()) {
conn.setAutoCommit(false);
String createTimingTable =
"CREATE TABLE IF NOT EXISTS timing_data (" +
"id SERIAL PRIMARY KEY," +
"name text NOT NULL," +
"host text NOT NULL," +
"timestamp bigint NOT NULL," +
"count bigint NOT NULL," +
"average bigint NOT NULL," +
"max bigint NOT NULL," +
"min bigint NOT NULL" +
")";
statement.execute(createTimingTable);
String createDbInfoTable =
"CREATE TABLE IF NOT EXISTS db_info (" +
"id SERIAL PRIMARY KEY NOT NULL," +
"name text NOT NULL," +
"value text NOT NULL" +
")";
statement.execute(createDbInfoTable);
statement.execute("INSERT INTO db_info (name, value) VALUES ('SCHEMA_VERSION', '" + CURRENT_DB_SCHEMA_VERSION.getMajor() + "')");
statement.execute("INSERT INTO db_info (name, value) VALUES ('SCHEMA_MINOR_VERSION', '" + CURRENT_DB_SCHEMA_VERSION.getMinor() + "')");
conn.commit();
} catch (SQLException ex) {
try {
conn.rollback();
} catch (SQLException ex2) {
logger.log(Level.SEVERE, "Rollback error");
}
throw new HealthMonitorException("Error initializing database", ex);
} finally {
try {
conn.close();
} catch (SQLException ex) {
logger.log(Level.SEVERE, "Error closing connection.", ex);
}
}
}
/**
* The task called by the ScheduledThreadPoolExecutor to handle
* the database writes.
*/
static final class DatabaseWriteTask implements Runnable {
/**
* Write current metric data to the database
*/
@Override
public void run() {
try {
getInstance().writeCurrentStateToDatabase();
} catch (HealthMonitorException ex) {
logger.log(Level.SEVERE, "Error writing current metrics to database", ex); //NON-NLS
}
}
}
@Override
public void propertyChange(PropertyChangeEvent evt) {
switch (Case.Events.valueOf(evt.getPropertyName())) {
case CURRENT_CASE:
if ((null == evt.getNewValue()) && (evt.getOldValue() instanceof Case)) {
// When a case is closed, write the current metrics to the database
healthMonitorExecutor.submit(new EnterpriseHealthMonitor.DatabaseWriteTask());
}
break;
}
}
/**
* Get an exclusive lock for the health monitor database.
* Acquire this before creating, initializing, or updating the database schema.
* @return The lock
* @throws HealthMonitorException
*/
private CoordinationService.Lock getExclusiveDbLock() throws HealthMonitorException{
try {
CoordinationService.Lock lock = CoordinationService.getInstance().tryGetExclusiveLock(CoordinationService.CategoryNode.HEALTH_MONITOR, DATABASE_NAME, 5, TimeUnit.MINUTES);
if(lock != null){
return lock;
}
throw new HealthMonitorException("Error acquiring database lock");
} catch (InterruptedException | CoordinationService.CoordinationServiceException ex){
throw new HealthMonitorException("Error acquiring database lock", ex);
}
}
/**
* Get an shared lock for the health monitor database.
* Acquire this before database reads or writes.
* @return The lock
* @throws HealthMonitorException
*/
private CoordinationService.Lock getSharedDbLock() throws HealthMonitorException{
try {
String databaseNodeName = DATABASE_NAME;
CoordinationService.Lock lock = CoordinationService.getInstance().tryGetSharedLock(CoordinationService.CategoryNode.HEALTH_MONITOR, databaseNodeName, 5, TimeUnit.MINUTES);
if(lock != null){
return lock;
}
throw new HealthMonitorException("Error acquiring database lock");
} catch (InterruptedException | CoordinationService.CoordinationServiceException ex){
throw new HealthMonitorException("Error acquiring database lock");
}
}
/**
* Internal class for collecting timing metrics.
* Instead of storing each TimingMetric, we only store the min and max
* seen and the number of metrics and total duration to compute the average
* later.
* One TimingInfo instance should be created per metric name, and
* additional timing metrics will be added to it.
*/
private class TimingInfo {
private long count; // Number of metrics collected
private long sum; // Sum of the durations collected (nanoseconds)
private long max; // Maximum value found (nanoseconds)
private long min; // Minimum value found (nanoseconds)
TimingInfo(TimingMetric metric) throws HealthMonitorException {
count = 1;
sum = metric.getDuration();
max = metric.getDuration();
min = metric.getDuration();
}
/**
* Add a new TimingMetric to an existing TimingInfo object.
* This is called in a synchronized block for almost all new
* TimingMetric objects, so do as little processing here as possible.
* @param metric The new metric
* @throws HealthMonitorException Will be thrown if the metric hasn't been stopped
*/
void addMetric(TimingMetric metric) throws HealthMonitorException {
// Keep track of needed info to calculate the average
count++;
sum += metric.getDuration();
// Check if this is the longest duration seen
if(max < metric.getDuration()) {
max = metric.getDuration();
}
// Check if this is the lowest duration seen
if(min > metric.getDuration()) {
min = metric.getDuration();
}
}
/**
* Get the average duration
* @return average duration (nanoseconds)
*/
long getAverage() {
return sum / count;
}
/**
* Get the maximum duration
* @return maximum duration (nanoseconds)
*/
long getMax() {
return max;
}
/**
* Get the minimum duration
* @return minimum duration (nanoseconds)
*/
long getMin() {
return min;
}
/**
* Get the total number of metrics collected
* @return number of metrics collected
*/
long getCount() {
return count;
}
}
}

View File

@ -16,13 +16,19 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.sleuthkit.autopsy.healthmonitor;
package org.sleuthkit.autopsy.communications;
/** /**
* * Exception used internally by the Services Health Monitor
*/ */
public interface EventHandler<T> { class HealthMonitorException extends Exception {
private static final long serialVersionUID = 1L;
void handle(T event); HealthMonitorException(String message) {
super(message);
}
HealthMonitorException(String message, Throwable cause) {
super(message, cause);
}
} }

View File

@ -0,0 +1,53 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2018 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.healthmonitor;
import java.util.logging.Level;
import org.openide.modules.ModuleInstall;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.coreutils.Logger;
public class Installer extends ModuleInstall {
private static final Logger logger = Logger.getLogger(Installer.class.getName());
private static final long serialVersionUID = 1L;
private static Installer instance;
public synchronized static Installer getDefault() {
if (instance == null) {
instance = new Installer();
}
return instance;
}
private Installer() {
super();
}
@Override
public void restored() {
try {
EnterpriseHealthMonitor.startUpIfEnabled();
} catch (HealthMonitorException ex) {
logger.log(Level.SEVERE, "Error starting health services monitor", ex);
}
}
}

View File

@ -0,0 +1,65 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2018 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.healthmonitor;
/**
* Used to calculate and report timing metrics.
*/
public class TimingMetric {
private final String name;
private final long startingTimestamp;
private Long duration;
TimingMetric(String name) {
this.name = name;
this.startingTimestamp = System.nanoTime();
this.duration = null;
}
/**
* Record how long the metric was running.
*/
void stopTiming() {
long endingTimestamp = System.nanoTime();
this.duration = endingTimestamp - startingTimestamp;
}
/**
* Get the name of metric
* @return name
*/
String getName() {
return name;
}
/**
* Get the duration of the metric. Will throw an exception if the
* metric has not been stopped.
* @return how long the metric was running (nanoseconds)
* @throws HealthMonitorException
*/
long getDuration() throws HealthMonitorException {
if (duration != null) {
return duration;
} else {
throw new HealthMonitorException("getDuration() called before stopTiming()");
}
}
}

View File

@ -136,7 +136,6 @@ class VolatilityProcessor {
break; break;
} }
String pluginToRun = pluginsToRun.get(i); String pluginToRun = pluginsToRun.get(i);
progressMonitor.setProgressText(Bundle.VolatilityProcessor_progressMessage_runningImageInfo(pluginToRun));
runVolatilityPlugin(pluginToRun); runVolatilityPlugin(pluginToRun);
progressMonitor.setProgress(i); progressMonitor.setProgress(i);
} }
@ -172,6 +171,8 @@ class VolatilityProcessor {
"VolatilityProcessor_exceptionMessage_errorIndexingOutput=Error indexing output for {0} plugin" "VolatilityProcessor_exceptionMessage_errorIndexingOutput=Error indexing output for {0} plugin"
}) })
private void runVolatilityPlugin(String pluginToRun) throws VolatilityProcessorException { private void runVolatilityPlugin(String pluginToRun) throws VolatilityProcessorException {
progressMonitor.setProgressText("Running module " + pluginToRun);
List<String> commandLine = new ArrayList<>(); List<String> commandLine = new ArrayList<>();
commandLine.add("\"" + executableFile + "\""); //NON-NLS commandLine.add("\"" + executableFile + "\""); //NON-NLS
File memoryImage = new File(memoryImagePath); File memoryImage = new File(memoryImagePath);
@ -314,6 +315,8 @@ class VolatilityProcessor {
String filePath = volfile.getParent(); String filePath = volfile.getParent();
logger.log(Level.INFO, "Looking up file " + fileName + " at path " + filePath);
try { try {
List<AbstractFile> resolvedFiles; List<AbstractFile> resolvedFiles;
if (filePath == null) { if (filePath == null) {
@ -333,12 +336,13 @@ class VolatilityProcessor {
} }
fileName += ".%"; //NON-NLS fileName += ".%"; //NON-NLS
logger.log(Level.INFO, "Looking up file (extension wildcard) " + fileName + " at path " + filePath);
if (filePath == null) { if (filePath == null) {
resolvedFiles = fileManager.findFiles(fileName); //NON-NLS resolvedFiles = fileManager.findFiles(fileName); //NON-NLS
} else { } else {
resolvedFiles = fileManager.findFiles(fileName, filePath); //NON-NLS resolvedFiles = fileManager.findFiles(fileName, filePath); //NON-NLS
} }
} }
if (resolvedFiles.isEmpty()) { if (resolvedFiles.isEmpty()) {
@ -387,6 +391,7 @@ class VolatilityProcessor {
* @param pluginOutputFile File that contains the output to parse. * @param pluginOutputFile File that contains the output to parse.
*/ */
private void createArtifactsFromPluginOutput(String pluginName, File pluginOutputFile) throws VolatilityProcessorException { private void createArtifactsFromPluginOutput(String pluginName, File pluginOutputFile) throws VolatilityProcessorException {
progressMonitor.setProgressText("Parsing module " + pluginName);
Set<String> fileSet = null; Set<String> fileSet = null;
switch (pluginName) { switch (pluginName) {
case "dlllist": //NON-NLS case "dlllist": //NON-NLS
@ -421,6 +426,7 @@ class VolatilityProcessor {
} }
if (fileSet != null && !fileSet.isEmpty()) { if (fileSet != null && !fileSet.isEmpty()) {
progressMonitor.setProgressText("Flagging files from module " + pluginName);
flagFiles(fileSet, pluginName); flagFiles(fileSet, pluginName);
} }
} }

View File

@ -27,6 +27,8 @@ import org.apache.solr.common.SolrInputDocument;
import org.openide.util.NbBundle; import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.datamodel.ContentUtils; import org.sleuthkit.autopsy.datamodel.ContentUtils;
import org.sleuthkit.autopsy.healthmonitor.EnterpriseHealthMonitor;
import org.sleuthkit.autopsy.healthmonitor.TimingMetric;
import org.sleuthkit.autopsy.ingest.IngestJobContext; import org.sleuthkit.autopsy.ingest.IngestJobContext;
import org.sleuthkit.autopsy.keywordsearch.Chunker.Chunk; import org.sleuthkit.autopsy.keywordsearch.Chunker.Chunk;
import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.AbstractFile;
@ -235,7 +237,9 @@ class Ingester {
try { try {
//TODO: consider timeout thread, or vary socket timeout based on size of indexed content //TODO: consider timeout thread, or vary socket timeout based on size of indexed content
TimingMetric metric = EnterpriseHealthMonitor.getTimingMetric("Solr: Index chunk");
solrServer.addDocument(updateDoc); solrServer.addDocument(updateDoc);
EnterpriseHealthMonitor.submitTimingMetric(metric);
uncommitedIngests = true; uncommitedIngests = true;
} catch (KeywordSearchModuleException | NoOpenCoreException ex) { } catch (KeywordSearchModuleException | NoOpenCoreException ex) {

View File

@ -70,6 +70,8 @@ import org.sleuthkit.autopsy.core.UserPreferences;
import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.coreutils.ModuleSettings; import org.sleuthkit.autopsy.coreutils.ModuleSettings;
import org.sleuthkit.autopsy.coreutils.PlatformUtil; import org.sleuthkit.autopsy.coreutils.PlatformUtil;
import org.sleuthkit.autopsy.healthmonitor.EnterpriseHealthMonitor;
import org.sleuthkit.autopsy.healthmonitor.TimingMetric;
import org.sleuthkit.autopsy.keywordsearchservice.KeywordSearchServiceException; import org.sleuthkit.autopsy.keywordsearchservice.KeywordSearchServiceException;
import org.sleuthkit.datamodel.Content; import org.sleuthkit.datamodel.Content;
@ -773,7 +775,9 @@ public class Server {
IndexingServerProperties properties = getMultiUserServerProperties(theCase.getCaseDirectory()); IndexingServerProperties properties = getMultiUserServerProperties(theCase.getCaseDirectory());
currentSolrServer = new HttpSolrServer("http://" + properties.getHost() + ":" + properties.getPort() + "/solr"); //NON-NLS currentSolrServer = new HttpSolrServer("http://" + properties.getHost() + ":" + properties.getPort() + "/solr"); //NON-NLS
} }
TimingMetric metric = EnterpriseHealthMonitor.getTimingMetric("Solr: Connectivity check");
connectToSolrServer(currentSolrServer); connectToSolrServer(currentSolrServer);
EnterpriseHealthMonitor.submitTimingMetric(metric);
} catch (SolrServerException | IOException ex) { } catch (SolrServerException | IOException ex) {
throw new KeywordSearchModuleException(NbBundle.getMessage(Server.class, "Server.connect.exception.msg", ex.getLocalizedMessage()), ex); throw new KeywordSearchModuleException(NbBundle.getMessage(Server.class, "Server.connect.exception.msg", ex.getLocalizedMessage()), ex);