private Object getAttribute(ObjectName objName, String attrName) throws MBeanException, InstanceNotFoundException, AttributeNotFoundException, ReflectionException, IOException { final NameValueMap values = getCachedAttributes(objName, Collections.singleton(attrName)); Object value = values.get(attrName); if (value != null || values.containsKey(attrName)) { return value; } // Not in cache, presumably because it was omitted from the // getAttributes result because of an exception. Following // call will probably provoke the same exception. return conn.getAttribute(objName, attrName); }
public class ProxyClient implements JConsoleContext { private ConnectionState connectionState = ConnectionState.DISCONNECTED; // The SwingPropertyChangeSupport will fire events on the EDT private SwingPropertyChangeSupport propertyChangeSupport = new SwingPropertyChangeSupport(this, true); private static Map<String, ProxyClient> cache = Collections.synchronizedMap(new HashMap<String, ProxyClient>()); private volatile boolean isDead = true; private String hostName = null; private int port = 0; private String userName = null; private String password = null; private boolean hasPlatformMXBeans = false; private boolean hasHotSpotDiagnosticMXBean = false; private boolean hasCompilationMXBean = false; private boolean supportsLockUsage = false; // REVISIT: VMPanel and other places relying using getUrl(). // set only if it's created for local monitoring private LocalVirtualMachine lvm; // set only if it's created from a given URL via the Advanced tab private String advancedUrl = null; private JMXServiceURL jmxUrl = null; private MBeanServerConnection mbsc = null; private SnapshotMBeanServerConnection server = null; private JMXConnector jmxc = null; private RMIServer stub = null; private static final SslRMIClientSocketFactory sslRMIClientSocketFactory = new SslRMIClientSocketFactory(); private String registryHostName = null; private int registryPort = 0; private boolean vmConnector = false; private boolean sslRegistry = false; private boolean sslStub = false; private final String connectionName; private final String displayName; private ClassLoadingMXBean classLoadingMBean = null; private CompilationMXBean compilationMBean = null; private MemoryMXBean memoryMBean = null; private OperatingSystemMXBean operatingSystemMBean = null; private RuntimeMXBean runtimeMBean = null; private ThreadMXBean threadMBean = null; private com.sun.management.OperatingSystemMXBean sunOperatingSystemMXBean = null; private HotSpotDiagnosticMXBean hotspotDiagnosticMXBean = null; private List<MemoryPoolProxy> memoryPoolProxies = null; private List<GarbageCollectorMXBean> garbageCollectorMBeans = null; private static final String HOTSPOT_DIAGNOSTIC_MXBEAN_NAME = "com.sun.management:type=HotSpotDiagnostic"; private ProxyClient(String hostName, int port, String userName, String password) throws IOException { this.connectionName = getConnectionName(hostName, port, userName); this.displayName = connectionName; if (hostName.equals("localhost") && port == 0) { // Monitor self this.hostName = hostName; this.port = port; } else { // Create an RMI connector client and connect it to // the RMI connector server final String urlPath = "/jndi/rmi://" + hostName + ":" + port + "/jmxrmi"; JMXServiceURL url = new JMXServiceURL("rmi", "", 0, urlPath); setParameters(url, userName, password); vmConnector = true; registryHostName = hostName; registryPort = port; checkSslConfig(); } } private ProxyClient(String url, String userName, String password) throws IOException { this.advancedUrl = url; this.connectionName = getConnectionName(url, userName); this.displayName = connectionName; setParameters(new JMXServiceURL(url), userName, password); } private ProxyClient(LocalVirtualMachine lvm) throws IOException { this.lvm = lvm; this.connectionName = getConnectionName(lvm); this.displayName = "pid: " + lvm.vmid() + " " + lvm.displayName(); } private void setParameters(JMXServiceURL url, String userName, String password) { this.jmxUrl = url; this.hostName = jmxUrl.getHost(); this.port = jmxUrl.getPort(); this.userName = userName; this.password = password; } private static void checkStub(Remote stub, Class<? extends Remote> stubClass) { // Check remote stub is from the expected class. // if (stub.getClass() != stubClass) { if (!Proxy.isProxyClass(stub.getClass())) { throw new SecurityException("Expecting a " + stubClass.getName() + " stub!"); } else { InvocationHandler handler = Proxy.getInvocationHandler(stub); if (handler.getClass() != RemoteObjectInvocationHandler.class) { throw new SecurityException( "Expecting a dynamic proxy instance with a " + RemoteObjectInvocationHandler.class.getName() + " invocation handler!"); } else { stub = (Remote) handler; } } } // Check RemoteRef in stub is from the expected class // "sun.rmi.server.UnicastRef2". // RemoteRef ref = ((RemoteObject) stub).getRef(); if (ref.getClass() != UnicastRef2.class) { throw new SecurityException( "Expecting a " + UnicastRef2.class.getName() + " remote reference in stub!"); } // Check RMIClientSocketFactory in stub is from the expected class // "javax.rmi.ssl.SslRMIClientSocketFactory". // LiveRef liveRef = ((UnicastRef2) ref).getLiveRef(); RMIClientSocketFactory csf = liveRef.getClientSocketFactory(); if (csf == null || csf.getClass() != SslRMIClientSocketFactory.class) { throw new SecurityException( "Expecting a " + SslRMIClientSocketFactory.class.getName() + " RMI client socket factory in stub!"); } } private static final String rmiServerImplStubClassName = "javax.management.remote.rmi.RMIServerImpl_Stub"; private static final Class<? extends Remote> rmiServerImplStubClass; static { // FIXME: RMIServerImpl_Stub is generated at build time // after jconsole is built. We need to investigate if // the Makefile can be fixed to build jconsole in the // right order. As a workaround for now, we dynamically // load RMIServerImpl_Stub class instead of statically // referencing it. Class<? extends Remote> serverStubClass = null; try { serverStubClass = Class.forName(rmiServerImplStubClassName).asSubclass(Remote.class); } catch (ClassNotFoundException e) { // should never reach here throw (InternalError) new InternalError(e.getMessage()).initCause(e); } rmiServerImplStubClass = serverStubClass; } private void checkSslConfig() throws IOException { // Get the reference to the RMI Registry and lookup RMIServer stub // Registry registry; try { registry = LocateRegistry.getRegistry(registryHostName, registryPort, sslRMIClientSocketFactory); try { stub = (RMIServer) registry.lookup("jmxrmi"); } catch (NotBoundException nbe) { throw (IOException) new IOException(nbe.getMessage()).initCause(nbe); } sslRegistry = true; } catch (IOException e) { registry = LocateRegistry.getRegistry(registryHostName, registryPort); try { stub = (RMIServer) registry.lookup("jmxrmi"); } catch (NotBoundException nbe) { throw (IOException) new IOException(nbe.getMessage()).initCause(nbe); } sslRegistry = false; } // Perform the checks for secure stub // try { checkStub(stub, rmiServerImplStubClass); sslStub = true; } catch (SecurityException e) { sslStub = false; } } /** * Returns true if the underlying RMI registry is SSL-protected. * * @exception UnsupportedOperationException If this {@code ProxyClient} does not denote a JMX * connector for a JMX VM agent. */ public boolean isSslRmiRegistry() { // Check for VM connector // if (!isVmConnector()) { throw new UnsupportedOperationException( "ProxyClient.isSslRmiRegistry() is only supported if this " + "ProxyClient is a JMX connector for a JMX VM agent"); } return sslRegistry; } /** * Returns true if the retrieved RMI stub is SSL-protected. * * @exception UnsupportedOperationException If this {@code ProxyClient} does not denote a JMX * connector for a JMX VM agent. */ public boolean isSslRmiStub() { // Check for VM connector // if (!isVmConnector()) { throw new UnsupportedOperationException( "ProxyClient.isSslRmiStub() is only supported if this " + "ProxyClient is a JMX connector for a JMX VM agent"); } return sslStub; } /** Returns true if this {@code ProxyClient} denotes a JMX connector for a JMX VM agent. */ public boolean isVmConnector() { return vmConnector; } private void setConnectionState(ConnectionState state) { ConnectionState oldState = this.connectionState; this.connectionState = state; propertyChangeSupport.firePropertyChange(CONNECTION_STATE_PROPERTY, oldState, state); } public ConnectionState getConnectionState() { return this.connectionState; } void flush() { if (server != null) { server.flush(); } } void connect(boolean requireSSL) { setConnectionState(ConnectionState.CONNECTING); try { tryConnect(requireSSL); setConnectionState(ConnectionState.CONNECTED); } catch (Exception e) { if (JConsole.isDebug()) { e.printStackTrace(); } setConnectionState(ConnectionState.DISCONNECTED); } } private void tryConnect(boolean requireRemoteSSL) throws IOException { if (jmxUrl == null && "localhost".equals(hostName) && port == 0) { // Monitor self this.jmxc = null; this.mbsc = ManagementFactory.getPlatformMBeanServer(); this.server = Snapshot.newSnapshot(mbsc); } else { // Monitor another process if (lvm != null) { if (!lvm.isManageable()) { lvm.startManagementAgent(); if (!lvm.isManageable()) { // FIXME: what to throw throw new IOException(lvm + "not manageable"); } } if (this.jmxUrl == null) { this.jmxUrl = new JMXServiceURL(lvm.connectorAddress()); } } Map<String, Object> env = new HashMap<String, Object>(); if (requireRemoteSSL) { env.put("jmx.remote.x.check.stub", "true"); } // Need to pass in credentials ? if (userName == null && password == null) { if (isVmConnector()) { // Check for SSL config on reconnection only if (stub == null) { checkSslConfig(); } this.jmxc = new RMIConnector(stub, null); jmxc.connect(env); } else { this.jmxc = JMXConnectorFactory.connect(jmxUrl, env); } } else { env.put(JMXConnector.CREDENTIALS, new String[] {userName, password}); if (isVmConnector()) { // Check for SSL config on reconnection only if (stub == null) { checkSslConfig(); } this.jmxc = new RMIConnector(stub, null); jmxc.connect(env); } else { this.jmxc = JMXConnectorFactory.connect(jmxUrl, env); } } this.mbsc = jmxc.getMBeanServerConnection(); this.server = Snapshot.newSnapshot(mbsc); } this.isDead = false; try { ObjectName on = new ObjectName(THREAD_MXBEAN_NAME); this.hasPlatformMXBeans = server.isRegistered(on); this.hasHotSpotDiagnosticMXBean = server.isRegistered(new ObjectName(HOTSPOT_DIAGNOSTIC_MXBEAN_NAME)); // check if it has 6.0 new APIs if (this.hasPlatformMXBeans) { MBeanOperationInfo[] mopis = server.getMBeanInfo(on).getOperations(); // look for findDeadlockedThreads operations; for (MBeanOperationInfo op : mopis) { if (op.getName().equals("findDeadlockedThreads")) { this.supportsLockUsage = true; break; } } on = new ObjectName(COMPILATION_MXBEAN_NAME); this.hasCompilationMXBean = server.isRegistered(on); } } catch (MalformedObjectNameException e) { // should not reach here throw new InternalError(e.getMessage()); } catch (IntrospectionException e) { InternalError ie = new InternalError(e.getMessage()); ie.initCause(e); throw ie; } catch (InstanceNotFoundException e) { InternalError ie = new InternalError(e.getMessage()); ie.initCause(e); throw ie; } catch (ReflectionException e) { InternalError ie = new InternalError(e.getMessage()); ie.initCause(e); throw ie; } if (hasPlatformMXBeans) { // WORKAROUND for bug 5056632 // Check if the access role is correct by getting a RuntimeMXBean getRuntimeMXBean(); } } /** Gets a proxy client for a given local virtual machine. */ public static ProxyClient getProxyClient(LocalVirtualMachine lvm) throws IOException { final String key = getCacheKey(lvm); ProxyClient proxyClient = cache.get(key); if (proxyClient == null) { proxyClient = new ProxyClient(lvm); cache.put(key, proxyClient); } return proxyClient; } public static String getConnectionName(LocalVirtualMachine lvm) { return Integer.toString(lvm.vmid()); } private static String getCacheKey(LocalVirtualMachine lvm) { return Integer.toString(lvm.vmid()); } /** Gets a proxy client for a given JMXServiceURL. */ public static ProxyClient getProxyClient(String url, String userName, String password) throws IOException { final String key = getCacheKey(url, userName, password); ProxyClient proxyClient = cache.get(key); if (proxyClient == null) { proxyClient = new ProxyClient(url, userName, password); cache.put(key, proxyClient); } return proxyClient; } public static String getConnectionName(String url, String userName) { if (userName != null && userName.length() > 0) { return userName + "@" + url; } else { return url; } } private static String getCacheKey(String url, String userName, String password) { return (url == null ? "" : url) + ":" + (userName == null ? "" : userName) + ":" + (password == null ? "" : password); } /** Gets a proxy client for a given "hostname:port". */ public static ProxyClient getProxyClient( String hostName, int port, String userName, String password) throws IOException { final String key = getCacheKey(hostName, port, userName, password); ProxyClient proxyClient = cache.get(key); if (proxyClient == null) { proxyClient = new ProxyClient(hostName, port, userName, password); cache.put(key, proxyClient); } return proxyClient; } public static String getConnectionName(String hostName, int port, String userName) { String name = hostName + ":" + port; if (userName != null && userName.length() > 0) { return userName + "@" + name; } else { return name; } } private static String getCacheKey(String hostName, int port, String userName, String password) { return (hostName == null ? "" : hostName) + ":" + port + ":" + (userName == null ? "" : userName) + ":" + (password == null ? "" : password); } public String connectionName() { return connectionName; } public String getDisplayName() { return displayName; } public String toString() { if (!isConnected()) { return Resources.format(Messages.CONNECTION_NAME__DISCONNECTED_, displayName); } else { return displayName; } } public MBeanServerConnection getMBeanServerConnection() { return mbsc; } public SnapshotMBeanServerConnection getSnapshotMBeanServerConnection() { return server; } public String getUrl() { return advancedUrl; } public String getHostName() { return hostName; } public int getPort() { return port; } public int getVmid() { return (lvm != null) ? lvm.vmid() : 0; } public String getUserName() { return userName; } public String getPassword() { return password; } public void disconnect() { // Reset remote stub stub = null; // Close MBeanServer connection if (jmxc != null) { try { jmxc.close(); } catch (IOException e) { // Ignore ??? } } // Reset platform MBean references classLoadingMBean = null; compilationMBean = null; memoryMBean = null; operatingSystemMBean = null; runtimeMBean = null; threadMBean = null; sunOperatingSystemMXBean = null; garbageCollectorMBeans = null; // Set connection state to DISCONNECTED if (!isDead) { isDead = true; setConnectionState(ConnectionState.DISCONNECTED); } } /** Returns the list of domains in which any MBean is currently registered. */ public String[] getDomains() throws IOException { return server.getDomains(); } /** * Returns a map of MBeans with ObjectName as the key and MBeanInfo value of a given domain. If * domain is <tt>null</tt>, all MBeans are returned. If no MBean found, an empty map is returned. */ public Map<ObjectName, MBeanInfo> getMBeans(String domain) throws IOException { ObjectName name = null; if (domain != null) { try { name = new ObjectName(domain + ":*"); } catch (MalformedObjectNameException e) { // should not reach here assert (false); } } Set<ObjectName> mbeans = server.queryNames(name, null); Map<ObjectName, MBeanInfo> result = new HashMap<ObjectName, MBeanInfo>(mbeans.size()); Iterator<ObjectName> iterator = mbeans.iterator(); while (iterator.hasNext()) { Object object = iterator.next(); if (object instanceof ObjectName) { ObjectName o = (ObjectName) object; try { MBeanInfo info = server.getMBeanInfo(o); result.put(o, info); } catch (IntrospectionException e) { // TODO: should log the error } catch (InstanceNotFoundException e) { // TODO: should log the error } catch (ReflectionException e) { // TODO: should log the error } } } return result; } /** Returns a list of attributes of a named MBean. */ public AttributeList getAttributes(ObjectName name, String[] attributes) throws IOException { AttributeList list = null; try { list = server.getAttributes(name, attributes); } catch (InstanceNotFoundException e) { // TODO: A MBean may have been unregistered. // need to set up listener to listen for MBeanServerNotification. } catch (ReflectionException e) { // TODO: should log the error } return list; } /** Set the value of a specific attribute of a named MBean. */ public void setAttribute(ObjectName name, Attribute attribute) throws InvalidAttributeValueException, MBeanException, IOException { try { server.setAttribute(name, attribute); } catch (InstanceNotFoundException e) { // TODO: A MBean may have been unregistered. } catch (AttributeNotFoundException e) { assert (false); } catch (ReflectionException e) { // TODO: should log the error } } /** * Invokes an operation of a named MBean. * * @throws MBeanException Wraps an exception thrown by the MBean's invoked method. */ public Object invoke(ObjectName name, String operationName, Object[] params, String[] signature) throws IOException, MBeanException { Object result = null; try { result = server.invoke(name, operationName, params, signature); } catch (InstanceNotFoundException e) { // TODO: A MBean may have been unregistered. } catch (ReflectionException e) { // TODO: should log the error } return result; } public synchronized ClassLoadingMXBean getClassLoadingMXBean() throws IOException { if (hasPlatformMXBeans && classLoadingMBean == null) { classLoadingMBean = newPlatformMXBeanProxy(server, CLASS_LOADING_MXBEAN_NAME, ClassLoadingMXBean.class); } return classLoadingMBean; } public synchronized CompilationMXBean getCompilationMXBean() throws IOException { if (hasCompilationMXBean && compilationMBean == null) { compilationMBean = newPlatformMXBeanProxy(server, COMPILATION_MXBEAN_NAME, CompilationMXBean.class); } return compilationMBean; } public Collection<MemoryPoolProxy> getMemoryPoolProxies() throws IOException { // TODO: How to deal with changes to the list?? if (memoryPoolProxies == null) { ObjectName poolName = null; try { poolName = new ObjectName(MEMORY_POOL_MXBEAN_DOMAIN_TYPE + ",*"); } catch (MalformedObjectNameException e) { // should not reach here assert (false); } Set<ObjectName> mbeans = server.queryNames(poolName, null); if (mbeans != null) { memoryPoolProxies = new ArrayList<MemoryPoolProxy>(); Iterator<ObjectName> iterator = mbeans.iterator(); while (iterator.hasNext()) { ObjectName objName = (ObjectName) iterator.next(); MemoryPoolProxy p = new MemoryPoolProxy(this, objName); memoryPoolProxies.add(p); } } } return memoryPoolProxies; } public synchronized Collection<GarbageCollectorMXBean> getGarbageCollectorMXBeans() throws IOException { // TODO: How to deal with changes to the list?? if (garbageCollectorMBeans == null) { ObjectName gcName = null; try { gcName = new ObjectName(GARBAGE_COLLECTOR_MXBEAN_DOMAIN_TYPE + ",*"); } catch (MalformedObjectNameException e) { // should not reach here assert (false); } Set<ObjectName> mbeans = server.queryNames(gcName, null); if (mbeans != null) { garbageCollectorMBeans = new ArrayList<GarbageCollectorMXBean>(); Iterator<ObjectName> iterator = mbeans.iterator(); while (iterator.hasNext()) { ObjectName on = (ObjectName) iterator.next(); String name = GARBAGE_COLLECTOR_MXBEAN_DOMAIN_TYPE + ",name=" + on.getKeyProperty("name"); GarbageCollectorMXBean mBean = newPlatformMXBeanProxy(server, name, GarbageCollectorMXBean.class); garbageCollectorMBeans.add(mBean); } } } return garbageCollectorMBeans; } public synchronized MemoryMXBean getMemoryMXBean() throws IOException { if (hasPlatformMXBeans && memoryMBean == null) { memoryMBean = newPlatformMXBeanProxy(server, MEMORY_MXBEAN_NAME, MemoryMXBean.class); } return memoryMBean; } public synchronized RuntimeMXBean getRuntimeMXBean() throws IOException { if (hasPlatformMXBeans && runtimeMBean == null) { runtimeMBean = newPlatformMXBeanProxy(server, RUNTIME_MXBEAN_NAME, RuntimeMXBean.class); } return runtimeMBean; } public synchronized ThreadMXBean getThreadMXBean() throws IOException { if (hasPlatformMXBeans && threadMBean == null) { threadMBean = newPlatformMXBeanProxy(server, THREAD_MXBEAN_NAME, ThreadMXBean.class); } return threadMBean; } public synchronized OperatingSystemMXBean getOperatingSystemMXBean() throws IOException { if (hasPlatformMXBeans && operatingSystemMBean == null) { operatingSystemMBean = newPlatformMXBeanProxy(server, OPERATING_SYSTEM_MXBEAN_NAME, OperatingSystemMXBean.class); } return operatingSystemMBean; } public synchronized com.sun.management.OperatingSystemMXBean getSunOperatingSystemMXBean() throws IOException { try { ObjectName on = new ObjectName(OPERATING_SYSTEM_MXBEAN_NAME); if (sunOperatingSystemMXBean == null) { if (server.isInstanceOf(on, "com.sun.management.OperatingSystemMXBean")) { sunOperatingSystemMXBean = newPlatformMXBeanProxy( server, OPERATING_SYSTEM_MXBEAN_NAME, com.sun.management.OperatingSystemMXBean.class); } } } catch (InstanceNotFoundException e) { return null; } catch (MalformedObjectNameException e) { return null; // should never reach here } return sunOperatingSystemMXBean; } public synchronized HotSpotDiagnosticMXBean getHotSpotDiagnosticMXBean() throws IOException { if (hasHotSpotDiagnosticMXBean && hotspotDiagnosticMXBean == null) { hotspotDiagnosticMXBean = newPlatformMXBeanProxy( server, HOTSPOT_DIAGNOSTIC_MXBEAN_NAME, HotSpotDiagnosticMXBean.class); } return hotspotDiagnosticMXBean; } public <T> T getMXBean(ObjectName objName, Class<T> interfaceClass) throws IOException { return newPlatformMXBeanProxy(server, objName.toString(), interfaceClass); } // Return thread IDs of deadlocked threads or null if any. // It finds deadlocks involving only monitors if it's a Tiger VM. // Otherwise, it finds deadlocks involving both monitors and // the concurrent locks. public long[] findDeadlockedThreads() throws IOException { ThreadMXBean tm = getThreadMXBean(); if (supportsLockUsage && tm.isSynchronizerUsageSupported()) { return tm.findDeadlockedThreads(); } else { return tm.findMonitorDeadlockedThreads(); } } public synchronized void markAsDead() { disconnect(); } public boolean isDead() { return isDead; } boolean isConnected() { return !isDead(); } boolean hasPlatformMXBeans() { return this.hasPlatformMXBeans; } boolean hasHotSpotDiagnosticMXBean() { return this.hasHotSpotDiagnosticMXBean; } boolean isLockUsageSupported() { return supportsLockUsage; } public boolean isRegistered(ObjectName name) throws IOException { return server.isRegistered(name); } public void addPropertyChangeListener(PropertyChangeListener listener) { propertyChangeSupport.addPropertyChangeListener(listener); } public void addWeakPropertyChangeListener(PropertyChangeListener listener) { if (!(listener instanceof WeakPCL)) { listener = new WeakPCL(listener); } propertyChangeSupport.addPropertyChangeListener(listener); } public void removePropertyChangeListener(PropertyChangeListener listener) { if (!(listener instanceof WeakPCL)) { // Search for the WeakPCL holding this listener (if any) for (PropertyChangeListener pcl : propertyChangeSupport.getPropertyChangeListeners()) { if (pcl instanceof WeakPCL && ((WeakPCL) pcl).get() == listener) { listener = pcl; break; } } } propertyChangeSupport.removePropertyChangeListener(listener); } /** * The PropertyChangeListener is handled via a WeakReference so as not to pin down the listener. */ private class WeakPCL extends WeakReference<PropertyChangeListener> implements PropertyChangeListener { WeakPCL(PropertyChangeListener referent) { super(referent); } public void propertyChange(PropertyChangeEvent pce) { PropertyChangeListener pcl = get(); if (pcl == null) { // The referent listener was GC'ed, we're no longer // interested in PropertyChanges, remove the listener. dispose(); } else { pcl.propertyChange(pce); } } private void dispose() { removePropertyChangeListener(this); } } // // Snapshot MBeanServerConnection: // // This is an object that wraps an existing MBeanServerConnection and adds // caching to it, as follows: // // - The first time an attribute is called in a given MBean, the result is // cached. Every subsequent time getAttribute is called for that attribute // the cached result is returned. // // - Before every call to VMPanel.update() or when the Refresh button in the // Attributes table is pressed down the attributes cache is flushed. Then // any subsequent call to getAttribute will retrieve all the values for // the attributes that are known to the cache. // // - The attributes cache uses a learning approach and only the attributes // that are in the cache will be retrieved between two subsequent updates. // public interface SnapshotMBeanServerConnection extends MBeanServerConnection { /** Flush all cached values of attributes. */ public void flush(); } public static class Snapshot { private Snapshot() {} public static SnapshotMBeanServerConnection newSnapshot(MBeanServerConnection mbsc) { final InvocationHandler ih = new SnapshotInvocationHandler(mbsc); return (SnapshotMBeanServerConnection) Proxy.newProxyInstance( Snapshot.class.getClassLoader(), new Class[] {SnapshotMBeanServerConnection.class}, ih); } } static class SnapshotInvocationHandler implements InvocationHandler { private final MBeanServerConnection conn; private Map<ObjectName, NameValueMap> cachedValues = newMap(); private Map<ObjectName, Set<String>> cachedNames = newMap(); @SuppressWarnings("serial") private static final class NameValueMap extends HashMap<String, Object> {} SnapshotInvocationHandler(MBeanServerConnection conn) { this.conn = conn; } synchronized void flush() { cachedValues = newMap(); } public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { final String methodName = method.getName(); if (methodName.equals("getAttribute")) { return getAttribute((ObjectName) args[0], (String) args[1]); } else if (methodName.equals("getAttributes")) { return getAttributes((ObjectName) args[0], (String[]) args[1]); } else if (methodName.equals("flush")) { flush(); return null; } else { try { return method.invoke(conn, args); } catch (InvocationTargetException e) { throw e.getCause(); } } } private Object getAttribute(ObjectName objName, String attrName) throws MBeanException, InstanceNotFoundException, AttributeNotFoundException, ReflectionException, IOException { final NameValueMap values = getCachedAttributes(objName, Collections.singleton(attrName)); Object value = values.get(attrName); if (value != null || values.containsKey(attrName)) { return value; } // Not in cache, presumably because it was omitted from the // getAttributes result because of an exception. Following // call will probably provoke the same exception. return conn.getAttribute(objName, attrName); } private AttributeList getAttributes(ObjectName objName, String[] attrNames) throws InstanceNotFoundException, ReflectionException, IOException { final NameValueMap values = getCachedAttributes(objName, new TreeSet<String>(Arrays.asList(attrNames))); final AttributeList list = new AttributeList(); for (String attrName : attrNames) { final Object value = values.get(attrName); if (value != null || values.containsKey(attrName)) { list.add(new Attribute(attrName, value)); } } return list; } private synchronized NameValueMap getCachedAttributes(ObjectName objName, Set<String> attrNames) throws InstanceNotFoundException, ReflectionException, IOException { NameValueMap values = cachedValues.get(objName); if (values != null && values.keySet().containsAll(attrNames)) { return values; } attrNames = new TreeSet<String>(attrNames); Set<String> oldNames = cachedNames.get(objName); if (oldNames != null) { attrNames.addAll(oldNames); } values = new NameValueMap(); final AttributeList attrs = conn.getAttributes(objName, attrNames.toArray(new String[attrNames.size()])); for (Attribute attr : attrs.asList()) { values.put(attr.getName(), attr.getValue()); } cachedValues.put(objName, values); cachedNames.put(objName, attrNames); return values; } // See http://www.artima.com/weblogs/viewpost.jsp?thread=79394 private static <K, V> Map<K, V> newMap() { return new HashMap<K, V>(); } } }