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 {
tabInfos.add(new TabInfo(OverviewTab.class, OverviewTab.getTabName(), true))83         tabInfos.add(new TabInfo(OverviewTab.class, OverviewTab.getTabName(), true));
tabInfos.add(new TabInfo(MemoryTab.class, MemoryTab.getTabName(), true))84         tabInfos.add(new TabInfo(MemoryTab.class, MemoryTab.getTabName(), true));
tabInfos.add(new TabInfo(ThreadTab.class, ThreadTab.getTabName(), true))85         tabInfos.add(new TabInfo(ThreadTab.class, ThreadTab.getTabName(), true));
tabInfos.add(new TabInfo(ClassTab.class, ClassTab.getTabName(), true))86         tabInfos.add(new TabInfo(ClassTab.class, ClassTab.getTabName(), true));
tabInfos.add(new TabInfo(SummaryTab.class, SummaryTab.getTabName(), true))87         tabInfos.add(new TabInfo(SummaryTab.class, SummaryTab.getTabName(), true));
tabInfos.add(new TabInfo(MBeansTab.class, MBeansTab.getTabName(), true))88         tabInfos.add(new TabInfo(MBeansTab.class, MBeansTab.getTabName(), true));
89     }
90 
getTabInfos()91     public static TabInfo[] getTabInfos() {
92         return tabInfos.toArray(new TabInfo[tabInfos.size()]);
93     }
94 
VMPanel(ProxyClient proxyClient, int updateInterval)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.
setUI(TabbedPaneUI ui)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
paintComponent(Graphics g)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 
getToolTipText(MouseEvent event)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 
addTab(TabInfo tabInfo)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 
insertTab(TabInfo tabInfo, int index)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 
removeTabAt(int index)217     public synchronized void removeTabAt(int index) {
218         super.removeTabAt(index);
219     }
220 
instantiate(TabInfo tabInfo)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 
isConnected()231     boolean isConnected() {
232         return proxyClient.isConnected();
233     }
234 
getUpdateInterval()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      */
getProxyClient(boolean assertThread)245     ProxyClient getProxyClient(boolean assertThread) {
246         if (assertThread) {
247             return getProxyClient();
248         } else {
249             return proxyClient;
250         }
251     }
252 
getProxyClient()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 
cleanUp()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
connect()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
disconnect()303     public void disconnect() {
304         proxyClient.disconnect();
305         updateFrameTitle();
306     }
307 
308     // Called on EDT
propertyChange(PropertyChangeEvent ev)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
onConnecting()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
closeOptionPane()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 
updateFrameTitle()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 
getFrame()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
getTabs()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 
startUpdateTimer()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
vmPanelDied()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 
update()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 
getHostName()611     public String getHostName() {
612         return hostName;
613     }
614 
getPort()615     public int getPort() {
616         return port;
617     }
618 
getUserName()619     public String getUserName() {
620         return userName;
621     }
622 
getUrl()623     public String getUrl() {
624         return url;
625     }
626 
getPassword()627     public String getPassword() {
628         return password;
629     }
630 
getConnectionName()631     public String getConnectionName() {
632         return proxyClient.connectionName();
633     }
634 
getDisplayName()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 
TabInfo(Class<? extends Tab> tabClass, String name, boolean tabVisible)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 
createPluginTabs()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 
fireConnectedChange(boolean connected)665     private void fireConnectedChange(boolean connected) {
666         for (Tab tab : getTabs()) {
667             tab.firePropertyChange(JConsoleContext.CONNECTION_STATE_PROPERTY, !connected, connected);
668         }
669     }
670 }
671