/*
* Copyright (c) 2007, 2011, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.sun.tools.visualvm.tools.jmx;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.IOException;
import java.lang.ref.WeakReference;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import java.util.WeakHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import javax.management.Attribute;
import javax.management.AttributeList;
import javax.management.AttributeNotFoundException;
import javax.management.InstanceNotFoundException;
import javax.management.MBeanException;
import javax.management.MBeanServerConnection;
import javax.management.ObjectName;
import javax.management.ReflectionException;
import javax.swing.Timer;
import org.openide.util.RequestProcessor;
/**
* <p>The {@code CachedMBeanServerConnectionFactory} class is a factory class that
* allows to get instances of {@link CachedMBeanServerConnection} for a given
* {@link MBeanServerConnection} or {@link JmxModel}.</p>
*
* <p>The factory methods allow to supply an interval value at which the cache will
* be automatically flushed and interested {@link MBeanCacheListener}s notified.</p>
*
* <p>If the factory methods which do not take an interval value are used then
* no automatic flush is performed and the user will be in charge of flushing
* the cache by calling {@link CachedMBeanServerConnection#flush()}.</p>
*
* @author Eamonn McManus
* @author Luis-Miguel Alventosa
*/
public final class CachedMBeanServerConnectionFactory {
private static final Map<Integer, Map<MBeanServerConnection, WeakReference<CachedMBeanServerConnection>>> snapshots =
new HashMap<Integer, Map<MBeanServerConnection, WeakReference<CachedMBeanServerConnection>>>();
private CachedMBeanServerConnectionFactory() {
}
/**
* <p>Factory method for obtaining the {@link CachedMBeanServerConnection} for
* the given {@link MBeanServerConnection}.</p>
*
* @param mbsc an MBeanServerConnection.
*
* @return a {@link CachedMBeanServerConnection} instance which caches the
* attribute values of the supplied {@link MBeanServerConnection}.
*/
public static CachedMBeanServerConnection getCachedMBeanServerConnection(MBeanServerConnection mbsc) {
return getCachedMBeanServerConnection(mbsc, 0);
}
/**
* <p>Factory method for obtaining the {@link CachedMBeanServerConnection} for
* the given {@link MBeanServerConnection}. The cache will be flushed at the
* given interval and the interested {@link MBeanCacheListener}s will be notified.</p>
*
* @param mbsc an MBeanServerConnection.
* @param interval the interval (in milliseconds) at which the cache is flushed.
* An interval equal to zero means no automatic flush of the MBean cache.
*
* @return a {@link CachedMBeanServerConnection} instance which caches the
* attribute values of the supplied {@link MBeanServerConnection} and is
* flushed at the end of every interval period.
*
* @throws IllegalArgumentException if the supplied interval is negative.
*/
public static CachedMBeanServerConnection
getCachedMBeanServerConnection(MBeanServerConnection mbsc, int interval)
throws IllegalArgumentException {
if (interval < 0) {
throw new IllegalArgumentException("interval cannot be negative"); // NOI18N
}
return retrieveCachedMBeanServerConnection(mbsc, interval);
}
/**
* <p>Factory method for obtaining the {@link CachedMBeanServerConnection} for
* the given {@link JmxModel}.</p>
*
* @param jmx a JmxModel.
*
* @return a {@link CachedMBeanServerConnection} instance which caches the
* attribute values of the supplied {@link JmxModel}.
*/
public static CachedMBeanServerConnection getCachedMBeanServerConnection(JmxModel jmx) {
return getCachedMBeanServerConnection(jmx.getMBeanServerConnection(), 0);
}
/**
* <p>Factory method for obtaining the {@link CachedMBeanServerConnection} for
* the given {@link JmxModel}. The cache will be flushed at the given interval
* and the interested {@link MBeanCacheListener}s will be notified.</p>
*
* @param jmx a JmxModel.
* @param interval the interval (in milliseconds) at which the cache is flushed.
* An interval equal to zero means no automatic flush of the MBean cache.
*
* @return a {@link CachedMBeanServerConnection} instance which caches the
* attribute values of the supplied {@link JmxModel} and is flushed at the
* end of every interval period.
*
* @throws IllegalArgumentException if the supplied interval is negative.
*/
public static CachedMBeanServerConnection
getCachedMBeanServerConnection(JmxModel jmx, int interval)
throws IllegalArgumentException {
return getCachedMBeanServerConnection(jmx.getMBeanServerConnection(), interval);
}
private static synchronized CachedMBeanServerConnection
retrieveCachedMBeanServerConnection(MBeanServerConnection mbsc, int interval) {
Map<MBeanServerConnection, WeakReference<CachedMBeanServerConnection>> mbscMap = snapshots.get(interval);
if (mbscMap == null) {
CachedMBeanServerConnection cmbsc = Snapshot.newSnapshot(mbsc, interval);
Map<MBeanServerConnection, WeakReference<CachedMBeanServerConnection>> mbscMapNew =
new WeakHashMap<MBeanServerConnection, WeakReference<CachedMBeanServerConnection>>();
mbscMapNew.put(mbsc, new WeakReference<CachedMBeanServerConnection>(cmbsc));
snapshots.put(interval, mbscMapNew);
return cmbsc;
} else {
WeakReference<CachedMBeanServerConnection> cmbscRef = mbscMap.get(mbsc);
CachedMBeanServerConnection cmbsc = (cmbscRef == null) ? null : cmbscRef.get();
if (cmbsc == null) {
cmbsc = Snapshot.newSnapshot(mbsc, interval);
mbscMap.put(mbsc, new WeakReference<CachedMBeanServerConnection>(cmbsc));
}
return cmbsc;
}
}
static class Snapshot {
private Snapshot() {
}
public static CachedMBeanServerConnection newSnapshot(MBeanServerConnection mbsc, int interval) {
final InvocationHandler ih = new SnapshotInvocationHandler(mbsc, interval);
return (CachedMBeanServerConnection) Proxy.newProxyInstance(
Snapshot.class.getClassLoader(),
new Class[]{CachedMBeanServerConnection.class},
ih);
}
}
static class SnapshotInvocationHandler implements InvocationHandler {
private final MBeanServerConnection conn;
private final int interval;
private Timer timer = null;
private Map<ObjectName, NameValueMap> cachedValues = newMap();
private Map<ObjectName, Set<String>> cachedNames = newMap();
private List<MBeanCacheListener> listenerList = new CopyOnWriteArrayList<MBeanCacheListener>();
private volatile boolean flushRunning;
@SuppressWarnings("serial")
private static final class NameValueMap
extends HashMap<String, Object> {
}
SnapshotInvocationHandler(MBeanServerConnection conn, int interval) {
this.conn = conn;
this.interval = interval;
if (interval > 0) {
timer = new Timer(interval, new ActionListener() {
public void actionPerformed(ActionEvent e) {
intervalElapsed();
}
});
timer.setCoalesce(true);
timer.start();
}
}
void intervalElapsed() {
if (flushRunning) return;
flushRunning = true;
RequestProcessor.getDefault().post(new Runnable() {
public void run() {
flush();
connectionPinger();
notifyListeners();
flushRunning = false;
}
});
}
void notifyListeners() {
for (MBeanCacheListener listener : listenerList) {
listener.flushed();
}
}
private void connectionPinger() {
try {
conn.getDefaultDomain();
} catch (Exception e) {
timer.stop();
listenerList.clear();
cachedValues.clear();
cachedNames.clear();
Collection<Map<MBeanServerConnection, WeakReference<CachedMBeanServerConnection>>> values = snapshots.values();
for (Map<MBeanServerConnection, WeakReference<CachedMBeanServerConnection>> value : values) {
value.remove(conn);
}
}
}
synchronized void flush() {
cachedValues = newMap();
}
int getInterval() {
return interval;
}
void addMBeanCacheListener(MBeanCacheListener listener) {
listenerList.add(listener);
}
void removeMBeanCacheListener(MBeanCacheListener listener) {
listenerList.remove(listener);
}
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
final String methodName = method.getName();
if (methodName.equals("getAttribute")) { // NOI18N
return getAttribute((ObjectName) args[0], (String) args[1]);
} else if (methodName.equals("getAttributes")) { // NOI18N
return getAttributes((ObjectName) args[0], (String[]) args[1]);
} else if (methodName.equals("flush")) { // NOI18N
flush();
return null;
} else if (methodName.equals("getInterval")) { // NOI18N
return getInterval();
} else if (methodName.equals("addMBeanCacheListener")) { // NOI18N
addMBeanCacheListener((MBeanCacheListener) args[0]);
return null;
} else if (methodName.equals("removeMBeanCacheListener")) { // NOI18N
removeMBeanCacheListener((MBeanCacheListener) args[0]);
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>();
}
}
}