package com.kostbot.zoodirector.ui; import com.google.common.base.Strings; import com.kostbot.zoodirector.ui.helpers.DynamicTable; import com.kostbot.zoodirector.ui.workers.LoadDataWorker; import com.kostbot.zoodirector.zookeepersync.ZookeeperSync; import org.apache.zookeeper.data.Stat; import org.jdesktop.swingx.JXTable; import org.joda.time.LocalDateTime; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.swing.*; import javax.swing.table.*; import java.awt.*; import java.awt.event.*; import java.util.HashSet; import java.util.Set; import java.util.regex.Pattern; import java.util.regex.PatternSyntaxException; public class ZooDirectorWatchPanel extends JPanel { private static final Logger logger = LoggerFactory.getLogger(ZooDirectorWatchPanel.class); private ZookeeperSync zookeeperSync; private final JTextField pathTextField; private final DefaultTableModel patternTableModel; private final JXTable patternWatchTable; private final Set<String> watches; private final DefaultTableModel tableModel; private final JXTable watchTable; private int clickedRow = -1; public ZooDirectorWatchPanel(final ZooDirectorPanel parent) { watches = new HashSet<String>(10); setLayout(new BorderLayout()); JPanel patternWatchPanel = new JPanel(new GridBagLayout()); GridBagConstraints c = new GridBagConstraints(); c.anchor = GridBagConstraints.FIRST_LINE_START; c.gridx = c.gridy = 0; c.weightx = 0.5; c.insets.top = 5; c.insets.bottom = 2; c.fill = GridBagConstraints.HORIZONTAL; pathTextField = new JTextField(); pathTextField.addKeyListener(new KeyAdapter() { @Override public void keyReleased(KeyEvent e) { if (e.getKeyCode() == KeyEvent.VK_ENTER) { addPatternWatch(); } } }); patternWatchPanel.add(pathTextField, c); c.gridx += 1; c.weightx = 0; JButton addButton = new JButton("Add Pattern"); addButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { addPatternWatch(); } }); patternWatchPanel.add(addButton, c); c.fill = GridBagConstraints.BOTH; c.gridx = 0; c.gridy += 1; c.gridwidth = 2; c.weighty = 0.25; patternTableModel = new DefaultTableModel(new String[]{"pattern"}, 0); patternWatchTable = new JXTable(patternTableModel) { @Override public boolean isCellEditable(int row, int column) { return false; } }; patternWatchTable.setFont(ZooDirectorFrame.FONT_MONOSPACED); patternWatchTable.setHorizontalScrollEnabled(true); final JPopupMenu watchPatternTableMenu = new JPopupMenu(); JMenuItem removePatternWatchMenuItem = new JMenuItem("remove watch pattern"); removePatternWatchMenuItem.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { synchronized (patternTableModel) { DynamicTable.removeSelectedRows(patternWatchTable); } } }); watchPatternTableMenu.add(removePatternWatchMenuItem); patternWatchTable.addMouseListener(new MouseAdapter() { @Override public void mouseClicked(MouseEvent e) { if (e.getButton() == MouseEvent.BUTTON3) { // Show context menu if clicking selected row if (patternWatchTable.isRowSelected(patternWatchTable.rowAtPoint(e.getPoint()))) { watchPatternTableMenu.show(patternWatchTable, e.getX(), e.getY()); } } } }); patternWatchPanel.add(new JScrollPane(patternWatchTable), c); tableModel = new DefaultTableModel(new String[]{"path", "ephemeral", "created", "modified", "version", "data"}, 0); watchTable = new JXTable(tableModel) { @Override public boolean isCellEditable(int row, int column) { return false; } }; watchTable.setFont(ZooDirectorFrame.FONT_MONOSPACED); watchTable.setHorizontalScrollEnabled(true); final JPopupMenu tableMenu = new JPopupMenu(); JMenuItem viewEditWatchMenuItem = new JMenuItem("view/edit"); viewEditWatchMenuItem.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { String path = getPath(clickedRow); parent.viewEditTreeNode(path); } }); tableMenu.add(viewEditWatchMenuItem); tableMenu.addSeparator(); JMenuItem removeWatchMenuItem = new JMenuItem("remove watch"); removeWatchMenuItem.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { DynamicTable.removeSelectedRows(watchTable, new DynamicTable.Callback() { @Override public void execute(int row) { watches.remove(tableModel.getValueAt(row, 0)); } }); } }); tableMenu.add(removeWatchMenuItem); watchTable.addMouseListener(new MouseAdapter() { @Override public void mouseClicked(MouseEvent e) { if (e.getButton() == MouseEvent.BUTTON3) { // Show context menu if clicking selected row clickedRow = watchTable.rowAtPoint(e.getPoint()); if (watchTable.isRowSelected(clickedRow)) { tableMenu.show(watchTable, e.getX(), e.getY()); } } } }); JSplitPane splitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT, patternWatchPanel, new JScrollPane(watchTable)); splitPane.setOneTouchExpandable(true); splitPane.setDividerLocation(200); add(splitPane, BorderLayout.CENTER); } synchronized private void setData(int row, Stat stat, byte[] data) { tableModel.setValueAt(stat == null ? null : (stat.getEphemeralOwner() != 0), row, 1); tableModel.setValueAt(stat == null ? null : new LocalDateTime(stat.getCtime()), row, 2); tableModel.setValueAt(stat == null ? null : new LocalDateTime(stat.getMtime()), row, 3); tableModel.setValueAt(stat == null ? null : stat.getVersion(), row, 4); tableModel.setValueAt(data == null ? null : new String(data), row, 5); tableModel.fireTableRowsUpdated(row, row); } private String getPath(int row) { return (String) tableModel.getValueAt(watchTable.convertRowIndexToModel(row), 0); } private int getRow(String path) { for (int i = 0; i < tableModel.getRowCount(); ++i) { if (tableModel.getValueAt(i, 0).equals(path)) { return i; } } // Should never get here logger.error("could not determine row for {}", path); return -1; } synchronized private void updateData(String path, boolean deleted) { if (watches.contains(path)) { final int row = getRow(path); if (row < 0) { return; } if (deleted) { logger.info("[watch] {} deleted", path); setData(row, null, null); } else { logger.info("[watch] {} updated", path); new LoadDataWorker(zookeeperSync, path, new LoadDataWorker.Callback() { @Override public void onComplete(String path, final Stat stat, final byte[] data) { if (stat == null) { logger.error("[watch] {} update failed", path); } setData(row, stat, data); } }).execute(); } } else if (!deleted) { synchronized (patternTableModel) { for (int i = 0; i < patternTableModel.getRowCount(); ++i) { Pattern pattern = (Pattern) patternTableModel.getValueAt(i, 0); if (pattern.matcher(path).matches()) { addWatch(path); } } } } } synchronized public boolean removeWatch(String path) { if (watches.remove(path)) { tableModel.removeRow(getRow(path)); logger.debug("{} watch removed", path); return true; } return false; } synchronized public boolean hasWatch(String path) { return watches.contains(path); } synchronized public void addWatch(String path) { if (!watches.contains(path)) { logger.debug("{} watch added", path); watches.add(path); tableModel.addRow(new Object[]{path, null, null, null, null, null}); updateData(path, false); } } private void addPatternWatch() { String watchPattern = pathTextField.getText(); if (Strings.isNullOrEmpty(watchPattern)) { return; } Pattern pattern; try { pattern = Pattern.compile(watchPattern); } catch (PatternSyntaxException e) { logger.error("bad wild card pattern [{}]", e.getMessage()); return; } synchronized (patternTableModel) { // Scan to see if pattern already exists for (int i = 0; i < patternTableModel.getRowCount(); ++i) { if (((Pattern) patternTableModel.getValueAt(i, 0)).pattern().equals(watchPattern)) { logger.debug("watch pattern {} already exists", watchPattern); return; } } logger.debug("{} watch pattern added", watchPattern); patternTableModel.addRow(new Object[]{pattern}); patternWatchTable.packAll(); for (String node : zookeeperSync.getNodes()) { if (pattern.matcher(node).matches()) { addWatch(node); } } } pathTextField.setText(""); } public void setZookeeperSync(ZookeeperSync zookeeperSync) { this.zookeeperSync = zookeeperSync; zookeeperSync.addListener(new ZookeeperSync.Listener() { @Override public void process(ZookeeperSync.Event e) { updateData(e.path, e.type == ZookeeperSync.Event.Type.delete); } }); } }