From 642981ff697af65efac3b92aa8f8a94f2d46f8fd Mon Sep 17 00:00:00 2001 From: millmanorama Date: Thu, 29 Mar 2018 15:58:34 +0200 Subject: [PATCH 01/25] show applied layout in bold --- .../communications/CommunicationsGraph.java | 4 +- .../communications/VisualizationPanel.form | 22 +-- .../communications/VisualizationPanel.java | 173 ++++++++++-------- 3 files changed, 113 insertions(+), 86 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/communications/CommunicationsGraph.java b/Core/src/org/sleuthkit/autopsy/communications/CommunicationsGraph.java index f274ab00d7..ddeb89ada6 100644 --- a/Core/src/org/sleuthkit/autopsy/communications/CommunicationsGraph.java +++ b/Core/src/org/sleuthkit/autopsy/communications/CommunicationsGraph.java @@ -278,13 +278,13 @@ final class CommunicationsGraph extends mxGraph { for (final AccountDeviceInstanceKey adiKey : pinnedAccountModel.getPinnedAccounts()) { if (isCancelled()) { break; - } + } + //get accounts related to pinned account final List relatedAccountDeviceInstances = commsManager.getRelatedAccountDeviceInstances(adiKey.getAccountDeviceInstance(), currentFilter); relatedAccounts.put(adiKey.getAccountDeviceInstance(), adiKey); getOrCreateVertex(adiKey); - //get accounts related to pinned account for (final AccountDeviceInstance relatedADI : relatedAccountDeviceInstances) { final long adiRelationshipsCount = commsManager.getRelationshipSourcesCount(relatedADI, currentFilter); final AccountDeviceInstanceKey relatedADIKey = new AccountDeviceInstanceKey(relatedADI, currentFilter, adiRelationshipsCount); diff --git a/Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.form b/Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.form index 9d5722443e..3fb2b658d9 100644 --- a/Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.form +++ b/Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.form @@ -68,15 +68,15 @@ + + + - - - @@ -222,11 +222,11 @@ - - + + @@ -241,11 +241,11 @@ - - + + @@ -260,11 +260,11 @@ - - + + @@ -279,11 +279,11 @@ - - + + diff --git a/Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.java b/Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.java index cd75ab5185..a19ccb3f8d 100644 --- a/Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.java +++ b/Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.java @@ -41,6 +41,7 @@ import java.awt.BorderLayout; import java.awt.Color; import java.awt.Cursor; import java.awt.Dimension; +import java.awt.Font; import java.awt.Frame; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; @@ -56,6 +57,7 @@ import java.util.EnumSet; import java.util.HashSet; import java.util.List; import java.util.concurrent.Future; +import java.util.function.Consumer; import java.util.logging.Level; import javax.swing.AbstractAction; import javax.swing.ImageIcon; @@ -142,8 +144,12 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider private final PinnedAccountModel pinnedAccountModel; private final LockedVertexModel lockedVertexModel; + private final List layoutButtons; + public VisualizationPanel() { initComponents(); + this.layoutButtons = Arrays.asList(circleLayoutButton, fastOrganicLayoutButton, organicLayoutButton, hierarchyLayoutButton); + graph = new CommunicationsGraph(); pinnedAccountModel = graph.getPinnedAccountModel(); lockedVertexModel = graph.getLockedVertexModel(); @@ -173,83 +179,13 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider graph.getView().addListener(mxEvent.SCALE, scaleListener); graph.getView().addListener(mxEvent.SCALE_AND_TRANSLATE, scaleListener); - graphComponent.getGraphControl().addMouseWheelListener(new 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(); - } - } - }); + final GraphMouseListener graphMouseListener = new GraphMouseListener(); - 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(); + graphComponent.getGraphControl().addMouseWheelListener(graphMouseListener); + graphComponent.getGraphControl().addMouseListener(graphMouseListener); - 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); - splitPane.setRightComponent(messageBrowser); - proxyLookup = new ProxyLookup( messageBrowser.getLookup(), ExplorerUtils.createLookup(vizEM, getActionMap())); @@ -261,6 +197,7 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider graph.getModel().addListener(mxEvent.UNDO, undoListener); graph.getView().addListener(mxEvent.UNDO, undoListener); + } @Override @@ -621,18 +558,35 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider }//GEN-LAST:event_zoomOutButtonActionPerformed private void circleLayoutButtonActionPerformed(ActionEvent evt) {//GEN-FIRST:event_circleLayoutButtonActionPerformed + makeButtonBold(circleLayoutButton); morph(circleLayout); }//GEN-LAST:event_circleLayoutButtonActionPerformed private void organicLayoutButtonActionPerformed(ActionEvent evt) {//GEN-FIRST:event_organicLayoutButtonActionPerformed + makeButtonBold(organicLayoutButton); applyOrganicLayout(10); }//GEN-LAST:event_organicLayoutButtonActionPerformed private void fastOrganicLayoutButtonActionPerformed(ActionEvent evt) {//GEN-FIRST:event_fastOrganicLayoutButtonActionPerformed + makeButtonBold(fastOrganicLayoutButton); morph(fastOrganicLayout); }//GEN-LAST:event_fastOrganicLayoutButtonActionPerformed + /** + * Sets only the given button to have bold text. + * + * @param layoutButton The button to make bold. + */ + private void makeButtonBold(JButton layoutButton) { + layoutButtons.forEach((JButton t) -> { + t.setFont(t.getFont().deriveFont(Font.PLAIN)); + }); + + layoutButton.setFont(layoutButton.getFont().deriveFont(Font.BOLD)); + } + private void hierarchyLayoutButtonActionPerformed(ActionEvent evt) {//GEN-FIRST:event_hierarchyLayoutButtonActionPerformed + makeButtonBold(hierarchyLayoutButton); morph(hierarchicalLayout); }//GEN-LAST:event_hierarchyLayoutButtonActionPerformed @@ -901,4 +855,77 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider } } + + 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(); + + 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()); + } + } + } + } } From 268308874eb2dc1a1085405e34cadce0f85e9621 Mon Sep 17 00:00:00 2001 From: millmanorama Date: Wed, 4 Apr 2018 14:07:30 +0200 Subject: [PATCH 02/25] refactoring layout buttons --- .../communications/VisualizationPanel.form | 12 -- .../communications/VisualizationPanel.java | 112 +++++++----------- 2 files changed, 46 insertions(+), 78 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.form b/Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.form index 3fb2b658d9..a847f700c7 100644 --- a/Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.form +++ b/Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.form @@ -166,9 +166,6 @@ - - - @@ -179,9 +176,6 @@ - - - @@ -192,9 +186,6 @@ - - - @@ -205,9 +196,6 @@ - - - diff --git a/Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.java b/Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.java index a19ccb3f8d..965832b3bd 100644 --- a/Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.java +++ b/Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.java @@ -22,6 +22,7 @@ import com.google.common.eventbus.Subscribe; import com.mxgraph.layout.hierarchical.mxHierarchicalLayout; import com.mxgraph.layout.mxCircleLayout; import com.mxgraph.layout.mxFastOrganicLayout; +import com.mxgraph.layout.mxGraphLayout; import com.mxgraph.layout.mxIGraphLayout; import com.mxgraph.layout.mxOrganicLayout; import com.mxgraph.model.mxCell; @@ -54,10 +55,14 @@ import java.text.DecimalFormat; import java.util.Arrays; import static java.util.Collections.singleton; import java.util.EnumSet; +import java.util.HashMap; import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.concurrent.Future; +import java.util.function.BiConsumer; import java.util.function.Consumer; +import java.util.function.Function; import java.util.logging.Level; import javax.swing.AbstractAction; import javax.swing.ImageIcon; @@ -137,27 +142,23 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider private final mxFastOrganicLayout fastOrganicLayout; private final mxCircleLayout circleLayout; private final mxOrganicLayout organicLayout; - private final mxHierarchicalLayout hierarchicalLayout; + private final mxHierarchicalLayout hierarchyLayout; @ThreadConfined(type = ThreadConfined.ThreadType.AWT) private SwingWorker worker; private final PinnedAccountModel pinnedAccountModel; private final LockedVertexModel lockedVertexModel; - private final List layoutButtons; + private final Map layoutButtons = new HashMap<>(); + private mxGraphLayout currentLayout; public VisualizationPanel() { initComponents(); - this.layoutButtons = Arrays.asList(circleLayoutButton, fastOrganicLayoutButton, organicLayoutButton, hierarchyLayoutButton); - graph = new CommunicationsGraph(); + pinnedAccountModel = graph.getPinnedAccountModel(); lockedVertexModel = graph.getLockedVertexModel(); - - fastOrganicLayout = new mxFastOrganicLayoutImpl(graph); - circleLayout = new mxCircleLayoutImpl(graph); - organicLayout = new mxOrganicLayoutImpl(graph); - hierarchicalLayout = new mxHierarchicalLayoutImpl(graph); + graphComponent = new mxGraphComponent(graph); graphComponent.setAutoExtend(true); @@ -197,9 +198,32 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider graph.getModel().addListener(mxEvent.UNDO, undoListener); graph.getView().addListener(mxEvent.UNDO, undoListener); + + fastOrganicLayout = new mxFastOrganicLayoutImpl(graph); + circleLayout = new mxCircleLayoutImpl(graph); + organicLayout = new mxOrganicLayoutImpl(graph); + organicLayout.setMaxIterations(10); + hierarchyLayout = new mxHierarchicalLayoutImpl(graph); + //local method to configure layout buttons + BiConsumer 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(circleLayout); } + /** + * + * @param layoutButton the value of layoutButton + * @param layout the value of layout + */ @Override public Lookup getLookup() { @@ -264,6 +288,7 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider if (worker.isCancelled()) { graph.resetGraph(); rebuildGraph(); + morph(organicLayout); } else { morph(fastOrganicLayout); } @@ -350,11 +375,11 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider borderLayoutPanel.setLayout(new BorderLayout()); + jTextArea1.setBackground(new Color(240, 240, 240)); jTextArea1.setColumns(20); jTextArea1.setLineWrap(true); jTextArea1.setRows(5); jTextArea1.setText(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.jTextArea1.text")); // NOI18N - jTextArea1.setBackground(new Color(240, 240, 240)); GroupLayout placeHolderPanelLayout = new GroupLayout(placeHolderPanel); placeHolderPanel.setLayout(placeHolderPanelLayout); @@ -379,49 +404,29 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider hierarchyLayoutButton.setFocusable(false); hierarchyLayoutButton.setHorizontalTextPosition(SwingConstants.CENTER); 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.setFocusable(false); fastOrganicLayoutButton.setHorizontalTextPosition(SwingConstants.CENTER); 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.setFocusable(false); organicLayoutButton.setHorizontalTextPosition(SwingConstants.CENTER); 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.setFocusable(false); circleLayoutButton.setHorizontalTextPosition(SwingConstants.CENTER); circleLayoutButton.setVerticalTextPosition(SwingConstants.BOTTOM); - circleLayoutButton.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent evt) { - circleLayoutButtonActionPerformed(evt); - } - }); jSeparator1.setOrientation(SwingConstants.VERTICAL); 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.setToolTipText(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.zoomOutButton.toolTipText")); // NOI18N zoomOutButton.setFocusable(false); zoomOutButton.setHorizontalTextPosition(SwingConstants.CENTER); - zoomOutButton.setToolTipText(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.zoomOutButton.toolTipText")); // NOI18N zoomOutButton.setVerticalTextPosition(SwingConstants.BOTTOM); zoomOutButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent evt) { @@ -431,9 +436,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.setText(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.zoomInButton.text")); // NOI18N + zoomInButton.setToolTipText(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.zoomInButton.toolTipText")); // NOI18N zoomInButton.setFocusable(false); zoomInButton.setHorizontalTextPosition(SwingConstants.CENTER); - zoomInButton.setToolTipText(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.zoomInButton.toolTipText")); // NOI18N zoomInButton.setVerticalTextPosition(SwingConstants.BOTTOM); zoomInButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent evt) { @@ -443,9 +448,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.setText(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.zoomActualButton.text")); // NOI18N + zoomActualButton.setToolTipText(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.zoomActualButton.toolTipText")); // NOI18N zoomActualButton.setFocusable(false); zoomActualButton.setHorizontalTextPosition(SwingConstants.CENTER); - zoomActualButton.setToolTipText(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.zoomActualButton.toolTipText")); // NOI18N zoomActualButton.setVerticalTextPosition(SwingConstants.BOTTOM); zoomActualButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent evt) { @@ -455,9 +460,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.setText(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.fitZoomButton.text")); // NOI18N + fitZoomButton.setToolTipText(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.fitZoomButton.toolTipText")); // NOI18N fitZoomButton.setFocusable(false); fitZoomButton.setHorizontalTextPosition(SwingConstants.CENTER); - fitZoomButton.setToolTipText(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.fitZoomButton.toolTipText")); // NOI18N fitZoomButton.setVerticalTextPosition(SwingConstants.BOTTOM); fitZoomButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent evt) { @@ -557,38 +562,18 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider graphComponent.zoomOut(); }//GEN-LAST:event_zoomOutButtonActionPerformed - private void circleLayoutButtonActionPerformed(ActionEvent evt) {//GEN-FIRST:event_circleLayoutButtonActionPerformed - makeButtonBold(circleLayoutButton); - morph(circleLayout); - }//GEN-LAST:event_circleLayoutButtonActionPerformed - - private void organicLayoutButtonActionPerformed(ActionEvent evt) {//GEN-FIRST:event_organicLayoutButtonActionPerformed - makeButtonBold(organicLayoutButton); - applyOrganicLayout(10); - }//GEN-LAST:event_organicLayoutButtonActionPerformed - - private void fastOrganicLayoutButtonActionPerformed(ActionEvent evt) {//GEN-FIRST:event_fastOrganicLayoutButtonActionPerformed - makeButtonBold(fastOrganicLayoutButton); - morph(fastOrganicLayout); - }//GEN-LAST:event_fastOrganicLayoutButtonActionPerformed - /** - * Sets only the given button to have bold text. * - * @param layoutButton The button to make bold. + * @param layoutButton the value of layoutButton + * @param layout the value of layout */ - private void makeButtonBold(JButton layoutButton) { - layoutButtons.forEach((JButton t) -> { - t.setFont(t.getFont().deriveFont(Font.PLAIN)); - }); - - layoutButton.setFont(layoutButton.getFont().deriveFont(Font.BOLD)); + private void applyLayout(mxGraphLayout layout) { + currentLayout = layout; + layoutButtons.forEach((layoutKey, button) + -> button.setFont(button.getFont().deriveFont(layoutKey == layout ? Font.BOLD : Font.PLAIN)) ); + morph(layout); } - private void hierarchyLayoutButtonActionPerformed(ActionEvent evt) {//GEN-FIRST:event_hierarchyLayoutButtonActionPerformed - makeButtonBold(hierarchyLayoutButton); - morph(hierarchicalLayout); - }//GEN-LAST:event_hierarchyLayoutButtonActionPerformed private void clearVizButtonActionPerformed(ActionEvent evt) {//GEN-FIRST:event_clearVizButtonActionPerformed setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); @@ -602,11 +587,6 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider }//GEN-LAST:event_clearVizButtonActionPerformed - private void applyOrganicLayout(int iterations) { - organicLayout.setMaxIterations(iterations); - morph(organicLayout); - } - private void fitGraph() { graphComponent.zoomTo(1, true); mxPoint translate = graph.getView().getTranslate(); From 08cc853fa8e8e447268ecde3c18f4f980508c11f Mon Sep 17 00:00:00 2001 From: millmanorama Date: Wed, 4 Apr 2018 16:22:45 +0200 Subject: [PATCH 03/25] refactor to use LockedVertexLayoutWrapper --- .../communications/LockedVertexModel.java | 123 ++++++++++++++++ .../communications/VisualizationPanel.form | 6 +- .../communications/VisualizationPanel.java | 131 ++++-------------- 3 files changed, 151 insertions(+), 109 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/communications/LockedVertexModel.java b/Core/src/org/sleuthkit/autopsy/communications/LockedVertexModel.java index f26e9515b3..cde4eb5698 100644 --- a/Core/src/org/sleuthkit/autopsy/communications/LockedVertexModel.java +++ b/Core/src/org/sleuthkit/autopsy/communications/LockedVertexModel.java @@ -19,8 +19,13 @@ package org.sleuthkit.autopsy.communications; import com.google.common.eventbus.EventBus; +import com.mxgraph.layout.mxGraphLayout; import com.mxgraph.model.mxCell; +import com.mxgraph.util.mxPoint; +import com.mxgraph.util.mxRectangle; +import com.mxgraph.view.mxGraph; import java.util.HashSet; +import java.util.List; import java.util.Set; class LockedVertexModel { @@ -95,4 +100,122 @@ class LockedVertexModel { this.locked = locked; } } + + mxGraphLayout createLockedVertexWrapper(L layout) { + return new LockedVertexLayoutWrapper<>(layout, this); + } + + /** An mxGraphLayout that wrapps an other layout and ignores locked vertes. + * + * @param + */ + private static final class LockedVertexLayoutWrapper extends mxGraphLayout { + + private final L wrappedLayout; + private final LockedVertexModel lockedVertexModel; + + /** + * + * + * @param layout the value of graph + * @param lockedVertexModel the value of lockedVertexModel2 + */ + private LockedVertexLayoutWrapper(L layout, LockedVertexModel lockedVertexModel) { + super(layout.getGraph()); + this.lockedVertexModel = lockedVertexModel; + wrappedLayout = layout; + } + + @Override + public boolean isVertexIgnored(Object vertex) { + return wrappedLayout.isVertexIgnored(vertex) + || lockedVertexModel.isVertexLocked((mxCell) vertex); + } + + @Override + public mxRectangle setVertexLocation(Object vertex, double x, double y) { + if (isVertexIgnored(vertex)) { + return getVertexBounds(vertex); + } else { + return wrappedLayout.setVertexLocation(vertex, x, y); + } + } + + @Override + public void execute(Object parent) { + wrappedLayout.execute(parent); + } + + @Override + public void moveCell(Object cell, double x, double y) { + wrappedLayout.moveCell(cell, x, y); + } + + @Override + public mxGraph getGraph() { + return wrappedLayout.getGraph(); + } + + @Override + public Object getConstraint(Object key, Object cell) { + return wrappedLayout.getConstraint(key, cell); + } + + @Override + public Object getConstraint(Object key, Object cell, Object edge, boolean source) { + return wrappedLayout.getConstraint(key, cell, edge, source); + } + + @Override + public boolean isUseBoundingBox() { + return wrappedLayout.isUseBoundingBox(); + } + + @Override + public void setUseBoundingBox(boolean useBoundingBox) { + wrappedLayout.setUseBoundingBox(useBoundingBox); + } + + @Override + public boolean isVertexMovable(Object vertex) { + return wrappedLayout.isVertexMovable(vertex); + } + + @Override + public boolean isEdgeIgnored(Object edge) { + return wrappedLayout.isEdgeIgnored(edge); + } + + @Override + public void setEdgeStyleEnabled(Object edge, boolean value) { + wrappedLayout.setEdgeStyleEnabled(edge, value); + } + + @Override + public void setOrthogonalEdge(Object edge, boolean value) { + wrappedLayout.setOrthogonalEdge(edge, value); + } + + @Override + public mxPoint getParentOffset(Object parent) { + return wrappedLayout.getParentOffset(parent); + } + + @Override + public void setEdgePoints(Object edge, List points) { + wrappedLayout.setEdgePoints(edge, points); + } + + @Override + public mxRectangle getVertexBounds(Object vertex) { + return wrappedLayout.getVertexBounds(vertex); + } + + @Override + public void arrangeGroups(Object[] groups, int border) { + wrappedLayout.arrangeGroups(groups, border); + } + + } + } diff --git a/Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.form b/Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.form index a847f700c7..deae56fd17 100644 --- a/Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.form +++ b/Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.form @@ -11,7 +11,7 @@ - + @@ -106,7 +106,7 @@ - + @@ -120,7 +120,7 @@ - + diff --git a/Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.java b/Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.java index 965832b3bd..a5175cfb4b 100644 --- a/Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.java +++ b/Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.java @@ -21,10 +21,12 @@ package org.sleuthkit.autopsy.communications; import com.google.common.eventbus.Subscribe; import com.mxgraph.layout.hierarchical.mxHierarchicalLayout; import com.mxgraph.layout.mxCircleLayout; +import com.mxgraph.layout.mxEdgeLabelLayout; import com.mxgraph.layout.mxFastOrganicLayout; import com.mxgraph.layout.mxGraphLayout; import com.mxgraph.layout.mxIGraphLayout; import com.mxgraph.layout.mxOrganicLayout; +import com.mxgraph.layout.mxParallelEdgeLayout; import com.mxgraph.model.mxCell; import com.mxgraph.model.mxICell; import com.mxgraph.swing.handler.mxRubberband; @@ -61,8 +63,6 @@ import java.util.List; import java.util.Map; import java.util.concurrent.Future; import java.util.function.BiConsumer; -import java.util.function.Consumer; -import java.util.function.Function; import java.util.logging.Level; import javax.swing.AbstractAction; import javax.swing.ImageIcon; @@ -139,10 +139,11 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider private final mxUndoManager undoManager = new mxUndoManager(); private final mxRubberband rubberband; - private final mxFastOrganicLayout fastOrganicLayout; - private final mxCircleLayout circleLayout; - private final mxOrganicLayout organicLayout; - private final mxHierarchicalLayout hierarchyLayout; + + private final mxGraphLayout fastOrganicLayout; + private final mxGraphLayout circleLayout; + private final mxGraphLayout organicLayout; + private final mxGraphLayout hierarchyLayout; @ThreadConfined(type = ThreadConfined.ThreadType.AWT) private SwingWorker worker; @@ -158,7 +159,6 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider pinnedAccountModel = graph.getPinnedAccountModel(); lockedVertexModel = graph.getLockedVertexModel(); - graphComponent = new mxGraphComponent(graph); graphComponent.setAutoExtend(true); @@ -198,12 +198,20 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider graph.getModel().addListener(mxEvent.UNDO, undoListener); graph.getView().addListener(mxEvent.UNDO, undoListener); - - fastOrganicLayout = new mxFastOrganicLayoutImpl(graph); - circleLayout = new mxCircleLayoutImpl(graph); - organicLayout = new mxOrganicLayoutImpl(graph); - organicLayout.setMaxIterations(10); - hierarchyLayout = new mxHierarchicalLayoutImpl(graph); + + fastOrganicLayout = lockedVertexModel.createLockedVertexWrapper(new mxFastOrganicLayout(graph)); + hierarchyLayout = lockedVertexModel.createLockedVertexWrapper(new mxHierarchicalLayout(graph)); + circleLayout = lockedVertexModel.createLockedVertexWrapper(new mxCircleLayout(graph) { + { + setResetEdges(true); + } + }); + organicLayout = lockedVertexModel.createLockedVertexWrapper(new mxOrganicLayout(graph) { + { + setResetEdges(true); + setMaxIterations(10); + } + }); //local method to configure layout buttons BiConsumer configure = (layoutButton, layout) -> { @@ -502,7 +510,7 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider .add(hierarchyLayoutButton) .addPreferredGap(LayoutStyle.RELATED) .add(circleLayoutButton) - .addPreferredGap(LayoutStyle.RELATED) + .addPreferredGap(LayoutStyle.UNRELATED) .add(jSeparator2, GroupLayout.PREFERRED_SIZE, 10, GroupLayout.PREFERRED_SIZE) .addPreferredGap(LayoutStyle.RELATED) .add(jLabel2) @@ -516,7 +524,7 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider .add(zoomActualButton, GroupLayout.PREFERRED_SIZE, 33, GroupLayout.PREFERRED_SIZE) .addPreferredGap(LayoutStyle.RELATED) .add(fitZoomButton, GroupLayout.PREFERRED_SIZE, 32, GroupLayout.PREFERRED_SIZE) - .addContainerGap(12, Short.MAX_VALUE)) + .addContainerGap(GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) ); toolbarLayout.setVerticalGroup(toolbarLayout.createParallelGroup(GroupLayout.LEADING) .add(toolbarLayout.createSequentialGroup() @@ -570,7 +578,7 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider private void applyLayout(mxGraphLayout layout) { currentLayout = layout; layoutButtons.forEach((layoutKey, button) - -> button.setFont(button.getFont().deriveFont(layoutKey == layout ? Font.BOLD : Font.PLAIN)) ); + -> button.setFont(button.getFont().deriveFont(layoutKey == layout ? Font.BOLD : Font.PLAIN))); morph(layout); } @@ -727,96 +735,6 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider } } - final private class mxFastOrganicLayoutImpl extends mxFastOrganicLayout { - - mxFastOrganicLayoutImpl(mxGraph graph) { - super(graph); - } - - @Override - public boolean isVertexIgnored(Object vertex) { - return super.isVertexIgnored(vertex) - || lockedVertexModel.isVertexLocked((mxCell) vertex); - } - - @Override - public mxRectangle setVertexLocation(Object vertex, double x, double y) { - if (isVertexIgnored(vertex)) { - return getVertexBounds(vertex); - } else { - return super.setVertexLocation(vertex, x, y); - } - } - } - - final private class mxCircleLayoutImpl extends mxCircleLayout { - - mxCircleLayoutImpl(mxGraph graph) { - super(graph); - setResetEdges(true); - } - - @Override - public boolean isVertexIgnored(Object vertex) { - return super.isVertexIgnored(vertex) - || lockedVertexModel.isVertexLocked((mxCell) vertex); - } - - @Override - public mxRectangle setVertexLocation(Object vertex, double x, double y) { - if (isVertexIgnored(vertex)) { - return getVertexBounds(vertex); - } else { - return super.setVertexLocation(vertex, x, y); - } - } - } - - final private class mxOrganicLayoutImpl extends mxOrganicLayout { - - mxOrganicLayoutImpl(mxGraph graph) { - super(graph); - setResetEdges(true); - } - - @Override - public boolean isVertexIgnored(Object vertex) { - return super.isVertexIgnored(vertex) - || lockedVertexModel.isVertexLocked((mxCell) vertex); - } - - @Override - public mxRectangle setVertexLocation(Object vertex, double x, double y) { - if (isVertexIgnored(vertex)) { - return getVertexBounds(vertex); - } else { - return super.setVertexLocation(vertex, x, y); - } - } - } - - final private class mxHierarchicalLayoutImpl extends mxHierarchicalLayout { - - mxHierarchicalLayoutImpl(mxGraph graph) { - super(graph); - } - - @Override - public boolean isVertexIgnored(Object vertex) { - return super.isVertexIgnored(vertex) - || lockedVertexModel.isVertexLocked((mxCell) vertex); - } - - @Override - public mxRectangle setVertexLocation(Object vertex, double x, double y) { - if (isVertexIgnored(vertex)) { - return getVertexBounds(vertex); - } else { - return super.setVertexLocation(vertex, x, y); - } - } - } - private class CancelationListener implements ActionListener { private Future cancellable; @@ -908,4 +826,5 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider } } } + } From 581955ea958086f554796c833e3f876b7473ae00 Mon Sep 17 00:00:00 2001 From: Ann Priestman Date: Wed, 4 Apr 2018 12:38:20 -0400 Subject: [PATCH 04/25] Health monitor can create database --- .../CoordinationService.java | 3 +- .../org/sleuthkit/autopsy/core/Installer.java | 1 + .../healthmonitor/HealthMonitorException.java | 21 + .../autopsy/healthmonitor/Installer.java | 53 ++ .../healthmonitor/ServicesHealthMonitor.java | 508 ++++++++++++++++++ .../autopsy/healthmonitor/TimingMetric.java | 46 ++ 6 files changed, 631 insertions(+), 1 deletion(-) create mode 100644 Core/src/org/sleuthkit/autopsy/healthmonitor/HealthMonitorException.java create mode 100644 Core/src/org/sleuthkit/autopsy/healthmonitor/Installer.java create mode 100644 Core/src/org/sleuthkit/autopsy/healthmonitor/ServicesHealthMonitor.java create mode 100644 Core/src/org/sleuthkit/autopsy/healthmonitor/TimingMetric.java diff --git a/Core/src/org/sleuthkit/autopsy/coordinationservice/CoordinationService.java b/Core/src/org/sleuthkit/autopsy/coordinationservice/CoordinationService.java index 9b2afff6b4..d7ef8c4750 100644 --- a/Core/src/org/sleuthkit/autopsy/coordinationservice/CoordinationService.java +++ b/Core/src/org/sleuthkit/autopsy/coordinationservice/CoordinationService.java @@ -457,7 +457,8 @@ public final class CoordinationService { CASES("cases"), MANIFESTS("manifests"), CONFIG("config"), - CENTRAL_REPO("centralRepository"); + CENTRAL_REPO("centralRepository"), + HEALTH_MONITOR("healthMonitor"); private final String displayName; diff --git a/Core/src/org/sleuthkit/autopsy/core/Installer.java b/Core/src/org/sleuthkit/autopsy/core/Installer.java index 83250f719d..63ac880171 100644 --- a/Core/src/org/sleuthkit/autopsy/core/Installer.java +++ b/Core/src/org/sleuthkit/autopsy/core/Installer.java @@ -216,6 +216,7 @@ public class Installer extends ModuleInstall { packageInstallers.add(org.sleuthkit.autopsy.datamodel.Installer.getDefault()); packageInstallers.add(org.sleuthkit.autopsy.ingest.Installer.getDefault()); packageInstallers.add(org.sleuthkit.autopsy.centralrepository.eventlisteners.Installer.getDefault()); + packageInstallers.add(org.sleuthkit.autopsy.healthmonitor.Installer.getDefault()); } /** diff --git a/Core/src/org/sleuthkit/autopsy/healthmonitor/HealthMonitorException.java b/Core/src/org/sleuthkit/autopsy/healthmonitor/HealthMonitorException.java new file mode 100644 index 0000000000..26c39ef5b5 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/healthmonitor/HealthMonitorException.java @@ -0,0 +1,21 @@ +/* + * 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. + */ +package org.sleuthkit.autopsy.healthmonitor; + +/** + * + */ +class HealthMonitorException extends Exception { + private static final long serialVersionUID = 1L; + + HealthMonitorException(String message) { + super(message); + } + + HealthMonitorException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/Core/src/org/sleuthkit/autopsy/healthmonitor/Installer.java b/Core/src/org/sleuthkit/autopsy/healthmonitor/Installer.java new file mode 100644 index 0000000000..537f3593ae --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/healthmonitor/Installer.java @@ -0,0 +1,53 @@ +/* + * 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. + */ +package org.sleuthkit.autopsy.healthmonitor; + +import java.util.logging.Level; +import org.openide.modules.ModuleInstall; +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 { + ServicesHealthMonitor.startUp(); + } catch (HealthMonitorException ex) { + logger.log(Level.SEVERE, "Error starting health services monitor", ex); + } + } + + @Override + public boolean closing() { + //platform about to close + ServicesHealthMonitor.close(); + + return true; + } + + @Override + public void uninstalled() { + //module is being unloaded + ServicesHealthMonitor.close(); + + } +} \ No newline at end of file diff --git a/Core/src/org/sleuthkit/autopsy/healthmonitor/ServicesHealthMonitor.java b/Core/src/org/sleuthkit/autopsy/healthmonitor/ServicesHealthMonitor.java new file mode 100644 index 0000000000..67f096f77c --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/healthmonitor/ServicesHealthMonitor.java @@ -0,0 +1,508 @@ +/* + * 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. + */ +package org.sleuthkit.autopsy.healthmonitor; + +import com.google.common.util.concurrent.ThreadFactoryBuilder; +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.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.centralrepository.datamodel.EamDbException; +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.datamodel.CaseDbConnectionInfo; +import org.sleuthkit.datamodel.CaseDbSchemaVersionNumber; + +/** + * + */ +public class ServicesHealthMonitor { + + private final static Logger logger = Logger.getLogger(ServicesHealthMonitor.class.getName()); + private final static String DATABASE_NAME = "ServicesHealthMonitor"; + private final static String MODULE_NAME = "ServicesHealthMonitor"; + private final static String IS_ENABLED_KEY = "is_enabled"; + private final static long DATABASE_WRITE_INTERVAL = 1; // Minutes + public static final CaseDbSchemaVersionNumber CURRENT_DB_SCHEMA_VERSION + = new CaseDbSchemaVersionNumber(1, 0); + + private static final AtomicBoolean isEnabled = new AtomicBoolean(false); + private static ServicesHealthMonitor instance; + + private ScheduledThreadPoolExecutor periodicTasksExecutor; + private Map timingInfoMap; + private static final int CONN_POOL_SIZE = 5; + private BasicDataSource connectionPool = null; + + private ServicesHealthMonitor() throws HealthMonitorException { + System.out.println("\nCreating ServicesHealthMonitor"); + + // Create the map to collect timing metrics + timingInfoMap = new HashMap<>(); + + if (ModuleSettings.settingExists(MODULE_NAME, IS_ENABLED_KEY)) { + if(ModuleSettings.getConfigSetting(MODULE_NAME, IS_ENABLED_KEY).equals("true")){ + isEnabled.set(true); + activateMonitor(); + return; + } + } + isEnabled.set(false); + } + + private synchronized void activateMonitor() throws HealthMonitorException { + // Set up database (if needed) + System.out.println(" Setting up database..."); + if (!UserPreferences.getIsMultiUserModeEnabled()) { + throw new HealthMonitorException("Multi user mode is not enabled - can not activate services health monitor"); + } + + CoordinationService.Lock lock = getExclusiveDbLock(); + if(lock == null) { + throw new HealthMonitorException("Error getting database lock"); + } + + try { + // Check if the database exists + if (! databaseExists()) { + + // If not, create a new one + createDatabase(); + initializeDatabaseSchema(); + } + + // Any database upgrades would happen here + + } finally { + try { + lock.release(); + } catch (CoordinationService.CoordinationServiceException ex) { + throw new HealthMonitorException("Error releasing database lock", ex); + } + } + + // Prepare metric storage + System.out.println(" Clearing hash map..."); + timingInfoMap = new HashMap<>(); + + // Start the timer + System.out.println(" Starting the timer..."); + if(periodicTasksExecutor != null) { + // Make sure the previous executor (if it exists) has been stopped + periodicTasksExecutor.shutdown(); + } + periodicTasksExecutor = new ScheduledThreadPoolExecutor(1, new ThreadFactoryBuilder().setNameFormat("health_monitor_timer").build()); + periodicTasksExecutor.scheduleWithFixedDelay(new DatabaseWriteTask(), DATABASE_WRITE_INTERVAL, DATABASE_WRITE_INTERVAL, TimeUnit.MINUTES); + + } + + private synchronized void deactivateMonitor() throws HealthMonitorException { + // Clear out the collected data + System.out.println(" Clearing hash map..."); + timingInfoMap.clear(); + + // Stop the timer + System.out.println(" Stopping the timer..."); + if(periodicTasksExecutor != null) { + periodicTasksExecutor.shutdown(); + } + + // Shut down the connection pool + shutdownConnections(); + } + + synchronized static ServicesHealthMonitor getInstance() throws HealthMonitorException { + if (instance == null) { + instance = new ServicesHealthMonitor(); + } + return instance; + } + + static synchronized void startUp() throws HealthMonitorException { + System.out.println("\nServicesHealthMonitor starting up"); + getInstance(); + } + + static synchronized void setEnabled(boolean enabled) throws HealthMonitorException { + System.out.println("\nServicesHealthMonitor setting enabled to " + enabled + "(previous: " + isEnabled.get() + ")"); + if(enabled == isEnabled.get()) { + // The setting has not changed, so do nothing + return; + } + + if(enabled) { + ModuleSettings.setConfigSetting(MODULE_NAME, IS_ENABLED_KEY, "true"); + isEnabled.set(true); + getInstance().activateMonitor(); + } else { + ModuleSettings.setConfigSetting(MODULE_NAME, IS_ENABLED_KEY, "false"); + isEnabled.set(false); + getInstance().deactivateMonitor(); + } + } + + public static TimingMetric getTimingMetric(String name) { + if(isEnabled.get()) { + return new TimingMetric(name); + } + return null; + } + + 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 accessing services health monitor", ex); + } + } + } + + private void addTimingMetric(TimingMetric metric) { + try{ + synchronized(this) { + // There's a small check-then-act situation here where isEnabled + // may have changed before reaching this code, but it doesn't cause + // any errors to load a few extra entries into the map after disabling + // the monitor (they will be deleted if the monitor is re-enabled). + if(timingInfoMap.containsKey(metric.getName())) { + timingInfoMap.get(metric.getName()).addMetric(metric); + } else { + timingInfoMap.put(metric.getName(), new TimingInfo(metric)); + } + } + } catch (HealthMonitorException ex) { + logger.log(Level.SEVERE, "Error adding timing metric", ex); + } + } + + // Make private once testing is done + void writeCurrentStateToDatabase() { + System.out.println("\nwriteCurrentStateToDatabase"); + + Map timingMapCopy; + synchronized(this) { + if(! isEnabled.get()) { + return; + } + + // Make a shallow copy of the map. The map should be small - one entry + // per metric type. + timingMapCopy = new HashMap<>(timingInfoMap); + timingInfoMap.clear(); + } + + for(String name:timingMapCopy.keySet()){ + TimingInfo info = timingMapCopy.get(name); + long timestamp = System.currentTimeMillis(); + System.out.println(" Name: " + name + "\tTimestamp: " + timestamp + "\tAverage: " + info.getAverage() + + "\tMax: " + info.getMax() + "\tMin: " + info.getMin()); + } + } + + synchronized void clearCurrentState() { + timingInfoMap.clear(); + } + + static synchronized void close() { + if(isEnabled.get()) { + // Stop the timer + + // Write current data + try { + getInstance().writeCurrentStateToDatabase(); + } catch (HealthMonitorException ex) { + logger.log(Level.SEVERE, "Error writing final metric data to database", ex); + } + + // Shutdown connection pool + try { + getInstance().shutdownConnections(); + } catch (HealthMonitorException ex) { + logger.log(Level.SEVERE, "Error shutting down connection pool", ex); + } + + } + } + + synchronized void printCurrentState() { + System.out.println("\nTiming Info Map:"); + for(String name:timingInfoMap.keySet()) { + System.out.print(name + "\t"); + timingInfoMap.get(name).print(); + } + } + + // Change to private after testing + boolean databaseExists() throws HealthMonitorException { + + System.out.println("\nChecking database existence"); + + 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 + "'"; + System.out.println(" query: " + createCommand); + rs = statement.executeQuery(createCommand); + if(rs.next()) { + System.out.println(" Exists!"); + return true; + } + } finally { + if(rs != null) { + rs.close(); + } + } + } catch (UserPreferencesException | ClassNotFoundException | SQLException ex) { + throw new HealthMonitorException("Failed check for health monitor database", ex); + } + System.out.println(" Does not exist"); + return false; + } + + private void createDatabase() throws HealthMonitorException { + try { + System.out.println("\nCreating database " + DATABASE_NAME); + // 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); + } + } catch (UserPreferencesException | ClassNotFoundException | SQLException ex) { + throw new HealthMonitorException("Failed to delete health monitor database", ex); + } + } + + + /** + * Delete the current health monitor database (for testing only) + * Make private after test + */ + void deleteDatabase() { + 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 deleteCommand = "DROP DATABASE \"" + DATABASE_NAME + "\""; //NON-NLS + statement.execute(deleteCommand); + } + } catch (UserPreferencesException | ClassNotFoundException | SQLException ex) { + logger.log(Level.SEVERE, "Failed to delete health monitor database", ex); + } + } + + /** + * Setup a connection pool for db connections. + * + */ + private void setupConnectionPool() throws HealthMonitorException { + try { + CaseDbConnectionInfo db = UserPreferences.getDatabaseConnectionInfo(); + + connectionPool = new BasicDataSource(); + //connectionPool.setUsername(db.getUserName()); + //connectionPool.setPassword(db.getPassword()); + 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(5); // start with 5 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); + } + } + + public 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 + } + } + + 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 + } + } + + private void initializeDatabaseSchema() throws HealthMonitorException { + Connection conn = connect(); + if(conn == null) { + throw new HealthMonitorException("Error getting database connection"); + } + + try (Statement statement = conn.createStatement()) { + StringBuilder createTimingTable = new StringBuilder(); + createTimingTable.append("CREATE TABLE IF NOT EXISTS timingData ("); + createTimingTable.append("id SERIAL PRIMARY KEY,"); + createTimingTable.append("name text NOT NULL,"); + createTimingTable.append("timestamp bigint NOT NULL,"); + createTimingTable.append("count bigint NOT NULL,"); + createTimingTable.append("average int NOT NULL,"); + createTimingTable.append("max int NOT NULL,"); + createTimingTable.append("min int NOT NULL"); + createTimingTable.append(")"); + statement.execute(createTimingTable.toString()); + + StringBuilder createDbInfoTable = new StringBuilder(); + createDbInfoTable.append("CREATE TABLE IF NOT EXISTS db_info ("); + createDbInfoTable.append("id SERIAL PRIMARY KEY NOT NULL,"); + createDbInfoTable.append("name text NOT NULL,"); + createDbInfoTable.append("value text NOT NULL"); + createDbInfoTable.append(")"); + statement.execute(createDbInfoTable.toString()); + + 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() + "')"); + + } catch (SQLException ex) { + throw new HealthMonitorException("Error initializing database", ex); + } finally { + try { + conn.close(); + } catch (SQLException ex) { + logger.log(Level.SEVERE, "Error closing Connection.", ex); + } + } + } + + private final class DatabaseWriteTask implements Runnable { + + /** + * Write current metric data to the database + */ + @Override + public void run() { + try { + System.out.println("\nTimer up - writing to DB"); + getInstance().writeCurrentStateToDatabase(); + } catch (Exception ex) { + logger.log(Level.SEVERE, "Unexpected exception in DatabaseWriteTask", ex); //NON-NLS + } + } + } + + private CoordinationService.Lock getExclusiveDbLock() throws HealthMonitorException{ + try { + String databaseNodeName = DATABASE_NAME; + CoordinationService.Lock lock = CoordinationService.getInstance().tryGetExclusiveLock(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"); + } + } + + 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"); + } + } + + private class TimingInfo { + private long count; + private long sum; + private long max; + private long min; + + TimingInfo(TimingMetric metric) throws HealthMonitorException { + count = 1; + sum = metric.getDuration(); + max = metric.getDuration(); + min = metric.getDuration(); + } + + void addMetric(TimingMetric metric) throws HealthMonitorException { + count++; + sum += metric.getDuration(); + + if(max < metric.getDuration()) { + max = metric.getDuration(); + } + + if(min > metric.getDuration()) { + min = metric.getDuration(); + } + } + + long getAverage() { + return sum / count; + } + + long getMax() { + return max; + } + + long getMin() { + return min; + } + + void print() { + System.out.println("count: " + count + "\tsum: " + sum + "\tmax: " + max + "\tmin: " + min); + } + } +} diff --git a/Core/src/org/sleuthkit/autopsy/healthmonitor/TimingMetric.java b/Core/src/org/sleuthkit/autopsy/healthmonitor/TimingMetric.java new file mode 100644 index 0000000000..431e585710 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/healthmonitor/TimingMetric.java @@ -0,0 +1,46 @@ +/* + * 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. + */ +package org.sleuthkit.autopsy.healthmonitor; + +/** + * + */ +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; + } + + long getDuration() throws HealthMonitorException { + if (duration != null) { + return duration; + } else { + throw new HealthMonitorException("getDuration() called before stopTiming()"); + } + } +} From 94107ef1dca4e82f013b26a8af25252fdc2ef608 Mon Sep 17 00:00:00 2001 From: Ann Priestman Date: Wed, 4 Apr 2018 14:33:16 -0400 Subject: [PATCH 05/25] Added metrics to Keyword Search --- Core/nbproject/project.xml | 1 + .../healthmonitor/ServicesHealthMonitor.java | 71 +++++++++++++++++-- .../autopsy/keywordsearch/Ingester.java | 4 ++ .../autopsy/keywordsearch/Server.java | 4 ++ 4 files changed, 74 insertions(+), 6 deletions(-) diff --git a/Core/nbproject/project.xml b/Core/nbproject/project.xml index 0159ec9880..5dabff9f5d 100644 --- a/Core/nbproject/project.xml +++ b/Core/nbproject/project.xml @@ -327,6 +327,7 @@ org.sleuthkit.autopsy.events org.sleuthkit.autopsy.filesearch org.sleuthkit.autopsy.guiutils + org.sleuthkit.autopsy.healthmonitor org.sleuthkit.autopsy.ingest org.sleuthkit.autopsy.keywordsearchservice org.sleuthkit.autopsy.menuactions diff --git a/Core/src/org/sleuthkit/autopsy/healthmonitor/ServicesHealthMonitor.java b/Core/src/org/sleuthkit/autopsy/healthmonitor/ServicesHealthMonitor.java index 67f096f77c..0bdf9d5e79 100644 --- a/Core/src/org/sleuthkit/autopsy/healthmonitor/ServicesHealthMonitor.java +++ b/Core/src/org/sleuthkit/autopsy/healthmonitor/ServicesHealthMonitor.java @@ -82,8 +82,11 @@ public class ServicesHealthMonitor { if (! databaseExists()) { // If not, create a new one + System.out.println(" No database exists - setting up new one"); createDatabase(); initializeDatabaseSchema(); + } else { + System.out.println(" Database already exists"); } // Any database upgrades would happen here @@ -194,7 +197,7 @@ public class ServicesHealthMonitor { } // Make private once testing is done - void writeCurrentStateToDatabase() { + void writeCurrentStateToDatabase() throws HealthMonitorException { System.out.println("\nwriteCurrentStateToDatabase"); Map timingMapCopy; @@ -208,13 +211,65 @@ public class ServicesHealthMonitor { timingMapCopy = new HashMap<>(timingInfoMap); timingInfoMap.clear(); } - + + // Check if there's anything to report + if(timingMapCopy.keySet().isEmpty()) { + System.out.println("No timing data to save"); + return; + } + + // Debug for(String name:timingMapCopy.keySet()){ TimingInfo info = timingMapCopy.get(name); long timestamp = System.currentTimeMillis(); System.out.println(" Name: " + name + "\tTimestamp: " + timestamp + "\tAverage: " + info.getAverage() + "\tMax: " + info.getMax() + "\tMin: " + info.getMin()); } + + CoordinationService.Lock lock = getSharedDbLock(); + if(lock == null) { + throw new HealthMonitorException("Error getting database lock"); + } + + try { + Connection conn = connect(); + if(conn == null) { + throw new HealthMonitorException("Error getting database connection"); + } + + //"INSERT INTO db_info (name, value) VALUES (?, ?)" + String addTimingInfoSql = "INSERT INTO timing_data (name, 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.setLong(2, System.currentTimeMillis()); + statement.setLong(3, info.getCount()); + statement.setLong(4, info.getAverage()); + statement.setLong(5, info.getMax()); + statement.setLong(6, 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); + } + } + } finally { + try { + lock.release(); + } catch (CoordinationService.CoordinationServiceException ex) { + throw new HealthMonitorException("Error releasing database lock", ex); + } + } } synchronized void clearCurrentState() { @@ -386,14 +441,14 @@ public class ServicesHealthMonitor { try (Statement statement = conn.createStatement()) { StringBuilder createTimingTable = new StringBuilder(); - createTimingTable.append("CREATE TABLE IF NOT EXISTS timingData ("); + createTimingTable.append("CREATE TABLE IF NOT EXISTS timing_data ("); createTimingTable.append("id SERIAL PRIMARY KEY,"); createTimingTable.append("name text NOT NULL,"); createTimingTable.append("timestamp bigint NOT NULL,"); createTimingTable.append("count bigint NOT NULL,"); - createTimingTable.append("average int NOT NULL,"); - createTimingTable.append("max int NOT NULL,"); - createTimingTable.append("min int NOT NULL"); + createTimingTable.append("average bigint NOT NULL,"); + createTimingTable.append("max bigint NOT NULL,"); + createTimingTable.append("min bigint NOT NULL"); createTimingTable.append(")"); statement.execute(createTimingTable.toString()); @@ -501,6 +556,10 @@ public class ServicesHealthMonitor { return min; } + long getCount() { + return count; + } + void print() { System.out.println("count: " + count + "\tsum: " + sum + "\tmax: " + max + "\tmin: " + min); } diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Ingester.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Ingester.java index c7f4c07f6a..679a646995 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Ingester.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Ingester.java @@ -27,6 +27,8 @@ import org.apache.solr.common.SolrInputDocument; import org.openide.util.NbBundle; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.datamodel.ContentUtils; +import org.sleuthkit.autopsy.healthmonitor.ServicesHealthMonitor; +import org.sleuthkit.autopsy.healthmonitor.TimingMetric; import org.sleuthkit.autopsy.ingest.IngestJobContext; import org.sleuthkit.autopsy.keywordsearch.Chunker.Chunk; import org.sleuthkit.datamodel.AbstractFile; @@ -235,7 +237,9 @@ class Ingester { try { //TODO: consider timeout thread, or vary socket timeout based on size of indexed content + TimingMetric metric = ServicesHealthMonitor.getTimingMetric("solr index chunk"); solrServer.addDocument(updateDoc); + ServicesHealthMonitor.submitTimingMetric(metric); uncommitedIngests = true; } catch (KeywordSearchModuleException | NoOpenCoreException ex) { diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Server.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Server.java index 416f2b394f..3b4e494480 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Server.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Server.java @@ -70,6 +70,8 @@ import org.sleuthkit.autopsy.core.UserPreferences; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.ModuleSettings; import org.sleuthkit.autopsy.coreutils.PlatformUtil; +import org.sleuthkit.autopsy.healthmonitor.ServicesHealthMonitor; +import org.sleuthkit.autopsy.healthmonitor.TimingMetric; import org.sleuthkit.autopsy.keywordsearchservice.KeywordSearchServiceException; import org.sleuthkit.datamodel.Content; @@ -773,7 +775,9 @@ public class Server { IndexingServerProperties properties = getMultiUserServerProperties(theCase.getCaseDirectory()); currentSolrServer = new HttpSolrServer("http://" + properties.getHost() + ":" + properties.getPort() + "/solr"); //NON-NLS } + TimingMetric metric = ServicesHealthMonitor.getTimingMetric("solr connectivity check"); connectToSolrServer(currentSolrServer); + ServicesHealthMonitor.submitTimingMetric(metric); } catch (SolrServerException | IOException ex) { throw new KeywordSearchModuleException(NbBundle.getMessage(Server.class, "Server.connect.exception.msg", ex.getLocalizedMessage()), ex); From 32868a97f1d49b0142ed703b1fbf016bcd6a0f7e Mon Sep 17 00:00:00 2001 From: millmanorama Date: Thu, 5 Apr 2018 12:06:13 +0200 Subject: [PATCH 06/25] WIP - broken --- .../communications/AbstractCVTAction.java | 47 ++++++++++ .../AccountDeviceInstanceNode.java | 54 ----------- .../autopsy/communications/Bundle.properties | 3 - .../autopsy/communications/CVTEvents.java | 2 +- .../communications/PinAccountsAction.java | 55 +++++++++++ .../ResetAndPinAccountsAction.java | 59 ++++++++++++ .../communications/UnpinAccountsAction.java | 55 +++++++++++ .../communications/VisualizationPanel.java | 94 +++++++------------ 8 files changed, 251 insertions(+), 118 deletions(-) create mode 100644 Core/src/org/sleuthkit/autopsy/communications/AbstractCVTAction.java create mode 100644 Core/src/org/sleuthkit/autopsy/communications/PinAccountsAction.java create mode 100644 Core/src/org/sleuthkit/autopsy/communications/ResetAndPinAccountsAction.java create mode 100644 Core/src/org/sleuthkit/autopsy/communications/UnpinAccountsAction.java diff --git a/Core/src/org/sleuthkit/autopsy/communications/AbstractCVTAction.java b/Core/src/org/sleuthkit/autopsy/communications/AbstractCVTAction.java new file mode 100644 index 0000000000..c2b43f2041 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/communications/AbstractCVTAction.java @@ -0,0 +1,47 @@ +/* + * 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. + */ +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; + +/** + * + * + */ +abstract class AbstractCVTAction extends AbstractAction implements Presenter.Popup { + + /** + * Get the selected accounts that will be acted upon. + * + * @return The selected accounts + */ + Collection getSelectedAccounts() { + return Utilities.actionsGlobalContext().lookupAll(AccountDeviceInstanceKey.class); + } + + @Override + final public void putValue(String key, Object newValue) { + super.putValue(key, newValue); + } + + @Override + public JMenuItem getPopupPresenter() { + JMenuItem presenter = new JMenuItem(this); + presenter.setText(getActionDisplayName()); + presenter.setIcon(getIcon()); + return presenter; + } + + abstract String getActionDisplayName(); + + abstract ImageIcon getIcon(); + +} diff --git a/Core/src/org/sleuthkit/autopsy/communications/AccountDeviceInstanceNode.java b/Core/src/org/sleuthkit/autopsy/communications/AccountDeviceInstanceNode.java index 40cb15d27d..044cec322f 100644 --- a/Core/src/org/sleuthkit/autopsy/communications/AccountDeviceInstanceNode.java +++ b/Core/src/org/sleuthkit/autopsy/communications/AccountDeviceInstanceNode.java @@ -18,17 +18,13 @@ */ package org.sleuthkit.autopsy.communications; -import java.awt.event.ActionEvent; import java.util.ArrayList; import java.util.Arrays; -import java.util.Collection; -import javax.swing.AbstractAction; import javax.swing.Action; import org.openide.nodes.AbstractNode; import org.openide.nodes.Children; import org.openide.nodes.Sheet; import org.openide.util.NbBundle; -import org.openide.util.Utilities; import org.openide.util.lookup.Lookups; import org.sleuthkit.autopsy.datamodel.NodeProperty; import org.sleuthkit.datamodel.Account; @@ -106,54 +102,4 @@ final class AccountDeviceInstanceNode extends AbstractNode { actions.add(ResetAndPinAccountsAction.getInstance()); 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 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 lookupAll = - Utilities.actionsGlobalContext().lookupAll(AccountDeviceInstanceKey.class); - CVTEvents.getCVTEventBus().post(new CVTEvents.PinAccountsEvent(lookupAll, true)); - } - } } diff --git a/Core/src/org/sleuthkit/autopsy/communications/Bundle.properties b/Core/src/org/sleuthkit/autopsy/communications/Bundle.properties index 559e9bba86..ea3049f895 100644 --- a/Core/src/org/sleuthkit/autopsy/communications/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/communications/Bundle.properties @@ -36,9 +36,6 @@ VisualizationPanel.zoomInButton.toolTipText=Zoom in VisualizationPanel.zoomInButton.text= VisualizationPanel.zoomOutButton.toolTipText=Zoom out 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.organicLayoutButton.text=Organic VisualizationPanel.fastOrganicLayoutButton.text=Fast Organic diff --git a/Core/src/org/sleuthkit/autopsy/communications/CVTEvents.java b/Core/src/org/sleuthkit/autopsy/communications/CVTEvents.java index e0f945bb69..54e5b8a1ef 100644 --- a/Core/src/org/sleuthkit/autopsy/communications/CVTEvents.java +++ b/Core/src/org/sleuthkit/autopsy/communications/CVTEvents.java @@ -79,7 +79,7 @@ final class CVTEvents { return accountDeviceInstances; } - public UnpinAccountsEvent(Set accountDeviceInstances) { + UnpinAccountsEvent(Collection accountDeviceInstances) { this.accountDeviceInstances = ImmutableSet.copyOf(accountDeviceInstances); } } diff --git a/Core/src/org/sleuthkit/autopsy/communications/PinAccountsAction.java b/Core/src/org/sleuthkit/autopsy/communications/PinAccountsAction.java new file mode 100644 index 0000000000..d820373284 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/communications/PinAccountsAction.java @@ -0,0 +1,55 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2018 Basis Technology Corp. + * Contact: carrier sleuthkit 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; + +@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 e) { + 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; + } +} diff --git a/Core/src/org/sleuthkit/autopsy/communications/ResetAndPinAccountsAction.java b/Core/src/org/sleuthkit/autopsy/communications/ResetAndPinAccountsAction.java new file mode 100644 index 0000000000..a42b2882ed --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/communications/ResetAndPinAccountsAction.java @@ -0,0 +1,59 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2018 Basis Technology Corp. + * Contact: carrier sleuthkit 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; + +/** + * + */ +@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 e) { + 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; + } + +} diff --git a/Core/src/org/sleuthkit/autopsy/communications/UnpinAccountsAction.java b/Core/src/org/sleuthkit/autopsy/communications/UnpinAccountsAction.java new file mode 100644 index 0000000000..357f5a0451 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/communications/UnpinAccountsAction.java @@ -0,0 +1,55 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2018 Basis Technology Corp. + * Contact: carrier sleuthkit 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; + +@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; + } +} diff --git a/Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.java b/Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.java index cd75ab5185..fa095fd0a8 100644 --- a/Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.java +++ b/Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.java @@ -51,7 +51,6 @@ import java.beans.PropertyChangeEvent; import java.beans.PropertyVetoException; import java.text.DecimalFormat; import java.util.Arrays; -import static java.util.Collections.singleton; import java.util.EnumSet; import java.util.HashSet; import java.util.List; @@ -106,12 +105,6 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider private static final long serialVersionUID = 1L; private static final Logger logger = Logger.getLogger(VisualizationPanel.class.getName()); 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 = new ImageIcon(VisualizationPanel.class.getResource(BASE_IMAGE_PATH + "/lock_large_unlocked.png")); static final private ImageIcon lockIcon @@ -132,6 +125,7 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider private final mxUndoManager undoManager = new mxUndoManager(); private final mxRubberband rubberband; + private final mxFastOrganicLayout fastOrganicLayout; private final mxCircleLayout circleLayout; private final mxOrganicLayout organicLayout; @@ -199,52 +193,40 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider @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(); + SwingUtilities.invokeLater(() -> { + 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 (lockedVertexModel.isVertexLocked(cellAt)) { + jPopupMenu.add(new JMenuItem(new AbstractAction("UnLock", unlockIcon) { + @Override + public void actionPerformed(final ActionEvent event) { + lockedVertexModel.unlockVertex(cellAt); + } + })); + } else { + jPopupMenu.add(new JMenuItem(new AbstractAction("Lock", lockIcon) { + @Override + public void actionPerformed(final ActionEvent event) { + lockedVertexModel.lockVertex(cellAt); + } + })); + } + 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()); } - 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); @@ -255,7 +237,7 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider ExplorerUtils.createLookup(vizEM, getActionMap())); //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) -> undoManager.undoableEditHappened((mxUndoableEdit) evt.getProperty("edit")); @@ -277,7 +259,6 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider rebuildGraph(); // Updates the display graph.getModel().endUpdate(); - } @Subscribe @@ -290,7 +271,6 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider rebuildGraph(); // Updates the display graph.getModel().endUpdate(); - } @Subscribe @@ -302,7 +282,6 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider rebuildGraph(); // Updates the display graph.getModel().endUpdate(); - } @ThreadConfined(type = ThreadConfined.ThreadType.AWT) @@ -328,13 +307,12 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider graph.resetGraph(); rebuildGraph(); } else { - morph(fastOrganicLayout); + morph(fastOrganicLayout); } } }); worker.execute(); - } } @@ -372,11 +350,6 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider }); } - @Override - public void removeNotify() { - super.removeNotify(); - } - /** * 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 @@ -901,4 +874,5 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider } } + } From f289685f254ddecadddb6a225cd877128ed8f346 Mon Sep 17 00:00:00 2001 From: Ann Priestman Date: Thu, 5 Apr 2018 10:24:56 -0400 Subject: [PATCH 07/25] Cleanup and improved documentation --- .../healthmonitor/HealthMonitorException.java | 21 +- .../autopsy/healthmonitor/Installer.java | 19 +- .../healthmonitor/ServicesHealthMonitor.java | 334 +++++++++++++----- .../autopsy/healthmonitor/TimingMetric.java | 27 +- 4 files changed, 304 insertions(+), 97 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/healthmonitor/HealthMonitorException.java b/Core/src/org/sleuthkit/autopsy/healthmonitor/HealthMonitorException.java index 26c39ef5b5..6df918acaa 100644 --- a/Core/src/org/sleuthkit/autopsy/healthmonitor/HealthMonitorException.java +++ b/Core/src/org/sleuthkit/autopsy/healthmonitor/HealthMonitorException.java @@ -1,12 +1,25 @@ /* - * 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. + * Autopsy Forensic Browser + * + * Copyright 2018 Basis Technology Corp. + * Contact: carrier sleuthkit 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; /** - * + * Exception used internally by the Services Health Monitor */ class HealthMonitorException extends Exception { private static final long serialVersionUID = 1L; diff --git a/Core/src/org/sleuthkit/autopsy/healthmonitor/Installer.java b/Core/src/org/sleuthkit/autopsy/healthmonitor/Installer.java index 537f3593ae..b77ba067d7 100644 --- a/Core/src/org/sleuthkit/autopsy/healthmonitor/Installer.java +++ b/Core/src/org/sleuthkit/autopsy/healthmonitor/Installer.java @@ -1,7 +1,20 @@ /* - * 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. + * Autopsy Forensic Browser + * + * Copyright 2018 Basis Technology Corp. + * Contact: carrier sleuthkit 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; diff --git a/Core/src/org/sleuthkit/autopsy/healthmonitor/ServicesHealthMonitor.java b/Core/src/org/sleuthkit/autopsy/healthmonitor/ServicesHealthMonitor.java index 0bdf9d5e79..ab2510ca59 100644 --- a/Core/src/org/sleuthkit/autopsy/healthmonitor/ServicesHealthMonitor.java +++ b/Core/src/org/sleuthkit/autopsy/healthmonitor/ServicesHealthMonitor.java @@ -1,7 +1,20 @@ /* - * 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. + * Autopsy Forensic Browser + * + * Copyright 2018 Basis Technology Corp. + * Contact: carrier sleuthkit 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; @@ -19,7 +32,6 @@ 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.centralrepository.datamodel.EamDbException; import org.sleuthkit.autopsy.coordinationservice.CoordinationService; import org.sleuthkit.autopsy.core.UserPreferences; import org.sleuthkit.autopsy.core.UserPreferencesException; @@ -29,7 +41,11 @@ 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 class ServicesHealthMonitor { @@ -45,16 +61,17 @@ public class ServicesHealthMonitor { private static ServicesHealthMonitor instance; private ScheduledThreadPoolExecutor periodicTasksExecutor; - private Map timingInfoMap; - private static final int CONN_POOL_SIZE = 5; + private final Map timingInfoMap; + private static final int CONN_POOL_SIZE = 10; private BasicDataSource connectionPool = null; private ServicesHealthMonitor() throws HealthMonitorException { - System.out.println("\nCreating ServicesHealthMonitor"); - // Create the map to collect timing metrics + // Create the map to collect timing metrics. The map will exist regardless + // of whether the monitor is enabled. timingInfoMap = new HashMap<>(); + // 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); @@ -65,13 +82,32 @@ public class ServicesHealthMonitor { isEnabled.set(false); } + /** + * Get the instance of the ServicesHealthMonitor + * @return the instance + * @throws HealthMonitorException + */ + synchronized static ServicesHealthMonitor getInstance() throws HealthMonitorException { + if (instance == null) { + instance = new ServicesHealthMonitor(); + } + 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 { - // Set up database (if needed) - System.out.println(" Setting up database..."); + + logger.log(Level.INFO, "Activating Servies Health Monitor"); + if (!UserPreferences.getIsMultiUserModeEnabled()) { throw new HealthMonitorException("Multi user mode is not enabled - can not activate services health monitor"); } - + // Set up database (if needed) CoordinationService.Lock lock = getExclusiveDbLock(); if(lock == null) { throw new HealthMonitorException("Error getting database lock"); @@ -85,8 +121,6 @@ public class ServicesHealthMonitor { System.out.println(" No database exists - setting up new one"); createDatabase(); initializeDatabaseSchema(); - } else { - System.out.println(" Database already exists"); } // Any database upgrades would happen here @@ -99,50 +133,69 @@ public class ServicesHealthMonitor { } } - // Prepare metric storage - System.out.println(" Clearing hash map..."); - timingInfoMap = new HashMap<>(); + // Clear out any old data + timingInfoMap.clear(); - // Start the timer - System.out.println(" Starting the timer..."); + // 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() { if(periodicTasksExecutor != null) { // Make sure the previous executor (if it exists) has been stopped periodicTasksExecutor.shutdown(); } periodicTasksExecutor = new ScheduledThreadPoolExecutor(1, new ThreadFactoryBuilder().setNameFormat("health_monitor_timer").build()); periodicTasksExecutor.scheduleWithFixedDelay(new DatabaseWriteTask(), DATABASE_WRITE_INTERVAL, DATABASE_WRITE_INTERVAL, TimeUnit.MINUTES); - } - private synchronized void deactivateMonitor() throws HealthMonitorException { - // Clear out the collected data - System.out.println(" Clearing hash map..."); - timingInfoMap.clear(); - - // Stop the timer - System.out.println(" Stopping the timer..."); + /** + * Stop the ScheduledThreadPoolExecutor to prevent further database writes. + */ + private synchronized void stopTimer() { if(periodicTasksExecutor != null) { periodicTasksExecutor.shutdown(); } - - // Shut down the connection pool - shutdownConnections(); } - - synchronized static ServicesHealthMonitor getInstance() throws HealthMonitorException { - if (instance == null) { - instance = new ServicesHealthMonitor(); - } - return instance; - } - + + /** + * Called from the installer to set up the Health Monitor instance at startup. + * @throws HealthMonitorException + */ static synchronized void startUp() throws HealthMonitorException { - System.out.println("\nServicesHealthMonitor starting up"); 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 { - System.out.println("\nServicesHealthMonitor setting enabled to " + enabled + "(previous: " + isEnabled.get() + ")"); if(enabled == isEnabled.get()) { // The setting has not changed, so do nothing return; @@ -159,6 +212,16 @@ public class ServicesHealthMonitor { } } + /** + * 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 Services 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); @@ -166,6 +229,13 @@ public class ServicesHealthMonitor { 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 Services 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(); @@ -173,52 +243,63 @@ public class ServicesHealthMonitor { 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 accessing services health monitor", ex); + logger.log(Level.SEVERE, "Error adding timing metric", ex); } } } - private void addTimingMetric(TimingMetric metric) { - try{ - synchronized(this) { - // There's a small check-then-act situation here where isEnabled - // may have changed before reaching this code, but it doesn't cause - // any errors to load a few extra entries into the map after disabling - // the monitor (they will be deleted if the monitor is re-enabled). - if(timingInfoMap.containsKey(metric.getName())) { - timingInfoMap.get(metric.getName()).addMetric(metric); - } else { - timingInfoMap.put(metric.getName(), new TimingInfo(metric)); - } + /** + * 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)); } - } catch (HealthMonitorException ex) { - logger.log(Level.SEVERE, "Error adding timing metric", ex); } } - // Make private once testing is done + // TODO: Make private once testing is done + /** + * Write the collected metrics to the database. + * @throws HealthMonitorException + */ void writeCurrentStateToDatabase() throws HealthMonitorException { - System.out.println("\nwriteCurrentStateToDatabase"); + logger.log(Level.INFO, "Writing health monitor metrics to database"); Map 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 map. The map should be small - one entry - // per metric type. + // 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 + // Check if there's anything to report (right now we only have the timing map) if(timingMapCopy.keySet().isEmpty()) { - System.out.println("No timing data to save"); return; } - // Debug + // TODO: Debug for(String name:timingMapCopy.keySet()){ TimingInfo info = timingMapCopy.get(name); long timestamp = System.currentTimeMillis(); @@ -226,6 +307,7 @@ public class ServicesHealthMonitor { "\tMax: " + info.getMax() + "\tMin: " + info.getMin()); } + // Write to the database CoordinationService.Lock lock = getSharedDbLock(); if(lock == null) { throw new HealthMonitorException("Error getting database lock"); @@ -237,7 +319,7 @@ public class ServicesHealthMonitor { throw new HealthMonitorException("Error getting database connection"); } - //"INSERT INTO db_info (name, value) VALUES (?, ?)" + // Add timing metrics to the database String addTimingInfoSql = "INSERT INTO timing_data (name, timestamp, count, average, max, min) VALUES (?, ?, ?, ?, ?, ?)"; try (PreparedStatement statement = conn.prepareStatement(addTimingInfoSql)) { @@ -272,13 +354,23 @@ public class ServicesHealthMonitor { } } + // TODO: debug synchronized void clearCurrentState() { timingInfoMap.clear(); } + /** + * Call during application closing - attempts to log any remaining entries. + */ static synchronized void close() { if(isEnabled.get()) { + // Stop the timer + try { + getInstance().stopTimer(); + } catch (HealthMonitorException ex) { + logger.log(Level.SEVERE, "Error shutting down timer", ex); + } // Write current data try { @@ -293,10 +385,10 @@ public class ServicesHealthMonitor { } catch (HealthMonitorException ex) { logger.log(Level.SEVERE, "Error shutting down connection pool", ex); } - } } + // TODO: debug synchronized void printCurrentState() { System.out.println("\nTiming Info Map:"); for(String name:timingInfoMap.keySet()) { @@ -305,11 +397,14 @@ public class ServicesHealthMonitor { } } - // Change to private after testing - boolean databaseExists() throws HealthMonitorException { - - System.out.println("\nChecking database existence"); - + // TODO: Change to private after testing + /** + * Check whether the health monitor database exists. + * Does not check the schema. + * @return true if the database exists, false otherwise + * @throws HealthMonitorException + */ + boolean databaseExists() throws HealthMonitorException { try { // Use the same database settings as the case CaseDbConnectionInfo db = UserPreferences.getDatabaseConnectionInfo(); @@ -321,7 +416,7 @@ public class ServicesHealthMonitor { System.out.println(" query: " + createCommand); rs = statement.executeQuery(createCommand); if(rs.next()) { - System.out.println(" Exists!"); + logger.log(Level.INFO, "Existing Services Health Monitor database found"); return true; } } finally { @@ -332,13 +427,15 @@ public class ServicesHealthMonitor { } catch (UserPreferencesException | ClassNotFoundException | SQLException ex) { throw new HealthMonitorException("Failed check for health monitor database", ex); } - System.out.println(" Does not exist"); return false; } + /** + * Create a new health monitor database. + * @throws HealthMonitorException + */ private void createDatabase() throws HealthMonitorException { try { - System.out.println("\nCreating database " + DATABASE_NAME); // Use the same database settings as the case CaseDbConnectionInfo db = UserPreferences.getDatabaseConnectionInfo(); Class.forName("org.postgresql.Driver"); //NON-NLS @@ -347,12 +444,13 @@ public class ServicesHealthMonitor { 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); } } - + // TODO: At least make private /** * Delete the current health monitor database (for testing only) * Make private after test @@ -371,18 +469,16 @@ public class ServicesHealthMonitor { logger.log(Level.SEVERE, "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.setUsername(db.getUserName()); - //connectionPool.setPassword(db.getPassword()); connectionPool.setDriverClassName("org.postgresql.Driver"); StringBuilder connectionURL = new StringBuilder(); @@ -398,7 +494,7 @@ public class ServicesHealthMonitor { connectionPool.setPassword(db.getPassword()); // tweak pool configuration - connectionPool.setInitialSize(5); // start with 5 connections + connectionPool.setInitialSize(2); // start with 2 connections connectionPool.setMaxIdle(CONN_POOL_SIZE); // max of 10 idle connections connectionPool.setValidationQuery("SELECT version()"); } catch (UserPreferencesException ex) { @@ -406,7 +502,11 @@ public class ServicesHealthMonitor { } } - public void shutdownConnections() throws HealthMonitorException { + /** + * Shut down the connection pool + * @throws HealthMonitorException + */ + private void shutdownConnections() throws HealthMonitorException { try { synchronized(this) { if(connectionPool != null){ @@ -419,6 +519,12 @@ public class ServicesHealthMonitor { } } + /** + * 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) { @@ -433,12 +539,17 @@ public class ServicesHealthMonitor { } } + /** + * Initialize the database. + * @throws HealthMonitorException + */ private void initializeDatabaseSchema() throws HealthMonitorException { Connection conn = connect(); if(conn == null) { throw new HealthMonitorException("Error getting database connection"); } + // TODO: transaction try (Statement statement = conn.createStatement()) { StringBuilder createTimingTable = new StringBuilder(); createTimingTable.append("CREATE TABLE IF NOT EXISTS timing_data ("); @@ -474,6 +585,10 @@ public class ServicesHealthMonitor { } } + /** + * The task called by the ScheduledThreadPoolExecutor to handle + * the database writes. + */ private final class DatabaseWriteTask implements Runnable { /** @@ -482,14 +597,19 @@ public class ServicesHealthMonitor { @Override public void run() { try { - System.out.println("\nTimer up - writing to DB"); getInstance().writeCurrentStateToDatabase(); - } catch (Exception ex) { - logger.log(Level.SEVERE, "Unexpected exception in DatabaseWriteTask", ex); //NON-NLS + } catch (HealthMonitorException ex) { + logger.log(Level.SEVERE, "Error writing current metrics to database", ex); //NON-NLS } } } + /** + * 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 { String databaseNodeName = DATABASE_NAME; @@ -504,6 +624,12 @@ public class ServicesHealthMonitor { } } + /** + * 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; @@ -518,11 +644,19 @@ public class ServicesHealthMonitor { } } + /** + * 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; - private long sum; - private long max; - private long min; + 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; @@ -531,35 +665,63 @@ public class ServicesHealthMonitor { 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; } + // TODO: debug void print() { System.out.println("count: " + count + "\tsum: " + sum + "\tmax: " + max + "\tmin: " + min); } diff --git a/Core/src/org/sleuthkit/autopsy/healthmonitor/TimingMetric.java b/Core/src/org/sleuthkit/autopsy/healthmonitor/TimingMetric.java index 431e585710..d94dfd7161 100644 --- a/Core/src/org/sleuthkit/autopsy/healthmonitor/TimingMetric.java +++ b/Core/src/org/sleuthkit/autopsy/healthmonitor/TimingMetric.java @@ -1,12 +1,25 @@ /* - * 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. + * Autopsy Forensic Browser + * + * Copyright 2018 Basis Technology Corp. + * Contact: carrier sleuthkit 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 { @@ -36,6 +49,12 @@ public class TimingMetric { 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; From 3d6dd20d90b138f43c2b69e255d7d85868f769bd Mon Sep 17 00:00:00 2001 From: Ann Priestman Date: Thu, 5 Apr 2018 14:11:06 -0400 Subject: [PATCH 08/25] Improved schema initialization. Added property change listener. Cleanup. --- .../HealthMonitorCaseEventListener.java | 53 +++++ .../autopsy/healthmonitor/Installer.java | 20 +- .../healthmonitor/ServicesHealthMonitor.java | 182 ++++++++++++------ 3 files changed, 177 insertions(+), 78 deletions(-) create mode 100644 Core/src/org/sleuthkit/autopsy/healthmonitor/HealthMonitorCaseEventListener.java diff --git a/Core/src/org/sleuthkit/autopsy/healthmonitor/HealthMonitorCaseEventListener.java b/Core/src/org/sleuthkit/autopsy/healthmonitor/HealthMonitorCaseEventListener.java new file mode 100644 index 0000000000..5c8a6f675d --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/healthmonitor/HealthMonitorCaseEventListener.java @@ -0,0 +1,53 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2018 Basis Technology Corp. + * Contact: carrier sleuthkit 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.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import org.sleuthkit.autopsy.casemodule.Case; + +/** + * Listener for case events + */ +final class HealthMonitorCaseEventListener implements PropertyChangeListener { + + private final ExecutorService jobProcessingExecutor; + private static final String CASE_EVENT_THREAD_NAME = "Health-Monitor-Event-Listener-%d"; + + HealthMonitorCaseEventListener() { + jobProcessingExecutor = Executors.newSingleThreadExecutor(new ThreadFactoryBuilder().setNameFormat(CASE_EVENT_THREAD_NAME).build()); + } + + @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 + jobProcessingExecutor.submit(new ServicesHealthMonitor.DatabaseWriteTask()); + } + break; + } + } +} diff --git a/Core/src/org/sleuthkit/autopsy/healthmonitor/Installer.java b/Core/src/org/sleuthkit/autopsy/healthmonitor/Installer.java index b77ba067d7..5d294aca31 100644 --- a/Core/src/org/sleuthkit/autopsy/healthmonitor/Installer.java +++ b/Core/src/org/sleuthkit/autopsy/healthmonitor/Installer.java @@ -20,11 +20,13 @@ 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 final HealthMonitorCaseEventListener pcl = new HealthMonitorCaseEventListener(); private static final long serialVersionUID = 1L; private static Installer instance; @@ -42,25 +44,13 @@ public class Installer extends ModuleInstall { @Override public void restored() { + + Case.addPropertyChangeListener(pcl); + try { ServicesHealthMonitor.startUp(); } catch (HealthMonitorException ex) { logger.log(Level.SEVERE, "Error starting health services monitor", ex); } } - - @Override - public boolean closing() { - //platform about to close - ServicesHealthMonitor.close(); - - return true; - } - - @Override - public void uninstalled() { - //module is being unloaded - ServicesHealthMonitor.close(); - - } } \ No newline at end of file diff --git a/Core/src/org/sleuthkit/autopsy/healthmonitor/ServicesHealthMonitor.java b/Core/src/org/sleuthkit/autopsy/healthmonitor/ServicesHealthMonitor.java index ab2510ca59..18fe6ba0ca 100644 --- a/Core/src/org/sleuthkit/autopsy/healthmonitor/ServicesHealthMonitor.java +++ b/Core/src/org/sleuthkit/autopsy/healthmonitor/ServicesHealthMonitor.java @@ -53,7 +53,7 @@ public class ServicesHealthMonitor { private final static String DATABASE_NAME = "ServicesHealthMonitor"; private final static String MODULE_NAME = "ServicesHealthMonitor"; private final static String IS_ENABLED_KEY = "is_enabled"; - private final static long DATABASE_WRITE_INTERVAL = 1; // Minutes + private final static long DATABASE_WRITE_INTERVAL = 60; // Minutes public static final CaseDbSchemaVersionNumber CURRENT_DB_SCHEMA_VERSION = new CaseDbSchemaVersionNumber(1, 0); @@ -75,7 +75,14 @@ public class ServicesHealthMonitor { if (ModuleSettings.settingExists(MODULE_NAME, IS_ENABLED_KEY)) { if(ModuleSettings.getConfigSetting(MODULE_NAME, IS_ENABLED_KEY).equals("true")){ isEnabled.set(true); - activateMonitor(); + 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; } } @@ -118,12 +125,12 @@ public class ServicesHealthMonitor { if (! databaseExists()) { // If not, create a new one - System.out.println(" No database exists - setting up new one"); createDatabase(); - initializeDatabaseSchema(); } - // Any database upgrades would happen here + if( ! databaseIsInitialized()) { + initializeDatabaseSchema(); + } } finally { try { @@ -202,9 +209,11 @@ public class ServicesHealthMonitor { } 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); - getInstance().activateMonitor(); } else { ModuleSettings.setConfigSetting(MODULE_NAME, IS_ENABLED_KEY, "false"); isEnabled.set(false); @@ -271,14 +280,12 @@ public class ServicesHealthMonitor { } } - // TODO: Make private once testing is done /** * Write the collected metrics to the database. * @throws HealthMonitorException */ - void writeCurrentStateToDatabase() throws HealthMonitorException { - logger.log(Level.INFO, "Writing health monitor metrics to database"); - + private void writeCurrentStateToDatabase() throws HealthMonitorException { + Map timingMapCopy; // Do as little as possible within the synchronized block since it will @@ -293,20 +300,13 @@ public class ServicesHealthMonitor { timingMapCopy = new HashMap<>(timingInfoMap); timingInfoMap.clear(); } + logger.log(Level.INFO, "Writing health monitor metrics to database"); // Check if there's anything to report (right now we only have the timing map) if(timingMapCopy.keySet().isEmpty()) { return; } - // TODO: Debug - for(String name:timingMapCopy.keySet()){ - TimingInfo info = timingMapCopy.get(name); - long timestamp = System.currentTimeMillis(); - System.out.println(" Name: " + name + "\tTimestamp: " + timestamp + "\tAverage: " + info.getAverage() + - "\tMax: " + info.getMax() + "\tMin: " + info.getMin()); - } - // Write to the database CoordinationService.Lock lock = getSharedDbLock(); if(lock == null) { @@ -354,11 +354,6 @@ public class ServicesHealthMonitor { } } - // TODO: debug - synchronized void clearCurrentState() { - timingInfoMap.clear(); - } - /** * Call during application closing - attempts to log any remaining entries. */ @@ -388,23 +383,13 @@ public class ServicesHealthMonitor { } } - // TODO: debug - synchronized void printCurrentState() { - System.out.println("\nTiming Info Map:"); - for(String name:timingInfoMap.keySet()) { - System.out.print(name + "\t"); - timingInfoMap.get(name).print(); - } - } - - // TODO: Change to private after testing /** * Check whether the health monitor database exists. * Does not check the schema. * @return true if the database exists, false otherwise * @throws HealthMonitorException */ - boolean databaseExists() throws HealthMonitorException { + private boolean databaseExists() throws HealthMonitorException { try { // Use the same database settings as the case CaseDbConnectionInfo db = UserPreferences.getDatabaseConnectionInfo(); @@ -449,26 +434,6 @@ public class ServicesHealthMonitor { throw new HealthMonitorException("Failed to delete health monitor database", ex); } } - - // TODO: At least make private - /** - * Delete the current health monitor database (for testing only) - * Make private after test - */ - void deleteDatabase() { - 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 deleteCommand = "DROP DATABASE \"" + DATABASE_NAME + "\""; //NON-NLS - statement.execute(deleteCommand); - } - } catch (UserPreferencesException | ClassNotFoundException | SQLException ex) { - logger.log(Level.SEVERE, "Failed to delete health monitor database", ex); - } - } /** * Setup a connection pool for db connections. @@ -494,7 +459,7 @@ public class ServicesHealthMonitor { connectionPool.setPassword(db.getPassword()); // tweak pool configuration - connectionPool.setInitialSize(2); // start with 2 connections + connectionPool.setInitialSize(3); // start with 3 connections connectionPool.setMaxIdle(CONN_POOL_SIZE); // max of 10 idle connections connectionPool.setValidationQuery("SELECT version()"); } catch (UserPreferencesException ex) { @@ -539,6 +504,95 @@ public class ServicesHealthMonitor { } } + /** + * 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 @@ -549,8 +603,9 @@ public class ServicesHealthMonitor { throw new HealthMonitorException("Error getting database connection"); } - // TODO: transaction try (Statement statement = conn.createStatement()) { + conn.setAutoCommit(false); + StringBuilder createTimingTable = new StringBuilder(); createTimingTable.append("CREATE TABLE IF NOT EXISTS timing_data ("); createTimingTable.append("id SERIAL PRIMARY KEY,"); @@ -574,13 +629,19 @@ public class ServicesHealthMonitor { 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); + logger.log(Level.SEVERE, "Error closing connection.", ex); } } } @@ -589,7 +650,7 @@ public class ServicesHealthMonitor { * The task called by the ScheduledThreadPoolExecutor to handle * the database writes. */ - private final class DatabaseWriteTask implements Runnable { + static final class DatabaseWriteTask implements Runnable { /** * Write current metric data to the database @@ -720,10 +781,5 @@ public class ServicesHealthMonitor { long getCount() { return count; } - - // TODO: debug - void print() { - System.out.println("count: " + count + "\tsum: " + sum + "\tmax: " + max + "\tmin: " + min); - } } } From f21feb0d3afe606941c30b848e92f19182f34137 Mon Sep 17 00:00:00 2001 From: Ann Priestman Date: Thu, 5 Apr 2018 14:15:02 -0400 Subject: [PATCH 09/25] Modified metric names. --- .../src/org/sleuthkit/autopsy/keywordsearch/Ingester.java | 2 +- .../src/org/sleuthkit/autopsy/keywordsearch/Server.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Ingester.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Ingester.java index 679a646995..f956513eca 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Ingester.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Ingester.java @@ -237,7 +237,7 @@ class Ingester { try { //TODO: consider timeout thread, or vary socket timeout based on size of indexed content - TimingMetric metric = ServicesHealthMonitor.getTimingMetric("solr index chunk"); + TimingMetric metric = ServicesHealthMonitor.getTimingMetric("Solr: Index chunk"); solrServer.addDocument(updateDoc); ServicesHealthMonitor.submitTimingMetric(metric); uncommitedIngests = true; diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Server.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Server.java index 3b4e494480..5748524ada 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Server.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Server.java @@ -775,7 +775,7 @@ public class Server { IndexingServerProperties properties = getMultiUserServerProperties(theCase.getCaseDirectory()); currentSolrServer = new HttpSolrServer("http://" + properties.getHost() + ":" + properties.getPort() + "/solr"); //NON-NLS } - TimingMetric metric = ServicesHealthMonitor.getTimingMetric("solr connectivity check"); + TimingMetric metric = ServicesHealthMonitor.getTimingMetric("Solr: Connectivity check"); connectToSolrServer(currentSolrServer); ServicesHealthMonitor.submitTimingMetric(metric); From fc53bff61aeaff82b2d29817111104c52777dbd3 Mon Sep 17 00:00:00 2001 From: Ann Priestman Date: Thu, 5 Apr 2018 14:17:43 -0400 Subject: [PATCH 10/25] Remove unused method --- .../healthmonitor/ServicesHealthMonitor.java | 29 ------------------- 1 file changed, 29 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/healthmonitor/ServicesHealthMonitor.java b/Core/src/org/sleuthkit/autopsy/healthmonitor/ServicesHealthMonitor.java index 18fe6ba0ca..ebed5d5f61 100644 --- a/Core/src/org/sleuthkit/autopsy/healthmonitor/ServicesHealthMonitor.java +++ b/Core/src/org/sleuthkit/autopsy/healthmonitor/ServicesHealthMonitor.java @@ -354,35 +354,6 @@ public class ServicesHealthMonitor { } } - /** - * Call during application closing - attempts to log any remaining entries. - */ - static synchronized void close() { - if(isEnabled.get()) { - - // Stop the timer - try { - getInstance().stopTimer(); - } catch (HealthMonitorException ex) { - logger.log(Level.SEVERE, "Error shutting down timer", ex); - } - - // Write current data - try { - getInstance().writeCurrentStateToDatabase(); - } catch (HealthMonitorException ex) { - logger.log(Level.SEVERE, "Error writing final metric data to database", ex); - } - - // Shutdown connection pool - try { - getInstance().shutdownConnections(); - } catch (HealthMonitorException ex) { - logger.log(Level.SEVERE, "Error shutting down connection pool", ex); - } - } - } - /** * Check whether the health monitor database exists. * Does not check the schema. From 34106c0f72e1b6b8eff9c77552c2dc29de905a4a Mon Sep 17 00:00:00 2001 From: "U-BASIS\\dgrove" Date: Thu, 5 Apr 2018 14:55:25 -0400 Subject: [PATCH 11/25] Event subscribers now handled for all factory classes. --- .../sleuthkit/autopsy/datamodel/accounts/Accounts.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/accounts/Accounts.java b/Core/src/org/sleuthkit/autopsy/datamodel/accounts/Accounts.java index 3b6878ce23..a06d3eeeea 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/accounts/Accounts.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/accounts/Accounts.java @@ -408,6 +408,7 @@ final public class Accounts implements AutopsyVisitableItem { protected void addNotify() { IngestManager.getInstance().addIngestJobEventListener(pcl); IngestManager.getInstance().addIngestModuleEventListener(pcl); + Case.addEventTypeSubscriber(EnumSet.of(Case.Events.CURRENT_CASE), pcl); super.addNotify(); } @@ -415,6 +416,7 @@ final public class Accounts implements AutopsyVisitableItem { protected void removeNotify() { IngestManager.getInstance().removeIngestJobEventListener(pcl); IngestManager.getInstance().removeIngestModuleEventListener(pcl); + Case.removeEventTypeSubscriber(EnumSet.of(Case.Events.CURRENT_CASE), pcl); super.removeNotify(); } @@ -569,6 +571,7 @@ final public class Accounts implements AutopsyVisitableItem { protected void addNotify() { IngestManager.getInstance().addIngestJobEventListener(pcl); IngestManager.getInstance().addIngestModuleEventListener(pcl); + Case.addEventTypeSubscriber(EnumSet.of(Case.Events.CURRENT_CASE), pcl); super.addNotify(); } @@ -576,6 +579,7 @@ final public class Accounts implements AutopsyVisitableItem { protected void removeNotify() { IngestManager.getInstance().removeIngestJobEventListener(pcl); IngestManager.getInstance().removeIngestModuleEventListener(pcl); + Case.removeEventTypeSubscriber(EnumSet.of(Case.Events.CURRENT_CASE), pcl); super.removeNotify(); } @@ -691,6 +695,7 @@ final public class Accounts implements AutopsyVisitableItem { protected void addNotify() { IngestManager.getInstance().addIngestJobEventListener(pcl); IngestManager.getInstance().addIngestModuleEventListener(pcl); + Case.addEventTypeSubscriber(EnumSet.of(Case.Events.CURRENT_CASE), pcl); super.addNotify(); } @@ -698,6 +703,7 @@ final public class Accounts implements AutopsyVisitableItem { protected void removeNotify() { IngestManager.getInstance().removeIngestJobEventListener(pcl); IngestManager.getInstance().removeIngestModuleEventListener(pcl); + Case.removeEventTypeSubscriber(EnumSet.of(Case.Events.CURRENT_CASE), pcl); super.removeNotify(); } @@ -901,6 +907,7 @@ final public class Accounts implements AutopsyVisitableItem { protected void addNotify() { IngestManager.getInstance().addIngestJobEventListener(pcl); IngestManager.getInstance().addIngestModuleEventListener(pcl); + Case.addEventTypeSubscriber(EnumSet.of(Case.Events.CURRENT_CASE), pcl); super.addNotify(); } @@ -908,6 +915,7 @@ final public class Accounts implements AutopsyVisitableItem { protected void removeNotify() { IngestManager.getInstance().removeIngestJobEventListener(pcl); IngestManager.getInstance().removeIngestModuleEventListener(pcl); + Case.removeEventTypeSubscriber(EnumSet.of(Case.Events.CURRENT_CASE), pcl); super.removeNotify(); } From 1882690b727012cf0e4833d272360038716779ed Mon Sep 17 00:00:00 2001 From: millmanorama Date: Fri, 6 Apr 2018 15:55:44 +0200 Subject: [PATCH 12/25] refactor actions to support multiselect in VisualizationPanel via the ActionsGlobalContext lookup --- .../communications/AbstractCVTAction.java | 41 ++++++++--- .../communications/MessageBrowser.java | 4 +- .../autopsy/communications/SelectionNode.java | 18 +++-- .../communications/VisualizationPanel.java | 71 +++++++++---------- 4 files changed, 79 insertions(+), 55 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/communications/AbstractCVTAction.java b/Core/src/org/sleuthkit/autopsy/communications/AbstractCVTAction.java index c2b43f2041..82cbe5e0f8 100644 --- a/Core/src/org/sleuthkit/autopsy/communications/AbstractCVTAction.java +++ b/Core/src/org/sleuthkit/autopsy/communications/AbstractCVTAction.java @@ -1,7 +1,20 @@ /* - * 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. + * Autopsy Forensic Browser + * + * Copyright 2018 Basis Technology Corp. + * Contact: carrier sleuthkit 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; @@ -14,7 +27,9 @@ 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 { @@ -24,12 +39,8 @@ abstract class AbstractCVTAction extends AbstractAction implements Presenter.Pop * @return The selected accounts */ Collection getSelectedAccounts() { - return Utilities.actionsGlobalContext().lookupAll(AccountDeviceInstanceKey.class); - } - - @Override - final public void putValue(String key, Object newValue) { - super.putValue(key, newValue); + final Collection lookupAll = Utilities.actionsGlobalContext().lookupAll(AccountDeviceInstanceKey.class); + return lookupAll; } @Override @@ -40,8 +51,18 @@ abstract class AbstractCVTAction extends AbstractAction implements Presenter.Pop 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(); } diff --git a/Core/src/org/sleuthkit/autopsy/communications/MessageBrowser.java b/Core/src/org/sleuthkit/autopsy/communications/MessageBrowser.java index 68309f5195..7c0bf99c62 100644 --- a/Core/src/org/sleuthkit/autopsy/communications/MessageBrowser.java +++ b/Core/src/org/sleuthkit/autopsy/communications/MessageBrowser.java @@ -151,10 +151,10 @@ public final class MessageBrowser extends JPanel implements ExplorerManager.Prov //Use lookup here? final AccountDeviceInstanceNode adiNode = (AccountDeviceInstanceNode) selectedNodes[0]; - final Set accountDeviceInstances = new HashSet<>(); + final Set accountDeviceInstances = new HashSet<>(); for (final Node n : selectedNodes) { //Use lookup here? - accountDeviceInstances.add(((AccountDeviceInstanceNode) n).getAccountDeviceInstance()); + accountDeviceInstances.add(((AccountDeviceInstanceNode) n).getAccountDeviceInstanceKey()); } return SelectionNode.createFromAccounts(accountDeviceInstances, adiNode.getFilter(), adiNode.getCommsManager()); } diff --git a/Core/src/org/sleuthkit/autopsy/communications/SelectionNode.java b/Core/src/org/sleuthkit/autopsy/communications/SelectionNode.java index 40739b49cc..be564c01cc 100644 --- a/Core/src/org/sleuthkit/autopsy/communications/SelectionNode.java +++ b/Core/src/org/sleuthkit/autopsy/communications/SelectionNode.java @@ -20,14 +20,18 @@ package org.sleuthkit.autopsy.communications; import com.google.common.collect.Iterables; import com.google.common.collect.Sets; +import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Set; import java.util.logging.Level; +import java.util.stream.Collectors; import org.openide.nodes.AbstractNode; import org.openide.nodes.ChildFactory; import org.openide.nodes.Children; 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.datamodel.AccountDeviceInstance; import org.sleuthkit.datamodel.BlackboardArtifact; @@ -44,23 +48,27 @@ import org.sleuthkit.datamodel.TskCoreException; */ final class SelectionNode extends AbstractNode { - private SelectionNode(Children children) { - super(children); + SelectionNode(Children children, Lookup lookup) { + super(children, lookup); } static SelectionNode createFromAccountsAndRelationships( Set edgeRelationshipArtifacts, - Set accountDeviceInstances, + Set accountDeviceInstanceKeys, CommunicationsFilter filter, CommunicationsManager commsManager) { + Set accountDeviceInstances = accountDeviceInstanceKeys.stream() + .map(AccountDeviceInstanceKey::getAccountDeviceInstance) + .collect(Collectors.toSet()); + SelectionNode node = new SelectionNode(Children.create( new RelationshipChildren( edgeRelationshipArtifacts, accountDeviceInstances, commsManager, filter), - true)); + true), Lookups.fixed(accountDeviceInstanceKeys.toArray())); //This is not good for internationalization!!! String name = ""; @@ -82,7 +90,7 @@ final class SelectionNode extends AbstractNode { } static SelectionNode createFromAccounts( - Set accountDeviceInstances, + Set accountDeviceInstances, CommunicationsFilter filter, CommunicationsManager commsManager) { diff --git a/Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.java b/Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.java index fa095fd0a8..559bdf2bd5 100644 --- a/Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.java +++ b/Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.java @@ -193,48 +193,47 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider @Override public void mouseClicked(final MouseEvent event) { super.mouseClicked(event); - SwingUtilities.invokeLater(() -> { - 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 (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", unlockIcon) { - @Override - public void actionPerformed(final ActionEvent event) { - lockedVertexModel.unlockVertex(cellAt); - } - })); - } else { - jPopupMenu.add(new JMenuItem(new AbstractAction("Lock", lockIcon) { - @Override - public void actionPerformed(final ActionEvent event) { - lockedVertexModel.lockVertex(cellAt); - } - })); - } - 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()); + if (lockedVertexModel.isVertexLocked(cellAt)) { + jPopupMenu.add(new JMenuItem(new AbstractAction("UnLock", unlockIcon) { + @Override + public void actionPerformed(final ActionEvent event) { + lockedVertexModel.unlockVertex(cellAt); + } + })); + } else { + jPopupMenu.add(new JMenuItem(new AbstractAction("Lock", lockIcon) { + @Override + public void actionPerformed(final ActionEvent event) { + lockedVertexModel.lockVertex(cellAt); + } + })); } + 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()); } - }); + } } - }); + final MessageBrowser messageBrowser = new MessageBrowser(vizEM, gacEM); splitPane.setRightComponent(messageBrowser); proxyLookup = new ProxyLookup( - messageBrowser.getLookup(), - ExplorerUtils.createLookup(vizEM, getActionMap())); + ExplorerUtils.createLookup(vizEM, getActionMap()), + messageBrowser.getLookup() + ); //feed selection to explorermanager graph.getSelectionModel().addListener(mxEvent.CHANGE, new SelectionListener()); @@ -246,7 +245,6 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider } @Override - public Lookup getLookup() { return proxyLookup; } @@ -275,7 +273,6 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider @Subscribe void handleFilterEvent(final CVTEvents.FilterChangeEvent filterChangeEvent) { - graph.getModel().beginUpdate(); graph.clear(); currentFilter = filterChangeEvent.getNewFilter(); @@ -733,7 +730,7 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider if (selectionCells.length > 0) { mxICell[] selectedCells = Arrays.asList(selectionCells).toArray(new mxCell[selectionCells.length]); HashSet relationshipSources = new HashSet<>(); - HashSet adis = new HashSet<>(); + HashSet adis = new HashSet<>(); for (mxICell cell : selectedCells) { if (cell.isEdge()) { mxICell source = (mxICell) graph.getModel().getTerminal(cell, true); @@ -750,7 +747,7 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider logger.log(Level.SEVERE, " Error getting relationsips....", tskCoreException); } } else if (cell.isVertex()) { - adis.add(((AccountDeviceInstanceKey) cell.getValue()).getAccountDeviceInstance()); + adis.add((AccountDeviceInstanceKey) cell.getValue()); } } @@ -872,7 +869,5 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider cancellable.cancel(true); progress.finish(); } - } - } From d5f1064e8855b7523e5e63a16dfeb5d11aa3e6a4 Mon Sep 17 00:00:00 2001 From: Ann Priestman Date: Fri, 6 Apr 2018 13:55:49 -0400 Subject: [PATCH 13/25] Added host name to timing metric --- .../healthmonitor/ServicesHealthMonitor.java | 23 ++++++++++++++----- 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/healthmonitor/ServicesHealthMonitor.java b/Core/src/org/sleuthkit/autopsy/healthmonitor/ServicesHealthMonitor.java index ebed5d5f61..d628f2e8b9 100644 --- a/Core/src/org/sleuthkit/autopsy/healthmonitor/ServicesHealthMonitor.java +++ b/Core/src/org/sleuthkit/autopsy/healthmonitor/ServicesHealthMonitor.java @@ -301,6 +301,15 @@ public class ServicesHealthMonitor { timingInfoMap.clear(); } logger.log(Level.INFO, "Writing health monitor metrics to database"); + + String hostName; + try { + hostName = java.net.InetAddress.getLocalHost().getHostName(); + } catch (java.net.UnknownHostException ex) { + // Write it to the database but log a warning + logger.log(Level.WARNING, "Unable to look up host name"); + hostName = "unknown"; + } // Check if there's anything to report (right now we only have the timing map) if(timingMapCopy.keySet().isEmpty()) { @@ -320,18 +329,19 @@ public class ServicesHealthMonitor { } // Add timing metrics to the database - String addTimingInfoSql = "INSERT INTO timing_data (name, timestamp, count, average, max, min) VALUES (?, ?, ?, ?, ?, ?)"; + 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.setLong(2, System.currentTimeMillis()); - statement.setLong(3, info.getCount()); - statement.setLong(4, info.getAverage()); - statement.setLong(5, info.getMax()); - statement.setLong(6, info.getMin()); + 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(); } @@ -581,6 +591,7 @@ public class ServicesHealthMonitor { createTimingTable.append("CREATE TABLE IF NOT EXISTS timing_data ("); createTimingTable.append("id SERIAL PRIMARY KEY,"); createTimingTable.append("name text NOT NULL,"); + createTimingTable.append("host text NOT NULL,"); createTimingTable.append("timestamp bigint NOT NULL,"); createTimingTable.append("count bigint NOT NULL,"); createTimingTable.append("average bigint NOT NULL,"); From f3161c7af6607d9156be14cf783efa486b973cdc Mon Sep 17 00:00:00 2001 From: millmanorama Date: Sat, 7 Apr 2018 14:56:15 +0200 Subject: [PATCH 14/25] multiselect for locking. Not updating the icon properly... --- .../communications/CommunicationsGraph.java | 28 ++--- .../communications/LockedVertexModel.java | 46 ++++--- .../communications/PinnedAccountModel.java | 7 +- .../communications/VisualizationPanel.java | 116 ++++++++++++++---- 4 files changed, 124 insertions(+), 73 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/communications/CommunicationsGraph.java b/Core/src/org/sleuthkit/autopsy/communications/CommunicationsGraph.java index f274ab00d7..98d9aa7d88 100644 --- a/Core/src/org/sleuthkit/autopsy/communications/CommunicationsGraph.java +++ b/Core/src/org/sleuthkit/autopsy/communications/CommunicationsGraph.java @@ -38,6 +38,7 @@ import java.util.Map; import java.util.Set; import java.util.concurrent.CancellationException; import java.util.concurrent.ExecutionException; +import java.util.function.Consumer; import java.util.logging.Level; import javax.swing.SwingWorker; import org.sleuthkit.autopsy.coreutils.Logger; @@ -84,17 +85,19 @@ final class CommunicationsGraph extends mxGraph { 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 nodeMap = new HashMap<>(); - /* Map from relationship source (Content) to mxCell (edge). */ + /** Map from relationship source (Content) to mxCell (edge). */ private final Multimap edgeMap = MultimapBuilder.hashKeys().hashSetValues().build(); private final LockedVertexModel lockedVertexModel; private final PinnedAccountModel pinnedAccountModel; - CommunicationsGraph() { + CommunicationsGraph(PinnedAccountModel pinnedAccountModel, LockedVertexModel lockedVertexModel) { super(mxStylesheet); + this.pinnedAccountModel =pinnedAccountModel; + this.lockedVertexModel = lockedVertexModel; //set fixed properties of graph. setAutoSizeCells(true); setCellsCloneable(false); @@ -113,21 +116,6 @@ final class CommunicationsGraph extends mxGraph { setKeepEdgesInBackground(true); setResetEdgesOnMove(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); } /** @@ -299,7 +287,7 @@ final class CommunicationsGraph extends mxGraph { int total = relationshipCounts.size(); int k = 0; - String progressText = ""; + String progressText = ""; progress.switchToDeterminate("", 0, total); for (Map.Entry entry : relationshipCounts.entrySet()) { Long count = entry.getValue(); @@ -308,7 +296,7 @@ final class CommunicationsGraph extends mxGraph { AccountDeviceInstanceKey account2 = relatedAccounts.get(relationshipKey.getSecond()); if (pinnedAccountModel.isAccountPinned(account1) - || pinnedAccountModel.isAccountPinned(account2)) { + || pinnedAccountModel.isAccountPinned(account2)) { mxCell addEdge = addOrUpdateEdge(count, account1, account2); progressText = addEdge.getId(); } diff --git a/Core/src/org/sleuthkit/autopsy/communications/LockedVertexModel.java b/Core/src/org/sleuthkit/autopsy/communications/LockedVertexModel.java index f26e9515b3..ad189a8bea 100644 --- a/Core/src/org/sleuthkit/autopsy/communications/LockedVertexModel.java +++ b/Core/src/org/sleuthkit/autopsy/communications/LockedVertexModel.java @@ -18,12 +18,22 @@ */ package org.sleuthkit.autopsy.communications; +import com.google.common.collect.ImmutableSet; import com.google.common.eventbus.EventBus; import com.mxgraph.model.mxCell; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; import java.util.HashSet; +import java.util.List; import java.util.Set; -class LockedVertexModel { +/** + * Model of which vertices in a graph are locked ( not moveable by layout + * algorithms). + * + */ +final class LockedVertexModel { void registerhandler(EventHandler handler) { eventBus.register(handler); @@ -42,30 +52,26 @@ class LockedVertexModel { */ private final Set lockedVertices = new HashSet<>(); - LockedVertexModel() { - } - /** - * Lock the given vertex so that applying a layout algorithm doesn't move - * it. The user can still manually position the vertex. + * Lock the given vertices so that applying a layout algorithm doesn't move + * them. The user can still manually position the vertices. * * @param vertex The vertex to lock. */ - void lockVertex(mxCell vertex) { - lockedVertices.add(vertex); - eventBus.post(new VertexLockEvent(vertex, true)); - + void lock(Collection vertices) { + lockedVertices.addAll(vertices); + 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. */ - void unlockVertex(mxCell vertex) { - lockedVertices.remove(vertex); - eventBus.post(new VertexLockEvent(vertex, false)); - + void unlock(Collection vertices) { + lockedVertices.removeAll(vertices); + eventBus.post(new VertexLockEvent(false, vertices)); } boolean isVertexLocked(mxCell vertex) { @@ -79,10 +85,10 @@ class LockedVertexModel { static class VertexLockEvent { - private final mxCell vertex; + private final Set vertices; - public mxCell getVertex() { - return vertex; + public Set getVertices() { + return vertices; } public boolean isVertexLocked() { @@ -90,8 +96,8 @@ class LockedVertexModel { } private final boolean locked; - VertexLockEvent(mxCell vertex, boolean locked) { - this.vertex = vertex; + VertexLockEvent(boolean locked, Collection< mxCell> vertices) { + this.vertices = ImmutableSet.copyOf(vertices); this.locked = locked; } } diff --git a/Core/src/org/sleuthkit/autopsy/communications/PinnedAccountModel.java b/Core/src/org/sleuthkit/autopsy/communications/PinnedAccountModel.java index d5f7a7cc03..beff35173d 100644 --- a/Core/src/org/sleuthkit/autopsy/communications/PinnedAccountModel.java +++ b/Core/src/org/sleuthkit/autopsy/communications/PinnedAccountModel.java @@ -25,17 +25,12 @@ import java.util.Set; 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 * to pinned accounts and pass the filters are show. Pinning accounts is the * primary way to populate the graph. */ private final Set pinnedAccountDevices = new HashSet<>(); - private final CommunicationsGraph graph; - - PinnedAccountModel(CommunicationsGraph graph) { - this.graph = graph; - } boolean isAccountPinned(AccountDeviceInstanceKey account) { return pinnedAccountDevices.contains(account); diff --git a/Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.java b/Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.java index 559bdf2bd5..d33ca867d7 100644 --- a/Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.java +++ b/Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.java @@ -36,7 +36,9 @@ import com.mxgraph.util.mxPoint; import com.mxgraph.util.mxRectangle; import com.mxgraph.util.mxUndoManager; import com.mxgraph.util.mxUndoableEdit; +import com.mxgraph.view.mxCellState; import com.mxgraph.view.mxGraph; +import com.mxgraph.view.mxGraphView; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Cursor; @@ -54,8 +56,11 @@ import java.util.Arrays; import java.util.EnumSet; import java.util.HashSet; import java.util.List; +import java.util.Set; import java.util.concurrent.Future; import java.util.logging.Level; +import java.util.stream.Collectors; +import java.util.stream.Stream; import javax.swing.AbstractAction; import javax.swing.ImageIcon; import javax.swing.JButton; @@ -83,7 +88,6 @@ import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.ThreadConfined; import org.sleuthkit.autopsy.progress.ModalDialogProgressIndicator; -import org.sleuthkit.datamodel.AccountDeviceInstance; import org.sleuthkit.datamodel.CommunicationsFilter; import org.sleuthkit.datamodel.CommunicationsManager; import org.sleuthkit.datamodel.Content; @@ -99,7 +103,6 @@ import org.sleuthkit.datamodel.TskCoreException; * CVTTopComponent when this tab is active allowing for context sensitive * actions to work correctly. */ -@NbBundle.Messages("VisualizationPanel.cancelButton.text=Cancel") final public class VisualizationPanel extends JPanel implements Lookup.Provider { private static final long serialVersionUID = 1L; @@ -110,6 +113,7 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider static final private ImageIcon lockIcon = 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 final ExplorerManager vizEM = new ExplorerManager(); @@ -124,7 +128,7 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider private final CommunicationsGraph graph; 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; @@ -133,14 +137,13 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider @ThreadConfined(type = ThreadConfined.ThreadType.AWT) private SwingWorker worker; - private final PinnedAccountModel pinnedAccountModel; - private final LockedVertexModel lockedVertexModel; + private final PinnedAccountModel pinnedAccountModel = new PinnedAccountModel(); + private final LockedVertexModel lockedVertexModel = new LockedVertexModel(); public VisualizationPanel() { initComponents(); - graph = new CommunicationsGraph(); - pinnedAccountModel = graph.getPinnedAccountModel(); - lockedVertexModel = graph.getLockedVertexModel(); + + graph = new CommunicationsGraph(pinnedAccountModel, lockedVertexModel); fastOrganicLayout = new mxFastOrganicLayoutImpl(graph); circleLayout = new mxCircleLayoutImpl(graph); @@ -159,9 +162,26 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider graphComponent.setBackground(Color.WHITE); borderLayoutPanel.add(graphComponent, BorderLayout.CENTER); - //install rubber band selection handler + //install rubber band other handlers rubberband = new mxRubberband(graphComponent); + lockedVertexModel.registerhandler((LockedVertexModel.VertexLockEvent event) -> { + final Set vertices = event.getVertices(); + mxGraphView view = graph.getView(); + if (event.isVertexLocked()) { + vertices.forEach(vertex -> view.clear(vertex, true, true)); + view.validate(); + } else { + vertices.forEach(vertex -> { + final mxCellState state = view.getState(vertex, true); + view.updateLabel(state); + view.updateLabelBounds(state); + view.updateBoundingBox(state); + }); + } + graphComponent.getGraphControl().repaint(); + }); + final mxEventSource.mxIEventListener scaleListener = (Object sender, mxEventObject evt) -> zoomLabel.setText(DecimalFormat.getPercentInstance().format(graph.getView().getScale())); graph.getView().addListener(mxEvent.SCALE, scaleListener); @@ -199,20 +219,16 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider final JPopupMenu jPopupMenu = new JPopupMenu(); final AccountDeviceInstanceKey adiKey = (AccountDeviceInstanceKey) cellAt.getValue(); + Set 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 AbstractAction("UnLock", unlockIcon) { - @Override - public void actionPerformed(final ActionEvent event) { - lockedVertexModel.unlockVertex(cellAt); - } - })); + jPopupMenu.add(new JMenuItem(new UnlockAction(selectedVertices))); } else { - jPopupMenu.add(new JMenuItem(new AbstractAction("Lock", lockIcon) { - @Override - public void actionPerformed(final ActionEvent event) { - lockedVertexModel.lockVertex(cellAt); - } - })); + jPopupMenu.add(new JMenuItem(new LockAction(selectedVertices))); } if (pinnedAccountModel.isAccountPinned(adiKey)) { jPopupMenu.add(UnpinAccountsAction.getInstance().getPopupPresenter()); @@ -224,6 +240,7 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider } } } + }); final MessageBrowser messageBrowser = new MessageBrowser(vizEM, gacEM); @@ -650,16 +667,18 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider } + @NbBundle.Messages({"Visualization.computingLayout=Computing Layout"}) 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); + ModalDialogProgressIndicator progress = new ModalDialogProgressIndicator(windowAncestor, + Bundle.Visualization_computingLayout(), new String[]{CANCEL}, CANCEL, cancelationListener); SwingWorker morphWorker = new SwingWorker() { @Override protected Void doInBackground() { - progress.start("Computing layout"); + progress.start(Bundle.Visualization_computingLayout()); layout.execute(graph.getDefaultParent()); if (isCancelled()) { progress.finish(); @@ -719,6 +738,10 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider private JButton zoomOutButton; // 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 { @SuppressWarnings("unchecked") @@ -776,7 +799,7 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider } @Override - public mxRectangle setVertexLocation(Object vertex, double x, double y) { + public mxRectangle setVertexLocation(Object vertex, double x, double y) { //NOPMD x,y variable names are standard if (isVertexIgnored(vertex)) { return getVertexBounds(vertex); } else { @@ -799,7 +822,7 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider } @Override - public mxRectangle setVertexLocation(Object vertex, double x, double y) { + public mxRectangle setVertexLocation(Object vertex, double x, double y) { //NOPMD x,y variable names are standard if (isVertexIgnored(vertex)) { return getVertexBounds(vertex); } else { @@ -822,7 +845,7 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider } @Override - public mxRectangle setVertexLocation(Object vertex, double x, double y) { + public mxRectangle setVertexLocation(Object vertex, double x, double y) { //NOPMD x,y variable names are standard if (isVertexIgnored(vertex)) { return getVertexBounds(vertex); } else { @@ -844,7 +867,7 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider } @Override - public mxRectangle setVertexLocation(Object vertex, double x, double y) { + public mxRectangle setVertexLocation(Object vertex, double x, double y) { //NOPMD x,y variable names are standard if (isVertexIgnored(vertex)) { return getVertexBounds(vertex); } else { @@ -870,4 +893,43 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider progress.finish(); } } + + @NbBundle.Messages({ + "VisualizationPanel.unlockAction.singularText=Unlock Selected Account", + "VisualizationPanel.unlockAction.pluralText=Unlock Selected Accounts",}) + private final class UnlockAction extends AbstractAction { + + private final Set selectedVertices; + + UnlockAction(Set 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); + } + } + + @NbBundle.Messages({ + "VisualizationPanel.lockAction.singularText=Lock Selected Account", + "VisualizationPanel.lockAction.pluralText=Lock Selected Accounts"}) + private final class LockAction extends AbstractAction { + + private final Set selectedVertices; + + LockAction(Set 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); + } + } } From b5a50fd62a0ad00291c6a0555a9843fce9aa0cf9 Mon Sep 17 00:00:00 2001 From: millmanorama Date: Sat, 7 Apr 2018 15:49:25 +0200 Subject: [PATCH 15/25] fix repainting of lock icons --- .../autopsy/communications/EventHandler.java | 28 -------------- .../communications/LockedVertexModel.java | 24 ++++++++---- .../communications/VisualizationPanel.java | 37 +++++++++---------- 3 files changed, 33 insertions(+), 56 deletions(-) delete mode 100644 Core/src/org/sleuthkit/autopsy/communications/EventHandler.java diff --git a/Core/src/org/sleuthkit/autopsy/communications/EventHandler.java b/Core/src/org/sleuthkit/autopsy/communications/EventHandler.java deleted file mode 100644 index f1acedf1cf..0000000000 --- a/Core/src/org/sleuthkit/autopsy/communications/EventHandler.java +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2018 Basis Technology Corp. - * Contact: carrier sleuthkit 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; - -/** - * - */ -public interface EventHandler { - - void handle(T event); -} diff --git a/Core/src/org/sleuthkit/autopsy/communications/LockedVertexModel.java b/Core/src/org/sleuthkit/autopsy/communications/LockedVertexModel.java index ad189a8bea..873866f3f5 100644 --- a/Core/src/org/sleuthkit/autopsy/communications/LockedVertexModel.java +++ b/Core/src/org/sleuthkit/autopsy/communications/LockedVertexModel.java @@ -21,11 +21,8 @@ package org.sleuthkit.autopsy.communications; import com.google.common.collect.ImmutableSet; import com.google.common.eventbus.EventBus; import com.mxgraph.model.mxCell; -import java.util.Arrays; import java.util.Collection; -import java.util.Collections; import java.util.HashSet; -import java.util.List; import java.util.Set; /** @@ -35,11 +32,11 @@ import java.util.Set; */ final class LockedVertexModel { - void registerhandler(EventHandler handler) { + void registerhandler(Object handler) { eventBus.register(handler); } - void unregisterhandler(EventHandler handler) { + void unregisterhandler(Object handler) { eventBus.unregister(handler); } @@ -83,18 +80,29 @@ final class LockedVertexModel { lockedVertices.clear(); } - static class VertexLockEvent { + /** + * Event that represents a change in the locked state of one or more + * vertices. + */ + final static class VertexLockEvent { + private final boolean locked; private final Set vertices; + /** + * @return The vertices whose locked state has changed. + */ public Set getVertices() { return vertices; } - public boolean isVertexLocked() { + /** + * @return True if the vertices are locked, False if the vertices are + * unlocked. + */ + public boolean isLocked() { return locked; } - private final boolean locked; VertexLockEvent(boolean locked, Collection< mxCell> vertices) { this.vertices = ImmutableSet.copyOf(vertices); diff --git a/Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.java b/Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.java index d33ca867d7..e0fe563e81 100644 --- a/Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.java +++ b/Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.java @@ -165,22 +165,7 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider //install rubber band other handlers rubberband = new mxRubberband(graphComponent); - lockedVertexModel.registerhandler((LockedVertexModel.VertexLockEvent event) -> { - final Set vertices = event.getVertices(); - mxGraphView view = graph.getView(); - if (event.isVertexLocked()) { - vertices.forEach(vertex -> view.clear(vertex, true, true)); - view.validate(); - } else { - vertices.forEach(vertex -> { - final mxCellState state = view.getState(vertex, true); - view.updateLabel(state); - view.updateLabelBounds(state); - view.updateBoundingBox(state); - }); - } - graphComponent.getGraphControl().repaint(); - }); + lockedVertexModel.registerhandler(this); final mxEventSource.mxIEventListener scaleListener = (Object sender, mxEventObject evt) -> zoomLabel.setText(DecimalFormat.getPercentInstance().format(graph.getView().getScale())); @@ -267,7 +252,20 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider } @Subscribe - void handleUnPinEvent(final CVTEvents.UnpinAccountsEvent pinEvent) { + void handle(LockedVertexModel.VertexLockEvent event) { + final Set 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(); pinnedAccountModel.unpinAccount(pinEvent.getAccountDeviceInstances()); graph.clear(); @@ -277,7 +275,7 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider } @Subscribe - void handlePinEvent(final CVTEvents.PinAccountsEvent pinEvent) { + void handle(final CVTEvents.PinAccountsEvent pinEvent) { graph.getModel().beginUpdate(); if (pinEvent.isReplace()) { graph.resetGraph(); @@ -289,7 +287,7 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider } @Subscribe - void handleFilterEvent(final CVTEvents.FilterChangeEvent filterChangeEvent) { + void handle(final CVTEvents.FilterChangeEvent filterChangeEvent) { graph.getModel().beginUpdate(); graph.clear(); currentFilter = filterChangeEvent.getNewFilter(); @@ -360,7 +358,6 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider logger.log(Level.SEVERE, "Error getting CommunicationsManager for the current case.", ex); } } - }); } From 1bec33604b401edffa5511c086ca0da6fe2ff77c Mon Sep 17 00:00:00 2001 From: millmanorama Date: Sat, 7 Apr 2018 16:19:14 +0200 Subject: [PATCH 16/25] fix codacy warnings --- .../communications/AbstractCVTAction.java | 5 +--- .../autopsy/communications/CVTEvents.java | 1 - .../communications/CommunicationsGraph.java | 23 ++++++++----------- .../communications/LockedVertexModel.java | 18 ++++++++------- .../communications/MessageBrowser.java | 1 - .../communications/PinAccountsAction.java | 8 +++++-- .../communications/PinnedAccountModel.java | 14 +++++++++++ .../ResetAndPinAccountsAction.java | 6 ++--- .../autopsy/communications/SelectionNode.java | 9 ++++---- .../communications/UnpinAccountsAction.java | 4 ++++ .../communications/VisualizationPanel.java | 11 ++++++++- 11 files changed, 62 insertions(+), 38 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/communications/AbstractCVTAction.java b/Core/src/org/sleuthkit/autopsy/communications/AbstractCVTAction.java index 82cbe5e0f8..ef751d262c 100644 --- a/Core/src/org/sleuthkit/autopsy/communications/AbstractCVTAction.java +++ b/Core/src/org/sleuthkit/autopsy/communications/AbstractCVTAction.java @@ -26,7 +26,6 @@ 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. @@ -39,8 +38,7 @@ abstract class AbstractCVTAction extends AbstractAction implements Presenter.Pop * @return The selected accounts */ Collection getSelectedAccounts() { - final Collection lookupAll = Utilities.actionsGlobalContext().lookupAll(AccountDeviceInstanceKey.class); - return lookupAll; + return Utilities.actionsGlobalContext().lookupAll(AccountDeviceInstanceKey.class); } @Override @@ -64,5 +62,4 @@ abstract class AbstractCVTAction extends AbstractAction implements Presenter.Pop * @return An ImageIcon used to represent this action. */ abstract ImageIcon getIcon(); - } diff --git a/Core/src/org/sleuthkit/autopsy/communications/CVTEvents.java b/Core/src/org/sleuthkit/autopsy/communications/CVTEvents.java index 54e5b8a1ef..6a6a60d3da 100644 --- a/Core/src/org/sleuthkit/autopsy/communications/CVTEvents.java +++ b/Core/src/org/sleuthkit/autopsy/communications/CVTEvents.java @@ -21,7 +21,6 @@ package org.sleuthkit.autopsy.communications; import com.google.common.collect.ImmutableSet; import com.google.common.eventbus.EventBus; import java.util.Collection; -import java.util.Set; import org.sleuthkit.datamodel.CommunicationsFilter; /** diff --git a/Core/src/org/sleuthkit/autopsy/communications/CommunicationsGraph.java b/Core/src/org/sleuthkit/autopsy/communications/CommunicationsGraph.java index 98d9aa7d88..2cf04eacea 100644 --- a/Core/src/org/sleuthkit/autopsy/communications/CommunicationsGraph.java +++ b/Core/src/org/sleuthkit/autopsy/communications/CommunicationsGraph.java @@ -25,7 +25,6 @@ import com.google.common.collect.MultimapBuilder; import com.mxgraph.model.mxCell; import com.mxgraph.model.mxICell; import com.mxgraph.util.mxConstants; -import com.mxgraph.view.mxCellState; import com.mxgraph.view.mxGraph; import com.mxgraph.view.mxStylesheet; import java.io.InputStream; @@ -38,7 +37,6 @@ import java.util.Map; import java.util.Set; import java.util.concurrent.CancellationException; import java.util.concurrent.ExecutionException; -import java.util.function.Consumer; import java.util.logging.Level; import javax.swing.SwingWorker; import org.sleuthkit.autopsy.coreutils.Logger; @@ -96,7 +94,7 @@ final class CommunicationsGraph extends mxGraph { CommunicationsGraph(PinnedAccountModel pinnedAccountModel, LockedVertexModel lockedVertexModel) { super(mxStylesheet); - this.pinnedAccountModel =pinnedAccountModel; + this.pinnedAccountModel = pinnedAccountModel; this.lockedVertexModel = lockedVertexModel; //set fixed properties of graph. setAutoSizeCells(true); @@ -243,20 +241,20 @@ final class CommunicationsGraph extends mxGraph { */ private class RebuildWorker extends SwingWorker { - private final ProgressIndicator progress; + private final ProgressIndicator progressIndicator; private final CommunicationsManager commsManager; private final CommunicationsFilter currentFilter; RebuildWorker(ProgressIndicator progress, CommunicationsManager commsManager, CommunicationsFilter currentFilter) { - this.progress = progress; + this.progressIndicator = progress; this.currentFilter = currentFilter; this.commsManager = commsManager; } @Override - protected Void doInBackground() throws Exception { - progress.start("Loading accounts"); + protected Void doInBackground() { + progressIndicator.start("Loading accounts"); int progressCounter = 0; try { /** @@ -278,7 +276,7 @@ final class CommunicationsGraph extends mxGraph { final AccountDeviceInstanceKey relatedADIKey = new AccountDeviceInstanceKey(relatedADI, currentFilter, adiRelationshipsCount); relatedAccounts.put(relatedADI, relatedADIKey); //store related accounts } - progress.progress(++progressCounter); + progressIndicator.progress(++progressCounter); } Set accounts = relatedAccounts.keySet(); @@ -286,9 +284,9 @@ final class CommunicationsGraph extends mxGraph { Map relationshipCounts = commsManager.getRelationshipCountsPairwise(accounts, currentFilter); int total = relationshipCounts.size(); - int k = 0; + int progress = 0; String progressText = ""; - progress.switchToDeterminate("", 0, total); + progressIndicator.switchToDeterminate("", 0, total); for (Map.Entry entry : relationshipCounts.entrySet()) { Long count = entry.getValue(); AccountPair relationshipKey = entry.getKey(); @@ -300,11 +298,10 @@ final class CommunicationsGraph extends mxGraph { mxCell addEdge = addOrUpdateEdge(count, account1, account2); progressText = addEdge.getId(); } - progress.progress(progressText, k++); + progressIndicator.progress(progressText, progress++); } } catch (TskCoreException tskCoreException) { logger.log(Level.SEVERE, "Error", tskCoreException); - } finally { } return null; @@ -320,7 +317,7 @@ final class CommunicationsGraph extends mxGraph { } catch (CancellationException ex) { logger.log(Level.INFO, "Graph visualization cancelled"); } finally { - progress.finish(); + progressIndicator.finish(); } } } diff --git a/Core/src/org/sleuthkit/autopsy/communications/LockedVertexModel.java b/Core/src/org/sleuthkit/autopsy/communications/LockedVertexModel.java index 873866f3f5..3ff5880b63 100644 --- a/Core/src/org/sleuthkit/autopsy/communications/LockedVertexModel.java +++ b/Core/src/org/sleuthkit/autopsy/communications/LockedVertexModel.java @@ -32,14 +32,6 @@ import java.util.Set; */ final class LockedVertexModel { - void registerhandler(Object handler) { - eventBus.register(handler); - } - - void unregisterhandler(Object handler) { - eventBus.unregister(handler); - } - private final EventBus eventBus = new EventBus(); /** @@ -49,6 +41,16 @@ final class LockedVertexModel { */ private final Set lockedVertices = new HashSet<>(); + + void registerhandler(Object handler) { + eventBus.register(handler); + } + + void unregisterhandler(Object handler) { + eventBus.unregister(handler); + } + + /** * Lock the given vertices so that applying a layout algorithm doesn't move * them. The user can still manually position the vertices. diff --git a/Core/src/org/sleuthkit/autopsy/communications/MessageBrowser.java b/Core/src/org/sleuthkit/autopsy/communications/MessageBrowser.java index 7c0bf99c62..7b90357993 100644 --- a/Core/src/org/sleuthkit/autopsy/communications/MessageBrowser.java +++ b/Core/src/org/sleuthkit/autopsy/communications/MessageBrowser.java @@ -35,7 +35,6 @@ import org.sleuthkit.autopsy.corecomponents.DataResultPanel; import org.sleuthkit.autopsy.corecomponents.DataResultViewerTable; import org.sleuthkit.autopsy.corecomponents.TableFilterNode; 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 diff --git a/Core/src/org/sleuthkit/autopsy/communications/PinAccountsAction.java b/Core/src/org/sleuthkit/autopsy/communications/PinAccountsAction.java index d820373284..52136d1b1a 100644 --- a/Core/src/org/sleuthkit/autopsy/communications/PinAccountsAction.java +++ b/Core/src/org/sleuthkit/autopsy/communications/PinAccountsAction.java @@ -23,6 +23,10 @@ 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 { @@ -31,7 +35,7 @@ final class PinAccountsAction extends AbstractCVTAction { "/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() { @@ -39,7 +43,7 @@ final class PinAccountsAction extends AbstractCVTAction { } @Override - public void actionPerformed(ActionEvent e) { + public void actionPerformed(ActionEvent event) { CVTEvents.getCVTEventBus().post(new CVTEvents.PinAccountsEvent(getSelectedAccounts(), false)); } diff --git a/Core/src/org/sleuthkit/autopsy/communications/PinnedAccountModel.java b/Core/src/org/sleuthkit/autopsy/communications/PinnedAccountModel.java index beff35173d..f7fb7d1232 100644 --- a/Core/src/org/sleuthkit/autopsy/communications/PinnedAccountModel.java +++ b/Core/src/org/sleuthkit/autopsy/communications/PinnedAccountModel.java @@ -19,9 +19,13 @@ package org.sleuthkit.autopsy.communications; import com.google.common.collect.ImmutableSet; +import com.google.common.eventbus.EventBus; import java.util.HashSet; import java.util.Set; +/** + * Model of what accounts are pinned to a visualization. + */ class PinnedAccountModel { /** @@ -32,6 +36,16 @@ class PinnedAccountModel { */ private final Set pinnedAccountDevices = new HashSet<>(); + private final EventBus eventBus = new EventBus(); + + void registerhandler(Object handler) { + eventBus.register(handler); + } + + void unregisterhandler(Object handler) { + eventBus.unregister(handler); + } + boolean isAccountPinned(AccountDeviceInstanceKey account) { return pinnedAccountDevices.contains(account); } diff --git a/Core/src/org/sleuthkit/autopsy/communications/ResetAndPinAccountsAction.java b/Core/src/org/sleuthkit/autopsy/communications/ResetAndPinAccountsAction.java index a42b2882ed..385ac3348b 100644 --- a/Core/src/org/sleuthkit/autopsy/communications/ResetAndPinAccountsAction.java +++ b/Core/src/org/sleuthkit/autopsy/communications/ResetAndPinAccountsAction.java @@ -24,7 +24,8 @@ 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"}) @@ -42,7 +43,7 @@ final class ResetAndPinAccountsAction extends AbstractCVTAction { } @Override - public void actionPerformed(ActionEvent e) { + public void actionPerformed(ActionEvent event) { CVTEvents.getCVTEventBus().post(new CVTEvents.PinAccountsEvent(getSelectedAccounts(), true)); } @@ -55,5 +56,4 @@ final class ResetAndPinAccountsAction extends AbstractCVTAction { ImageIcon getIcon() { return ICON; } - } diff --git a/Core/src/org/sleuthkit/autopsy/communications/SelectionNode.java b/Core/src/org/sleuthkit/autopsy/communications/SelectionNode.java index be564c01cc..1618bb9ecc 100644 --- a/Core/src/org/sleuthkit/autopsy/communications/SelectionNode.java +++ b/Core/src/org/sleuthkit/autopsy/communications/SelectionNode.java @@ -20,7 +20,6 @@ package org.sleuthkit.autopsy.communications; import com.google.common.collect.Iterables; import com.google.common.collect.Sets; -import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Set; @@ -48,7 +47,7 @@ import org.sleuthkit.datamodel.TskCoreException; */ final class SelectionNode extends AbstractNode { - SelectionNode(Children children, Lookup lookup) { + private SelectionNode(Children children, Lookup lookup) { super(children, lookup); } @@ -130,9 +129,9 @@ final class SelectionNode extends AbstractNode { } @Override - protected Node createNodeForKey(Content t) { - if (t instanceof BlackboardArtifact) { - return new RelationshipNode((BlackboardArtifact) t); + protected Node createNodeForKey(Content content) { + if (content instanceof BlackboardArtifact) { + return new RelationshipNode((BlackboardArtifact) content); } else { throw new UnsupportedOperationException("Cannot create a RelationshipNode for non BlackboardArtifact content."); } diff --git a/Core/src/org/sleuthkit/autopsy/communications/UnpinAccountsAction.java b/Core/src/org/sleuthkit/autopsy/communications/UnpinAccountsAction.java index 357f5a0451..ba0bbc545b 100644 --- a/Core/src/org/sleuthkit/autopsy/communications/UnpinAccountsAction.java +++ b/Core/src/org/sleuthkit/autopsy/communications/UnpinAccountsAction.java @@ -23,6 +23,10 @@ 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 { diff --git a/Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.java b/Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.java index e0fe563e81..9fca11992a 100644 --- a/Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.java +++ b/Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.java @@ -873,7 +873,10 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider } } - private class CancelationListener implements ActionListener { + /** + * Listener that closes a ModalDialogProgreessIndicator when invoked. + */ + final private class CancelationListener implements ActionListener { private Future cancellable; private ModalDialogProgressIndicator progress; @@ -891,6 +894,9 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider } } + /** + * Action that un-locks the selected vertices. + */ @NbBundle.Messages({ "VisualizationPanel.unlockAction.singularText=Unlock Selected Account", "VisualizationPanel.unlockAction.pluralText=Unlock Selected Accounts",}) @@ -911,6 +917,9 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider } } + /** + * Action that locks the selected vertices. + */ @NbBundle.Messages({ "VisualizationPanel.lockAction.singularText=Lock Selected Account", "VisualizationPanel.lockAction.pluralText=Lock Selected Accounts"}) From f0e04d28f252238f88a270ca657e856151f40179 Mon Sep 17 00:00:00 2001 From: millmanorama Date: Mon, 9 Apr 2018 11:32:14 +0200 Subject: [PATCH 17/25] log errors in layout SwingWorker --- .../communications/VisualizationPanel.java | 172 +++++++++--------- 1 file changed, 91 insertions(+), 81 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.java b/Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.java index 9fca11992a..7d2c77dfce 100644 --- a/Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.java +++ b/Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.java @@ -57,6 +57,7 @@ import java.util.EnumSet; import java.util.HashSet; import java.util.List; import java.util.Set; +import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; import java.util.logging.Level; import java.util.stream.Collectors; @@ -104,7 +105,7 @@ import org.sleuthkit.datamodel.TskCoreException; * actions to work correctly. */ final public class VisualizationPanel extends JPanel implements Lookup.Provider { - + private static final long serialVersionUID = 1L; private static final Logger logger = Logger.getLogger(VisualizationPanel.class.getName()); private static final String BASE_IMAGE_PATH = "/org/sleuthkit/autopsy/communications/images"; @@ -112,21 +113,21 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider = new ImageIcon(VisualizationPanel.class.getResource(BASE_IMAGE_PATH + "/lock_large_unlocked.png")); static final private ImageIcon lockIcon = 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 final ExplorerManager vizEM = new ExplorerManager(); private final ExplorerManager gacEM = new ExplorerManager(); private final ProxyLookup proxyLookup; private Frame windowAncestor; - + private CommunicationsManager commsManager; private CommunicationsFilter currentFilter; - + private final mxGraphComponent graphComponent; private final CommunicationsGraph graph; - + private final mxUndoManager undoManager = new mxUndoManager(); private final mxRubberband rubberband; //NOPMD We keep a referenec as insurance to prevent garbage collection @@ -134,22 +135,22 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider private final mxCircleLayout circleLayout; private final mxOrganicLayout organicLayout; private final mxHierarchicalLayout hierarchicalLayout; - + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) private SwingWorker worker; private final PinnedAccountModel pinnedAccountModel = new PinnedAccountModel(); private final LockedVertexModel lockedVertexModel = new LockedVertexModel(); - + public VisualizationPanel() { initComponents(); - + graph = new CommunicationsGraph(pinnedAccountModel, lockedVertexModel); - + fastOrganicLayout = new mxFastOrganicLayoutImpl(graph); circleLayout = new mxCircleLayoutImpl(graph); organicLayout = new mxOrganicLayoutImpl(graph); hierarchicalLayout = new mxHierarchicalLayoutImpl(graph); - + graphComponent = new mxGraphComponent(graph); graphComponent.setAutoExtend(true); graphComponent.setAutoScroll(true); @@ -164,14 +165,14 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider //install rubber band other handlers rubberband = new mxRubberband(graphComponent); - + lockedVertexModel.registerhandler(this); - + final mxEventSource.mxIEventListener scaleListener = (Object sender, mxEventObject evt) -> zoomLabel.setText(DecimalFormat.getPercentInstance().format(graph.getView().getScale())); graph.getView().addListener(mxEvent.SCALE, scaleListener); graph.getView().addListener(mxEvent.SCALE_AND_TRANSLATE, scaleListener); - + graphComponent.getGraphControl().addMouseWheelListener(new MouseAdapter() { /** * Translate mouse wheel events into zooming. @@ -188,7 +189,7 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider } } }); - + graphComponent.getGraphControl().addMouseListener(new MouseAdapter() { /** * Right click handler: show context menu. @@ -203,13 +204,13 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider if (cellAt != null && cellAt.isVertex()) { final JPopupMenu jPopupMenu = new JPopupMenu(); final AccountDeviceInstanceKey adiKey = (AccountDeviceInstanceKey) cellAt.getValue(); - + Set 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 { @@ -225,13 +226,13 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider } } } - + }); - + final MessageBrowser messageBrowser = new MessageBrowser(vizEM, gacEM); - + splitPane.setRightComponent(messageBrowser); - + proxyLookup = new ProxyLookup( ExplorerUtils.createLookup(vizEM, getActionMap()), messageBrowser.getLookup() @@ -241,16 +242,16 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider graph.getSelectionModel().addListener(mxEvent.CHANGE, new SelectionListener()); final mxEventSource.mxIEventListener undoListener = (Object sender, mxEventObject evt) -> undoManager.undoableEditHappened((mxUndoableEdit) evt.getProperty("edit")); - + graph.getModel().addListener(mxEvent.UNDO, undoListener); graph.getView().addListener(mxEvent.UNDO, undoListener); } - + @Override public Lookup getLookup() { return proxyLookup; } - + @Subscribe void handle(LockedVertexModel.VertexLockEvent event) { final Set vertices = event.getVertices(); @@ -263,7 +264,7 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider graphComponent.redraw(state); }); } - + @Subscribe void handle(final CVTEvents.UnpinAccountsEvent pinEvent) { graph.getModel().beginUpdate(); @@ -273,7 +274,7 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider // Updates the display graph.getModel().endUpdate(); } - + @Subscribe void handle(final CVTEvents.PinAccountsEvent pinEvent) { graph.getModel().beginUpdate(); @@ -285,7 +286,7 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider // Updates the display graph.getModel().endUpdate(); } - + @Subscribe void handle(final CVTEvents.FilterChangeEvent filterChangeEvent) { graph.getModel().beginUpdate(); @@ -295,7 +296,7 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider // Updates the display graph.getModel().endUpdate(); } - + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) private void rebuildGraph() { if (pinnedAccountModel.isEmpty()) { @@ -308,7 +309,7 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider if (worker != null) { worker.cancel(true); } - + final CancelationListener cancelationListener = new CancelationListener(); final ModalDialogProgressIndicator progress = new ModalDialogProgressIndicator(windowAncestor, "Loading Visualization", new String[]{CANCEL}, CANCEL, cancelationListener); worker = graph.rebuild(progress, commsManager, currentFilter); @@ -319,20 +320,20 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider graph.resetGraph(); rebuildGraph(); } else { - morph(fastOrganicLayout); + animateLayout(fastOrganicLayout); } } }); - + worker.execute(); } } - + @Override public void addNotify() { super.addNotify(); windowAncestor = (Frame) SwingUtilities.getAncestorOfClass(Frame.class, this); - + try { commsManager = Case.getOpenCase().getSleuthkitCase().getCommunicationsManager(); } catch (TskCoreException ex) { @@ -340,7 +341,7 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider } catch (NoCurrentCaseException ex) { logger.log(Level.SEVERE, "Can't get CommunicationsManager when there is no case open.", ex); } - + Case.addEventTypeSubscriber(EnumSet.of(CURRENT_CASE), evt -> { graph.getModel().beginUpdate(); try { @@ -605,7 +606,7 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider }//GEN-LAST:event_zoomOutButtonActionPerformed private void circleLayoutButtonActionPerformed(ActionEvent evt) {//GEN-FIRST:event_circleLayoutButtonActionPerformed - morph(circleLayout); + animateLayout(circleLayout); }//GEN-LAST:event_circleLayoutButtonActionPerformed private void organicLayoutButtonActionPerformed(ActionEvent evt) {//GEN-FIRST:event_organicLayoutButtonActionPerformed @@ -613,11 +614,11 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider }//GEN-LAST:event_organicLayoutButtonActionPerformed private void fastOrganicLayoutButtonActionPerformed(ActionEvent evt) {//GEN-FIRST:event_fastOrganicLayoutButtonActionPerformed - morph(fastOrganicLayout); + animateLayout(fastOrganicLayout); }//GEN-LAST:event_fastOrganicLayoutButtonActionPerformed private void hierarchyLayoutButtonActionPerformed(ActionEvent evt) {//GEN-FIRST:event_hierarchyLayoutButtonActionPerformed - morph(hierarchicalLayout); + animateLayout(hierarchicalLayout); }//GEN-LAST:event_hierarchyLayoutButtonActionPerformed private void clearVizButtonActionPerformed(ActionEvent evt) {//GEN-FIRST:event_clearVizButtonActionPerformed @@ -631,44 +632,44 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider setCursor(Cursor.getDefaultCursor()); }//GEN-LAST:event_clearVizButtonActionPerformed - + private void applyOrganicLayout(int iterations) { organicLayout.setMaxIterations(iterations); - morph(organicLayout); + animateLayout(organicLayout); } - + private void fitGraph() { graphComponent.zoomTo(1, true); mxPoint translate = graph.getView().getTranslate(); if (translate == null || Double.isNaN(translate.getX()) || Double.isNaN(translate.getY())) { translate = new mxPoint(); } - + mxRectangle boundsForCells = graph.getCellBounds(graph.getDefaultParent(), true, true, true); if (boundsForCells == null || Double.isNaN(boundsForCells.getWidth()) || Double.isNaN(boundsForCells.getHeight())) { boundsForCells = new mxRectangle(0, 0, 1, 1); } final mxPoint mxPoint = new mxPoint(translate.getX() - boundsForCells.getX(), translate.getY() - boundsForCells.getY()); - + graph.cellsMoved(graph.getChildCells(graph.getDefaultParent()), mxPoint.getX(), mxPoint.getY(), false, false); - + boundsForCells = graph.getCellBounds(graph.getDefaultParent(), true, true, true); if (boundsForCells == null || Double.isNaN(boundsForCells.getWidth()) || Double.isNaN(boundsForCells.getHeight())) { boundsForCells = new mxRectangle(0, 0, 1, 1); } - + final Dimension size = graphComponent.getSize(); final double widthFactor = size.getWidth() / boundsForCells.getWidth(); - + graphComponent.zoom(widthFactor); - + } - + @NbBundle.Messages({"Visualization.computingLayout=Computing Layout"}) - private void morph(mxIGraphLayout layout) { + private void animateLayout(mxIGraphLayout layout) { // layout using morphing graph.getModel().beginUpdate(); - + CancelationListener cancelationListener = new CancelationListener(); ModalDialogProgressIndicator progress = new ModalDialogProgressIndicator(windowAncestor, Bundle.Visualization_computingLayout(), new String[]{CANCEL}, CANCEL, cancelationListener); @@ -685,10 +686,10 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider @Override public void updateAnimation() { fireEvent(new mxEventObject(mxEvent.EXECUTE)); - super.updateAnimation(); //To change body of generated methods, choose Tools | Templates. + super.updateAnimation(); } - }; + morph.addListener(mxEvent.EXECUTE, (Object sender, mxEventObject evt) -> { if (isCancelled()) { morph.stopAnimation(); @@ -703,10 +704,19 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider } progress.finish(); }); - + morph.startAnimation(); return null; - + } + + @Override + protected void done() { + super.done(); + try { + get(); + } catch (InterruptedException | ExecutionException ex) { + logger.log(Level.SEVERE, "Layout interupted", ex); + } } }; cancelationListener.configure(morphWorker, progress); @@ -740,7 +750,7 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider * changes in selection. */ final private class SelectionListener implements mxEventSource.mxIEventListener { - + @SuppressWarnings("unchecked") @Override public void invoke(Object sender, mxEventObject evt) { @@ -770,7 +780,7 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider adis.add((AccountDeviceInstanceKey) cell.getValue()); } } - + rootNode = SelectionNode.createFromAccountsAndRelationships(relationshipSources, adis, currentFilter, commsManager); selectedNodes = new Node[]{rootNode}; } @@ -782,19 +792,19 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider } } } - + final private class mxFastOrganicLayoutImpl extends mxFastOrganicLayout { - + mxFastOrganicLayoutImpl(mxGraph graph) { super(graph); } - + @Override public boolean isVertexIgnored(Object vertex) { return super.isVertexIgnored(vertex) || lockedVertexModel.isVertexLocked((mxCell) vertex); } - + @Override public mxRectangle setVertexLocation(Object vertex, double x, double y) { //NOPMD x,y variable names are standard if (isVertexIgnored(vertex)) { @@ -804,20 +814,20 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider } } } - + final private class mxCircleLayoutImpl extends mxCircleLayout { - + mxCircleLayoutImpl(mxGraph graph) { super(graph); setResetEdges(true); } - + @Override public boolean isVertexIgnored(Object vertex) { return super.isVertexIgnored(vertex) || lockedVertexModel.isVertexLocked((mxCell) vertex); } - + @Override public mxRectangle setVertexLocation(Object vertex, double x, double y) { //NOPMD x,y variable names are standard if (isVertexIgnored(vertex)) { @@ -827,20 +837,20 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider } } } - + final private class mxOrganicLayoutImpl extends mxOrganicLayout { - + mxOrganicLayoutImpl(mxGraph graph) { super(graph); setResetEdges(true); } - + @Override public boolean isVertexIgnored(Object vertex) { return super.isVertexIgnored(vertex) || lockedVertexModel.isVertexLocked((mxCell) vertex); } - + @Override public mxRectangle setVertexLocation(Object vertex, double x, double y) { //NOPMD x,y variable names are standard if (isVertexIgnored(vertex)) { @@ -850,19 +860,19 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider } } } - + final private class mxHierarchicalLayoutImpl extends mxHierarchicalLayout { - + mxHierarchicalLayoutImpl(mxGraph graph) { super(graph); } - + @Override public boolean isVertexIgnored(Object vertex) { return super.isVertexIgnored(vertex) || lockedVertexModel.isVertexLocked((mxCell) vertex); } - + @Override public mxRectangle setVertexLocation(Object vertex, double x, double y) { //NOPMD x,y variable names are standard if (isVertexIgnored(vertex)) { @@ -877,15 +887,15 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider * Listener that closes a ModalDialogProgreessIndicator when invoked. */ final private class CancelationListener implements ActionListener { - + private Future cancellable; private ModalDialogProgressIndicator progress; - + void configure(Future cancellable, ModalDialogProgressIndicator progress) { this.cancellable = cancellable; this.progress = progress; } - + @Override public void actionPerformed(ActionEvent event) { progress.setCancelling("Cancelling..."); @@ -901,17 +911,17 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider "VisualizationPanel.unlockAction.singularText=Unlock Selected Account", "VisualizationPanel.unlockAction.pluralText=Unlock Selected Accounts",}) private final class UnlockAction extends AbstractAction { - + private final Set selectedVertices; - + UnlockAction(Set 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); } @@ -924,15 +934,15 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider "VisualizationPanel.lockAction.singularText=Lock Selected Account", "VisualizationPanel.lockAction.pluralText=Lock Selected Accounts"}) private final class LockAction extends AbstractAction { - + private final Set selectedVertices; - + LockAction(Set 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); From 662babede1bd431757f7979108d44cd4c395145b Mon Sep 17 00:00:00 2001 From: millmanorama Date: Mon, 9 Apr 2018 13:52:10 +0200 Subject: [PATCH 18/25] Revert "refactor to use LockedVertexLayoutWrapper" This reverts commit 08cc853fa8e8e447268ecde3c18f4f980508c11f. # Conflicts: # Core/src/org/sleuthkit/autopsy/communications/LockedVertexModel.java # Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.java --- .../communications/LockedVertexModel.java | 118 -------------- .../communications/VisualizationPanel.form | 6 +- .../communications/VisualizationPanel.java | 144 ++++++++++++++---- 3 files changed, 116 insertions(+), 152 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/communications/LockedVertexModel.java b/Core/src/org/sleuthkit/autopsy/communications/LockedVertexModel.java index 4a2b82dc43..251f6382f7 100644 --- a/Core/src/org/sleuthkit/autopsy/communications/LockedVertexModel.java +++ b/Core/src/org/sleuthkit/autopsy/communications/LockedVertexModel.java @@ -20,14 +20,12 @@ package org.sleuthkit.autopsy.communications; import com.google.common.collect.ImmutableSet; import com.google.common.eventbus.EventBus; -import com.mxgraph.layout.mxGraphLayout; import com.mxgraph.model.mxCell; import com.mxgraph.util.mxPoint; import com.mxgraph.util.mxRectangle; import com.mxgraph.view.mxGraph; import java.util.Collection; import java.util.HashSet; -import java.util.List; import java.util.Set; /** @@ -114,120 +112,4 @@ final class LockedVertexModel { this.locked = locked; } } - - mxGraphLayout createLockedVertexWrapper(mxGraphLayout layout) { - return new LockedVertexLayoutWrapper(layout, this); - } - - /** An mxGraphLayout that wrapps an other layout and ignores locked vertes. - * - * @param - */ - private static final class LockedVertexLayoutWrapper extends mxGraphLayout { - - private final mxGraphLayout wrappedLayout; - private final LockedVertexModel lockedVertexModel; - - /** - * - * - * @param layout The layout to wrap - * @param lockedVertexModel the value of lockedVertexModel2 - */ - private LockedVertexLayoutWrapper(mxGraphLayout layout, LockedVertexModel lockedVertexModel) { - super(layout.getGraph()); - this.lockedVertexModel = lockedVertexModel; - wrappedLayout = layout; - } - - @Override - public boolean isVertexIgnored(Object vertex) { - return wrappedLayout.isVertexIgnored(vertex) - || lockedVertexModel.isVertexLocked((mxCell) vertex); - } - - @Override - public mxRectangle setVertexLocation(Object vertex, double xCoord, double yCoord) { - if (isVertexIgnored(vertex)) { - return getVertexBounds(vertex); - } else { - return wrappedLayout.setVertexLocation(vertex, xCoord, yCoord); - } - } - - @Override - public void execute(Object parent) { - wrappedLayout.execute(parent); - } - - @Override - public void moveCell(Object cell, double xCoord, double yCoord) { - wrappedLayout.moveCell(cell, xCoord, yCoord); - } - - @Override - public mxGraph getGraph() { - return wrappedLayout.getGraph(); - } - - @Override - public Object getConstraint(Object key, Object cell) { - return wrappedLayout.getConstraint(key, cell); - } - - @Override - public Object getConstraint(Object key, Object cell, Object edge, boolean source) { - return wrappedLayout.getConstraint(key, cell, edge, source); - } - - @Override - public boolean isUseBoundingBox() { - return wrappedLayout.isUseBoundingBox(); - } - - @Override - public void setUseBoundingBox(boolean useBoundingBox) { - wrappedLayout.setUseBoundingBox(useBoundingBox); - } - - @Override - public boolean isVertexMovable(Object vertex) { - return wrappedLayout.isVertexMovable(vertex); - } - - @Override - public boolean isEdgeIgnored(Object edge) { - return wrappedLayout.isEdgeIgnored(edge); - } - - @Override - public void setEdgeStyleEnabled(Object edge, boolean value) { - wrappedLayout.setEdgeStyleEnabled(edge, value); - } - - @Override - public void setOrthogonalEdge(Object edge, boolean value) { - wrappedLayout.setOrthogonalEdge(edge, value); - } - - @Override - public mxPoint getParentOffset(Object parent) { - return wrappedLayout.getParentOffset(parent); - } - - @Override - public void setEdgePoints(Object edge, List points) { - wrappedLayout.setEdgePoints(edge, points); - } - - @Override - public mxRectangle getVertexBounds(Object vertex) { - return wrappedLayout.getVertexBounds(vertex); - } - - @Override - public void arrangeGroups(Object[] groups, int border) { - wrappedLayout.arrangeGroups(groups, border); - } - } } diff --git a/Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.form b/Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.form index deae56fd17..a847f700c7 100644 --- a/Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.form +++ b/Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.form @@ -11,7 +11,7 @@ - + @@ -106,7 +106,7 @@ - + @@ -120,7 +120,7 @@ - + diff --git a/Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.java b/Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.java index adcdecf7ba..25eea8cc98 100644 --- a/Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.java +++ b/Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.java @@ -23,7 +23,6 @@ import com.mxgraph.layout.hierarchical.mxHierarchicalLayout; import com.mxgraph.layout.mxCircleLayout; import com.mxgraph.layout.mxFastOrganicLayout; import com.mxgraph.layout.mxGraphLayout; -import com.mxgraph.layout.mxIGraphLayout; import com.mxgraph.layout.mxOrganicLayout; import com.mxgraph.model.mxCell; import com.mxgraph.model.mxICell; @@ -38,6 +37,7 @@ import com.mxgraph.util.mxRectangle; import com.mxgraph.util.mxUndoManager; import com.mxgraph.util.mxUndoableEdit; import com.mxgraph.view.mxCellState; +import com.mxgraph.view.mxGraph; import com.mxgraph.view.mxGraphView; import java.awt.BorderLayout; import java.awt.Color; @@ -133,11 +133,10 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider private final mxUndoManager undoManager = new mxUndoManager(); private final mxRubberband rubberband; //NOPMD We keep a referenec as insurance to prevent garbage collection - - private final mxGraphLayout fastOrganicLayout; - private final mxGraphLayout circleLayout; - private final mxGraphLayout organicLayout; - private final mxGraphLayout hierarchyLayout; + private final mxFastOrganicLayout fastOrganicLayout; + private final mxCircleLayout circleLayout; + private final mxOrganicLayout organicLayout; + private final mxHierarchicalLayout hierarchyLayout; @ThreadConfined(type = ThreadConfined.ThreadType.AWT) private SwingWorker worker; @@ -193,19 +192,12 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider graph.getModel().addListener(mxEvent.UNDO, undoListener); graph.getView().addListener(mxEvent.UNDO, undoListener); - fastOrganicLayout = lockedVertexModel.createLockedVertexWrapper(new mxFastOrganicLayout(graph)); - hierarchyLayout = lockedVertexModel.createLockedVertexWrapper(new mxHierarchicalLayout(graph)); - circleLayout = lockedVertexModel.createLockedVertexWrapper(new mxCircleLayout(graph) { - { - setResetEdges(true); - } - }); - organicLayout = lockedVertexModel.createLockedVertexWrapper(new mxOrganicLayout(graph) { - { - setResetEdges(true); - setMaxIterations(10); - } - }); + fastOrganicLayout = new mxFastOrganicLayoutImpl(graph); + circleLayout = new mxCircleLayoutImpl(graph); + organicLayout = new mxOrganicLayoutImpl(graph); + organicLayout.setMaxIterations(10); + hierarchyLayout = new mxHierarchicalLayoutImpl(graph); + //local method to configure layout buttons BiConsumer configure = (layoutButton, layout) -> { layoutButtons.put(layout, layoutButton); @@ -502,7 +494,7 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider .add(hierarchyLayoutButton) .addPreferredGap(LayoutStyle.RELATED) .add(circleLayoutButton) - .addPreferredGap(LayoutStyle.UNRELATED) + .addPreferredGap(LayoutStyle.RELATED) .add(jSeparator2, GroupLayout.PREFERRED_SIZE, 10, GroupLayout.PREFERRED_SIZE) .addPreferredGap(LayoutStyle.RELATED) .add(jLabel2) @@ -516,7 +508,7 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider .add(zoomActualButton, GroupLayout.PREFERRED_SIZE, 33, GroupLayout.PREFERRED_SIZE) .addPreferredGap(LayoutStyle.RELATED) .add(fitZoomButton, GroupLayout.PREFERRED_SIZE, 32, GroupLayout.PREFERRED_SIZE) - .addContainerGap(GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + .addContainerGap(12, Short.MAX_VALUE)) ); toolbarLayout.setVerticalGroup(toolbarLayout.createParallelGroup(GroupLayout.LEADING) .add(toolbarLayout.createSequentialGroup() @@ -577,18 +569,18 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider graph.getModel().beginUpdate(); CancelationListener cancelationListener = new CancelationListener(); - ModalDialogProgressIndicator progress = new ModalDialogProgressIndicator(windowAncestor, + ModalDialogProgressIndicator progressIndicator = new ModalDialogProgressIndicator(windowAncestor, Bundle.Visualization_computingLayout(), new String[]{CANCEL}, CANCEL, cancelationListener); SwingWorker morphWorker = new SwingWorker() { - int i = 0; + int progress = 0; int max = 100; @Override protected Void doInBackground() { - progress.start(Bundle.Visualization_computingLayout()); + progressIndicator.start(Bundle.Visualization_computingLayout()); layout.execute(graph.getDefaultParent()); if (isCancelled()) { - progress.finish(); + progressIndicator.finish(); return null; } @@ -600,10 +592,10 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider if (isCancelled()) { stopAnimation(); } else { - i++; - progress.switchToDeterminate(Bundle.Visualization_computingLayout(), i, max); - if ( i >= max *3.0/4.0){ - max *=2; + progress++; + progressIndicator.switchToDeterminate(Bundle.Visualization_computingLayout(), progress, max); + if (progress >= max * 3.0 / 4.0) { + max *= 2; } } } @@ -616,7 +608,7 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider } else { fitGraph(); } - progress.finish(); + progressIndicator.finish(); }); morph.startAnimation(); @@ -633,7 +625,7 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider } } }; - cancelationListener.configure(morphWorker, progress); + cancelationListener.configure(morphWorker, progressIndicator); morphWorker.execute(); } @@ -746,6 +738,96 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider } } + final private class mxFastOrganicLayoutImpl extends mxFastOrganicLayout { + + mxFastOrganicLayoutImpl(mxGraph graph) { + super(graph); + } + + @Override + public boolean isVertexIgnored(Object vertex) { + return super.isVertexIgnored(vertex) + || lockedVertexModel.isVertexLocked((mxCell) vertex); + } + + @Override + public mxRectangle setVertexLocation(Object vertex, double x, double y) { //NOPMD x, y are standard coordinate names + if (isVertexIgnored(vertex)) { + return getVertexBounds(vertex); + } else { + return super.setVertexLocation(vertex, x, y); + } + } + } + + final private class mxCircleLayoutImpl extends mxCircleLayout { + + mxCircleLayoutImpl(mxGraph graph) { + super(graph); + setResetEdges(true); + } + + @Override + public boolean isVertexIgnored(Object vertex) { + return super.isVertexIgnored(vertex) + || lockedVertexModel.isVertexLocked((mxCell) vertex); + } + + @Override + public mxRectangle setVertexLocation(Object vertex, double x, double y) { //NOPMD x, y are standard coordinate names + if (isVertexIgnored(vertex)) { + return getVertexBounds(vertex); + } else { + return super.setVertexLocation(vertex, x, y); + } + } + } + + final private class mxOrganicLayoutImpl extends mxOrganicLayout { + + mxOrganicLayoutImpl(mxGraph graph) { + super(graph); + setResetEdges(true); + } + + @Override + public boolean isVertexIgnored(Object vertex) { + return super.isVertexIgnored(vertex) + || lockedVertexModel.isVertexLocked((mxCell) vertex); + } + + @Override + public mxRectangle setVertexLocation(Object vertex, double x, double y) { //NOPMD x, y are standard coordinate names + if (isVertexIgnored(vertex)) { + return getVertexBounds(vertex); + } else { + return super.setVertexLocation(vertex, x, y); + } + } + } + + final private class mxHierarchicalLayoutImpl extends mxHierarchicalLayout { + + mxHierarchicalLayoutImpl(mxGraph graph) { + super(graph); + } + + @Override + public boolean isVertexIgnored(Object vertex) { + return super.isVertexIgnored(vertex) + || lockedVertexModel.isVertexLocked((mxCell) vertex); + } + + @Override + public mxRectangle setVertexLocation(Object vertex, double x, double y) { //NOPMD x, y are standard coordinate names + if (isVertexIgnored(vertex)) { + return getVertexBounds(vertex); + } else { + return super.setVertexLocation(vertex, x, y); + } + } + } + /** * Listener that closses the given ModalDialogProgressIndicator and cancels * the future. From f3f650c2f364c8abb45faf28e93f83f15c4d4f65 Mon Sep 17 00:00:00 2001 From: millmanorama Date: Mon, 9 Apr 2018 13:54:40 +0200 Subject: [PATCH 19/25] fix codacy issues --- .../autopsy/communications/VisualizationPanel.java | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.java b/Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.java index 25eea8cc98..c76427aa62 100644 --- a/Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.java +++ b/Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.java @@ -738,6 +738,9 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider } } + /** + * Extension of mxFastOrganicLayout that ignores locked vertices. + */ final private class mxFastOrganicLayoutImpl extends mxFastOrganicLayout { mxFastOrganicLayoutImpl(mxGraph graph) { @@ -760,6 +763,9 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider } } + /** + * Extension of mxCircleLayout that ignores locked vertices. + */ final private class mxCircleLayoutImpl extends mxCircleLayout { mxCircleLayoutImpl(mxGraph graph) { @@ -783,6 +789,9 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider } } + /** + * Extension of mxOrganicLayout that ignores locked vertices. + */ final private class mxOrganicLayoutImpl extends mxOrganicLayout { mxOrganicLayoutImpl(mxGraph graph) { @@ -806,6 +815,9 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider } } + /** + * Extension of mxHierarchicalLayout that ignores locked vertices. + */ final private class mxHierarchicalLayoutImpl extends mxHierarchicalLayout { mxHierarchicalLayoutImpl(mxGraph graph) { From 206e599037ba390ad28c9ffc76685a380aaa2909 Mon Sep 17 00:00:00 2001 From: millmanorama Date: Mon, 9 Apr 2018 16:43:26 +0200 Subject: [PATCH 20/25] remove animation of layout and handle errors. --- .../autopsy/communications/Bundle.properties | 5 +- .../communications/CommunicationsGraph.java | 2 +- .../communications/LockedVertexModel.java | 4 + .../communications/VisualizationPanel.java | 143 +++++++++--------- 4 files changed, 77 insertions(+), 77 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/communications/Bundle.properties b/Core/src/org/sleuthkit/autopsy/communications/Bundle.properties index ea3049f895..358971eaa8 100644 --- a/Core/src/org/sleuthkit/autopsy/communications/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/communications/Bundle.properties @@ -20,9 +20,6 @@ CVTTopComponent.vizPanel.TabConstraints.tabTitle=Visualize CVTTopComponent.accountsBrowser.TabConstraints.tabTitle_1=Browse CVTTopComponent.browseVisualizeTabPane.AccessibleContext.accessibleName=Visualize CVTTopComponent.vizPanel.TabConstraints.tabTitle_1=Visualize -VisualizationPanel.jButton6.text=Hierarchy -VisualizationPanel.jButton7.text=Circle -VisualizationPanel.jButton8.text=Organic VisualizationPanel.fitGraphButton.text= VisualizationPanel.jTextArea1.text=Right-click an account in the Browse Accounts table, and select 'Visualize' to begin. VisualizationPanel.jLabel1.text=Layouts: @@ -39,5 +36,5 @@ VisualizationPanel.zoomOutButton.text= VisualizationPanel.circleLayoutButton.text=Circle VisualizationPanel.organicLayoutButton.text=Organic VisualizationPanel.fastOrganicLayoutButton.text=Fast Organic -VisualizationPanel.hierarchyLayoutButton.text=Hierarchy +VisualizationPanel.hierarchyLayoutButton.text=Hierarchical VisualizationPanel.clearVizButton.text_1=Clear Viz. diff --git a/Core/src/org/sleuthkit/autopsy/communications/CommunicationsGraph.java b/Core/src/org/sleuthkit/autopsy/communications/CommunicationsGraph.java index 2dadb888b9..716d00656c 100644 --- a/Core/src/org/sleuthkit/autopsy/communications/CommunicationsGraph.java +++ b/Core/src/org/sleuthkit/autopsy/communications/CommunicationsGraph.java @@ -264,7 +264,7 @@ final class CommunicationsGraph extends mxGraph { for (final AccountDeviceInstanceKey adiKey : pinnedAccountModel.getPinnedAccounts()) { if (isCancelled()) { break; - } + } //get accounts related to pinned account final List relatedAccountDeviceInstances = commsManager.getRelatedAccountDeviceInstances(adiKey.getAccountDeviceInstance(), currentFilter); diff --git a/Core/src/org/sleuthkit/autopsy/communications/LockedVertexModel.java b/Core/src/org/sleuthkit/autopsy/communications/LockedVertexModel.java index 251f6382f7..d8c2a28091 100644 --- a/Core/src/org/sleuthkit/autopsy/communications/LockedVertexModel.java +++ b/Core/src/org/sleuthkit/autopsy/communications/LockedVertexModel.java @@ -83,6 +83,10 @@ final class LockedVertexModel { lockedVertices.clear(); } + boolean isEmpty() { + return lockedVertices.isEmpty(); + } + /** * Event that represents a change in the locked state of one or more * vertices. diff --git a/Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.java b/Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.java index c76427aa62..325fbd51b9 100644 --- a/Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.java +++ b/Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.java @@ -22,13 +22,12 @@ import com.google.common.eventbus.Subscribe; import com.mxgraph.layout.hierarchical.mxHierarchicalLayout; import com.mxgraph.layout.mxCircleLayout; import com.mxgraph.layout.mxFastOrganicLayout; -import com.mxgraph.layout.mxGraphLayout; +import com.mxgraph.layout.mxIGraphLayout; import com.mxgraph.layout.mxOrganicLayout; import com.mxgraph.model.mxCell; import com.mxgraph.model.mxICell; import com.mxgraph.swing.handler.mxRubberband; import com.mxgraph.swing.mxGraphComponent; -import com.mxgraph.swing.util.mxMorphing; import com.mxgraph.util.mxEvent; import com.mxgraph.util.mxEventObject; import com.mxgraph.util.mxEventSource; @@ -90,6 +89,7 @@ import org.openide.util.lookup.ProxyLookup; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; import org.sleuthkit.autopsy.coreutils.ThreadConfined; import org.sleuthkit.autopsy.progress.ModalDialogProgressIndicator; import org.sleuthkit.datamodel.CommunicationsFilter; @@ -133,18 +133,14 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider private final mxUndoManager undoManager = new mxUndoManager(); 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 hierarchyLayout; @ThreadConfined(type = ThreadConfined.ThreadType.AWT) private SwingWorker worker; private final PinnedAccountModel pinnedAccountModel = new PinnedAccountModel(); private final LockedVertexModel lockedVertexModel = new LockedVertexModel(); - private final Map layoutButtons = new HashMap<>(); - private mxGraphLayout currentLayout; + private final Map layoutButtons = new HashMap<>(); + private NamedGraphLayout currentLayout; public VisualizationPanel() { initComponents(); @@ -192,14 +188,14 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider graph.getModel().addListener(mxEvent.UNDO, undoListener); graph.getView().addListener(mxEvent.UNDO, undoListener); - fastOrganicLayout = new mxFastOrganicLayoutImpl(graph); - circleLayout = new mxCircleLayoutImpl(graph); - organicLayout = new mxOrganicLayoutImpl(graph); + FastOrganicLayoutImpl fastOrganicLayout = new FastOrganicLayoutImpl(graph); + CircleLayoutImpl circleLayout = new CircleLayoutImpl(graph); + OrganicLayoutImpl organicLayout = new OrganicLayoutImpl(graph); organicLayout.setMaxIterations(10); - hierarchyLayout = new mxHierarchicalLayoutImpl(graph); + HierarchicalLayoutImpl hierarchyLayout = new HierarchicalLayoutImpl(graph); //local method to configure layout buttons - BiConsumer configure = (layoutButton, layout) -> { + BiConsumer configure = (layoutButton, layout) -> { layoutButtons.put(layout, layoutButton); layoutButton.addActionListener(event -> applyLayout(layout)); }; @@ -556,77 +552,52 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider /** * Apply the given layout. The given layout becomes the current layout. The - * layout is computed in the background and applied via mxMorphing. + * layout is computed in the background. * * @param layout The layout to apply. */ - @NbBundle.Messages({"Visualization.computingLayout=Computing Layout"}) - private void applyLayout(mxGraphLayout layout) { + @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))); - // layout using morphing - graph.getModel().beginUpdate(); - CancelationListener cancelationListener = new CancelationListener(); - ModalDialogProgressIndicator progressIndicator = new ModalDialogProgressIndicator(windowAncestor, - Bundle.Visualization_computingLayout(), new String[]{CANCEL}, CANCEL, cancelationListener); - SwingWorker morphWorker = new SwingWorker() { - int progress = 0; - int max = 100; + ModalDialogProgressIndicator progressIndicator = new ModalDialogProgressIndicator(windowAncestor, Bundle.VisualizationPanel_computingLayout()); + progressIndicator.start(Bundle.VisualizationPanel_computingLayout()); + new SwingWorker() { @Override protected Void doInBackground() { - progressIndicator.start(Bundle.Visualization_computingLayout()); - layout.execute(graph.getDefaultParent()); - if (isCancelled()) { - progressIndicator.finish(); - return null; - } - - mxMorphing morph = new mxMorphing(graphComponent, 20, 1.2, 20) { - @Override - public void updateAnimation() { - fireEvent(new mxEventObject(mxEvent.EXECUTE)); - super.updateAnimation(); - if (isCancelled()) { - stopAnimation(); - } else { - progress++; - progressIndicator.switchToDeterminate(Bundle.Visualization_computingLayout(), progress, max); - if (progress >= max * 3.0 / 4.0) { - max *= 2; - } - } - } - }; - - morph.addListener(mxEvent.DONE, (Object sender, mxEventObject event) -> { + graph.getModel().beginUpdate(); + try { + layout.execute(graph.getDefaultParent()); + } finally { graph.getModel().endUpdate(); - if (isCancelled()) { - undoManager.undo(); - } else { - fitGraph(); - } progressIndicator.finish(); - }); - - morph.startAnimation(); + } return null; } @Override protected void done() { - super.done(); try { get(); + fitGraph(); } catch (InterruptedException | ExecutionException ex) { - logger.log(Level.SEVERE, "Layout interupted", 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(); } } - }; - cancelationListener.configure(morphWorker, progressIndicator); - morphWorker.execute(); + }.execute(); } private void clearVizButtonActionPerformed(ActionEvent evt) {//GEN-FIRST:event_clearVizButtonActionPerformed @@ -662,9 +633,9 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider final Dimension size = graphComponent.getSize(); final double widthFactor = size.getWidth() / boundsForCells.getWidth(); + final double heightFactor = size.getHeight() / boundsForCells.getHeight(); - graphComponent.zoom(widthFactor); - + graphComponent.zoom((heightFactor + widthFactor) / 2.0); } @@ -738,12 +709,20 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider } } + /** + * Extend mxIGraphLayout with a getDisplayName method, + */ + private interface NamedGraphLayout extends mxIGraphLayout { + + String getDisplayName(); + } + /** * Extension of mxFastOrganicLayout that ignores locked vertices. */ - final private class mxFastOrganicLayoutImpl extends mxFastOrganicLayout { + final private class FastOrganicLayoutImpl extends mxFastOrganicLayout implements NamedGraphLayout { - mxFastOrganicLayoutImpl(mxGraph graph) { + FastOrganicLayoutImpl(mxGraph graph) { super(graph); } @@ -761,14 +740,19 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider return super.setVertexLocation(vertex, x, y); } } + + @Override + public String getDisplayName() { + return "Fast Organic"; + } } /** * Extension of mxCircleLayout that ignores locked vertices. */ - final private class mxCircleLayoutImpl extends mxCircleLayout { + final private class CircleLayoutImpl extends mxCircleLayout implements NamedGraphLayout { - mxCircleLayoutImpl(mxGraph graph) { + CircleLayoutImpl(mxGraph graph) { super(graph); setResetEdges(true); } @@ -787,14 +771,19 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider return super.setVertexLocation(vertex, x, y); } } + + @Override + public String getDisplayName() { + return "Circle"; + } } /** * Extension of mxOrganicLayout that ignores locked vertices. */ - final private class mxOrganicLayoutImpl extends mxOrganicLayout { + final private class OrganicLayoutImpl extends mxOrganicLayout implements NamedGraphLayout { - mxOrganicLayoutImpl(mxGraph graph) { + OrganicLayoutImpl(mxGraph graph) { super(graph); setResetEdges(true); } @@ -813,14 +802,19 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider return super.setVertexLocation(vertex, x, y); } } + + @Override + public String getDisplayName() { + return "Organic"; + } } /** * Extension of mxHierarchicalLayout that ignores locked vertices. */ - final private class mxHierarchicalLayoutImpl extends mxHierarchicalLayout { + final private class HierarchicalLayoutImpl extends mxHierarchicalLayout implements NamedGraphLayout { - mxHierarchicalLayoutImpl(mxGraph graph) { + HierarchicalLayoutImpl(mxGraph graph) { super(graph); } @@ -838,6 +832,11 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider return super.setVertexLocation(vertex, x, y); } } + + @Override + public String getDisplayName() { + return "Hierarchical"; + } } /** From 4379f81e176d3098b59411035d38405fdc052013 Mon Sep 17 00:00:00 2001 From: millmanorama Date: Mon, 9 Apr 2018 16:51:59 +0200 Subject: [PATCH 21/25] call fitGraph() within transaction to avoid 'blip' --- .../sleuthkit/autopsy/communications/LockedVertexModel.java | 3 --- .../sleuthkit/autopsy/communications/VisualizationPanel.java | 2 +- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/communications/LockedVertexModel.java b/Core/src/org/sleuthkit/autopsy/communications/LockedVertexModel.java index d8c2a28091..7d8b8c083d 100644 --- a/Core/src/org/sleuthkit/autopsy/communications/LockedVertexModel.java +++ b/Core/src/org/sleuthkit/autopsy/communications/LockedVertexModel.java @@ -21,9 +21,6 @@ package org.sleuthkit.autopsy.communications; import com.google.common.collect.ImmutableSet; import com.google.common.eventbus.EventBus; import com.mxgraph.model.mxCell; -import com.mxgraph.util.mxPoint; -import com.mxgraph.util.mxRectangle; -import com.mxgraph.view.mxGraph; import java.util.Collection; import java.util.HashSet; import java.util.Set; diff --git a/Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.java b/Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.java index 325fbd51b9..4f6b53ae39 100644 --- a/Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.java +++ b/Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.java @@ -575,6 +575,7 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider graph.getModel().beginUpdate(); try { layout.execute(graph.getDefaultParent()); + fitGraph(); } finally { graph.getModel().endUpdate(); progressIndicator.finish(); @@ -586,7 +587,6 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider protected void done() { try { get(); - fitGraph(); } catch (InterruptedException | ExecutionException ex) { logger.log(Level.WARNING, "CVT graph layout failed.", ex); if (lockedVertexModel.isEmpty()) { From 1a437d8686376ce710cca77d93876a0cec64a7d0 Mon Sep 17 00:00:00 2001 From: Ann Priestman Date: Mon, 9 Apr 2018 13:50:49 -0400 Subject: [PATCH 22/25] Cleanup addressing PR review comments. --- .../HealthMonitorCaseEventListener.java | 8 +- .../healthmonitor/ServicesHealthMonitor.java | 74 +++++++++---------- 2 files changed, 41 insertions(+), 41 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/healthmonitor/HealthMonitorCaseEventListener.java b/Core/src/org/sleuthkit/autopsy/healthmonitor/HealthMonitorCaseEventListener.java index 5c8a6f675d..f6c5583692 100644 --- a/Core/src/org/sleuthkit/autopsy/healthmonitor/HealthMonitorCaseEventListener.java +++ b/Core/src/org/sleuthkit/autopsy/healthmonitor/HealthMonitorCaseEventListener.java @@ -30,11 +30,11 @@ import org.sleuthkit.autopsy.casemodule.Case; */ final class HealthMonitorCaseEventListener implements PropertyChangeListener { - private final ExecutorService jobProcessingExecutor; - private static final String CASE_EVENT_THREAD_NAME = "Health-Monitor-Event-Listener-%d"; + private final ExecutorService healthMonitorExecutor; + private static final String HEALTH_MONITOR_EVENT_THREAD_NAME = "Health-Monitor-Event-Listener-%d"; HealthMonitorCaseEventListener() { - jobProcessingExecutor = Executors.newSingleThreadExecutor(new ThreadFactoryBuilder().setNameFormat(CASE_EVENT_THREAD_NAME).build()); + healthMonitorExecutor = Executors.newSingleThreadExecutor(new ThreadFactoryBuilder().setNameFormat(HEALTH_MONITOR_EVENT_THREAD_NAME).build()); } @Override @@ -45,7 +45,7 @@ final class HealthMonitorCaseEventListener implements PropertyChangeListener { case CURRENT_CASE: if ((null == evt.getNewValue()) && (evt.getOldValue() instanceof Case)) { // When a case is closed, write the current metrics to the database - jobProcessingExecutor.submit(new ServicesHealthMonitor.DatabaseWriteTask()); + healthMonitorExecutor.submit(new ServicesHealthMonitor.DatabaseWriteTask()); } break; } diff --git a/Core/src/org/sleuthkit/autopsy/healthmonitor/ServicesHealthMonitor.java b/Core/src/org/sleuthkit/autopsy/healthmonitor/ServicesHealthMonitor.java index d628f2e8b9..c73fa55106 100644 --- a/Core/src/org/sleuthkit/autopsy/healthmonitor/ServicesHealthMonitor.java +++ b/Core/src/org/sleuthkit/autopsy/healthmonitor/ServicesHealthMonitor.java @@ -60,10 +60,11 @@ public class ServicesHealthMonitor { private static final AtomicBoolean isEnabled = new AtomicBoolean(false); private static ServicesHealthMonitor instance; - private ScheduledThreadPoolExecutor periodicTasksExecutor; + private ScheduledThreadPoolExecutor healthMonitorOutputTimer; private final Map timingInfoMap; private static final int CONN_POOL_SIZE = 10; private BasicDataSource connectionPool = null; + private String hostName; private ServicesHealthMonitor() throws HealthMonitorException { @@ -71,6 +72,15 @@ public class ServicesHealthMonitor { // of whether the monitor is enabled. timingInfoMap = new HashMap<>(); + // Get the host name + try { + hostName = java.net.InetAddress.getLocalHost().getHostName(); + } catch (java.net.UnknownHostException ex) { + // Continue on but log a warning + logger.log(Level.WARNING, "Unable to look up host name"); + hostName = "unknown"; + } + // 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")){ @@ -172,20 +182,20 @@ public class ServicesHealthMonitor { * Start the ScheduledThreadPoolExecutor that will handle the database writes. */ private synchronized void startTimer() { - if(periodicTasksExecutor != null) { + if(healthMonitorOutputTimer != null) { // Make sure the previous executor (if it exists) has been stopped - periodicTasksExecutor.shutdown(); + healthMonitorOutputTimer.shutdown(); } - periodicTasksExecutor = new ScheduledThreadPoolExecutor(1, new ThreadFactoryBuilder().setNameFormat("health_monitor_timer").build()); - periodicTasksExecutor.scheduleWithFixedDelay(new DatabaseWriteTask(), DATABASE_WRITE_INTERVAL, DATABASE_WRITE_INTERVAL, TimeUnit.MINUTES); + 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(periodicTasksExecutor != null) { - periodicTasksExecutor.shutdown(); + if(healthMonitorOutputTimer != null) { + healthMonitorOutputTimer.shutdown(); } } @@ -301,15 +311,6 @@ public class ServicesHealthMonitor { timingInfoMap.clear(); } logger.log(Level.INFO, "Writing health monitor metrics to database"); - - String hostName; - try { - hostName = java.net.InetAddress.getLocalHost().getHostName(); - } catch (java.net.UnknownHostException ex) { - // Write it to the database but log a warning - logger.log(Level.WARNING, "Unable to look up host name"); - hostName = "unknown"; - } // Check if there's anything to report (right now we only have the timing map) if(timingMapCopy.keySet().isEmpty()) { @@ -587,26 +588,26 @@ public class ServicesHealthMonitor { try (Statement statement = conn.createStatement()) { conn.setAutoCommit(false); - StringBuilder createTimingTable = new StringBuilder(); - createTimingTable.append("CREATE TABLE IF NOT EXISTS timing_data ("); - createTimingTable.append("id SERIAL PRIMARY KEY,"); - createTimingTable.append("name text NOT NULL,"); - createTimingTable.append("host text NOT NULL,"); - createTimingTable.append("timestamp bigint NOT NULL,"); - createTimingTable.append("count bigint NOT NULL,"); - createTimingTable.append("average bigint NOT NULL,"); - createTimingTable.append("max bigint NOT NULL,"); - createTimingTable.append("min bigint NOT NULL"); - createTimingTable.append(")"); - statement.execute(createTimingTable.toString()); + 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); - StringBuilder createDbInfoTable = new StringBuilder(); - createDbInfoTable.append("CREATE TABLE IF NOT EXISTS db_info ("); - createDbInfoTable.append("id SERIAL PRIMARY KEY NOT NULL,"); - createDbInfoTable.append("name text NOT NULL,"); - createDbInfoTable.append("value text NOT NULL"); - createDbInfoTable.append(")"); - statement.execute(createDbInfoTable.toString()); + 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() + "')"); @@ -655,8 +656,7 @@ public class ServicesHealthMonitor { */ private CoordinationService.Lock getExclusiveDbLock() throws HealthMonitorException{ try { - String databaseNodeName = DATABASE_NAME; - CoordinationService.Lock lock = CoordinationService.getInstance().tryGetExclusiveLock(CoordinationService.CategoryNode.HEALTH_MONITOR, databaseNodeName, 5, TimeUnit.MINUTES); + CoordinationService.Lock lock = CoordinationService.getInstance().tryGetExclusiveLock(CoordinationService.CategoryNode.HEALTH_MONITOR, DATABASE_NAME, 5, TimeUnit.MINUTES); if(lock != null){ return lock; From 30a364c4175156c0436173f1c58964f2f16f574e Mon Sep 17 00:00:00 2001 From: Ann Priestman Date: Tue, 10 Apr 2018 07:29:51 -0400 Subject: [PATCH 23/25] Minor cleanup from PR review --- .../autopsy/healthmonitor/ServicesHealthMonitor.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/healthmonitor/ServicesHealthMonitor.java b/Core/src/org/sleuthkit/autopsy/healthmonitor/ServicesHealthMonitor.java index c73fa55106..33855df091 100644 --- a/Core/src/org/sleuthkit/autopsy/healthmonitor/ServicesHealthMonitor.java +++ b/Core/src/org/sleuthkit/autopsy/healthmonitor/ServicesHealthMonitor.java @@ -310,13 +310,14 @@ public class ServicesHealthMonitor { timingMapCopy = new HashMap<>(timingInfoMap); timingInfoMap.clear(); } - logger.log(Level.INFO, "Writing health monitor metrics to database"); // 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 CoordinationService.Lock lock = getSharedDbLock(); if(lock == null) { @@ -380,7 +381,6 @@ public class ServicesHealthMonitor { 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 + "'"; - System.out.println(" query: " + createCommand); rs = statement.executeQuery(createCommand); if(rs.next()) { logger.log(Level.INFO, "Existing Services Health Monitor database found"); From aff1f64ced8d0063c77dccc79dbc2b1fbaa47c13 Mon Sep 17 00:00:00 2001 From: Brian Carrier Date: Tue, 10 Apr 2018 22:41:06 -0400 Subject: [PATCH 24/25] Added more logging and status --- .../volatilityDSP/VolatilityProcessor.java | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/volatilityDSP/VolatilityProcessor.java b/Experimental/src/org/sleuthkit/autopsy/experimental/volatilityDSP/VolatilityProcessor.java index fd285ae1aa..50246f5255 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/volatilityDSP/VolatilityProcessor.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/volatilityDSP/VolatilityProcessor.java @@ -136,7 +136,6 @@ class VolatilityProcessor { break; } String pluginToRun = pluginsToRun.get(i); - progressMonitor.setProgressText(Bundle.VolatilityProcessor_progressMessage_runningImageInfo(pluginToRun)); runVolatilityPlugin(pluginToRun); progressMonitor.setProgress(i); } @@ -172,6 +171,8 @@ class VolatilityProcessor { "VolatilityProcessor_exceptionMessage_errorIndexingOutput=Error indexing output for {0} plugin" }) private void runVolatilityPlugin(String pluginToRun) throws VolatilityProcessorException { + progressMonitor.setProgressText("Running module " + pluginToRun); + List commandLine = new ArrayList<>(); commandLine.add("\"" + executableFile + "\""); //NON-NLS File memoryImage = new File(memoryImagePath); @@ -314,6 +315,8 @@ class VolatilityProcessor { String filePath = volfile.getParent(); + logger.log(Level.INFO, "Looking up file " + fileName + " at path " + filePath); + try { List resolvedFiles; if (filePath == null) { @@ -333,12 +336,13 @@ class VolatilityProcessor { } fileName += ".%"; //NON-NLS + logger.log(Level.INFO, "Looking up file (extension wildcard) " + fileName + " at path " + filePath); + if (filePath == null) { resolvedFiles = fileManager.findFiles(fileName); //NON-NLS } else { resolvedFiles = fileManager.findFiles(fileName, filePath); //NON-NLS } - } if (resolvedFiles.isEmpty()) { @@ -387,6 +391,7 @@ class VolatilityProcessor { * @param pluginOutputFile File that contains the output to parse. */ private void createArtifactsFromPluginOutput(String pluginName, File pluginOutputFile) throws VolatilityProcessorException { + progressMonitor.setProgressText("Parsing module " + pluginName); Set fileSet = null; switch (pluginName) { case "dlllist": //NON-NLS @@ -421,6 +426,7 @@ class VolatilityProcessor { } if (fileSet != null && !fileSet.isEmpty()) { + progressMonitor.setProgressText("Flagging files from module " + pluginName); flagFiles(fileSet, pluginName); } } From 2ba0df6f8c136845641cff43eeb64e72b83dafa5 Mon Sep 17 00:00:00 2001 From: Ann Priestman Date: Wed, 11 Apr 2018 14:34:50 -0400 Subject: [PATCH 25/25] Code cleanup and renaming to address PR review comments. --- ...itor.java => EnterpriseHealthMonitor.java} | 114 ++++++++++-------- .../HealthMonitorCaseEventListener.java | 53 -------- .../autopsy/healthmonitor/Installer.java | 5 +- .../autopsy/keywordsearch/Ingester.java | 6 +- .../autopsy/keywordsearch/Server.java | 6 +- 5 files changed, 73 insertions(+), 111 deletions(-) rename Core/src/org/sleuthkit/autopsy/healthmonitor/{ServicesHealthMonitor.java => EnterpriseHealthMonitor.java} (89%) delete mode 100644 Core/src/org/sleuthkit/autopsy/healthmonitor/HealthMonitorCaseEventListener.java diff --git a/Core/src/org/sleuthkit/autopsy/healthmonitor/ServicesHealthMonitor.java b/Core/src/org/sleuthkit/autopsy/healthmonitor/EnterpriseHealthMonitor.java similarity index 89% rename from Core/src/org/sleuthkit/autopsy/healthmonitor/ServicesHealthMonitor.java rename to Core/src/org/sleuthkit/autopsy/healthmonitor/EnterpriseHealthMonitor.java index 33855df091..35bc6b6bfa 100644 --- a/Core/src/org/sleuthkit/autopsy/healthmonitor/ServicesHealthMonitor.java +++ b/Core/src/org/sleuthkit/autopsy/healthmonitor/EnterpriseHealthMonitor.java @@ -19,6 +19,8 @@ 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; @@ -27,16 +29,21 @@ 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; @@ -47,18 +54,21 @@ import org.sleuthkit.datamodel.CaseDbSchemaVersionNumber; * 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 class ServicesHealthMonitor { +public final class EnterpriseHealthMonitor implements PropertyChangeListener { - private final static Logger logger = Logger.getLogger(ServicesHealthMonitor.class.getName()); - private final static String DATABASE_NAME = "ServicesHealthMonitor"; - private final static String MODULE_NAME = "ServicesHealthMonitor"; + 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 ServicesHealthMonitor instance; + 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 timingInfoMap; @@ -66,19 +76,22 @@ public class ServicesHealthMonitor { private BasicDataSource connectionPool = null; private String hostName; - private ServicesHealthMonitor() throws HealthMonitorException { + 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 a warning - logger.log(Level.WARNING, "Unable to look up host name"); - hostName = "unknown"; + // 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 @@ -100,13 +113,14 @@ public class ServicesHealthMonitor { } /** - * Get the instance of the ServicesHealthMonitor + * Get the instance of the EnterpriseHealthMonitor * @return the instance * @throws HealthMonitorException */ - synchronized static ServicesHealthMonitor getInstance() throws HealthMonitorException { + synchronized static EnterpriseHealthMonitor getInstance() throws HealthMonitorException { if (instance == null) { - instance = new ServicesHealthMonitor(); + instance = new EnterpriseHealthMonitor(); + Case.addPropertyChangeListener(instance); } return instance; } @@ -122,15 +136,15 @@ public class ServicesHealthMonitor { logger.log(Level.INFO, "Activating Servies Health Monitor"); if (!UserPreferences.getIsMultiUserModeEnabled()) { - throw new HealthMonitorException("Multi user mode is not enabled - can not activate services health monitor"); - } - // Set up database (if needed) - CoordinationService.Lock lock = getExclusiveDbLock(); - if(lock == null) { - throw new HealthMonitorException("Error getting database lock"); + throw new HealthMonitorException("Multi user mode is not enabled - can not activate health monitor"); } - try { + // 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()) { @@ -142,12 +156,8 @@ public class ServicesHealthMonitor { initializeDatabaseSchema(); } - } finally { - try { - lock.release(); - } catch (CoordinationService.CoordinationServiceException ex) { - throw new HealthMonitorException("Error releasing database lock", ex); - } + } catch (CoordinationService.CoordinationServiceException ex) { + throw new HealthMonitorException("Error releasing database lock", ex); } // Clear out any old data @@ -182,10 +192,9 @@ public class ServicesHealthMonitor { * Start the ScheduledThreadPoolExecutor that will handle the database writes. */ private synchronized void startTimer() { - if(healthMonitorOutputTimer != null) { - // Make sure the previous executor (if it exists) has been stopped - healthMonitorOutputTimer.shutdown(); - } + // 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); } @@ -195,7 +204,7 @@ public class ServicesHealthMonitor { */ private synchronized void stopTimer() { if(healthMonitorOutputTimer != null) { - healthMonitorOutputTimer.shutdown(); + ThreadUtils.shutDownTaskExecutor(healthMonitorOutputTimer); } } @@ -203,7 +212,7 @@ public class ServicesHealthMonitor { * Called from the installer to set up the Health Monitor instance at startup. * @throws HealthMonitorException */ - static synchronized void startUp() throws HealthMonitorException { + static synchronized void startUpIfEnabled() throws HealthMonitorException { getInstance(); } @@ -235,7 +244,7 @@ public class ServicesHealthMonitor { * 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 Services Health + * 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. @@ -251,7 +260,7 @@ public class ServicesHealthMonitor { /** * 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 Services Health + * This method is safe to call regardless of whether the Enterprise Health * Monitor is enabled. * @param metric The TimingMetric object obtained from getTimingMetric() */ @@ -318,13 +327,12 @@ public class ServicesHealthMonitor { logger.log(Level.INFO, "Writing health monitor metrics to database"); - // Write to the database - CoordinationService.Lock lock = getSharedDbLock(); - if(lock == null) { - throw new HealthMonitorException("Error getting database lock"); - } - - try { + // 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"); @@ -357,12 +365,8 @@ public class ServicesHealthMonitor { logger.log(Level.SEVERE, "Error closing Connection.", ex); } } - } finally { - try { - lock.release(); - } catch (CoordinationService.CoordinationServiceException ex) { - throw new HealthMonitorException("Error releasing database lock", ex); - } + } catch (CoordinationService.CoordinationServiceException ex) { + throw new HealthMonitorException("Error releasing database lock", ex); } } @@ -383,7 +387,7 @@ public class ServicesHealthMonitor { 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 Services Health Monitor database found"); + logger.log(Level.INFO, "Existing Enterprise Health Monitor database found"); return true; } } finally { @@ -648,6 +652,20 @@ public class ServicesHealthMonitor { } } + @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. @@ -663,7 +681,7 @@ public class ServicesHealthMonitor { } throw new HealthMonitorException("Error acquiring database lock"); } catch (InterruptedException | CoordinationService.CoordinationServiceException ex){ - throw new HealthMonitorException("Error acquiring database lock"); + throw new HealthMonitorException("Error acquiring database lock", ex); } } diff --git a/Core/src/org/sleuthkit/autopsy/healthmonitor/HealthMonitorCaseEventListener.java b/Core/src/org/sleuthkit/autopsy/healthmonitor/HealthMonitorCaseEventListener.java deleted file mode 100644 index f6c5583692..0000000000 --- a/Core/src/org/sleuthkit/autopsy/healthmonitor/HealthMonitorCaseEventListener.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2018 Basis Technology Corp. - * Contact: carrier sleuthkit 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.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import org.sleuthkit.autopsy.casemodule.Case; - -/** - * Listener for case events - */ -final class HealthMonitorCaseEventListener implements PropertyChangeListener { - - private final ExecutorService healthMonitorExecutor; - private static final String HEALTH_MONITOR_EVENT_THREAD_NAME = "Health-Monitor-Event-Listener-%d"; - - HealthMonitorCaseEventListener() { - healthMonitorExecutor = Executors.newSingleThreadExecutor(new ThreadFactoryBuilder().setNameFormat(HEALTH_MONITOR_EVENT_THREAD_NAME).build()); - } - - @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 ServicesHealthMonitor.DatabaseWriteTask()); - } - break; - } - } -} diff --git a/Core/src/org/sleuthkit/autopsy/healthmonitor/Installer.java b/Core/src/org/sleuthkit/autopsy/healthmonitor/Installer.java index 5d294aca31..61ea5a5244 100644 --- a/Core/src/org/sleuthkit/autopsy/healthmonitor/Installer.java +++ b/Core/src/org/sleuthkit/autopsy/healthmonitor/Installer.java @@ -26,7 +26,6 @@ import org.sleuthkit.autopsy.coreutils.Logger; public class Installer extends ModuleInstall { private static final Logger logger = Logger.getLogger(Installer.class.getName()); - private final HealthMonitorCaseEventListener pcl = new HealthMonitorCaseEventListener(); private static final long serialVersionUID = 1L; private static Installer instance; @@ -45,10 +44,8 @@ public class Installer extends ModuleInstall { @Override public void restored() { - Case.addPropertyChangeListener(pcl); - try { - ServicesHealthMonitor.startUp(); + EnterpriseHealthMonitor.startUpIfEnabled(); } catch (HealthMonitorException ex) { logger.log(Level.SEVERE, "Error starting health services monitor", ex); } diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Ingester.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Ingester.java index f956513eca..b9c4541c7b 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Ingester.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Ingester.java @@ -27,7 +27,7 @@ import org.apache.solr.common.SolrInputDocument; import org.openide.util.NbBundle; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.datamodel.ContentUtils; -import org.sleuthkit.autopsy.healthmonitor.ServicesHealthMonitor; +import org.sleuthkit.autopsy.healthmonitor.EnterpriseHealthMonitor; import org.sleuthkit.autopsy.healthmonitor.TimingMetric; import org.sleuthkit.autopsy.ingest.IngestJobContext; import org.sleuthkit.autopsy.keywordsearch.Chunker.Chunk; @@ -237,9 +237,9 @@ class Ingester { try { //TODO: consider timeout thread, or vary socket timeout based on size of indexed content - TimingMetric metric = ServicesHealthMonitor.getTimingMetric("Solr: Index chunk"); + TimingMetric metric = EnterpriseHealthMonitor.getTimingMetric("Solr: Index chunk"); solrServer.addDocument(updateDoc); - ServicesHealthMonitor.submitTimingMetric(metric); + EnterpriseHealthMonitor.submitTimingMetric(metric); uncommitedIngests = true; } catch (KeywordSearchModuleException | NoOpenCoreException ex) { diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Server.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Server.java index 5748524ada..eea4d2914e 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Server.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Server.java @@ -70,7 +70,7 @@ import org.sleuthkit.autopsy.core.UserPreferences; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.ModuleSettings; import org.sleuthkit.autopsy.coreutils.PlatformUtil; -import org.sleuthkit.autopsy.healthmonitor.ServicesHealthMonitor; +import org.sleuthkit.autopsy.healthmonitor.EnterpriseHealthMonitor; import org.sleuthkit.autopsy.healthmonitor.TimingMetric; import org.sleuthkit.autopsy.keywordsearchservice.KeywordSearchServiceException; import org.sleuthkit.datamodel.Content; @@ -775,9 +775,9 @@ public class Server { IndexingServerProperties properties = getMultiUserServerProperties(theCase.getCaseDirectory()); currentSolrServer = new HttpSolrServer("http://" + properties.getHost() + ":" + properties.getPort() + "/solr"); //NON-NLS } - TimingMetric metric = ServicesHealthMonitor.getTimingMetric("Solr: Connectivity check"); + TimingMetric metric = EnterpriseHealthMonitor.getTimingMetric("Solr: Connectivity check"); connectToSolrServer(currentSolrServer); - ServicesHealthMonitor.submitTimingMetric(metric); + EnterpriseHealthMonitor.submitTimingMetric(metric); } catch (SolrServerException | IOException ex) { throw new KeywordSearchModuleException(NbBundle.getMessage(Server.class, "Server.connect.exception.msg", ex.getLocalizedMessage()), ex);