mirror of
https://github.com/overcuriousity/autopsy-flatpak.git
synced 2025-07-15 09:17:42 +00:00
Merge branch 'custom-release-may-2018' of https://github.com/sleuthkit/autopsy into 3610-SearchableAutoIngestJobs
This commit is contained in:
commit
10cc0aceb9
@ -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>
|
||||||
|
@ -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();
|
||||||
|
}
|
@ -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));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -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.
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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();
|
||||||
@ -308,15 +294,14 @@ final class CommunicationsGraph extends mxGraph {
|
|||||||
AccountDeviceInstanceKey account2 = relatedAccounts.get(relationshipKey.getSecond());
|
AccountDeviceInstanceKey account2 = relatedAccounts.get(relationshipKey.getSecond());
|
||||||
|
|
||||||
if (pinnedAccountModel.isAccountPinned(account1)
|
if (pinnedAccountModel.isAccountPinned(account1)
|
||||||
|| pinnedAccountModel.isAccountPinned(account2)) {
|
|| pinnedAccountModel.isAccountPinned(account2)) {
|
||||||
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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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;
|
/**
|
||||||
|
* Event that represents a change in the locked state of one or more
|
||||||
|
* vertices.
|
||||||
|
*/
|
||||||
|
final static class VertexLockEvent {
|
||||||
|
|
||||||
public mxCell getVertex() {
|
private final boolean locked;
|
||||||
return vertex;
|
private final Set<mxCell> vertices;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The vertices whose locked state has changed.
|
||||||
|
*/
|
||||||
|
public Set<mxCell> getVertices() {
|
||||||
|
return vertices;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isVertexLocked() {
|
/**
|
||||||
|
* @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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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());
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
@ -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) {
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
@ -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.");
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
@ -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, "{key}")"/>
|
<ResourceString bundle="org/sleuthkit/autopsy/communications/Bundle.properties" key="VisualizationPanel.jTextArea1.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||||
</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, "{key}")"/>
|
<ResourceString bundle="org/sleuthkit/autopsy/communications/Bundle.properties" key="VisualizationPanel.zoomOutButton.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||||
</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, "{key}")"/>
|
<ResourceString bundle="org/sleuthkit/autopsy/communications/Bundle.properties" key="VisualizationPanel.zoomOutButton.toolTipText" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||||
</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, "{key}")"/>
|
<ResourceString bundle="org/sleuthkit/autopsy/communications/Bundle.properties" key="VisualizationPanel.zoomInButton.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||||
</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, "{key}")"/>
|
<ResourceString bundle="org/sleuthkit/autopsy/communications/Bundle.properties" key="VisualizationPanel.zoomInButton.toolTipText" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||||
</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, "{key}")"/>
|
<ResourceString bundle="org/sleuthkit/autopsy/communications/Bundle.properties" key="VisualizationPanel.zoomActualButton.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||||
</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, "{key}")"/>
|
<ResourceString bundle="org/sleuthkit/autopsy/communications/Bundle.properties" key="VisualizationPanel.zoomActualButton.toolTipText" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||||
</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, "{key}")"/>
|
<ResourceString bundle="org/sleuthkit/autopsy/communications/Bundle.properties" key="VisualizationPanel.fitZoomButton.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||||
</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, "{key}")"/>
|
<ResourceString bundle="org/sleuthkit/autopsy/communications/Bundle.properties" key="VisualizationPanel.fitZoomButton.toolTipText" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||||
</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>
|
||||||
|
@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
@ -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());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
53
Core/src/org/sleuthkit/autopsy/healthmonitor/Installer.java
Normal file
53
Core/src/org/sleuthkit/autopsy/healthmonitor/Installer.java
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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()");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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) {
|
||||||
|
@ -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);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user