|
@@ -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);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+}
|