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