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 }