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