blob: e4fe180af8a3b6ff5d963ea9cd25df0b2274ab8b [file] [log] [blame]
J. Duke319a3b92007-12-01 00:00:00 +00001/*
2 * Copyright 2004-2007 Sun Microsystems, Inc. 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. Sun designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Sun 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
22 * CA 95054 USA or visit www.sun.com if you need additional information or
23 * have any questions.
24 */
25
26package sun.tools.jconsole;
27
28import com.sun.management.HotSpotDiagnosticMXBean;
29import com.sun.tools.jconsole.JConsoleContext;
30import com.sun.tools.jconsole.JConsoleContext.ConnectionState;
31import java.awt.Component;
32import java.beans.PropertyChangeListener;
33import java.beans.PropertyChangeEvent;
34import java.io.IOException;
35import java.lang.management.*;
36import static java.lang.management.ManagementFactory.*;
37import java.lang.ref.WeakReference;
38import java.lang.reflect.*;
39import java.rmi.*;
40import java.rmi.registry.*;
41import java.rmi.server.*;
42import java.util.*;
43import javax.management.*;
44import javax.management.remote.*;
45import javax.management.remote.rmi.*;
46import javax.rmi.ssl.SslRMIClientSocketFactory;
47import javax.swing.event.SwingPropertyChangeSupport;
48import sun.rmi.server.UnicastRef2;
49import sun.rmi.transport.LiveRef;
50
51public class ProxyClient implements JConsoleContext {
52
53 private ConnectionState connectionState = ConnectionState.DISCONNECTED;
54
55 // The SwingPropertyChangeSupport will fire events on the EDT
56 private SwingPropertyChangeSupport propertyChangeSupport =
57 new SwingPropertyChangeSupport(this, true);
58
59 private static Map<String, ProxyClient> cache =
60 Collections.synchronizedMap(new HashMap<String, ProxyClient>());
61
62 private volatile boolean isDead = true;
63 private String hostName = null;
64 private int port = 0;
65 private String userName = null;
66 private String password = null;
67 private boolean hasPlatformMXBeans = false;
68 private boolean hasHotSpotDiagnosticMXBean= false;
69 private boolean hasCompilationMXBean = false;
70 private boolean supportsLockUsage = false;
71
72 // REVISIT: VMPanel and other places relying using getUrl().
73
74 // set only if it's created for local monitoring
75 private LocalVirtualMachine lvm;
76
77 // set only if it's created from a given URL via the Advanced tab
78 private String advancedUrl = null;
79
80 private JMXServiceURL jmxUrl = null;
81 private SnapshotMBeanServerConnection server = null;
82 private JMXConnector jmxc = null;
83 private RMIServer stub = null;
84 private static final SslRMIClientSocketFactory sslRMIClientSocketFactory =
85 new SslRMIClientSocketFactory();
86 private String registryHostName = null;
87 private int registryPort = 0;
88 private boolean vmConnector = false;
89 private boolean sslRegistry = false;
90 private boolean sslStub = false;
91 final private String connectionName;
92 final private String displayName;
93
94 private ClassLoadingMXBean classLoadingMBean = null;
95 private CompilationMXBean compilationMBean = null;
96 private MemoryMXBean memoryMBean = null;
97 private OperatingSystemMXBean operatingSystemMBean = null;
98 private RuntimeMXBean runtimeMBean = null;
99 private ThreadMXBean threadMBean = null;
100
101 private com.sun.management.OperatingSystemMXBean sunOperatingSystemMXBean = null;
102 private HotSpotDiagnosticMXBean hotspotDiagnosticMXBean = null;
103
104 private List<MemoryPoolProxy> memoryPoolProxies = null;
105 private List<GarbageCollectorMXBean> garbageCollectorMBeans = null;
106 private String detectDeadlocksOperation = null;
107
108 final static private String HOTSPOT_DIAGNOSTIC_MXBEAN_NAME =
109 "com.sun.management:type=HotSpotDiagnostic";
110
111 private ProxyClient(String hostName, int port,
112 String userName, String password) throws IOException {
113 this.connectionName = getConnectionName(hostName, port, userName);
114 this.displayName = connectionName;
115 if (hostName.equals("localhost") && port == 0) {
116 // Monitor self
117 this.hostName = hostName;
118 this.port = port;
119 } else {
120 // Create an RMI connector client and connect it to
121 // the RMI connector server
122 final String urlPath = "/jndi/rmi://" + hostName + ":" + port +
123 "/jmxrmi";
124 JMXServiceURL url = new JMXServiceURL("rmi", "", 0, urlPath);
125 setParameters(url, userName, password);
126 vmConnector = true;
127 registryHostName = hostName;
128 registryPort = port;
129 checkSslConfig();
130 }
131 }
132
133 private ProxyClient(String url,
134 String userName, String password) throws IOException {
135 this.advancedUrl = url;
136 this.connectionName = getConnectionName(url, userName);
137 this.displayName = connectionName;
138 setParameters(new JMXServiceURL(url), userName, password);
139 }
140
141 private ProxyClient(LocalVirtualMachine lvm) throws IOException {
142 this.lvm = lvm;
143 this.connectionName = getConnectionName(lvm);
144 this.displayName = "pid: " + lvm.vmid() + " " + lvm.displayName();
145 }
146
147 private void setParameters(JMXServiceURL url, String userName, String password) {
148 this.jmxUrl = url;
149 this.hostName = jmxUrl.getHost();
150 this.port = jmxUrl.getPort();
151 this.userName = userName;
152 this.password = password;
153 }
154
155 private static void checkStub(Remote stub,
156 Class<? extends Remote> stubClass) {
157 // Check remote stub is from the expected class.
158 //
159 if (stub.getClass() != stubClass) {
160 if (!Proxy.isProxyClass(stub.getClass())) {
161 throw new SecurityException(
162 "Expecting a " + stubClass.getName() + " stub!");
163 } else {
164 InvocationHandler handler = Proxy.getInvocationHandler(stub);
165 if (handler.getClass() != RemoteObjectInvocationHandler.class) {
166 throw new SecurityException(
167 "Expecting a dynamic proxy instance with a " +
168 RemoteObjectInvocationHandler.class.getName() +
169 " invocation handler!");
170 } else {
171 stub = (Remote) handler;
172 }
173 }
174 }
175 // Check RemoteRef in stub is from the expected class
176 // "sun.rmi.server.UnicastRef2".
177 //
178 RemoteRef ref = ((RemoteObject)stub).getRef();
179 if (ref.getClass() != UnicastRef2.class) {
180 throw new SecurityException(
181 "Expecting a " + UnicastRef2.class.getName() +
182 " remote reference in stub!");
183 }
184 // Check RMIClientSocketFactory in stub is from the expected class
185 // "javax.rmi.ssl.SslRMIClientSocketFactory".
186 //
187 LiveRef liveRef = ((UnicastRef2)ref).getLiveRef();
188 RMIClientSocketFactory csf = liveRef.getClientSocketFactory();
189 if (csf == null || csf.getClass() != SslRMIClientSocketFactory.class) {
190 throw new SecurityException(
191 "Expecting a " + SslRMIClientSocketFactory.class.getName() +
192 " RMI client socket factory in stub!");
193 }
194 }
195
196 private static final String rmiServerImplStubClassName =
197 "javax.management.remote.rmi.RMIServerImpl_Stub";
198 private static final Class<? extends Remote> rmiServerImplStubClass;
199
200 static {
201 // FIXME: RMIServerImpl_Stub is generated at build time
202 // after jconsole is built. We need to investigate if
203 // the Makefile can be fixed to build jconsole in the
204 // right order. As a workaround for now, we dynamically
205 // load RMIServerImpl_Stub class instead of statically
206 // referencing it.
207 Class<? extends Remote> serverStubClass = null;
208 try {
209 serverStubClass = Class.forName(rmiServerImplStubClassName).asSubclass(Remote.class);
210 } catch (ClassNotFoundException e) {
211 // should never reach here
212 throw (InternalError) new InternalError(e.getMessage()).initCause(e);
213 }
214 rmiServerImplStubClass = serverStubClass;
215 }
216
217 private void checkSslConfig() throws IOException {
218 // Get the reference to the RMI Registry and lookup RMIServer stub
219 //
220 Registry registry;
221 try {
222 registry =
223 LocateRegistry.getRegistry(registryHostName, registryPort,
224 sslRMIClientSocketFactory);
225 try {
226 stub = (RMIServer) registry.lookup("jmxrmi");
227 } catch (NotBoundException nbe) {
228 throw (IOException)
229 new IOException(nbe.getMessage()).initCause(nbe);
230 }
231 sslRegistry = true;
232 } catch (IOException e) {
233 registry =
234 LocateRegistry.getRegistry(registryHostName, registryPort);
235 try {
236 stub = (RMIServer) registry.lookup("jmxrmi");
237 } catch (NotBoundException nbe) {
238 throw (IOException)
239 new IOException(nbe.getMessage()).initCause(nbe);
240 }
241 sslRegistry = false;
242 }
243 // Perform the checks for secure stub
244 //
245 try {
246 checkStub(stub, rmiServerImplStubClass);
247 sslStub = true;
248 } catch (SecurityException e) {
249 sslStub = false;
250 }
251 }
252
253 /**
254 * Returns true if the underlying RMI registry is SSL-protected.
255 *
256 * @exception UnsupportedOperationException If this {@code ProxyClient}
257 * does not denote a JMX connector for a JMX VM agent.
258 */
259 public boolean isSslRmiRegistry() {
260 // Check for VM connector
261 //
262 if (!isVmConnector()) {
263 throw new UnsupportedOperationException(
264 "ProxyClient.isSslRmiRegistry() is only supported if this " +
265 "ProxyClient is a JMX connector for a JMX VM agent");
266 }
267 return sslRegistry;
268 }
269
270 /**
271 * Returns true if the retrieved RMI stub is SSL-protected.
272 *
273 * @exception UnsupportedOperationException If this {@code ProxyClient}
274 * does not denote a JMX connector for a JMX VM agent.
275 */
276 public boolean isSslRmiStub() {
277 // Check for VM connector
278 //
279 if (!isVmConnector()) {
280 throw new UnsupportedOperationException(
281 "ProxyClient.isSslRmiStub() is only supported if this " +
282 "ProxyClient is a JMX connector for a JMX VM agent");
283 }
284 return sslStub;
285 }
286
287 /**
288 * Returns true if this {@code ProxyClient} denotes
289 * a JMX connector for a JMX VM agent.
290 */
291 public boolean isVmConnector() {
292 return vmConnector;
293 }
294
295 private void setConnectionState(ConnectionState state) {
296 ConnectionState oldState = this.connectionState;
297 this.connectionState = state;
298 propertyChangeSupport.firePropertyChange(CONNECTION_STATE_PROPERTY,
299 oldState, state);
300 }
301
302 public ConnectionState getConnectionState() {
303 return this.connectionState;
304 }
305
306 void flush() {
307 if (server != null) {
308 server.flush();
309 }
310 }
311
312 void connect() {
313 setConnectionState(ConnectionState.CONNECTING);
314 try {
315 tryConnect();
316 setConnectionState(ConnectionState.CONNECTED);
317 } catch (Exception e) {
318 if (JConsole.isDebug()) {
319 e.printStackTrace();
320 }
321 setConnectionState(ConnectionState.DISCONNECTED);
322 }
323 }
324
325 private void tryConnect() throws IOException {
326 if (jmxUrl == null && "localhost".equals(hostName) && port == 0) {
327 // Monitor self
328 this.jmxc = null;
329 this.server = Snapshot.newSnapshot(
330 ManagementFactory.getPlatformMBeanServer());
331 } else {
332 // Monitor another process
333 if (lvm != null) {
334 if (!lvm.isManageable()) {
335 lvm.startManagementAgent();
336 if (!lvm.isManageable()) {
337 // FIXME: what to throw
338 throw new IOException(lvm + "not manageable");
339 }
340 }
341 if (this.jmxUrl == null) {
342 this.jmxUrl = new JMXServiceURL(lvm.connectorAddress());
343 }
344 }
345 // Need to pass in credentials ?
346 if (userName == null && password == null) {
347 if (isVmConnector()) {
348 // Check for SSL config on reconnection only
349 if (stub == null) {
350 checkSslConfig();
351 }
352 this.jmxc = new RMIConnector(stub, null);
353 jmxc.connect();
354 } else {
355 this.jmxc = JMXConnectorFactory.connect(jmxUrl);
356 }
357 } else {
358 Map<String, String[]> env = new HashMap<String, String[]>();
359 env.put(JMXConnector.CREDENTIALS,
360 new String[] {userName, password});
361 if (isVmConnector()) {
362 // Check for SSL config on reconnection only
363 if (stub == null) {
364 checkSslConfig();
365 }
366 this.jmxc = new RMIConnector(stub, null);
367 jmxc.connect(env);
368 } else {
369 this.jmxc = JMXConnectorFactory.connect(jmxUrl, env);
370 }
371 }
372 this.server = Snapshot.newSnapshot(jmxc.getMBeanServerConnection());
373 }
374 this.isDead = false;
375
376 try {
377 ObjectName on = new ObjectName(THREAD_MXBEAN_NAME);
378 this.hasPlatformMXBeans = server.isRegistered(on);
379 this.hasHotSpotDiagnosticMXBean =
380 server.isRegistered(new ObjectName(HOTSPOT_DIAGNOSTIC_MXBEAN_NAME));
381 // check if it has 6.0 new APIs
382 if (this.hasPlatformMXBeans) {
383 MBeanOperationInfo[] mopis = server.getMBeanInfo(on).getOperations();
384 // look for findDeadlockedThreads operations;
385 for (MBeanOperationInfo op : mopis) {
386 if (op.getName().equals("findDeadlockedThreads")) {
387 this.supportsLockUsage = true;
388 break;
389 }
390 }
391
392 on = new ObjectName(COMPILATION_MXBEAN_NAME);
393 this.hasCompilationMXBean = server.isRegistered(on);
394 }
395 } catch (MalformedObjectNameException e) {
396 // should not reach here
397 throw new InternalError(e.getMessage());
398 } catch (IntrospectionException e) {
399 InternalError ie = new InternalError(e.getMessage());
400 ie.initCause(e);
401 throw ie;
402 } catch (InstanceNotFoundException e) {
403 InternalError ie = new InternalError(e.getMessage());
404 ie.initCause(e);
405 throw ie;
406 } catch (ReflectionException e) {
407 InternalError ie = new InternalError(e.getMessage());
408 ie.initCause(e);
409 throw ie;
410 }
411
412 if (hasPlatformMXBeans) {
413 // WORKAROUND for bug 5056632
414 // Check if the access role is correct by getting a RuntimeMXBean
415 getRuntimeMXBean();
416 }
417 }
418
419 /**
420 * Gets a proxy client for a given local virtual machine.
421 */
422 public static ProxyClient getProxyClient(LocalVirtualMachine lvm)
423 throws IOException {
424 final String key = getCacheKey(lvm);
425 ProxyClient proxyClient = cache.get(key);
426 if (proxyClient == null) {
427 proxyClient = new ProxyClient(lvm);
428 cache.put(key, proxyClient);
429 }
430 return proxyClient;
431 }
432
433 public static String getConnectionName(LocalVirtualMachine lvm) {
434 return Integer.toString(lvm.vmid());
435 }
436
437 private static String getCacheKey(LocalVirtualMachine lvm) {
438 return Integer.toString(lvm.vmid());
439 }
440
441 /**
442 * Gets a proxy client for a given JMXServiceURL.
443 */
444 public static ProxyClient getProxyClient(String url,
445 String userName, String password)
446 throws IOException {
447 final String key = getCacheKey(url, userName, password);
448 ProxyClient proxyClient = cache.get(key);
449 if (proxyClient == null) {
450 proxyClient = new ProxyClient(url, userName, password);
451 cache.put(key, proxyClient);
452 }
453 return proxyClient;
454 }
455
456 public static String getConnectionName(String url,
457 String userName) {
458 if (userName != null && userName.length() > 0) {
459 return userName + "@" + url;
460 } else {
461 return url;
462 }
463 }
464
465 private static String getCacheKey(String url,
466 String userName, String password) {
467 return (url == null ? "" : url) + ":" +
468 (userName == null ? "" : userName) + ":" +
469 (password == null ? "" : password);
470 }
471
472 /**
473 * Gets a proxy client for a given "hostname:port".
474 */
475 public static ProxyClient getProxyClient(String hostName, int port,
476 String userName, String password)
477 throws IOException {
478 final String key = getCacheKey(hostName, port, userName, password);
479 ProxyClient proxyClient = cache.get(key);
480 if (proxyClient == null) {
481 proxyClient = new ProxyClient(hostName, port, userName, password);
482 cache.put(key, proxyClient);
483 }
484 return proxyClient;
485 }
486
487 public static String getConnectionName(String hostName, int port,
488 String userName) {
489 String name = hostName + ":" + port;
490 if (userName != null && userName.length() > 0) {
491 return userName + "@" + name;
492 } else {
493 return name;
494 }
495 }
496
497 private static String getCacheKey(String hostName, int port,
498 String userName, String password) {
499 return (hostName == null ? "" : hostName) + ":" +
500 port + ":" +
501 (userName == null ? "" : userName) + ":" +
502 (password == null ? "" : password);
503 }
504
505 public String connectionName() {
506 return connectionName;
507 }
508
509 public String getDisplayName() {
510 return displayName;
511 }
512
513 public String toString() {
514 if (!isConnected()) {
515 return Resources.getText("ConnectionName (disconnected)", displayName);
516 } else {
517 return displayName;
518 }
519 }
520
521 public MBeanServerConnection getMBeanServerConnection() {
522 return server;
523 }
524
525 public String getUrl() {
526 return advancedUrl;
527 }
528
529 public String getHostName() {
530 return hostName;
531 }
532
533 public int getPort() {
534 return port;
535 }
536
537 public int getVmid() {
538 return (lvm != null) ? lvm.vmid() : 0;
539 }
540
541 public String getUserName() {
542 return userName;
543 }
544
545 public String getPassword() {
546 return password;
547 }
548
549 public void disconnect() {
550 // Reset remote stub
551 stub = null;
552 // Close MBeanServer connection
553 if (jmxc != null) {
554 try {
555 jmxc.close();
556 } catch (IOException e) {
557 // Ignore ???
558 }
559 }
560 // Reset platform MBean references
561 classLoadingMBean = null;
562 compilationMBean = null;
563 memoryMBean = null;
564 operatingSystemMBean = null;
565 runtimeMBean = null;
566 threadMBean = null;
567 sunOperatingSystemMXBean = null;
568 garbageCollectorMBeans = null;
569 // Set connection state to DISCONNECTED
570 if (!isDead) {
571 isDead = true;
572 setConnectionState(ConnectionState.DISCONNECTED);
573 }
574 }
575
576 /**
577 * Returns the list of domains in which any MBean is
578 * currently registered.
579 */
580 public String[] getDomains() throws IOException {
581 return server.getDomains();
582 }
583
584 /**
585 * Returns a map of MBeans with ObjectName as the key and MBeanInfo value
586 * of a given domain. If domain is <tt>null</tt>, all MBeans
587 * are returned. If no MBean found, an empty map is returned.
588 *
589 */
590 public Map<ObjectName, MBeanInfo> getMBeans(String domain)
591 throws IOException {
592
593 ObjectName name = null;
594 if (domain != null) {
595 try {
596 name = new ObjectName(domain + ":*");
597 } catch (MalformedObjectNameException e) {
598 // should not reach here
599 assert(false);
600 }
601 }
602 Set mbeans = server.queryNames(name, null);
603 Map<ObjectName,MBeanInfo> result =
604 new HashMap<ObjectName,MBeanInfo>(mbeans.size());
605 Iterator iterator = mbeans.iterator();
606 while (iterator.hasNext()) {
607 Object object = iterator.next();
608 if (object instanceof ObjectName) {
609 ObjectName o = (ObjectName)object;
610 try {
611 MBeanInfo info = server.getMBeanInfo(o);
612 result.put(o, info);
613 } catch (IntrospectionException e) {
614 // TODO: should log the error
615 } catch (InstanceNotFoundException e) {
616 // TODO: should log the error
617 } catch (ReflectionException e) {
618 // TODO: should log the error
619 }
620 }
621 }
622 return result;
623 }
624
625 /**
626 * Returns a list of attributes of a named MBean.
627 *
628 */
629 public AttributeList getAttributes(ObjectName name, String[] attributes)
630 throws IOException {
631 AttributeList list = null;
632 try {
633 list = server.getAttributes(name, attributes);
634 } catch (InstanceNotFoundException e) {
635 // TODO: A MBean may have been unregistered.
636 // need to set up listener to listen for MBeanServerNotification.
637 } catch (ReflectionException e) {
638 // TODO: should log the error
639 }
640 return list;
641 }
642
643 /**
644 * Set the value of a specific attribute of a named MBean.
645 */
646 public void setAttribute(ObjectName name, Attribute attribute)
647 throws InvalidAttributeValueException,
648 MBeanException,
649 IOException {
650 try {
651 server.setAttribute(name, attribute);
652 } catch (InstanceNotFoundException e) {
653 // TODO: A MBean may have been unregistered.
654 } catch (AttributeNotFoundException e) {
655 assert(false);
656 } catch (ReflectionException e) {
657 // TODO: should log the error
658 }
659 }
660
661 /**
662 * Invokes an operation of a named MBean.
663 *
664 * @throws MBeanException Wraps an exception thrown by
665 * the MBean's invoked method.
666 */
667 public Object invoke(ObjectName name, String operationName,
668 Object[] params, String[] signature)
669 throws IOException, MBeanException {
670 Object result = null;
671 try {
672 result = server.invoke(name, operationName, params, signature);
673 } catch (InstanceNotFoundException e) {
674 // TODO: A MBean may have been unregistered.
675 } catch (ReflectionException e) {
676 // TODO: should log the error
677 }
678 return result;
679 }
680
681 public synchronized ClassLoadingMXBean getClassLoadingMXBean() throws IOException {
682 if (hasPlatformMXBeans && classLoadingMBean == null) {
683 classLoadingMBean =
684 newPlatformMXBeanProxy(server, CLASS_LOADING_MXBEAN_NAME,
685 ClassLoadingMXBean.class);
686 }
687 return classLoadingMBean;
688 }
689
690 public synchronized CompilationMXBean getCompilationMXBean() throws IOException {
691 if (hasCompilationMXBean && compilationMBean == null) {
692 compilationMBean =
693 newPlatformMXBeanProxy(server, COMPILATION_MXBEAN_NAME,
694 CompilationMXBean.class);
695 }
696 return compilationMBean;
697 }
698
699 public Collection<MemoryPoolProxy> getMemoryPoolProxies()
700 throws IOException {
701
702 // TODO: How to deal with changes to the list??
703 if (memoryPoolProxies == null) {
704 ObjectName poolName = null;
705 try {
706 poolName = new ObjectName(MEMORY_POOL_MXBEAN_DOMAIN_TYPE + ",*");
707 } catch (MalformedObjectNameException e) {
708 // should not reach here
709 assert(false);
710 }
711 Set mbeans = server.queryNames(poolName, null);
712 if (mbeans != null) {
713 memoryPoolProxies = new ArrayList<MemoryPoolProxy>();
714 Iterator iterator = mbeans.iterator();
715 while (iterator.hasNext()) {
716 ObjectName objName = (ObjectName) iterator.next();
717 MemoryPoolProxy p = new MemoryPoolProxy(this, objName);
718 memoryPoolProxies.add(p);
719 }
720 }
721 }
722 return memoryPoolProxies;
723 }
724
725 public synchronized Collection<GarbageCollectorMXBean> getGarbageCollectorMXBeans()
726 throws IOException {
727
728 // TODO: How to deal with changes to the list??
729 if (garbageCollectorMBeans == null) {
730 ObjectName gcName = null;
731 try {
732 gcName = new ObjectName(GARBAGE_COLLECTOR_MXBEAN_DOMAIN_TYPE + ",*");
733 } catch (MalformedObjectNameException e) {
734 // should not reach here
735 assert(false);
736 }
737 Set mbeans = server.queryNames(gcName, null);
738 if (mbeans != null) {
739 garbageCollectorMBeans = new ArrayList<GarbageCollectorMXBean>();
740 Iterator iterator = mbeans.iterator();
741 while (iterator.hasNext()) {
742 ObjectName on = (ObjectName) iterator.next();
743 String name = GARBAGE_COLLECTOR_MXBEAN_DOMAIN_TYPE +
744 ",name=" + on.getKeyProperty("name");
745
746 GarbageCollectorMXBean mBean =
747 newPlatformMXBeanProxy(server, name,
748 GarbageCollectorMXBean.class);
749 garbageCollectorMBeans.add(mBean);
750 }
751 }
752 }
753 return garbageCollectorMBeans;
754 }
755
756 public synchronized MemoryMXBean getMemoryMXBean() throws IOException {
757 if (hasPlatformMXBeans && memoryMBean == null) {
758 memoryMBean =
759 newPlatformMXBeanProxy(server, MEMORY_MXBEAN_NAME,
760 MemoryMXBean.class);
761 }
762 return memoryMBean;
763 }
764
765 public synchronized RuntimeMXBean getRuntimeMXBean() throws IOException {
766 if (hasPlatformMXBeans && runtimeMBean == null) {
767 runtimeMBean =
768 newPlatformMXBeanProxy(server, RUNTIME_MXBEAN_NAME,
769 RuntimeMXBean.class);
770 }
771 return runtimeMBean;
772 }
773
774
775 public synchronized ThreadMXBean getThreadMXBean() throws IOException {
776 if (hasPlatformMXBeans && threadMBean == null) {
777 threadMBean =
778 newPlatformMXBeanProxy(server, THREAD_MXBEAN_NAME,
779 ThreadMXBean.class);
780 }
781 return threadMBean;
782 }
783
784 public synchronized OperatingSystemMXBean getOperatingSystemMXBean() throws IOException {
785 if (hasPlatformMXBeans && operatingSystemMBean == null) {
786 operatingSystemMBean =
787 newPlatformMXBeanProxy(server, OPERATING_SYSTEM_MXBEAN_NAME,
788 OperatingSystemMXBean.class);
789 }
790 return operatingSystemMBean;
791 }
792
793 public synchronized com.sun.management.OperatingSystemMXBean
794 getSunOperatingSystemMXBean() throws IOException {
795
796 try {
797 ObjectName on = new ObjectName(OPERATING_SYSTEM_MXBEAN_NAME);
798 if (sunOperatingSystemMXBean == null) {
799 if (server.isInstanceOf(on,
800 "com.sun.management.OperatingSystemMXBean")) {
801 sunOperatingSystemMXBean =
802 newPlatformMXBeanProxy(server,
803 OPERATING_SYSTEM_MXBEAN_NAME,
804 com.sun.management.OperatingSystemMXBean.class);
805 }
806 }
807 } catch (InstanceNotFoundException e) {
808 return null;
809 } catch (MalformedObjectNameException e) {
810 return null; // should never reach here
811 }
812 return sunOperatingSystemMXBean;
813 }
814
815 public synchronized HotSpotDiagnosticMXBean getHotSpotDiagnosticMXBean() throws IOException {
816 if (hasHotSpotDiagnosticMXBean && hotspotDiagnosticMXBean == null) {
817 hotspotDiagnosticMXBean =
818 newPlatformMXBeanProxy(server, HOTSPOT_DIAGNOSTIC_MXBEAN_NAME,
819 HotSpotDiagnosticMXBean.class);
820 }
821 return hotspotDiagnosticMXBean;
822 }
823
824 public <T> T getMXBean(ObjectName objName, Class<T> interfaceClass)
825 throws IOException {
826 return newPlatformMXBeanProxy(server,
827 objName.toString(),
828 interfaceClass);
829
830 }
831
832 // Return thread IDs of deadlocked threads or null if any.
833 // It finds deadlocks involving only monitors if it's a Tiger VM.
834 // Otherwise, it finds deadlocks involving both monitors and
835 // the concurrent locks.
836 public long[] findDeadlockedThreads() throws IOException {
837 ThreadMXBean tm = getThreadMXBean();
838 if (supportsLockUsage && tm.isSynchronizerUsageSupported()) {
839 return tm.findDeadlockedThreads();
840 } else {
841 return tm.findMonitorDeadlockedThreads();
842 }
843 }
844
845 public synchronized void markAsDead() {
846 disconnect();
847 }
848
849 public boolean isDead() {
850 return isDead;
851 }
852
853 boolean isConnected() {
854 return !isDead();
855 }
856
857 boolean hasPlatformMXBeans() {
858 return this.hasPlatformMXBeans;
859 }
860
861 boolean hasHotSpotDiagnosticMXBean() {
862 return this.hasHotSpotDiagnosticMXBean;
863 }
864
865 boolean isLockUsageSupported() {
866 return supportsLockUsage;
867 }
868
869 public boolean isRegistered(ObjectName name) throws IOException {
870 return server.isRegistered(name);
871 }
872
873 public void addPropertyChangeListener(PropertyChangeListener listener) {
874 propertyChangeSupport.addPropertyChangeListener(listener);
875 }
876
877 public void addWeakPropertyChangeListener(PropertyChangeListener listener) {
878 if (!(listener instanceof WeakPCL)) {
879 listener = new WeakPCL(listener);
880 }
881 propertyChangeSupport.addPropertyChangeListener(listener);
882 }
883
884 public void removePropertyChangeListener(PropertyChangeListener listener) {
885 if (!(listener instanceof WeakPCL)) {
886 // Search for the WeakPCL holding this listener (if any)
887 for (PropertyChangeListener pcl : propertyChangeSupport.getPropertyChangeListeners()) {
888 if (pcl instanceof WeakPCL && ((WeakPCL)pcl).get() == listener) {
889 listener = pcl;
890 break;
891 }
892 }
893 }
894 propertyChangeSupport.removePropertyChangeListener(listener);
895 }
896
897 /**
898 * The PropertyChangeListener is handled via a WeakReference
899 * so as not to pin down the listener.
900 */
901 private class WeakPCL extends WeakReference<PropertyChangeListener>
902 implements PropertyChangeListener {
903 WeakPCL(PropertyChangeListener referent) {
904 super(referent);
905 }
906
907 public void propertyChange(PropertyChangeEvent pce) {
908 PropertyChangeListener pcl = get();
909
910 if (pcl == null) {
911 // The referent listener was GC'ed, we're no longer
912 // interested in PropertyChanges, remove the listener.
913 dispose();
914 } else {
915 pcl.propertyChange(pce);
916 }
917 }
918
919 private void dispose() {
920 removePropertyChangeListener(this);
921 }
922 }
923
924 //
925 // Snapshot MBeanServerConnection:
926 //
927 // This is an object that wraps an existing MBeanServerConnection and adds
928 // caching to it, as follows:
929 //
930 // - The first time an attribute is called in a given MBean, the result is
931 // cached. Every subsequent time getAttribute is called for that attribute
932 // the cached result is returned.
933 //
934 // - Before every call to VMPanel.update() or when the Refresh button in the
935 // Attributes table is pressed down the attributes cache is flushed. Then
936 // any subsequent call to getAttribute will retrieve all the values for
937 // the attributes that are known to the cache.
938 //
939 // - The attributes cache uses a learning approach and only the attributes
940 // that are in the cache will be retrieved between two subsequent updates.
941 //
942
943 public interface SnapshotMBeanServerConnection
944 extends MBeanServerConnection {
945 /**
946 * Flush all cached values of attributes.
947 */
948 public void flush();
949 }
950
951 public static class Snapshot {
952 private Snapshot() {
953 }
954 public static SnapshotMBeanServerConnection
955 newSnapshot(MBeanServerConnection mbsc) {
956 final InvocationHandler ih = new SnapshotInvocationHandler(mbsc);
957 return (SnapshotMBeanServerConnection) Proxy.newProxyInstance(
958 Snapshot.class.getClassLoader(),
959 new Class[] {SnapshotMBeanServerConnection.class},
960 ih);
961 }
962 }
963
964 static class SnapshotInvocationHandler implements InvocationHandler {
965
966 private final MBeanServerConnection conn;
967 private Map<ObjectName, NameValueMap> cachedValues = newMap();
968 private Map<ObjectName, Set<String>> cachedNames = newMap();
969
970 @SuppressWarnings("serial")
971 private static final class NameValueMap
972 extends HashMap<String, Object> {}
973
974 SnapshotInvocationHandler(MBeanServerConnection conn) {
975 this.conn = conn;
976 }
977
978 synchronized void flush() {
979 cachedValues = newMap();
980 }
981
982 public Object invoke(Object proxy, Method method, Object[] args)
983 throws Throwable {
984 final String methodName = method.getName();
985 if (methodName.equals("getAttribute")) {
986 return getAttribute((ObjectName) args[0], (String) args[1]);
987 } else if (methodName.equals("getAttributes")) {
988 return getAttributes((ObjectName) args[0], (String[]) args[1]);
989 } else if (methodName.equals("flush")) {
990 flush();
991 return null;
992 } else {
993 try {
994 return method.invoke(conn, args);
995 } catch (InvocationTargetException e) {
996 throw e.getCause();
997 }
998 }
999 }
1000
1001 private Object getAttribute(ObjectName objName, String attrName)
1002 throws MBeanException, InstanceNotFoundException,
1003 AttributeNotFoundException, ReflectionException, IOException {
1004 final NameValueMap values = getCachedAttributes(
1005 objName, Collections.singleton(attrName));
1006 Object value = values.get(attrName);
1007 if (value != null || values.containsKey(attrName)) {
1008 return value;
1009 }
1010 // Not in cache, presumably because it was omitted from the
1011 // getAttributes result because of an exception. Following
1012 // call will probably provoke the same exception.
1013 return conn.getAttribute(objName, attrName);
1014 }
1015
1016 private AttributeList getAttributes(
1017 ObjectName objName, String[] attrNames) throws
1018 InstanceNotFoundException, ReflectionException, IOException {
1019 final NameValueMap values = getCachedAttributes(
1020 objName,
1021 new TreeSet<String>(Arrays.asList(attrNames)));
1022 final AttributeList list = new AttributeList();
1023 for (String attrName : attrNames) {
1024 final Object value = values.get(attrName);
1025 if (value != null || values.containsKey(attrName)) {
1026 list.add(new Attribute(attrName, value));
1027 }
1028 }
1029 return list;
1030 }
1031
1032 private synchronized NameValueMap getCachedAttributes(
1033 ObjectName objName, Set<String> attrNames) throws
1034 InstanceNotFoundException, ReflectionException, IOException {
1035 NameValueMap values = cachedValues.get(objName);
1036 if (values != null && values.keySet().containsAll(attrNames)) {
1037 return values;
1038 }
1039 attrNames = new TreeSet<String>(attrNames);
1040 Set<String> oldNames = cachedNames.get(objName);
1041 if (oldNames != null) {
1042 attrNames.addAll(oldNames);
1043 }
1044 values = new NameValueMap();
1045 final AttributeList attrs = conn.getAttributes(
1046 objName,
1047 attrNames.toArray(new String[attrNames.size()]));
1048 for (Attribute attr : attrs.asList()) {
1049 values.put(attr.getName(), attr.getValue());
1050 }
1051 cachedValues.put(objName, values);
1052 cachedNames.put(objName, attrNames);
1053 return values;
1054 }
1055
1056 // See http://www.artima.com/weblogs/viewpost.jsp?thread=79394
1057 private static <K, V> Map<K, V> newMap() {
1058 return new HashMap<K, V>();
1059 }
1060 }
1061}