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 }