summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBrent Nash <brent.nash@crowdstrike.com>2022-02-02 07:39:31 +0000
committerMate Szalay-Beko <symat@apache.org>2022-02-02 07:39:31 +0000
commitd32ec677f8fd00a367b305e1ae8abf127700511c (patch)
treeb4365bb2a1ce76fcfad345f7fc754f47595d77c4
parent04c7ad115e298387e4a75dfe14013f6c6aa68a6d (diff)
downloadzookeeper-d32ec677f8fd00a367b305e1ae8abf127700511c.tar.gz
ZOOKEEPER-4291: ZooInspector has very high latency when operating against remote clusters
Apache Jira Issue: https://issues.apache.org/jira/browse/ZOOKEEPER-4291 Zookeeper Contribution Guidelines: https://cwiki.apache.org/confluence/display/zookeeper/howtocontribute This is a fairly substantial change and I will also send a note on the zookeeper-dev mailing list to start a discussion in case there are more questions/comments. The existing version of ZooInspector had an extremely short cache retention time combined with tying high frequency UI operations to network calls which resulted in almost unusable latency when using ZooInspector to talk to a Zookeeper cluster over a network. This change refactors the tree view to move network calls onto background threads in the TreeModel while keeping the UI itself responsive and only reloading data on request after initial node expansion. In general, I think these changes take the tool from being virtually unusable when talking to remote clusters to being performant enough and responsive enough to be useful in most cases. Some other related highlights include: - Made getNumChildren() use the NodeCache (it wasn't previously) - Remove network calls getting triggered during UI redraw animations such as using scroll bars or resizing the window - Addition of a mouse spinner animation when work is in progress - Addition of the number of child nodes in parentheses after the node name on the UI - Move Add/Delete node logic to be encapsulated in TreeViewer logic (and remove separate Add/Delete NodeAction classes) - Addition of a right-click menu item to refresh a selected subtree (previously this was only doable for the entire tree) - Fixed the broken "Node Viewer Settings" icon and button (it previously had no icon set and didn't show on the UI) - Fixed the broken "About" button (it previously was failing to find the about.html file) - Fixed a number of Javadoc-related build warnings - Fixed a number of naming typos (e.g. NodeVeiwers -> NodeViewers) - Removed some dead/unused code Testing - The build succeeds, though ZooInspector currently has no unit tests. I did test it fairly extensively with some remote clusters I have available. <img width="974" alt="zooinspector-helix-screenshot" src="https://user-images.githubusercontent.com/33364712/148818936-065d19fc-e06b-4b00-8cae-374cd8ac82a0.png"> I have attached a screenshot of the updated version of ZooInspector (examining an Apache Helix cluster in Zookeeper). Author: Brent Nash <brent.nash@crowdstrike.com> Reviewers: Enrico Olivelli <eolivelli@apache.org>, Mate Szalay-Beko <symat@apache.org> Closes #1796 from brentwritescode/ZOOKEEPER-4291
-rw-r--r--zookeeper-contrib/zookeeper-contrib-zooinspector/src/main/java/com/nitido/utils/toaster/Toaster.java19
-rw-r--r--zookeeper-contrib/zookeeper-contrib-zooinspector/src/main/java/org/apache/zookeeper/inspector/gui/IconResource.java9
-rw-r--r--zookeeper-contrib/zookeeper-contrib-zooinspector/src/main/java/org/apache/zookeeper/inspector/gui/Toolbar.java14
-rw-r--r--zookeeper-contrib/zookeeper-contrib-zooinspector/src/main/java/org/apache/zookeeper/inspector/gui/ZooInspectorAboutDialog.java11
-rw-r--r--zookeeper-contrib/zookeeper-contrib-zooinspector/src/main/java/org/apache/zookeeper/inspector/gui/ZooInspectorConnectionPropertiesDialog.java2
-rw-r--r--zookeeper-contrib/zookeeper-contrib-zooinspector/src/main/java/org/apache/zookeeper/inspector/gui/ZooInspectorNodeViewersDialog.java5
-rw-r--r--zookeeper-contrib/zookeeper-contrib-zooinspector/src/main/java/org/apache/zookeeper/inspector/gui/ZooInspectorNodeViewersPanel.java56
-rw-r--r--zookeeper-contrib/zookeeper-contrib-zooinspector/src/main/java/org/apache/zookeeper/inspector/gui/ZooInspectorPanel.java59
-rw-r--r--zookeeper-contrib/zookeeper-contrib-zooinspector/src/main/java/org/apache/zookeeper/inspector/gui/ZooInspectorTreeView.java622
-rw-r--r--zookeeper-contrib/zookeeper-contrib-zooinspector/src/main/java/org/apache/zookeeper/inspector/gui/ZooInspectorTreeViewer.java384
-rw-r--r--zookeeper-contrib/zookeeper-contrib-zooinspector/src/main/java/org/apache/zookeeper/inspector/gui/actions/AddNodeAction.java74
-rw-r--r--zookeeper-contrib/zookeeper-contrib-zooinspector/src/main/java/org/apache/zookeeper/inspector/gui/actions/DeleteNodeAction.java79
-rw-r--r--zookeeper-contrib/zookeeper-contrib-zooinspector/src/main/java/org/apache/zookeeper/inspector/gui/nodeviewer/NodeSelectionListener.java26
-rw-r--r--zookeeper-contrib/zookeeper-contrib-zooinspector/src/main/java/org/apache/zookeeper/inspector/gui/nodeviewer/NodeViewerData.java8
-rw-r--r--zookeeper-contrib/zookeeper-contrib-zooinspector/src/main/java/org/apache/zookeeper/inspector/logger/LoggerFactory.java18
-rw-r--r--zookeeper-contrib/zookeeper-contrib-zooinspector/src/main/java/org/apache/zookeeper/inspector/manager/NodesCache.java55
-rw-r--r--zookeeper-contrib/zookeeper-contrib-zooinspector/src/main/java/org/apache/zookeeper/inspector/manager/ZooInspectorManager.java2
-rw-r--r--zookeeper-contrib/zookeeper-contrib-zooinspector/src/main/java/org/apache/zookeeper/inspector/manager/ZooInspectorManagerImpl.java143
-rw-r--r--zookeeper-contrib/zookeeper-contrib-zooinspector/src/main/java/org/apache/zookeeper/inspector/manager/ZooInspectorReadOnlyManager.java46
-rw-r--r--zookeeper-contrib/zookeeper-contrib-zooinspector/src/main/java/org/apache/zookeeper/retry/ZooKeeperRetry.java2
-rw-r--r--zookeeper-contrib/zookeeper-contrib-zooinspector/src/main/resources/about.html21
-rw-r--r--zookeeper-contrib/zookeeper-contrib-zooinspector/src/main/resources/icons/Tango/16x16/categories/applications-system.pngbin0 -> 588 bytes
-rw-r--r--zookeeper-contrib/zookeeper-contrib-zooinspector/src/main/resources/icons/Tango/22x22/categories/applications-system.pngbin0 -> 1488 bytes
-rw-r--r--zookeeper-contrib/zookeeper-contrib-zooinspector/src/main/resources/icons/Tango/24x24/categories/applications-system.pngbin0 -> 2447 bytes
-rw-r--r--zookeeper-contrib/zookeeper-contrib-zooinspector/src/main/resources/icons/Tango/32x32/categories/applications-system.pngbin0 -> 2544 bytes
25 files changed, 808 insertions, 847 deletions
diff --git a/zookeeper-contrib/zookeeper-contrib-zooinspector/src/main/java/com/nitido/utils/toaster/Toaster.java b/zookeeper-contrib/zookeeper-contrib-zooinspector/src/main/java/com/nitido/utils/toaster/Toaster.java
index 0a4fe94b0..2888caff9 100644
--- a/zookeeper-contrib/zookeeper-contrib-zooinspector/src/main/java/com/nitido/utils/toaster/Toaster.java
+++ b/zookeeper-contrib/zookeeper-contrib-zooinspector/src/main/java/com/nitido/utils/toaster/Toaster.java
@@ -18,9 +18,6 @@
*
* This is a simple example of utilization:
*
- * import com.nitido.utils.toaster.*;
- * import javax.swing.*;
- *
* public class ToasterTest
* {
*
@@ -36,10 +33,20 @@
*/
package com.nitido.utils.toaster;
-import java.awt.*;
-import javax.swing.*;
-import javax.swing.border.*;
+import javax.swing.BorderFactory;
+import javax.swing.Icon;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JTextArea;
+import javax.swing.JWindow;
+import javax.swing.border.EtchedBorder;
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Font;
+import java.awt.GraphicsEnvironment;
+import java.awt.Insets;
+import java.awt.Rectangle;
/**
* Class to show tosters in multiplatform
diff --git a/zookeeper-contrib/zookeeper-contrib-zooinspector/src/main/java/org/apache/zookeeper/inspector/gui/IconResource.java b/zookeeper-contrib/zookeeper-contrib-zooinspector/src/main/java/org/apache/zookeeper/inspector/gui/IconResource.java
index 411900a9a..46ffb0c4f 100644
--- a/zookeeper-contrib/zookeeper-contrib-zooinspector/src/main/java/org/apache/zookeeper/inspector/gui/IconResource.java
+++ b/zookeeper-contrib/zookeeper-contrib-zooinspector/src/main/java/org/apache/zookeeper/inspector/gui/IconResource.java
@@ -27,12 +27,15 @@ import javax.swing.ImageIcon;
import org.apache.zookeeper.inspector.logger.LoggerFactory;
/**
- * @see http://standards.freedesktop.org/icon-naming-spec/icon-naming-spec-latest.html
- * I tried to take icons that are available in the Tango icon set
+ * @link http://standards.freedesktop.org/icon-naming-spec/icon-naming-spec-latest.html
+ * I tried to take icons that are available in the Tango icon set.
+ *
+ * @link http://tango.freedesktop.org/Tango_Icon_Library
+ * The Tango icon set can be found under the "Download" section.
*/
public class IconResource {
- public static final String ICON_ChangeNodeViewers = "";
+ public static final String ICON_CHANGE_NODE_VIEWERS = "categories/applications-system";
public static final String ICON_TREE_LEAF = "mimetypes/text-x-generic";
public static final String ICON_TREE_OPEN = "places/folder";
public static final String ICON_TREE_CLOSE = "places/folder";
diff --git a/zookeeper-contrib/zookeeper-contrib-zooinspector/src/main/java/org/apache/zookeeper/inspector/gui/Toolbar.java b/zookeeper-contrib/zookeeper-contrib-zooinspector/src/main/java/org/apache/zookeeper/inspector/gui/Toolbar.java
index 06e80a808..99c780d8d 100644
--- a/zookeeper-contrib/zookeeper-contrib-zooinspector/src/main/java/org/apache/zookeeper/inspector/gui/Toolbar.java
+++ b/zookeeper-contrib/zookeeper-contrib-zooinspector/src/main/java/org/apache/zookeeper/inspector/gui/Toolbar.java
@@ -64,13 +64,13 @@ public class Toolbar {
}
public static enum Button {
- connect("Connect",IconResource.ICON_START,true),
- disconnect("Disconnect",IconResource.ICON_STOP,false),
- refresh("Refresh",IconResource.ICON_REFRESH,false),
- addNode("Add Node",IconResource.ICON_DOCUMENT_ADD,false),
- deleteNode("Delete Node",IconResource.ICON_TRASH,false),
- nodeViewers("Change Node Viewers",IconResource.ICON_ChangeNodeViewers,true),
- about("About ZooInspector",IconResource.ICON_HELP_ABOUT,true);
+ connect("Connect", IconResource.ICON_START, true),
+ disconnect("Disconnect", IconResource.ICON_STOP, false),
+ refresh("Refresh All", IconResource.ICON_REFRESH, false),
+ addNode("Add Node", IconResource.ICON_DOCUMENT_ADD, false),
+ deleteNode("Delete Node", IconResource.ICON_TRASH, false),
+ nodeViewers("Change Node Viewers", IconResource.ICON_CHANGE_NODE_VIEWERS, true),
+ about("About ZooInspector", IconResource.ICON_HELP_ABOUT, true);
private String toolTip;
private String icon;
diff --git a/zookeeper-contrib/zookeeper-contrib-zooinspector/src/main/java/org/apache/zookeeper/inspector/gui/ZooInspectorAboutDialog.java b/zookeeper-contrib/zookeeper-contrib-zooinspector/src/main/java/org/apache/zookeeper/inspector/gui/ZooInspectorAboutDialog.java
index 58096b26e..b6cdf4bf4 100644
--- a/zookeeper-contrib/zookeeper-contrib-zooinspector/src/main/java/org/apache/zookeeper/inspector/gui/ZooInspectorAboutDialog.java
+++ b/zookeeper-contrib/zookeeper-contrib-zooinspector/src/main/java/org/apache/zookeeper/inspector/gui/ZooInspectorAboutDialog.java
@@ -23,6 +23,7 @@ import java.awt.FlowLayout;
import java.awt.Frame;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
+import java.io.File;
import java.io.IOException;
import javax.swing.JButton;
@@ -36,6 +37,9 @@ import org.apache.zookeeper.inspector.logger.LoggerFactory;
* The About Dialog for the application
*/
public class ZooInspectorAboutDialog extends JDialog {
+
+ private static final File aboutHtmlFile = new File("./src/main/resources/about.html");
+
/**
* @param frame
* - the Frame from which the dialog is displayed
@@ -53,13 +57,10 @@ public class ZooInspectorAboutDialog extends JDialog {
JEditorPane aboutPane = new JEditorPane();
aboutPane.setEditable(false);
aboutPane.setOpaque(false);
- java.net.URL aboutURL = ZooInspectorAboutDialog.class
- .getResource("about.html");
try {
- aboutPane.setPage(aboutURL);
+ aboutPane.setPage(aboutHtmlFile.toURI().toURL());
} catch (IOException e) {
- LoggerFactory.getLogger().error(
- "Error loading about.html, file may be corrupt", e);
+ LoggerFactory.getLogger().error("Error loading about.html, file may be corrupt", e);
}
panel.add(aboutPane, BorderLayout.CENTER);
panel.setPreferredSize(new Dimension(600, 200));
diff --git a/zookeeper-contrib/zookeeper-contrib-zooinspector/src/main/java/org/apache/zookeeper/inspector/gui/ZooInspectorConnectionPropertiesDialog.java b/zookeeper-contrib/zookeeper-contrib-zooinspector/src/main/java/org/apache/zookeeper/inspector/gui/ZooInspectorConnectionPropertiesDialog.java
index 1647021f5..da847294e 100644
--- a/zookeeper-contrib/zookeeper-contrib-zooinspector/src/main/java/org/apache/zookeeper/inspector/gui/ZooInspectorConnectionPropertiesDialog.java
+++ b/zookeeper-contrib/zookeeper-contrib-zooinspector/src/main/java/org/apache/zookeeper/inspector/gui/ZooInspectorConnectionPropertiesDialog.java
@@ -56,7 +56,7 @@ public class ZooInspectorConnectionPropertiesDialog extends JDialog {
/**
* @param lastConnectionProps
* - the last connection properties used. if this is the first
- * conneciton since starting the applications this will be the
+ * connection since starting the applications this will be the
* default settings
* @param connectionPropertiesTemplateAndLabels
* - the connection properties and labels to show in this dialog
diff --git a/zookeeper-contrib/zookeeper-contrib-zooinspector/src/main/java/org/apache/zookeeper/inspector/gui/ZooInspectorNodeViewersDialog.java b/zookeeper-contrib/zookeeper-contrib-zooinspector/src/main/java/org/apache/zookeeper/inspector/gui/ZooInspectorNodeViewersDialog.java
index e3cc7b12b..5c4485de5 100644
--- a/zookeeper-contrib/zookeeper-contrib-zooinspector/src/main/java/org/apache/zookeeper/inspector/gui/ZooInspectorNodeViewersDialog.java
+++ b/zookeeper-contrib/zookeeper-contrib-zooinspector/src/main/java/org/apache/zookeeper/inspector/gui/ZooInspectorNodeViewersDialog.java
@@ -54,7 +54,6 @@ import javax.swing.TransferHandler;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
-import org.apache.zookeeper.inspector.gui.Toolbar.Button;
import org.apache.zookeeper.inspector.gui.nodeviewer.ZooInspectorNodeViewer;
import org.apache.zookeeper.inspector.logger.LoggerFactory;
import org.apache.zookeeper.inspector.manager.ZooInspectorManager;
@@ -90,9 +89,9 @@ public class ZooInspectorNodeViewersDialog extends JDialog implements
final List<ZooInspectorNodeViewer> newViewers = new ArrayList<ZooInspectorNodeViewer>(
currentViewers);
this.setLayout(new BorderLayout());
- this.setIconImage(iconResource.get(IconResource.ICON_ChangeNodeViewers,"")
+ this.setIconImage(iconResource.get(IconResource.ICON_CHANGE_NODE_VIEWERS,"Change Node Viewers")
.getImage());
- this.setTitle("About ZooInspector");
+ this.setTitle("Change Node Viewers");
this.setModal(true);
this.setAlwaysOnTop(true);
this.setResizable(true);
diff --git a/zookeeper-contrib/zookeeper-contrib-zooinspector/src/main/java/org/apache/zookeeper/inspector/gui/ZooInspectorNodeViewersPanel.java b/zookeeper-contrib/zookeeper-contrib-zooinspector/src/main/java/org/apache/zookeeper/inspector/gui/ZooInspectorNodeViewersPanel.java
index 05c256b5a..9c3beb15e 100644
--- a/zookeeper-contrib/zookeeper-contrib-zooinspector/src/main/java/org/apache/zookeeper/inspector/gui/ZooInspectorNodeViewersPanel.java
+++ b/zookeeper-contrib/zookeeper-contrib-zooinspector/src/main/java/org/apache/zookeeper/inspector/gui/ZooInspectorNodeViewersPanel.java
@@ -25,10 +25,8 @@ import javax.swing.JPanel;
import javax.swing.JTabbedPane;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
-import javax.swing.event.TreeSelectionEvent;
-import javax.swing.event.TreeSelectionListener;
-import javax.swing.tree.TreePath;
+import org.apache.zookeeper.inspector.gui.nodeviewer.NodeSelectionListener;
import org.apache.zookeeper.inspector.gui.nodeviewer.ZooInspectorNodeViewer;
import org.apache.zookeeper.inspector.manager.ZooInspectorManager;
import org.apache.zookeeper.inspector.manager.ZooInspectorNodeManager;
@@ -36,10 +34,9 @@ import org.apache.zookeeper.inspector.manager.ZooInspectorNodeManager;
/**
* This is the {@link JPanel} which contains the {@link ZooInspectorNodeViewer}s
*/
-public class ZooInspectorNodeViewersPanel extends JPanel implements
- TreeSelectionListener, ChangeListener {
+public class ZooInspectorNodeViewersPanel extends JPanel implements ChangeListener, NodeSelectionListener {
- private final List<ZooInspectorNodeViewer> nodeVeiwers = new ArrayList<ZooInspectorNodeViewer>();
+ private final List<ZooInspectorNodeViewer> nodeViewers = new ArrayList<>();
private final List<Boolean> needsReload = new ArrayList<Boolean>();
private final JTabbedPane tabbedPane;
private final List<String> selectedNodes = new ArrayList<String>();
@@ -48,17 +45,17 @@ public class ZooInspectorNodeViewersPanel extends JPanel implements
/**
* @param zooInspectorManager
* - the {@link ZooInspectorManager} for the application
- * @param nodeVeiwers
+ * @param nodeViewers
* - the {@link ZooInspectorNodeViewer}s to show
*/
public ZooInspectorNodeViewersPanel(
ZooInspectorNodeManager zooInspectorManager,
- List<ZooInspectorNodeViewer> nodeVeiwers) {
+ List<ZooInspectorNodeViewer> nodeViewers) {
this.zooInspectorManager = zooInspectorManager;
this.setLayout(new BorderLayout());
tabbedPane = new JTabbedPane(JTabbedPane.TOP,
JTabbedPane.WRAP_TAB_LAYOUT);
- setNodeViewers(nodeVeiwers);
+ setNodeViewers(nodeViewers);
tabbedPane.addChangeListener(this);
this.add(tabbedPane, BorderLayout.CENTER);
reloadSelectedViewer();
@@ -69,11 +66,11 @@ public class ZooInspectorNodeViewersPanel extends JPanel implements
* - the {@link ZooInspectorNodeViewer}s to show
*/
public void setNodeViewers(List<ZooInspectorNodeViewer> nodeViewers) {
- this.nodeVeiwers.clear();
- this.nodeVeiwers.addAll(nodeViewers);
+ this.nodeViewers.clear();
+ this.nodeViewers.addAll(nodeViewers);
needsReload.clear();
tabbedPane.removeAll();
- for (ZooInspectorNodeViewer nodeViewer : nodeVeiwers) {
+ for (ZooInspectorNodeViewer nodeViewer : nodeViewers) {
nodeViewer.setZooInspectorManager(zooInspectorManager);
needsReload.add(true);
tabbedPane.add(nodeViewer.getTitle(), nodeViewer);
@@ -85,42 +82,19 @@ public class ZooInspectorNodeViewersPanel extends JPanel implements
private void reloadSelectedViewer() {
int index = this.tabbedPane.getSelectedIndex();
if (index != -1 && this.needsReload.get(index)) {
- ZooInspectorNodeViewer viewer = this.nodeVeiwers.get(index);
+ ZooInspectorNodeViewer viewer = this.nodeViewers.get(index);
viewer.nodeSelectionChanged(selectedNodes);
this.needsReload.set(index, false);
}
}
- /*
- * (non-Javadoc)
- *
- * @see
- * javax.swing.event.TreeSelectionListener#valueChanged(javax.swing.event
- * .TreeSelectionEvent)
+ /**
+ * @see org.apache.zookeeper.inspector.gui.nodeviewer.NodeSelectionListener#nodePathSelected(String)
*/
- public void valueChanged(TreeSelectionEvent e) {
- TreePath[] paths = e.getPaths();
+ @Override
+ public void nodePathSelected(String nodePath) {
selectedNodes.clear();
- for (TreePath path : paths) {
- boolean appended = false;
- StringBuilder sb = new StringBuilder();
- Object[] pathArray = path.getPath();
- for (Object o : pathArray) {
- if (o != null) {
- String nodeName = o.toString();
- if (nodeName != null) {
- if (nodeName.length() > 0) {
- appended = true;
- sb.append("/"); //$NON-NLS-1$
- sb.append(o.toString());
- }
- }
- }
- }
- if (appended) {
- selectedNodes.add(sb.toString());
- }
- }
+ selectedNodes.add(nodePath);
for (int i = 0; i < needsReload.size(); i++) {
this.needsReload.set(i, true);
}
diff --git a/zookeeper-contrib/zookeeper-contrib-zooinspector/src/main/java/org/apache/zookeeper/inspector/gui/ZooInspectorPanel.java b/zookeeper-contrib/zookeeper-contrib-zooinspector/src/main/java/org/apache/zookeeper/inspector/gui/ZooInspectorPanel.java
index e816ceb4b..eb08e722d 100644
--- a/zookeeper-contrib/zookeeper-contrib-zooinspector/src/main/java/org/apache/zookeeper/inspector/gui/ZooInspectorPanel.java
+++ b/zookeeper-contrib/zookeeper-contrib-zooinspector/src/main/java/org/apache/zookeeper/inspector/gui/ZooInspectorPanel.java
@@ -26,16 +26,12 @@ import java.util.List;
import java.util.Properties;
import java.util.concurrent.ExecutionException;
-import javax.swing.JButton;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JSplitPane;
-import javax.swing.JToolBar;
import javax.swing.SwingWorker;
-import org.apache.zookeeper.inspector.gui.actions.AddNodeAction;
-import org.apache.zookeeper.inspector.gui.actions.DeleteNodeAction;
import org.apache.zookeeper.inspector.gui.nodeviewer.ZooInspectorNodeViewer;
import org.apache.zookeeper.inspector.logger.LoggerFactory;
import org.apache.zookeeper.inspector.manager.ZooInspectorManager;
@@ -45,10 +41,14 @@ import org.apache.zookeeper.inspector.manager.ZooInspectorManager;
*/
public class ZooInspectorPanel extends JPanel implements
NodeViewersChangeListener {
+
+ /** Control how fast the scroll bar in the tree view window moves */
+ private static final int TREE_SCROLL_UNIT_INCREMENT = 16;
+
private final IconResource iconResource;
private final Toolbar toolbar;
private final ZooInspectorNodeViewersPanel nodeViewersPanel;
- private final ZooInspectorTreeViewer treeViewer;
+ private final ZooInspectorTreeView treeViewer;
private final ZooInspectorManager zooInspectorManager;
private final List<NodeViewersChangeListener> listeners = new ArrayList<NodeViewersChangeListener>();
@@ -81,10 +81,10 @@ public class ZooInspectorPanel extends JPanel implements
}
nodeViewersPanel = new ZooInspectorNodeViewersPanel(
zooInspectorManager, nodeViewers);
- treeViewer = new ZooInspectorTreeViewer(zooInspectorManager,
- nodeViewersPanel, iconResource);
+ this.treeViewer = new ZooInspectorTreeView(zooInspectorManager, iconResource);
+ this.treeViewer.addNodeSelectionListener(this.nodeViewersPanel);
this.setLayout(new BorderLayout());
-
+
toolbar.addActionListener(Toolbar.Button.connect, new ActionListener() {
public void actionPerformed(ActionEvent e) {
ZooInspectorConnectionPropertiesDialog zicpd = new ZooInspectorConnectionPropertiesDialog(
@@ -101,17 +101,20 @@ public class ZooInspectorPanel extends JPanel implements
});
toolbar.addActionListener(Toolbar.Button.refresh, new ActionListener() {
public void actionPerformed(ActionEvent e) {
- treeViewer.refreshView();
+ treeViewer.initialize();
+ }
+ });
+ toolbar.addActionListener(Toolbar.Button.addNode, new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ treeViewer.createNode();
+ }
+ });
+ toolbar.addActionListener(Toolbar.Button.deleteNode, new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ treeViewer.deleteNode();
}
});
-
- toolbar.addActionListener(Toolbar.Button.addNode,
- new AddNodeAction(this, treeViewer, zooInspectorManager));
- toolbar.addActionListener(Toolbar.Button.deleteNode,
- new DeleteNodeAction(this, treeViewer, zooInspectorManager));
-
toolbar.addActionListener(Toolbar.Button.nodeViewers, new ActionListener() {
-
public void actionPerformed(ActionEvent e) {
ZooInspectorNodeViewersDialog nvd = new ZooInspectorNodeViewersDialog(
JOptionPane.getRootFrame(), nodeViewers, listeners,
@@ -127,6 +130,7 @@ public class ZooInspectorPanel extends JPanel implements
}
});
JScrollPane treeScroller = new JScrollPane(treeViewer);
+ treeScroller.getVerticalScrollBar().setUnitIncrement(TREE_SCROLL_UNIT_INCREMENT);
JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT,
treeScroller, nodeViewersPanel);
splitPane.setResizeWeight(0.25);
@@ -152,20 +156,14 @@ public class ZooInspectorPanel extends JPanel implements
protected void done() {
try {
if (get()) {
- treeViewer.refreshView();
+ treeViewer.initialize();
toolbar.toggleButtons(true);
} else {
JOptionPane.showMessageDialog(ZooInspectorPanel.this,
"Unable to connect to zookeeper", "Error",
JOptionPane.ERROR_MESSAGE);
}
- } catch (InterruptedException e) {
- LoggerFactory
- .getLogger()
- .error(
- "Error occurred while connecting to ZooKeeper server",
- e);
- } catch (ExecutionException e) {
+ } catch (InterruptedException | ExecutionException e) {
LoggerFactory
.getLogger()
.error(
@@ -178,9 +176,6 @@ public class ZooInspectorPanel extends JPanel implements
worker.execute();
}
- /**
- *
- */
public void disconnect() {
disconnect(false);
}
@@ -202,16 +197,10 @@ public class ZooInspectorPanel extends JPanel implements
protected void done() {
try {
if (get()) {
- treeViewer.clearView();
+ treeViewer.clear();
toolbar.toggleButtons(false);
}
- } catch (InterruptedException e) {
- LoggerFactory
- .getLogger()
- .error(
- "Error occurred while disconnecting from ZooKeeper server",
- e);
- } catch (ExecutionException e) {
+ } catch (InterruptedException | ExecutionException e) {
LoggerFactory
.getLogger()
.error(
diff --git a/zookeeper-contrib/zookeeper-contrib-zooinspector/src/main/java/org/apache/zookeeper/inspector/gui/ZooInspectorTreeView.java b/zookeeper-contrib/zookeeper-contrib-zooinspector/src/main/java/org/apache/zookeeper/inspector/gui/ZooInspectorTreeView.java
new file mode 100644
index 000000000..6bff1ca43
--- /dev/null
+++ b/zookeeper-contrib/zookeeper-contrib-zooinspector/src/main/java/org/apache/zookeeper/inspector/gui/ZooInspectorTreeView.java
@@ -0,0 +1,622 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.apache.zookeeper.inspector.gui;
+
+import com.nitido.utils.toaster.Toaster;
+import org.apache.zookeeper.inspector.gui.nodeviewer.NodeSelectionListener;
+import org.apache.zookeeper.inspector.manager.NodeListener;
+import org.apache.zookeeper.inspector.manager.Pair;
+import org.apache.zookeeper.inspector.manager.ZooInspectorManager;
+
+import javax.swing.ImageIcon;
+import javax.swing.JMenuItem;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JPopupMenu;
+import javax.swing.JTree;
+import javax.swing.SwingUtilities;
+import javax.swing.SwingWorker;
+import javax.swing.event.TreeExpansionEvent;
+import javax.swing.event.TreeExpansionListener;
+import javax.swing.event.TreeSelectionEvent;
+import javax.swing.event.TreeSelectionListener;
+import javax.swing.tree.DefaultMutableTreeNode;
+import javax.swing.tree.DefaultTreeCellRenderer;
+import javax.swing.tree.DefaultTreeModel;
+import javax.swing.tree.TreePath;
+import javax.swing.tree.TreeSelectionModel;
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.Cursor;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.KeyAdapter;
+import java.awt.event.KeyEvent;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+public class ZooInspectorTreeView extends JPanel {
+ private static final String PATH_SEPARATOR = "/";
+ private static final String ROOT_PATH = PATH_SEPARATOR;
+
+ private final JTree tree;
+ private final ZooInspectorTreeModel treeModel;
+
+ private final JPopupMenu rightClickMenu;
+ private final JMenuItem createChildNodeMenuItem;
+ private final JMenuItem deleteNodeMenuItem;
+ private final JMenuItem refreshNodeMenuItem;
+ private final JMenuItem addWatchMenuItem;
+ private final JMenuItem removeWatchMenuItem;
+
+ private final Toaster toasterManager;
+ private final ImageIcon toasterIcon;
+
+ private final List<NodeSelectionListener> nodeSelectionListeners = new LinkedList<>();
+
+ public ZooInspectorTreeView(final ZooInspectorManager manager, IconResource iconResource) {
+ this.toasterManager = new Toaster();
+ this.toasterManager.setBorderColor(Color.BLACK);
+ this.toasterManager.setMessageColor(Color.BLACK);
+ this.toasterManager.setToasterColor(Color.WHITE);
+ this.toasterIcon = iconResource.get(IconResource.ICON_INFORMATION, "");
+
+ // Set up tree to display all ZNodes
+ this.treeModel = new ZooInspectorTreeModel(manager);
+ this.tree = new JTree();
+ this.tree.setEditable(false);
+ this.tree.setFocusable(true);
+ this.tree.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
+ this.tree.setModel(this.treeModel);
+ this.tree.setCellRenderer(new ZooInspectorTreeCellRenderer(iconResource));
+
+ // Set up right click menu for individual ZNodes
+ this.rightClickMenu = new JPopupMenu();
+ this.createChildNodeMenuItem = new JMenuItem("Create Child Node");
+ this.deleteNodeMenuItem = new JMenuItem("Delete Node");
+ this.refreshNodeMenuItem = new JMenuItem("Refresh Node");
+ this.addWatchMenuItem = new JMenuItem("Add Watch");
+ this.removeWatchMenuItem = new JMenuItem("Remove Watch");
+
+ // Add various event listeners (all implemented as inner classes on this class)
+ TreeEventHandler treeHandler = new TreeEventHandler();
+ MouseEventHandler mouseHandler = new MouseEventHandler();
+ KeyEventHandler keyHandler = new KeyEventHandler();
+ ActionEventHandler actionHandler = new ActionEventHandler();
+
+ this.tree.addKeyListener(keyHandler);
+ this.tree.addTreeExpansionListener(treeHandler);
+ this.tree.getSelectionModel().addTreeSelectionListener(treeHandler);
+ this.tree.addMouseListener(mouseHandler);
+ this.rightClickMenu.add(this.createChildNodeMenuItem).addActionListener(actionHandler);
+ this.rightClickMenu.add(this.deleteNodeMenuItem).addActionListener(actionHandler);
+ this.rightClickMenu.add(this.refreshNodeMenuItem).addActionListener(actionHandler);
+ this.rightClickMenu.add(this.addWatchMenuItem).addActionListener(actionHandler);
+ this.rightClickMenu.add(this.removeWatchMenuItem).addActionListener(actionHandler);
+
+ setLayout(new BorderLayout());
+ add(this.tree, BorderLayout.CENTER);
+ }
+
+ public void addNodeSelectionListener(NodeSelectionListener l) {
+ if (!this.nodeSelectionListeners.contains(l)) {
+ this.nodeSelectionListeners.add(l);
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public void removeNodeSelectionListener(NodeSelectionListener l) {
+ this.nodeSelectionListeners.remove(l);
+ }
+
+ ///////////////////////////////// EVENT HANDLERS /////////////////////////////////
+
+ private class NodeEventHandler implements NodeListener {
+ @Override
+ public void processEvent(String nodePath, String eventType, Map<String, String> eventInfo) {
+ StringBuilder sb = new StringBuilder(256);
+ sb.append("Node: ");
+ sb.append(nodePath);
+ sb.append("\nEvent: ");
+ sb.append(eventType);
+ if (eventInfo != null) {
+ for (Map.Entry<String, String> entry : eventInfo.entrySet()) {
+ sb.append("\n");
+ sb.append(entry.getKey());
+ sb.append(": ");
+ sb.append(entry.getValue());
+ }
+ }
+ toasterManager.showToaster(toasterIcon, sb.toString());
+ }
+ }
+
+ public class TreeEventHandler implements TreeExpansionListener, TreeSelectionListener {
+ @Override
+ public void treeExpanded(TreeExpansionEvent event) {
+ ZooInspectorTreeNode expandingNode = (ZooInspectorTreeNode) event.getPath().getLastPathComponent();
+
+ // This whole chunk of code before the "refreshNode" call is to deal with the fact that when lazy-loading a
+ // node, we first give it a single "placeholder" empty child node to mark it as having children, but we don't
+ // actually load those children until the user decides to expand it. These "if" statements figure out if the
+ // node has a single placeholder child or not.
+
+ if (expandingNode.isLeaf() || expandingNode.getChildCount() != 1) {
+ return;
+ }
+
+ ZooInspectorTreeNode onlyChild = ((ZooInspectorTreeNode) expandingNode.getChildAt(0));
+ if (!onlyChild.isPlaceholder()) {
+ return;
+ }
+
+ treeModel.refreshNode(expandingNode);
+ }
+
+ @Override
+ public void treeCollapsed(TreeExpansionEvent event) {
+ }
+
+ @Override
+ public void valueChanged(TreeSelectionEvent e) {
+ ZooInspectorTreeNode node = (ZooInspectorTreeNode) e.getPath().getLastPathComponent();
+ String selectedPath = node.getPathString();
+ for (NodeSelectionListener listener : nodeSelectionListeners) {
+ listener.nodePathSelected(selectedPath);
+ }
+ }
+ }
+
+ private class KeyEventHandler extends KeyAdapter {
+ @Override
+ public void keyReleased(KeyEvent e) {
+ if (!tree.hasFocus()) {
+ return;
+ }
+
+ switch (e.getKeyCode()) {
+ case KeyEvent.VK_D:
+ deleteNode();
+ break;
+ case KeyEvent.VK_N:
+ createNode();
+ break;
+ case KeyEvent.VK_R:
+ refreshNode();
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+ private class ActionEventHandler implements ActionListener {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ if (e.getSource() == createChildNodeMenuItem) {
+ createNode();
+ } else if (e.getSource() == deleteNodeMenuItem) {
+ deleteNode();
+ } else if (e.getSource() == refreshNodeMenuItem) {
+ refreshNode();
+ } else if (e.getSource() == addWatchMenuItem) {
+ addWatch();
+ } else if (e.getSource() == removeWatchMenuItem) {
+ removeWatch();
+ }
+ }
+ }
+
+ private class MouseEventHandler extends MouseAdapter {
+
+ @Override
+ public void mouseClicked(MouseEvent e) {
+ ZooInspectorTreeNode selectedNode = getSelectedNode();
+ if (selectedNode == null) {
+ //If there's no node currently selected, see if the mouse is on top of one and select it
+ int selectedRow = tree.getRowForLocation(e.getX(), e.getY());
+ if (selectedRow == -1) {
+ return;
+ }
+ tree.setSelectionRow(selectedRow);
+
+ TreePath selectedPath = tree.getPathForLocation(e.getX(), e.getY());
+ tree.setSelectionPath(selectedPath);
+ }
+
+ boolean shouldShowPopup = e.isPopupTrigger() || SwingUtilities.isRightMouseButton(e);
+ if (shouldShowPopup) {
+ rightClickMenu.show(ZooInspectorTreeView.this, e.getX(), e.getY());
+ }
+ }
+ }
+
+ ///////////////////////////////// BUSINESS LOGIC /////////////////////////////////
+
+ /**
+ * Initialize the view by creating a completely new tree by removing and refreshing all nodes.
+ */
+ public void initialize() {
+ this.treeModel.init();
+ }
+
+ /**
+ * Clear all the existing nodes from the tree view.
+ */
+ public void clear() {
+ this.treeModel.clear();
+ }
+
+ /**
+ * Start the UI workflow for creating a new ZNode as a child of the selected node.
+ */
+ public void createNode() {
+ ZooInspectorTreeNode parentNode = getSelectedNode();
+ if (parentNode == null) {
+ return;
+ }
+
+ final String newNodeName = JOptionPane.showInputDialog(
+ this,
+ "Please enter a name for the new node: ",
+ "Create Child Node",
+ JOptionPane.INFORMATION_MESSAGE);
+
+ if (newNodeName == null || newNodeName.trim().isEmpty()) {
+ return;
+ }
+
+ this.treeModel.createNode(parentNode, newNodeName);
+ }
+
+ /**
+ * Start the UI workflow for deleting the selected node.
+ */
+ public void deleteNode() {
+ ZooInspectorTreeNode nodeToDelete = getSelectedNode();
+ if (nodeToDelete == null) {
+ return;
+ }
+
+ int answer = JOptionPane.showConfirmDialog(
+ this,
+ "Are you sure you want to delete the selected node '" + nodeToDelete.getPathString() + "'?\n" +
+ "(This action cannot be reverted)",
+ "Confirm Delete",
+ JOptionPane.YES_NO_OPTION,
+ JOptionPane.WARNING_MESSAGE
+ );
+
+ if (answer != JOptionPane.YES_OPTION) {
+ return;
+ }
+
+ this.treeModel.deleteNode(nodeToDelete);
+ }
+
+ /**
+ * Refresh the selected node (i.e. deleting all children and re-fetch them from Zookeeper).
+ */
+ public void refreshNode() {
+ ZooInspectorTreeNode nodeToRefresh = getSelectedNode();
+ if (nodeToRefresh == null) {
+ return;
+ }
+ this.treeModel.refreshNode(nodeToRefresh);
+ }
+
+ /**
+ * Add a Zookeeper watch to the selected node.
+ */
+ public void addWatch() {
+ ZooInspectorTreeNode nodeToWatch = getSelectedNode();
+ if (nodeToWatch == null) {
+ return;
+ }
+ this.treeModel.addWatch(nodeToWatch);
+ }
+
+ /**
+ * Remove a Zookeeper watch from the selected node (has no effect if the node does not have an existing watch).
+ */
+ public void removeWatch() {
+ ZooInspectorTreeNode nodeToUnwatch = getSelectedNode();
+ if (nodeToUnwatch == null) {
+ return;
+ }
+ this.treeModel.removeWatch(nodeToUnwatch);
+ }
+
+ /**
+ * @return The node object corresponding to the selected node or null if there is no node selected.
+ */
+ private ZooInspectorTreeNode getSelectedNode() {
+ TreePath selected = this.tree.getSelectionPath();
+ return selected != null ? ((ZooInspectorTreeNode) selected.getLastPathComponent()) : null;
+ }
+
+ ///////////////////////////////// BACKING DATA MODEL /////////////////////////////////
+
+ /**
+ * An implement of the backing TreeModel data for the JTree in the user interface. Controls what data is actually
+ * available to be rendered in the UI.
+ */
+ private class ZooInspectorTreeModel extends DefaultTreeModel {
+ private final ZooInspectorManager manager;
+
+ public ZooInspectorTreeModel(ZooInspectorManager manager) {
+ super(new ZooInspectorTreeNode(ROOT_PATH, ROOT_PATH, 0));
+ this.manager = manager;
+ }
+
+ /**
+ * Create a new ZNode in Zookeeper as a child of the given parent node.
+ *
+ * @param parentNode The parent node to create a new child ZNode underneath
+ * @param newNodeName The name of the new child ZNode to create
+ */
+ public void createNode(ZooInspectorTreeNode parentNode, String newNodeName) {
+ SwingWorker<Boolean, Void> worker = new SwingWorker<Boolean, Void>() {
+ @Override
+ protected Boolean doInBackground() {
+ //runs on a background non-UI thread
+ return manager.createNode(parentNode.getPathString(), newNodeName);
+ }
+
+ @Override
+ protected void done() {
+ //runs on the UI event thread
+
+ //extra logic to find the correct spot alphabetically to insert the new node in the tree`
+ int i = 0;
+ for (; i < parentNode.getChildCount(); i++) {
+ ZooInspectorTreeNode existingChild = (ZooInspectorTreeNode) parentNode.getChildAt(i);
+ if (newNodeName.compareTo(existingChild.getName()) < 0) {
+ break;
+ }
+ }
+ insertNodeInto(new ZooInspectorTreeNode(newNodeName, parentNode, 0), parentNode, i);
+ parentNode.setNumDisplayChildren(parentNode.getNumDisplayChildren() + 1);
+ getRootPane().setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
+ }
+ };
+ getRootPane().setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
+ worker.execute();
+ }
+
+ /**
+ * Delete the specified ZNode in Zookeeper.
+ *
+ * @param nodeToDelete The node to delete.
+ */
+ public void deleteNode(ZooInspectorTreeNode nodeToDelete) {
+ SwingWorker<Boolean, Void> worker = new SwingWorker<Boolean, Void>() {
+ @Override
+ protected Boolean doInBackground() {
+ //runs on a background non-UI thread
+ return manager.deleteNode(nodeToDelete.getPathString());
+ }
+
+ @Override
+ protected void done() {
+ //runs on the UI event thread
+ ZooInspectorTreeNode parent = (ZooInspectorTreeNode) nodeToDelete.getParent();
+ parent.setNumDisplayChildren(parent.getNumDisplayChildren() - 1);
+ removeNodeFromParent(nodeToDelete);
+ getRootPane().setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
+ }
+ };
+ getRootPane().setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
+ worker.execute();
+ }
+
+ /**
+ * Refresh the specified node in the UI. Refresh is equivalent to removing all existing children from the UI
+ * view and re-fetching them from Zookeeper.
+ *
+ * @param nodeToRefresh The node whose subtree should be refreshed.
+ */
+ public void refreshNode(ZooInspectorTreeNode nodeToRefresh) {
+ SwingWorker<Boolean, Void> worker = new SwingWorker<Boolean, Void>() {
+ final LinkedList<Pair<String, Integer>> childrenToAdd = new LinkedList<>();
+
+ @Override
+ protected Boolean doInBackground() {
+ //runs on a background non-UI thread
+ //Make all the network calls here (to get children and their child counts) and collect the children,
+ //but we can't add them to the UI until we're back on the event thread in done()
+ List<String> children = manager.getChildren(nodeToRefresh.getPathString());
+ if (children == null) {
+ return false;
+ }
+ nodeToRefresh.setNumDisplayChildren(children.size());
+
+ for (String childName : children) {
+ ZooInspectorTreeNode childNode = new ZooInspectorTreeNode(childName, nodeToRefresh, 0);
+ int numChildren = manager.getNumChildren(childNode.getPathString());
+ childrenToAdd.add(new Pair<>(childName, numChildren));
+ }
+ return true;
+ }
+
+ @Override
+ protected void done() {
+ //runs on the UI event thread
+ nodeToRefresh.removeAllChildren();
+
+ for (Pair<String, Integer> childPair : childrenToAdd) {
+ ZooInspectorTreeNode childNode = new ZooInspectorTreeNode(childPair.getKey(),
+ nodeToRefresh,
+ childPair.getValue());
+
+ if (childPair.getValue() > 0) {
+ // add a placeholder child so the UI renders this node like it has children
+ childNode.add(new ZooInspectorTreeNode());
+ }
+ nodeToRefresh.add(childNode);
+ }
+
+ reload(nodeToRefresh);
+ getRootPane().setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
+ }
+ };
+ getRootPane().setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
+ worker.execute();
+ }
+
+ /**
+ * Add a Zookeeper watch to the specified node (which will display updates in a Toast)
+ *
+ * @param nodeToWatch A reference to the node to place a watch on
+ */
+ public void addWatch(ZooInspectorTreeNode nodeToWatch) {
+ this.manager.addWatchers(Collections.singletonList(nodeToWatch.getPathString()), new NodeEventHandler());
+ }
+
+ /**
+ * Remove a Zookeeper watch from the specified node (has no effect if this node has no existing watch)
+ *
+ * @param nodeToUnwatch A reference to the node to remove a watch from
+ */
+ public void removeWatch(ZooInspectorTreeNode nodeToUnwatch) {
+ this.manager.removeWatchers(Collections.singletonList(nodeToUnwatch.getPathString()));
+ }
+
+ /**
+ * Reinitialize the entire tree by refreshing the root node.
+ */
+ public void init() {
+ refreshNode((ZooInspectorTreeNode) getRoot());
+ }
+
+ /**
+ * Clear the tree by removing all children from the root node.
+ */
+ public void clear() {
+ ZooInspectorTreeNode root = (ZooInspectorTreeNode) getRoot();
+ root.setNumDisplayChildren(0);
+ root.removeAllChildren();
+ reload();
+ }
+ }
+
+ /**
+ * A representation of a single node in the TreeModel. Keeps track of a node's name, the number of children
+ * it has and its full Zookeeper path.
+ */
+ private static class ZooInspectorTreeNode extends DefaultMutableTreeNode {
+ private final String name;
+ private final String pathString;
+ private int numDisplayChildren;
+
+ public ZooInspectorTreeNode() {
+ this("", "", 0);
+ }
+
+ public ZooInspectorTreeNode(String name, ZooInspectorTreeNode parent, int numDisplayChildren) {
+ this(name, (PATH_SEPARATOR.equals(parent.getName()) ? "" : parent.getPathString()) + PATH_SEPARATOR + name, numDisplayChildren);
+ }
+
+ public ZooInspectorTreeNode(String name, String pathString, int numDisplayChildren) {
+ this.name = name;
+ this.pathString = pathString;
+ this.numDisplayChildren = numDisplayChildren;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+
+ ZooInspectorTreeNode that = (ZooInspectorTreeNode) o;
+ return this.pathString.equals(that.pathString);
+ }
+
+ @Override
+ public int hashCode() {
+ return this.pathString.hashCode();
+ }
+
+ public boolean isPlaceholder() {
+ //A placeholder node renders as "Loading..." on the UI and is identified by having an empty name and path
+ return this.name.isEmpty() && this.getPathString().isEmpty();
+ }
+
+ public String getName() {
+ return this.name;
+ }
+
+ public String getPathString() {
+ return this.pathString;
+ }
+
+ public int getNumDisplayChildren() {
+ return this.numDisplayChildren;
+ }
+
+ public void setNumDisplayChildren(int numDisplayChildren) {
+ this.numDisplayChildren = numDisplayChildren;
+ }
+
+ @Override
+ public String toString() {
+ //NOTE: Don't mess with this; it's actually used to construct the TreePath entries; if you want to
+ //change the name on the UI display, use the TreeCellRenderer below
+ return this.name;
+ }
+ }
+
+ /**
+ * A class that controls how a given tree node is rendered in the tree on the UI (this is what's responsible for
+ * drawing "Loading..." for a placeholder node or render the # of children for a node).
+ */
+ private static class ZooInspectorTreeCellRenderer extends DefaultTreeCellRenderer {
+ public ZooInspectorTreeCellRenderer(IconResource iconResource) {
+ setLeafIcon(iconResource.get(IconResource.ICON_TREE_LEAF, ""));
+ setOpenIcon(iconResource.get(IconResource.ICON_TREE_OPEN, ""));
+ setClosedIcon(iconResource.get(IconResource.ICON_TREE_CLOSE, ""));
+ }
+
+ @Override
+ public Component getTreeCellRendererComponent(JTree tree, Object value, boolean sel, boolean expanded,
+ boolean leaf, int row, boolean hasFocus) {
+ ZooInspectorTreeNode node = (ZooInspectorTreeNode) value;
+ String text = node.getName();
+
+ if (node.isPlaceholder()) {
+ text = "Loading...";
+ }
+
+ if (node.getNumDisplayChildren() > 0) {
+ text += " (" + node.getNumDisplayChildren() + ")";
+ }
+
+ return super.getTreeCellRendererComponent(tree, text, sel, expanded, leaf, row, hasFocus);
+ }
+ }
+}
diff --git a/zookeeper-contrib/zookeeper-contrib-zooinspector/src/main/java/org/apache/zookeeper/inspector/gui/ZooInspectorTreeViewer.java b/zookeeper-contrib/zookeeper-contrib-zooinspector/src/main/java/org/apache/zookeeper/inspector/gui/ZooInspectorTreeViewer.java
deleted file mode 100644
index e08f2d3b7..000000000
--- a/zookeeper-contrib/zookeeper-contrib-zooinspector/src/main/java/org/apache/zookeeper/inspector/gui/ZooInspectorTreeViewer.java
+++ /dev/null
@@ -1,384 +0,0 @@
-/**
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you 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.apache.zookeeper.inspector.gui;
-
-import java.awt.BorderLayout;
-import java.awt.Color;
-import java.awt.event.ActionEvent;
-import java.awt.event.ActionListener;
-import java.awt.event.InputEvent;
-import java.awt.event.KeyEvent;
-import java.awt.event.MouseAdapter;
-import java.awt.event.MouseEvent;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Enumeration;
-import java.util.LinkedHashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-import javax.swing.ImageIcon;
-import javax.swing.JMenuItem;
-import javax.swing.JPanel;
-import javax.swing.JPopupMenu;
-import javax.swing.JComponent;
-import javax.swing.JTree;
-import javax.swing.SwingWorker;
-import javax.swing.event.TreeSelectionListener;
-import javax.swing.tree.DefaultMutableTreeNode;
-import javax.swing.tree.DefaultTreeCellRenderer;
-import javax.swing.tree.DefaultTreeModel;
-import javax.swing.tree.TreeNode;
-import javax.swing.tree.TreePath;
-
-import org.apache.zookeeper.inspector.gui.actions.AddNodeAction;
-import org.apache.zookeeper.inspector.gui.actions.DeleteNodeAction;
-import org.apache.zookeeper.inspector.manager.NodeListener;
-import org.apache.zookeeper.inspector.manager.ZooInspectorManager;
-
-import com.nitido.utils.toaster.Toaster;
-import static javax.swing.KeyStroke.getKeyStroke;
-
-/**
- * A {@link JPanel} for showing the tree view of all the nodes in the zookeeper
- * instance
- */
-public class ZooInspectorTreeViewer extends JPanel implements NodeListener {
- private final ZooInspectorManager zooInspectorManager;
- private final JTree tree;
- private final Toaster toasterManager;
- private final ImageIcon toasterIcon;
-
- /**
- * @param zooInspectorManager
- * - the {@link ZooInspectorManager} for the application
- * @param listener
- * - the {@link TreeSelectionListener} to listen for changes in
- * the selected node on the node tree
- */
- public ZooInspectorTreeViewer(
- final ZooInspectorManager zooInspectorManager,
- TreeSelectionListener listener, IconResource iconResource) {
-
- this.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW)
- .put(getKeyStroke(KeyEvent.VK_D, InputEvent.CTRL_MASK), "deleteNode");
-
- this.getActionMap().put("deleteNode",
- new DeleteNodeAction(this, this, zooInspectorManager));
-
- this.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW)
- .put(getKeyStroke(KeyEvent.VK_N, InputEvent.CTRL_MASK), "addNode");
-
- this.getActionMap().put("addNode",
- new AddNodeAction(this, this, zooInspectorManager));
-
- this.zooInspectorManager = zooInspectorManager;
- this.setLayout(new BorderLayout());
- final JPopupMenu popupMenu = new JPopupMenu();
-
- final JMenuItem addNode = new JMenuItem("Add Node");
- addNode.addActionListener(new AddNodeAction(this, this, zooInspectorManager));
-
- final JMenuItem deleteNode = new JMenuItem("Delete Node");
- deleteNode.addActionListener(new DeleteNodeAction(this, this, zooInspectorManager));
-
- final JMenuItem addNotify = new JMenuItem("Add Change Notification");
- this.toasterManager = new Toaster();
- this.toasterManager.setBorderColor(Color.BLACK);
- this.toasterManager.setMessageColor(Color.BLACK);
- this.toasterManager.setToasterColor(Color.WHITE);
- toasterIcon = iconResource.get(IconResource.ICON_INFORMATION,"");
- addNotify.addActionListener(new ActionListener() {
- public void actionPerformed(ActionEvent e) {
- List<String> selectedNodes = getSelectedNodes();
- zooInspectorManager.addWatchers(selectedNodes,
- ZooInspectorTreeViewer.this);
- }
- });
- final JMenuItem removeNotify = new JMenuItem(
- "Remove Change Notification");
- removeNotify.addActionListener(new ActionListener() {
- public void actionPerformed(ActionEvent e) {
- List<String> selectedNodes = getSelectedNodes();
- zooInspectorManager.removeWatchers(selectedNodes);
- }
- });
- tree = new JTree(new DefaultMutableTreeNode());
- tree.setCellRenderer(new ZooInspectorTreeCellRenderer(iconResource));
- tree.setEditable(false);
- tree.getSelectionModel().addTreeSelectionListener(listener);
- tree.addMouseListener(new MouseAdapter() {
- @Override
- public void mouseClicked(MouseEvent e) {
- if (e.isPopupTrigger() || e.getButton() == MouseEvent.BUTTON3) {
- // TODO only show add if a selected node isn't being
- // watched, and only show remove if a selected node is being
- // watched
- popupMenu.removeAll();
- popupMenu.add(addNode);
- popupMenu.add(deleteNode);
- popupMenu.add(addNotify);
- popupMenu.add(removeNotify);
- popupMenu.show(ZooInspectorTreeViewer.this, e.getX(), e
- .getY());
- }
- }
- });
- this.add(tree, BorderLayout.CENTER);
- }
-
- /**
- * Refresh the tree view
- */
- public void refreshView() {
- final Set<TreePath> expandedNodes = new LinkedHashSet<TreePath>();
- int rowCount = tree.getRowCount();
- for (int i = 0; i < rowCount; i++) {
- TreePath path = tree.getPathForRow(i);
- if (tree.isExpanded(path)) {
- expandedNodes.add(path);
- }
- }
- final TreePath[] selectedNodes = tree.getSelectionPaths();
- SwingWorker<Boolean, Void> worker = new SwingWorker<Boolean, Void>() {
-
- @Override
- protected Boolean doInBackground() throws Exception {
- tree.setModel(new DefaultTreeModel(new ZooInspectorTreeNode(
- "/", null)));
- return true;
- }
-
- @Override
- protected void done() {
- for (TreePath path : expandedNodes) {
- tree.expandPath(path);
- }
- tree.getSelectionModel().setSelectionPaths(selectedNodes);
- }
- };
- worker.execute();
- }
-
- /**
- * clear the tree view of all nodes
- */
- public void clearView() {
- tree.setModel(new DefaultTreeModel(new DefaultMutableTreeNode()));
- }
-
- private static class ZooInspectorTreeCellRenderer extends
- DefaultTreeCellRenderer {
- public ZooInspectorTreeCellRenderer(IconResource iconResource) {
- setLeafIcon(iconResource.get(IconResource.ICON_TREE_LEAF,""));
- setOpenIcon(iconResource.get(IconResource.ICON_TREE_OPEN,""));
- setClosedIcon(iconResource.get(IconResource.ICON_TREE_CLOSE,""));
- }
- }
-
- private class ZooInspectorTreeNode implements TreeNode {
- private final String nodePath;
- private final String nodeName;
- private final ZooInspectorTreeNode parent;
-
- public ZooInspectorTreeNode(String nodePath, ZooInspectorTreeNode parent) {
- this.parent = parent;
- this.nodePath = nodePath;
- int index = nodePath.lastIndexOf("/");
- if (index == -1) {
- throw new IllegalArgumentException("Invalid node path"
- + nodePath);
- }
- this.nodeName = nodePath.substring(index + 1);
- }
-
- /*
- * (non-Javadoc)
- *
- * @see javax.swing.tree.TreeNode#children()
- */
- public Enumeration<TreeNode> children() {
- List<String> children = zooInspectorManager
- .getChildren(this.nodePath);
- Collections.sort(children);
- List<TreeNode> returnChildren = new ArrayList<TreeNode>();
- for (String child : children) {
- returnChildren.add(new ZooInspectorTreeNode((this.nodePath
- .equals("/") ? "" : this.nodePath)
- + "/" + child, this));
- }
- return Collections.enumeration(returnChildren);
- }
-
- /*
- * (non-Javadoc)
- *
- * @see javax.swing.tree.TreeNode#getAllowsChildren()
- */
- public boolean getAllowsChildren() {
- return zooInspectorManager.isAllowsChildren(this.nodePath);
- }
-
- /*
- * (non-Javadoc)
- *
- * @see javax.swing.tree.TreeNode#getChildAt(int)
- */
- public TreeNode getChildAt(int childIndex) {
- String child = zooInspectorManager.getNodeChild(this.nodePath,
- childIndex);
- if (child != null) {
- return new ZooInspectorTreeNode((this.nodePath.equals("/") ? ""
- : this.nodePath)
- + "/" + child, this);
- }
- return null;
- }
-
- /*
- * (non-Javadoc)
- *
- * @see javax.swing.tree.TreeNode#getChildCount()
- */
- public int getChildCount() {
- return zooInspectorManager.getNumChildren(this.nodePath);
- }
-
- /*
- * (non-Javadoc)
- *
- * @see javax.swing.tree.TreeNode#getIndex(javax.swing.tree.TreeNode)
- */
- public int getIndex(TreeNode node) {
- return zooInspectorManager.getNodeIndex(this.nodePath);
- }
-
- /*
- * (non-Javadoc)
- *
- * @see javax.swing.tree.TreeNode#getParent()
- */
- public TreeNode getParent() {
- return this.parent;
- }
-
- /*
- * (non-Javadoc)
- *
- * @see javax.swing.tree.TreeNode#isLeaf()
- */
- public boolean isLeaf() {
- return !zooInspectorManager.hasChildren(this.nodePath);
- }
-
- @Override
- public String toString() {
- return this.nodeName;
- }
-
- @Override
- public int hashCode() {
- final int prime = 31;
- int result = 1;
- result = prime * result + getOuterType().hashCode();
- result = prime * result
- + ((nodePath == null) ? 0 : nodePath.hashCode());
- result = prime * result
- + ((parent == null) ? 0 : parent.hashCode());
- return result;
- }
-
- @Override
- public boolean equals(Object obj) {
- if (this == obj)
- return true;
- if (obj == null)
- return false;
- if (getClass() != obj.getClass())
- return false;
- ZooInspectorTreeNode other = (ZooInspectorTreeNode) obj;
- if (!getOuterType().equals(other.getOuterType()))
- return false;
- if (nodePath == null) {
- if (other.nodePath != null)
- return false;
- } else if (!nodePath.equals(other.nodePath))
- return false;
- if (parent == null) {
- if (other.parent != null)
- return false;
- } else if (!parent.equals(other.parent))
- return false;
- return true;
- }
-
- private ZooInspectorTreeViewer getOuterType() {
- return ZooInspectorTreeViewer.this;
- }
-
- }
-
- /**
- * @return {@link List} of the currently selected nodes
- */
- public List<String> getSelectedNodes() {
- TreePath[] paths = tree.getSelectionPaths();
- List<String> selectedNodes = new ArrayList<String>();
- if (paths != null) {
- for (TreePath path : paths) {
- StringBuilder sb = new StringBuilder();
- Object[] pathArray = path.getPath();
- for (Object o : pathArray) {
- String nodeName = o.toString();
- if (nodeName.length() > 0) {
- sb.append("/");
- sb.append(o.toString());
- }
- }
- selectedNodes.add(sb.toString());
- }
- }
- return selectedNodes;
- }
-
- /*
- * (non-Javadoc)
- *
- * @see
- * org.apache.zookeeper.inspector.manager.NodeListener#processEvent(java
- * .lang.String, java.lang.String, java.util.Map)
- */
- public void processEvent(String nodePath, String eventType,
- Map<String, String> eventInfo) {
- StringBuilder sb = new StringBuilder();
- sb.append("Node: ");
- sb.append(nodePath);
- sb.append("\nEvent: ");
- sb.append(eventType);
- if (eventInfo != null) {
- for (Map.Entry<String, String> entry : eventInfo.entrySet()) {
- sb.append("\n");
- sb.append(entry.getKey());
- sb.append(": ");
- sb.append(entry.getValue());
- }
- }
- this.toasterManager.showToaster(toasterIcon, sb.toString());
- }
-}
diff --git a/zookeeper-contrib/zookeeper-contrib-zooinspector/src/main/java/org/apache/zookeeper/inspector/gui/actions/AddNodeAction.java b/zookeeper-contrib/zookeeper-contrib-zooinspector/src/main/java/org/apache/zookeeper/inspector/gui/actions/AddNodeAction.java
deleted file mode 100644
index 30916112a..000000000
--- a/zookeeper-contrib/zookeeper-contrib-zooinspector/src/main/java/org/apache/zookeeper/inspector/gui/actions/AddNodeAction.java
+++ /dev/null
@@ -1,74 +0,0 @@
-/**
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you 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.apache.zookeeper.inspector.gui.actions;
-
-import org.apache.zookeeper.inspector.gui.ZooInspectorPanel;
-import org.apache.zookeeper.inspector.gui.ZooInspectorTreeViewer;
-import org.apache.zookeeper.inspector.manager.ZooInspectorManager;
-
-import javax.swing.*;
-import java.awt.event.ActionEvent;
-import java.awt.event.ActionListener;
-import java.util.List;
-import java.awt.event.KeyEvent;
-
-public class AddNodeAction extends AbstractAction {
-
- private JPanel panel;
- private ZooInspectorTreeViewer treeViewer;
- private ZooInspectorManager zooInspectorManager;
-
- public AddNodeAction(JPanel parentPanel,
- ZooInspectorTreeViewer treeViewer,
- ZooInspectorManager zooInspectorManager) {
- this.panel = parentPanel;
- this.treeViewer = treeViewer;
- this.zooInspectorManager = zooInspectorManager;
- }
-
- public void actionPerformed(ActionEvent e) {
- final List<String> selectedNodes = treeViewer
- .getSelectedNodes();
- if (selectedNodes.size() == 1) {
- final String nodeName = JOptionPane.showInputDialog(
- panel,
- "Please Enter a name for the new node",
- "Create Node", JOptionPane.INFORMATION_MESSAGE);
- if (nodeName != null && nodeName.length() > 0) {
- SwingWorker<Boolean, Void> worker = new SwingWorker<Boolean, Void>() {
-
- @Override
- protected Boolean doInBackground() throws Exception {
- return zooInspectorManager
- .createNode(selectedNodes.get(0),
- nodeName);
- }
-
- @Override
- protected void done() {
- treeViewer.refreshView();
- }
- };
- worker.execute();
- }
- } else {
- JOptionPane.showMessageDialog(panel,
- "Please select 1 parent node for the new node.");
- }
- }
-}
diff --git a/zookeeper-contrib/zookeeper-contrib-zooinspector/src/main/java/org/apache/zookeeper/inspector/gui/actions/DeleteNodeAction.java b/zookeeper-contrib/zookeeper-contrib-zooinspector/src/main/java/org/apache/zookeeper/inspector/gui/actions/DeleteNodeAction.java
deleted file mode 100644
index 90016701e..000000000
--- a/zookeeper-contrib/zookeeper-contrib-zooinspector/src/main/java/org/apache/zookeeper/inspector/gui/actions/DeleteNodeAction.java
+++ /dev/null
@@ -1,79 +0,0 @@
-/**
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you 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.apache.zookeeper.inspector.gui.actions;
-
-import org.apache.zookeeper.inspector.gui.ZooInspectorTreeViewer;
-import org.apache.zookeeper.inspector.manager.ZooInspectorManager;
-
-import javax.swing.*;
-import java.awt.event.ActionEvent;
-import java.awt.event.ActionListener;
-import java.util.List;
-import java.awt.event.KeyEvent;
-
-public class DeleteNodeAction extends AbstractAction {
-
- private JPanel parentPanel;
- private ZooInspectorTreeViewer treeViewer;
- private ZooInspectorManager zooInspectorManager;
-
- public DeleteNodeAction(JPanel parentPanel,
- ZooInspectorTreeViewer treeViewer,
- ZooInspectorManager zooInspectorManager) {
- this.parentPanel = parentPanel;
- this.treeViewer = treeViewer;
- this.zooInspectorManager = zooInspectorManager;
- }
-
-
- public void actionPerformed(ActionEvent e) {
- final List<String> selectedNodes = treeViewer
- .getSelectedNodes();
- if (selectedNodes.size() == 0) {
- JOptionPane.showMessageDialog(parentPanel,
- "Please select at least 1 node to be deleted");
- } else {
- int answer = JOptionPane.showConfirmDialog(
- parentPanel,
- "Are you sure you want to delete the selected nodes?"
- + "(This action cannot be reverted)",
- "Confirm Delete", JOptionPane.YES_NO_OPTION,
- JOptionPane.WARNING_MESSAGE
- );
- if (answer == JOptionPane.YES_OPTION) {
- SwingWorker<Boolean, Void> worker = new SwingWorker<Boolean, Void>() {
-
- @Override
- protected Boolean doInBackground() throws Exception {
- for (String nodePath : selectedNodes) {
- zooInspectorManager
- .deleteNode(nodePath);
- }
- return true;
- }
-
- @Override
- protected void done() {
- treeViewer.refreshView();
- }
- };
- worker.execute();
- }
- }
- }
-}
diff --git a/zookeeper-contrib/zookeeper-contrib-zooinspector/src/main/java/org/apache/zookeeper/inspector/gui/nodeviewer/NodeSelectionListener.java b/zookeeper-contrib/zookeeper-contrib-zooinspector/src/main/java/org/apache/zookeeper/inspector/gui/nodeviewer/NodeSelectionListener.java
new file mode 100644
index 000000000..a39edd55d
--- /dev/null
+++ b/zookeeper-contrib/zookeeper-contrib-zooinspector/src/main/java/org/apache/zookeeper/inspector/gui/nodeviewer/NodeSelectionListener.java
@@ -0,0 +1,26 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.apache.zookeeper.inspector.gui.nodeviewer;
+
+/**
+ * An interface to be implented by any component that needs notification when a new element
+ * is selected in the UI JTree representing the set of available ZNodes.
+ */
+public interface NodeSelectionListener {
+ void nodePathSelected(String nodePath);
+}
diff --git a/zookeeper-contrib/zookeeper-contrib-zooinspector/src/main/java/org/apache/zookeeper/inspector/gui/nodeviewer/NodeViewerData.java b/zookeeper-contrib/zookeeper-contrib-zooinspector/src/main/java/org/apache/zookeeper/inspector/gui/nodeviewer/NodeViewerData.java
index 6061e17bf..32e269ca0 100644
--- a/zookeeper-contrib/zookeeper-contrib-zooinspector/src/main/java/org/apache/zookeeper/inspector/gui/nodeviewer/NodeViewerData.java
+++ b/zookeeper-contrib/zookeeper-contrib-zooinspector/src/main/java/org/apache/zookeeper/inspector/gui/nodeviewer/NodeViewerData.java
@@ -60,7 +60,7 @@ public class NodeViewerData extends ZooInspectorNodeViewer {
public void actionPerformed(ActionEvent e) {
if (selectedNode != null) {
if (JOptionPane.showConfirmDialog(NodeViewerData.this,
- "Are you sure you want to save this node?"
+ "Are you sure you want to save the node '" + selectedNode + "'?\n"
+ " (this action cannot be reverted)",
"Confirm Save", JOptionPane.YES_NO_OPTION,
JOptionPane.WARNING_MESSAGE) == JOptionPane.YES_OPTION) {
@@ -110,11 +110,7 @@ public class NodeViewerData extends ZooInspectorNodeViewer {
String data = "";
try {
data = get();
- } catch (InterruptedException e) {
- LoggerFactory.getLogger().error(
- "Error retrieving data for node: "
- + NodeViewerData.this.selectedNode, e);
- } catch (ExecutionException e) {
+ } catch (InterruptedException | ExecutionException e) {
LoggerFactory.getLogger().error(
"Error retrieving data for node: "
+ NodeViewerData.this.selectedNode, e);
diff --git a/zookeeper-contrib/zookeeper-contrib-zooinspector/src/main/java/org/apache/zookeeper/inspector/logger/LoggerFactory.java b/zookeeper-contrib/zookeeper-contrib-zooinspector/src/main/java/org/apache/zookeeper/inspector/logger/LoggerFactory.java
index e4fae4169..c775d643e 100644
--- a/zookeeper-contrib/zookeeper-contrib-zooinspector/src/main/java/org/apache/zookeeper/inspector/logger/LoggerFactory.java
+++ b/zookeeper-contrib/zookeeper-contrib-zooinspector/src/main/java/org/apache/zookeeper/inspector/logger/LoggerFactory.java
@@ -18,19 +18,19 @@
package org.apache.zookeeper.inspector.logger;
/**
- * Provides a {@link Logger} for use across the entire application
+ * Provides a {@link org.slf4j.Logger} for use across the entire application
*
*/
public class LoggerFactory
{
- private static final org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger("org.apache.zookeeper.inspector"); //$NON-NLS-1$
+ private static final org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger("org.apache.zookeeper.inspector"); //$NON-NLS-1$
- /**
- * @return {@link Logger} for ZooInspector
- */
- public static org.slf4j.Logger getLogger()
- {
- return logger;
- }
+ /**
+ * @return {@link org.slf4j.Logger} for ZooInspector
+ */
+ public static org.slf4j.Logger getLogger()
+ {
+ return logger;
+ }
}
diff --git a/zookeeper-contrib/zookeeper-contrib-zooinspector/src/main/java/org/apache/zookeeper/inspector/manager/NodesCache.java b/zookeeper-contrib/zookeeper-contrib-zooinspector/src/main/java/org/apache/zookeeper/inspector/manager/NodesCache.java
index 45c5a2752..da30596a6 100644
--- a/zookeeper-contrib/zookeeper-contrib-zooinspector/src/main/java/org/apache/zookeeper/inspector/manager/NodesCache.java
+++ b/zookeeper-contrib/zookeeper-contrib-zooinspector/src/main/java/org/apache/zookeeper/inspector/manager/NodesCache.java
@@ -26,14 +26,17 @@ import org.apache.zookeeper.inspector.logger.LoggerFactory;
import java.util.Collections;
import java.util.List;
-import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
+/**
+ * A local cache of ZNodes in front of the Zookeeper server(s) to help cut down on network calls. This class will
+ * return results from the cache first, if available, and then fetch results remotely from Zookeeper if not.
+ */
public class NodesCache {
public static final int CACHE_SIZE = 40000;
- public static final int EXPIRATION_TIME = 100;
+ public static final int ENTRY_EXPIRATION_TIME_MILLIS = 10000;
private final LoadingCache<String, List<String>> nodes;
@@ -43,18 +46,26 @@ public class NodesCache {
this.zooKeeper = zooKeeper;
this.nodes = CacheBuilder.newBuilder()
.maximumSize(CACHE_SIZE)
- .expireAfterWrite(EXPIRATION_TIME, TimeUnit.MILLISECONDS)
- .build(
- new CacheLoader<String, List<String>>() {
- @Override
- public List<String> load(String nodePath) throws Exception {
- return getChildren(nodePath);
- }
- }
+ .expireAfterWrite(ENTRY_EXPIRATION_TIME_MILLIS, TimeUnit.MILLISECONDS)
+ .build(new CacheLoader<String, List<String>>() {
+ @Override
+ public List<String> load(String nodePath) {
+ return getChildrenRemote(nodePath);
+ }
+ }
);
}
- public List<String> getChildren(String nodePath) {
+ /**
+ * Whereas getChildren hits the cache first, getChildrenRemote goes straight (over the network) to Zookeeper to
+ * get the answer. This is the function used by the cache to insert entries that are requested, but don't exist
+ * in the cache yet.
+ *
+ * @param nodePath The full path to the parent whose children are to be fetched.
+ * @return The list of children of the given node as a list of full ZNode path strings or null if an
+ * error occurred.
+ */
+ private List<String> getChildrenRemote(String nodePath) {
try {
Stat s = zooKeeper.exists(nodePath, false);
if (s != null) {
@@ -64,23 +75,25 @@ public class NodesCache {
}
} catch (Exception e) {
LoggerFactory.getLogger().error(
- "Error occurred retrieving child of node: " + nodePath, e
+ "Error occurred retrieving children of node: " + nodePath, e
);
}
return null;
}
- public String getNodeChild(String nodePath, int index) {
- List<String> childNodes = null;
+ /**
+ * Fetch the children of the given node from the cache.
+ *
+ * @param nodePath The full path to the parent whose children are to be fetched.
+ * @return The list of children of the given node as a list of full ZNode path strings or null if an
+ * error occurred.
+ */
+ public List<String> getChildren(String nodePath) {
try {
- childNodes = nodes.get(nodePath);
- return childNodes.get(index);
- } catch (ExecutionException e) {
- LoggerFactory.getLogger().error(
- "Error occurred retrieving child " + index + "of node: " + nodePath, e
- );
+ return nodes.get(nodePath);
+ } catch (Exception e) {
+ LoggerFactory.getLogger().error("Error occurred retrieving children of node: " + nodePath, e);
}
return null;
}
-
}
diff --git a/zookeeper-contrib/zookeeper-contrib-zooinspector/src/main/java/org/apache/zookeeper/inspector/manager/ZooInspectorManager.java b/zookeeper-contrib/zookeeper-contrib-zooinspector/src/main/java/org/apache/zookeeper/inspector/manager/ZooInspectorManager.java
index 74c3cb20e..030b7d2ee 100644
--- a/zookeeper-contrib/zookeeper-contrib-zooinspector/src/main/java/org/apache/zookeeper/inspector/manager/ZooInspectorManager.java
+++ b/zookeeper-contrib/zookeeper-contrib-zooinspector/src/main/java/org/apache/zookeeper/inspector/manager/ZooInspectorManager.java
@@ -55,7 +55,7 @@ public interface ZooInspectorManager extends ZooInspectorNodeManager,
* {@link JComboBox} with the first selected as default.</li>
* <li>a {@link Map} of property keys to the label to show on the UI
* </li>
- * <ul>
+ * </ul>
*
*/
public Pair<Map<String, List<String>>, Map<String, String>> getConnectionPropertiesTemplate();
diff --git a/zookeeper-contrib/zookeeper-contrib-zooinspector/src/main/java/org/apache/zookeeper/inspector/manager/ZooInspectorManagerImpl.java b/zookeeper-contrib/zookeeper-contrib-zooinspector/src/main/java/org/apache/zookeeper/inspector/manager/ZooInspectorManagerImpl.java
index aa9ff6e32..0db6140ac 100644
--- a/zookeeper-contrib/zookeeper-contrib-zooinspector/src/main/java/org/apache/zookeeper/inspector/manager/ZooInspectorManagerImpl.java
+++ b/zookeeper-contrib/zookeeper-contrib-zooinspector/src/main/java/org/apache/zookeeper/inspector/manager/ZooInspectorManagerImpl.java
@@ -190,7 +190,7 @@ public class ZooInspectorManagerImpl implements ZooInspectorManager {
e.printStackTrace();
}
if (!connected){
- disconnect();
+ disconnect();
} else {
this.nodesCache = new NodesCache(zooKeeper);
}
@@ -223,7 +223,7 @@ public class ZooInspectorManagerImpl implements ZooInspectorManager {
/*
* (non-Javadoc)
*
- * @seeorg.apache.zookeeper.inspector.manager.ZooInspectorReadOnlyManager#
+ * @see org.apache.zookeeper.inspector.manager.ZooInspectorReadOnlyManager#
* getChildren(java.lang.String)
*/
public List<String> getChildren(String nodePath) {
@@ -263,46 +263,6 @@ public class ZooInspectorManagerImpl implements ZooInspectorManager {
/*
* (non-Javadoc)
*
- * @seeorg.apache.zookeeper.inspector.manager.ZooInspectorReadOnlyManager#
- * getNodeChild(java.lang.String, int)
- */
- public String getNodeChild(String nodePath, int childIndex) {
- if (connected) {
- return this.nodesCache.getNodeChild(nodePath, childIndex);
- }
- return null;
- }
-
- /*
- * (non-Javadoc)
- *
- * @seeorg.apache.zookeeper.inspector.manager.ZooInspectorReadOnlyManager#
- * getNodeIndex(java.lang.String)
- */
- public int getNodeIndex(String nodePath) {
- if (connected) {
- int index = nodePath.lastIndexOf("/");
- if (index == -1
- || (!nodePath.equals("/") && nodePath.charAt(nodePath
- .length() - 1) == '/')) {
- throw new IllegalArgumentException("Invalid node path: "
- + nodePath);
- }
- String parentPath = nodePath.substring(0, index);
- String child = nodePath.substring(index + 1);
- if (parentPath != null && parentPath.length() > 0) {
- List<String> children = this.nodesCache.getChildren(parentPath);
- if (children != null) {
- return children.indexOf(child);
- }
- }
- }
- return -1;
- }
-
- /*
- * (non-Javadoc)
- *
* @see
* org.apache.zookeeper.inspector.manager.ZooInspectorReadOnlyManager#getACLs
* (java.lang.String)
@@ -360,11 +320,7 @@ public class ZooInspectorManagerImpl implements ZooInspectorManager {
returnACLs.add(aclMap);
}
}
- } catch (InterruptedException e) {
- LoggerFactory.getLogger().error(
- "Error occurred retrieving ACLs of node: " + nodePath,
- e);
- } catch (KeeperException e) {
+ } catch (InterruptedException | KeeperException e) {
LoggerFactory.getLogger().error(
"Error occurred retrieving ACLs of node: " + nodePath,
e);
@@ -376,7 +332,7 @@ public class ZooInspectorManagerImpl implements ZooInspectorManager {
/*
* (non-Javadoc)
*
- * @seeorg.apache.zookeeper.inspector.manager.ZooInspectorReadOnlyManager#
+ * @see org.apache.zookeeper.inspector.manager.ZooInspectorReadOnlyManager#
* getNodeMeta(java.lang.String)
*/
public Map<String, String> getNodeMeta(String nodePath) {
@@ -415,81 +371,11 @@ public class ZooInspectorManagerImpl implements ZooInspectorManager {
/*
* (non-Javadoc)
*
- * @seeorg.apache.zookeeper.inspector.manager.ZooInspectorReadOnlyManager#
+ * @see org.apache.zookeeper.inspector.manager.ZooInspectorReadOnlyManager#
* getNumChildren(java.lang.String)
*/
public int getNumChildren(String nodePath) {
- if (connected) {
- try {
- Stat s = zooKeeper.exists(nodePath, false);
- if (s != null) {
- return s.getNumChildren();
- }
- } catch (Exception e) {
- LoggerFactory.getLogger().error(
- "Error occurred getting the number of children of node: "
- + nodePath, e);
- }
- }
- return -1;
- }
-
- /*
- * (non-Javadoc)
- *
- * @seeorg.apache.zookeeper.inspector.manager.ZooInspectorReadOnlyManager#
- * hasChildren(java.lang.String)
- */
- public boolean hasChildren(String nodePath) {
- return getNumChildren(nodePath) > 0;
- }
-
- /*
- * (non-Javadoc)
- *
- * @seeorg.apache.zookeeper.inspector.manager.ZooInspectorReadOnlyManager#
- * isAllowsChildren(java.lang.String)
- */
- public boolean isAllowsChildren(String nodePath) {
- if (connected) {
- try {
- Stat s = zooKeeper.exists(nodePath, false);
- if (s != null) {
- return s.getEphemeralOwner() == 0;
- }
- } catch (Exception e) {
- LoggerFactory.getLogger().error(
- "Error occurred determining whether node is allowed children: "
- + nodePath, e);
- }
- }
- return false;
- }
-
- /*
- * (non-Javadoc)
- *
- * @seeorg.apache.zookeeper.inspector.manager.ZooInspectorReadOnlyManager#
- * getSessionMeta()
- */
- public Map<String, String> getSessionMeta() {
- Map<String, String> sessionMeta = new LinkedHashMap<String, String>();
- try {
- if (zooKeeper != null) {
-
- sessionMeta.put(SESSION_ID, String.valueOf(zooKeeper
- .getSessionId()));
- sessionMeta.put(SESSION_STATE, String.valueOf(zooKeeper
- .getState().toString()));
- sessionMeta.put(CONNECT_STRING, this.connectString);
- sessionMeta.put(SESSION_TIMEOUT, String
- .valueOf(this.sessionTimeout));
- }
- } catch (Exception e) {
- LoggerFactory.getLogger().error(
- "Error occurred retrieving session meta data.", e);
- }
- return sessionMeta;
+ return connected ? getChildren(nodePath).size() : -1;
}
/*
@@ -576,7 +462,7 @@ public class ZooInspectorManagerImpl implements ZooInspectorManager {
/*
* (non-Javadoc)
*
- * @seeorg.apache.zookeeper.inspector.manager.ZooInspectorManager#
+ * @see org.apache.zookeeper.inspector.manager.ZooInspectorManager#
* getConnectionPropertiesTemplate()
*/
public Pair<Map<String, List<String>>, Map<String, String>> getConnectionPropertiesTemplate() {
@@ -696,7 +582,7 @@ public class ZooInspectorManagerImpl implements ZooInspectorManager {
}
} catch (Exception e) {
LoggerFactory.getLogger().error(
- "Error occurred re-adding node watcherfor node "
+ "Error occurred re-adding node watcher for node "
+ nodePath, e);
}
nodeListener.processEvent(event.getPath(), event.getType()
@@ -704,9 +590,6 @@ public class ZooInspectorManagerImpl implements ZooInspectorManager {
}
}
- /**
- *
- */
public void stop() {
this.closed = true;
}
@@ -716,7 +599,7 @@ public class ZooInspectorManagerImpl implements ZooInspectorManager {
/*
* (non-Javadoc)
*
- * @seeorg.apache.zookeeper.inspector.manager.ZooInspectorManager#
+ * @see org.apache.zookeeper.inspector.manager.ZooInspectorManager#
* loadNodeViewersFile(java.io.File)
*/
public List<String> loadNodeViewersFile(File selectedFile)
@@ -768,7 +651,7 @@ public class ZooInspectorManagerImpl implements ZooInspectorManager {
/*
* (non-Javadoc)
*
- * @seeorg.apache.zookeeper.inspector.manager.ZooInspectorManager#
+ * @see org.apache.zookeeper.inspector.manager.ZooInspectorManager#
* saveDefaultConnectionFile(java.util.Properties)
*/
public void saveDefaultConnectionFile(Properties props) throws IOException {
@@ -798,7 +681,7 @@ public class ZooInspectorManagerImpl implements ZooInspectorManager {
/*
* (non-Javadoc)
*
- * @seeorg.apache.zookeeper.inspector.manager.ZooInspectorManager#
+ * @see org.apache.zookeeper.inspector.manager.ZooInspectorManager#
* saveNodeViewersFile(java.io.File, java.util.List)
*/
public void saveNodeViewersFile(File selectedFile,
@@ -830,7 +713,7 @@ public class ZooInspectorManagerImpl implements ZooInspectorManager {
/*
* (non-Javadoc)
*
- * @seeorg.apache.zookeeper.inspector.manager.ZooInspectorManager#
+ * @see org.apache.zookeeper.inspector.manager.ZooInspectorManager#
* setDefaultNodeViewerConfiguration(java.io.File, java.util.List)
*/
public void setDefaultNodeViewerConfiguration(
@@ -857,7 +740,7 @@ public class ZooInspectorManagerImpl implements ZooInspectorManager {
/*
* (non-Javadoc)
*
- * @seeorg.apache.zookeeper.inspector.manager.ZooInspectorManager#
+ * @see org.apache.zookeeper.inspector.manager.ZooInspectorManager#
* getLastConnectionProps()
*/
public Properties getLastConnectionProps() {
diff --git a/zookeeper-contrib/zookeeper-contrib-zooinspector/src/main/java/org/apache/zookeeper/inspector/manager/ZooInspectorReadOnlyManager.java b/zookeeper-contrib/zookeeper-contrib-zooinspector/src/main/java/org/apache/zookeeper/inspector/manager/ZooInspectorReadOnlyManager.java
index d9fdf5c4a..69ce842a1 100644
--- a/zookeeper-contrib/zookeeper-contrib-zooinspector/src/main/java/org/apache/zookeeper/inspector/manager/ZooInspectorReadOnlyManager.java
+++ b/zookeeper-contrib/zookeeper-contrib-zooinspector/src/main/java/org/apache/zookeeper/inspector/manager/ZooInspectorReadOnlyManager.java
@@ -28,72 +28,36 @@ public interface ZooInspectorReadOnlyManager {
/**
* @param nodePath
- * - the path to the node to delete
+ * - the path to the node whose data is to be retrieved
* @return the data for the node
*/
public abstract String getData(String nodePath);
/**
* @param nodePath
- * - the path to the node to delete
+ * - the path to the node whose metadata is to be retrieved
* @return the metaData for the node
*/
public abstract Map<String, String> getNodeMeta(String nodePath);
/**
* @param nodePath
- * - the path to the node to delete
+ * - the path to the node whose ACLs are to be retrieved
* @return the ACLs set on the node
*/
public abstract List<Map<String, String>> getACLs(String nodePath);
/**
- * @return the metaData for the current session
- */
- public abstract Map<String, String> getSessionMeta();
-
- /**
- * @param nodePath
- * - the path to the node to delete
- * @return true if the node has children
- */
- public abstract boolean hasChildren(String nodePath);
-
- /**
- * @param nodePath
- * - the path to the node to delete
- * @return the index of the node within its siblings
- */
- public abstract int getNodeIndex(String nodePath);
-
- /**
* @param nodePath
- * - the path to the node to delete
+ * - the path to the node to parent node
* @return the number of children of the node
*/
public abstract int getNumChildren(String nodePath);
/**
* @param nodePath
- * - the path to the node to delete
- * @param childIndex
- * - the index to the node in the list of node children
- * @return the path to the node for the child of the nodePath at childIndex
- */
- public abstract String getNodeChild(String nodePath, int childIndex);
-
- /**
- * @param nodePath
- * - the path to the node to delete
- * @return true if the node allows children nodes
- */
- public abstract boolean isAllowsChildren(String nodePath);
-
- /**
- * @param nodePath
- * - the path to the node to delete
+ * - the path to the node whose children to retrieve
* @return a {@link List} of the children of the node
*/
public abstract List<String> getChildren(String nodePath);
-
}
diff --git a/zookeeper-contrib/zookeeper-contrib-zooinspector/src/main/java/org/apache/zookeeper/retry/ZooKeeperRetry.java b/zookeeper-contrib/zookeeper-contrib-zooinspector/src/main/java/org/apache/zookeeper/retry/ZooKeeperRetry.java
index ce959a1a0..dff8f24dc 100644
--- a/zookeeper-contrib/zookeeper-contrib-zooinspector/src/main/java/org/apache/zookeeper/retry/ZooKeeperRetry.java
+++ b/zookeeper-contrib/zookeeper-contrib-zooinspector/src/main/java/org/apache/zookeeper/retry/ZooKeeperRetry.java
@@ -31,7 +31,7 @@ import org.apache.zookeeper.inspector.logger.LoggerFactory;
/**
* A Class which extends {@link ZooKeeper} and will automatically retry calls to
- * zookeeper if a {@link KeeperException.ConnectionLossException} occurs
+ * zookeeper if a {@link org.apache.zookeeper.KeeperException.ConnectionLossException} occurs.
*/
public class ZooKeeperRetry extends ZooKeeper {
diff --git a/zookeeper-contrib/zookeeper-contrib-zooinspector/src/main/resources/about.html b/zookeeper-contrib/zookeeper-contrib-zooinspector/src/main/resources/about.html
new file mode 100644
index 000000000..17fb3dca2
--- /dev/null
+++ b/zookeeper-contrib/zookeeper-contrib-zooinspector/src/main/resources/about.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
+<title>ZooInspector v0.1</title>
+</head>
+<body>
+<p>ZooInspector was developed by Colin Goodheart-Smithe and is
+available under the Apache Software Licence v2.0.</p>
+<p>The Icons used were sourced from the Eclipse project (<a
+ href="http://www.eclipse.org">http://www.eclipse.org</a>) and licensed
+under the Eclipse Public Licence v1.0. [<a
+ href="http://www.eclipse.org/org/documents/epl-v10.php">http://www.eclipse.org/org/documents/epl-v10.php</a>]
+</p>
+<p>ZooKeeper is available from <a
+ href="http://zookeeper.apache.org/">http://zookeeper.apache.org/</a>
+and is licensed under an Apache Software Licence v2.0</p>
+<p>The ApacheSoftware Licence v2.0 can be found at <a
+ href="http://www.apache.org/licenses/LICENSE-2.0">http://www.apache.org/licenses/LICENSE-2.0</a></p>
+</body>
+</html>
diff --git a/zookeeper-contrib/zookeeper-contrib-zooinspector/src/main/resources/icons/Tango/16x16/categories/applications-system.png b/zookeeper-contrib/zookeeper-contrib-zooinspector/src/main/resources/icons/Tango/16x16/categories/applications-system.png
new file mode 100644
index 000000000..d90ab661c
--- /dev/null
+++ b/zookeeper-contrib/zookeeper-contrib-zooinspector/src/main/resources/icons/Tango/16x16/categories/applications-system.png
Binary files differ
diff --git a/zookeeper-contrib/zookeeper-contrib-zooinspector/src/main/resources/icons/Tango/22x22/categories/applications-system.png b/zookeeper-contrib/zookeeper-contrib-zooinspector/src/main/resources/icons/Tango/22x22/categories/applications-system.png
new file mode 100644
index 000000000..4decc893f
--- /dev/null
+++ b/zookeeper-contrib/zookeeper-contrib-zooinspector/src/main/resources/icons/Tango/22x22/categories/applications-system.png
Binary files differ
diff --git a/zookeeper-contrib/zookeeper-contrib-zooinspector/src/main/resources/icons/Tango/24x24/categories/applications-system.png b/zookeeper-contrib/zookeeper-contrib-zooinspector/src/main/resources/icons/Tango/24x24/categories/applications-system.png
new file mode 100644
index 000000000..9c8833865
--- /dev/null
+++ b/zookeeper-contrib/zookeeper-contrib-zooinspector/src/main/resources/icons/Tango/24x24/categories/applications-system.png
Binary files differ
diff --git a/zookeeper-contrib/zookeeper-contrib-zooinspector/src/main/resources/icons/Tango/32x32/categories/applications-system.png b/zookeeper-contrib/zookeeper-contrib-zooinspector/src/main/resources/icons/Tango/32x32/categories/applications-system.png
new file mode 100644
index 000000000..565f406dd
--- /dev/null
+++ b/zookeeper-contrib/zookeeper-contrib-zooinspector/src/main/resources/icons/Tango/32x32/categories/applications-system.png
Binary files differ