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 }