1 /* 2 * Copyright (c) 2004, 2013, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package sun.tools.jconsole; 27 28 import java.awt.*; 29 import java.awt.event.*; 30 import java.beans.*; 31 import java.lang.reflect.*; 32 import java.util.*; 33 import java.util.List; 34 import java.util.Timer; 35 import javax.swing.*; 36 import javax.swing.plaf.*; 37 38 39 import com.sun.tools.jconsole.JConsolePlugin; 40 import com.sun.tools.jconsole.JConsoleContext; 41 42 import static sun.tools.jconsole.ProxyClient.*; 43 44 @SuppressWarnings("serial") 45 public class VMPanel extends JTabbedPane implements PropertyChangeListener { 46 47 private ProxyClient proxyClient; 48 private Timer timer; 49 private int updateInterval; 50 private String hostName; 51 private int port; 52 private String userName; 53 private String password; 54 private String url; 55 private VMInternalFrame vmIF = null; 56 private static ArrayList<TabInfo> tabInfos = new ArrayList<TabInfo>(); 57 private boolean wasConnected = false; 58 private boolean userDisconnected = false; 59 private boolean shouldUseSSL = true; 60 61 // The everConnected flag keeps track of whether the window can be 62 // closed if the user clicks Cancel after a failed connection attempt. 63 // 64 private boolean everConnected = false; 65 66 // The initialUpdate flag is used to enable/disable tabs each time 67 // a connect or reconnect takes place. This flag avoids having to 68 // enable/disable tabs on each update call. 69 // 70 private boolean initialUpdate = true; 71 72 // Each VMPanel has its own instance of the JConsolePlugin 73 // A map of JConsolePlugin to the previous SwingWorker 74 private Map<ExceptionSafePlugin, SwingWorker<?, ?>> plugins = null; 75 private boolean pluginTabsAdded = false; 76 77 // Update these only on the EDT 78 private JOptionPane optionPane; 79 private JProgressBar progressBar; 80 private long time0; 81 82 static { 83 tabInfos.add(new TabInfo(OverviewTab.class, OverviewTab.getTabName(), true)); 84 tabInfos.add(new TabInfo(MemoryTab.class, MemoryTab.getTabName(), true)); 85 tabInfos.add(new TabInfo(ThreadTab.class, ThreadTab.getTabName(), true)); 86 tabInfos.add(new TabInfo(ClassTab.class, ClassTab.getTabName(), true)); 87 tabInfos.add(new TabInfo(SummaryTab.class, SummaryTab.getTabName(), true)); 88 tabInfos.add(new TabInfo(MBeansTab.class, MBeansTab.getTabName(), true)); 89 } 90 91 public static TabInfo[] getTabInfos() { 92 return tabInfos.toArray(new TabInfo[tabInfos.size()]); 93 } 94 95 VMPanel(ProxyClient proxyClient, int updateInterval) { 96 this.proxyClient = proxyClient; 97 this.updateInterval = updateInterval; 98 this.hostName = proxyClient.getHostName(); 99 this.port = proxyClient.getPort(); 100 this.userName = proxyClient.getUserName(); 101 this.password = proxyClient.getPassword(); 102 this.url = proxyClient.getUrl(); 103 104 for (TabInfo tabInfo : tabInfos) { 105 if (tabInfo.tabVisible) { 106 addTab(tabInfo); 107 } 108 } 109 110 plugins = new LinkedHashMap<ExceptionSafePlugin, SwingWorker<?, ?>>(); 111 for (JConsolePlugin p : JConsole.getPlugins()) { 112 p.setContext(proxyClient); 113 plugins.put(new ExceptionSafePlugin(p), null); 114 } 115 116 Utilities.updateTransparency(this); 117 118 ToolTipManager.sharedInstance().registerComponent(this); 119 120 // Start listening to connection state events 121 // 122 proxyClient.addPropertyChangeListener(this); 123 124 addMouseListener(new MouseAdapter() { 125 126 public void mouseClicked(MouseEvent e) { 127 if (connectedIconBounds != null && (e.getModifiers() & MouseEvent.BUTTON1_MASK) != 0 && connectedIconBounds.contains(e.getPoint())) { 128 129 if (isConnected()) { 130 userDisconnected = true; 131 disconnect(); 132 wasConnected = false; 133 } else { 134 connect(); 135 } 136 repaint(); 137 } 138 } 139 }); 140 141 } 142 private static Icon connectedIcon16 = 143 new ImageIcon(VMPanel.class.getResource("resources/connected16.png")); 144 private static Icon connectedIcon24 = 145 new ImageIcon(VMPanel.class.getResource("resources/connected24.png")); 146 private static Icon disconnectedIcon16 = 147 new ImageIcon(VMPanel.class.getResource("resources/disconnected16.png")); 148 private static Icon disconnectedIcon24 = 149 new ImageIcon(VMPanel.class.getResource("resources/disconnected24.png")); 150 private Rectangle connectedIconBounds; 151 152 // Override to increase right inset for tab area, 153 // in order to reserve space for the connect toggle. 154 public void setUI(TabbedPaneUI ui) { 155 Insets insets = (Insets) UIManager.getLookAndFeelDefaults().get("TabbedPane.tabAreaInsets"); 156 if (insets != null) { 157 insets = (Insets) insets.clone(); 158 insets.right += connectedIcon24.getIconWidth() + 8; 159 UIManager.put("TabbedPane.tabAreaInsets", insets); 160 } 161 super.setUI(ui); 162 } 163 164 // Override to paint the connect toggle 165 protected void paintComponent(Graphics g) { 166 super.paintComponent(g); 167 168 Icon icon; 169 Component c0 = getComponent(0); 170 if (c0 != null && c0.getY() > 24) { 171 icon = isConnected() ? connectedIcon24 : disconnectedIcon24; 172 } else { 173 icon = isConnected() ? connectedIcon16 : disconnectedIcon16; 174 } 175 Insets insets = getInsets(); 176 int x = getWidth() - insets.right - icon.getIconWidth() - 4; 177 int y = insets.top; 178 if (c0 != null) { 179 y = (c0.getY() - icon.getIconHeight()) / 2; 180 } 181 icon.paintIcon(this, g, x, y); 182 connectedIconBounds = new Rectangle(x, y, icon.getIconWidth(), icon.getIconHeight()); 183 } 184 185 public String getToolTipText(MouseEvent event) { 186 if (connectedIconBounds.contains(event.getPoint())) { 187 if (isConnected()) { 188 return Messages.CONNECTED_PUNCTUATION_CLICK_TO_DISCONNECT_; 189 } else { 190 return Messages.DISCONNECTED_PUNCTUATION_CLICK_TO_CONNECT_; 191 } 192 } else { 193 return super.getToolTipText(event); 194 } 195 } 196 197 private synchronized void addTab(TabInfo tabInfo) { 198 Tab tab = instantiate(tabInfo); 199 if (tab != null) { 200 addTab(tabInfo.name, tab); 201 } else { 202 tabInfo.tabVisible = false; 203 } 204 } 205 206 private synchronized void insertTab(TabInfo tabInfo, int index) { 207 Tab tab = instantiate(tabInfo); 208 if (tab != null) { 209 insertTab(tabInfo.name, null, tab, null, index); 210 } else { 211 tabInfo.tabVisible = false; 212 } 213 } 214 215 public synchronized void removeTabAt(int index) { 216 super.removeTabAt(index); 217 } 218 219 private Tab instantiate(TabInfo tabInfo) { 220 try { 221 Constructor<?> con = tabInfo.tabClass.getConstructor(VMPanel.class); 222 return (Tab) con.newInstance(this); 223 } catch (Exception ex) { 224 System.err.println(ex); 225 return null; 226 } 227 } 228 229 boolean isConnected() { 230 return proxyClient.isConnected(); 231 } 232 233 public int getUpdateInterval() { 234 return updateInterval; 235 } 236 237 /** 238 * WARNING NEVER CALL THIS METHOD TO MAKE JMX REQUEST 239 * IF assertThread == false. 240 * DISPATCHER THREAD IS NOT ASSERTED. 241 * IT IS USED TO MAKE SOME LOCAL MANIPULATIONS. 242 */ 243 ProxyClient getProxyClient(boolean assertThread) { 244 if (assertThread) { 245 return getProxyClient(); 246 } else { 247 return proxyClient; 248 } 249 } 250 251 public ProxyClient getProxyClient() { 252 String threadClass = Thread.currentThread().getClass().getName(); 253 if (threadClass.equals("java.awt.EventDispatchThread")) { 254 String msg = "Calling VMPanel.getProxyClient() from the Event Dispatch Thread!"; 255 new RuntimeException(msg).printStackTrace(); 256 System.exit(1); 257 } 258 return proxyClient; 259 } 260 261 public void cleanUp() { 262 //proxyClient.disconnect(); 263 for (Tab tab : getTabs()) { 264 tab.dispose(); 265 } 266 for (JConsolePlugin p : plugins.keySet()) { 267 p.dispose(); 268 } 269 // Cancel pending update tasks 270 // 271 if (timer != null) { 272 timer.cancel(); 273 } 274 // Stop listening to connection state events 275 // 276 proxyClient.removePropertyChangeListener(this); 277 } 278 279 // Call on EDT 280 public void connect() { 281 if (isConnected()) { 282 // create plugin tabs if not done 283 createPluginTabs(); 284 // Notify tabs 285 fireConnectedChange(true); 286 // Enable/disable tabs on initial update 287 initialUpdate = true; 288 // Start/Restart update timer on connect/reconnect 289 startUpdateTimer(); 290 } else { 291 new Thread("VMPanel.connect") { 292 293 public void run() { 294 proxyClient.connect(shouldUseSSL); 295 } 296 }.start(); 297 } 298 } 299 300 // Call on EDT 301 public void disconnect() { 302 proxyClient.disconnect(); 303 updateFrameTitle(); 304 } 305 306 // Called on EDT 307 public void propertyChange(PropertyChangeEvent ev) { 308 String prop = ev.getPropertyName(); 309 310 if (prop == CONNECTION_STATE_PROPERTY) { 311 ConnectionState oldState = (ConnectionState) ev.getOldValue(); 312 ConnectionState newState = (ConnectionState) ev.getNewValue(); 313 switch (newState) { 314 case CONNECTING: 315 onConnecting(); 316 break; 317 318 case CONNECTED: 319 if (progressBar != null) { 320 progressBar.setIndeterminate(false); 321 progressBar.setValue(100); 322 } 323 closeOptionPane(); 324 updateFrameTitle(); 325 // create tabs if not done 326 createPluginTabs(); 327 repaint(); 328 // Notify tabs 329 fireConnectedChange(true); 330 // Enable/disable tabs on initial update 331 initialUpdate = true; 332 // Start/Restart update timer on connect/reconnect 333 startUpdateTimer(); 334 break; 335 336 case DISCONNECTED: 337 if (progressBar != null) { 338 progressBar.setIndeterminate(false); 339 progressBar.setValue(0); 340 closeOptionPane(); 341 } 342 vmPanelDied(); 343 if (oldState == ConnectionState.CONNECTED) { 344 // Notify tabs 345 fireConnectedChange(false); 346 } 347 break; 348 } 349 } 350 } 351 352 // Called on EDT 353 private void onConnecting() { 354 time0 = System.currentTimeMillis(); 355 356 SwingUtilities.getWindowAncestor(this); 357 358 String connectionName = getConnectionName(); 359 progressBar = new JProgressBar(); 360 progressBar.setIndeterminate(true); 361 JPanel progressPanel = new JPanel(new FlowLayout(FlowLayout.CENTER)); 362 progressPanel.add(progressBar); 363 364 Object[] message = { 365 "<html><h3>" + Resources.format(Messages.CONNECTING_TO1, connectionName) + "</h3></html>", 366 progressPanel, 367 "<html><b>" + Resources.format(Messages.CONNECTING_TO2, connectionName) + "</b></html>" 368 }; 369 370 optionPane = 371 SheetDialog.showOptionDialog(this, 372 message, 373 JOptionPane.DEFAULT_OPTION, 374 JOptionPane.INFORMATION_MESSAGE, null, 375 new String[]{Messages.CANCEL}, 376 0); 377 378 379 } 380 381 // Called on EDT 382 private void closeOptionPane() { 383 if (optionPane != null) { 384 new Thread("VMPanel.sleeper") { 385 public void run() { 386 long elapsed = System.currentTimeMillis() - time0; 387 if (elapsed < 2000) { 388 try { 389 sleep(2000 - elapsed); 390 } catch (InterruptedException ex) { 391 // Ignore 392 } 393 } 394 SwingUtilities.invokeLater(new Runnable() { 395 396 public void run() { 397 optionPane.setVisible(false); 398 progressBar = null; 399 } 400 }); 401 } 402 }.start(); 403 } 404 } 405 406 void updateFrameTitle() { 407 VMInternalFrame vmIF = getFrame(); 408 if (vmIF != null) { 409 String displayName = getDisplayName(); 410 if (!proxyClient.isConnected()) { 411 displayName = Resources.format(Messages.CONNECTION_NAME__DISCONNECTED_, displayName); 412 } 413 vmIF.setTitle(displayName); 414 } 415 } 416 417 private VMInternalFrame getFrame() { 418 if (vmIF == null) { 419 vmIF = (VMInternalFrame) SwingUtilities.getAncestorOfClass(VMInternalFrame.class, 420 this); 421 } 422 return vmIF; 423 } 424 425 // TODO: this method is not needed when all JConsole tabs 426 // are migrated to use the new JConsolePlugin API. 427 // 428 // A thread safe clone of all JConsole tabs 429 synchronized List<Tab> getTabs() { 430 ArrayList<Tab> list = new ArrayList<Tab>(); 431 int n = getTabCount(); 432 for (int i = 0; i < n; i++) { 433 Component c = getComponentAt(i); 434 if (c instanceof Tab) { 435 list.add((Tab) c); 436 } 437 } 438 return list; 439 } 440 441 private void startUpdateTimer() { 442 if (timer != null) { 443 timer.cancel(); 444 } 445 TimerTask timerTask = new TimerTask() { 446 447 public void run() { 448 update(); 449 } 450 }; 451 String timerName = "Timer-" + getConnectionName(); 452 timer = new Timer(timerName, true); 453 timer.schedule(timerTask, 0, updateInterval); 454 } 455 456 // Call on EDT 457 private void vmPanelDied() { 458 disconnect(); 459 460 if (userDisconnected) { 461 userDisconnected = false; 462 return; 463 } 464 465 JOptionPane optionPane; 466 String msgTitle, msgExplanation, buttonStr; 467 468 if (wasConnected) { 469 wasConnected = false; 470 msgTitle = Messages.CONNECTION_LOST1; 471 msgExplanation = Resources.format(Messages.CONNECTING_TO2, getConnectionName()); 472 buttonStr = Messages.RECONNECT; 473 } else if (shouldUseSSL) { 474 msgTitle = Messages.CONNECTION_FAILED_SSL1; 475 msgExplanation = Resources.format(Messages.CONNECTION_FAILED_SSL2, getConnectionName()); 476 buttonStr = Messages.INSECURE; 477 } else { 478 msgTitle = Messages.CONNECTION_FAILED1; 479 msgExplanation = Resources.format(Messages.CONNECTION_FAILED2, getConnectionName()); 480 buttonStr = Messages.CONNECT; 481 } 482 483 optionPane = 484 SheetDialog.showOptionDialog(this, 485 "<html><h3>" + msgTitle + "</h3>" + 486 "<b>" + msgExplanation + "</b>", 487 JOptionPane.DEFAULT_OPTION, 488 JOptionPane.WARNING_MESSAGE, null, 489 new String[]{buttonStr, Messages.CANCEL}, 490 0); 491 492 optionPane.addPropertyChangeListener(new PropertyChangeListener() { 493 494 public void propertyChange(PropertyChangeEvent event) { 495 if (event.getPropertyName().equals(JOptionPane.VALUE_PROPERTY)) { 496 Object value = event.getNewValue(); 497 498 if (value == Messages.RECONNECT || value == Messages.CONNECT) { 499 connect(); 500 } else if (value == Messages.INSECURE) { 501 shouldUseSSL = false; 502 connect(); 503 } else if (!everConnected) { 504 try { 505 getFrame().setClosed(true); 506 } catch (PropertyVetoException ex) { 507 // Should not happen, but can be ignored. 508 } 509 } 510 } 511 } 512 }); 513 } 514 515 // Note: This method is called on a TimerTask thread. Any GUI manipulation 516 // must be performed with invokeLater() or invokeAndWait(). 517 private Object lockObject = new Object(); 518 519 private void update() { 520 synchronized (lockObject) { 521 if (!isConnected()) { 522 if (wasConnected) { 523 EventQueue.invokeLater(new Runnable() { 524 525 public void run() { 526 vmPanelDied(); 527 } 528 }); 529 } 530 wasConnected = false; 531 return; 532 } else { 533 wasConnected = true; 534 everConnected = true; 535 } 536 proxyClient.flush(); 537 List<Tab> tabs = getTabs(); 538 final int n = tabs.size(); 539 for (int i = 0; i < n; i++) { 540 final int index = i; 541 try { 542 if (!proxyClient.isDead()) { 543 // Update tab 544 // 545 tabs.get(index).update(); 546 // Enable tab on initial update 547 // 548 if (initialUpdate) { 549 EventQueue.invokeLater(new Runnable() { 550 551 public void run() { 552 setEnabledAt(index, true); 553 } 554 }); 555 } 556 } 557 } catch (Exception e) { 558 // Disable tab on initial update 559 // 560 if (initialUpdate) { 561 EventQueue.invokeLater(new Runnable() { 562 public void run() { 563 setEnabledAt(index, false); 564 } 565 }); 566 } 567 } 568 } 569 570 // plugin GUI update 571 for (ExceptionSafePlugin p : plugins.keySet()) { 572 SwingWorker<?, ?> sw = p.newSwingWorker(); 573 SwingWorker<?, ?> prevSW = plugins.get(p); 574 // schedule SwingWorker to run only if the previous 575 // SwingWorker has finished its task and it hasn't started. 576 if (prevSW == null || prevSW.isDone()) { 577 if (sw == null || sw.getState() == SwingWorker.StateValue.PENDING) { 578 plugins.put(p, sw); 579 if (sw != null) { 580 p.executeSwingWorker(sw); 581 } 582 } 583 } 584 } 585 586 // Set the first enabled tab in the tab's list 587 // as the selected tab on initial update 588 // 589 if (initialUpdate) { 590 EventQueue.invokeLater(new Runnable() { 591 public void run() { 592 // Select first enabled tab if current tab isn't. 593 int index = getSelectedIndex(); 594 if (index < 0 || !isEnabledAt(index)) { 595 for (int i = 0; i < n; i++) { 596 if (isEnabledAt(i)) { 597 setSelectedIndex(i); 598 break; 599 } 600 } 601 } 602 } 603 }); 604 initialUpdate = false; 605 } 606 } 607 } 608 609 public String getHostName() { 610 return hostName; 611 } 612 613 public int getPort() { 614 return port; 615 } 616 617 public String getUserName() { 618 return userName; 619 } 620 621 public String getUrl() { 622 return url; 623 } 624 625 public String getPassword() { 626 return password; 627 } 628 629 public String getConnectionName() { 630 return proxyClient.connectionName(); 631 } 632 633 public String getDisplayName() { 634 return proxyClient.getDisplayName(); 635 } 636 637 static class TabInfo { 638 639 Class<? extends Tab> tabClass; 640 String name; 641 boolean tabVisible; 642 643 TabInfo(Class<? extends Tab> tabClass, String name, boolean tabVisible) { 644 this.tabClass = tabClass; 645 this.name = name; 646 this.tabVisible = tabVisible; 647 } 648 } 649 650 private void createPluginTabs() { 651 // add plugin tabs if not done 652 if (!pluginTabsAdded) { 653 for (JConsolePlugin p : plugins.keySet()) { 654 Map<String, JPanel> tabs = p.getTabs(); 655 for (Map.Entry<String, JPanel> e : tabs.entrySet()) { 656 addTab(e.getKey(), e.getValue()); 657 } 658 } 659 pluginTabsAdded = true; 660 } 661 } 662 663 private void fireConnectedChange(boolean connected) { 664 for (Tab tab : getTabs()) { 665 tab.firePropertyChange(JConsoleContext.CONNECTION_STATE_PROPERTY, !connected, connected); 666 } 667 } 668 }