
com.addthis.basis.jmx.MBeanUtils Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of basis Show documentation
Show all versions of basis Show documentation
AddThis core java classes
/*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.addthis.basis.jmx;
import java.io.IOException;
import java.lang.management.ManagementFactory;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import javax.management.Attribute;
import javax.management.DynamicMBean;
import javax.management.JMException;
import javax.management.MBeanAttributeInfo;
import javax.management.MBeanInfo;
import javax.management.MBeanOperationInfo;
import javax.management.MBeanParameterInfo;
import javax.management.MBeanServer;
import javax.management.MBeanServerConnection;
import javax.management.MBeanServerNotification;
import javax.management.MalformedObjectNameException;
import javax.management.Notification;
import javax.management.NotificationListener;
import javax.management.ObjectName;
import javax.management.remote.JMXConnectorFactory;
import javax.management.remote.JMXServiceURL;
/**
* Utilities to make dealing with JMX easier.
*/
public class MBeanUtils {
/**
* ObjectName for the MBean server delegate
*/
public static final ObjectName DELEGATE = parseName("JMImplementation:type=MBeanServerDelegate");
/**
* Notification type for registration
*/
public static final String MBEAN_ADD = MBeanServerNotification.REGISTRATION_NOTIFICATION;
/**
* Notification type for unregistration
*/
public static final String MBEAN_DEL = MBeanServerNotification.UNREGISTRATION_NOTIFICATION;
/**
* URL pattern for connecting remotely
*/
public static final String URL_PATTERN = "service:jmx:rmi:///jndi/rmi://{{host}}:{{port}}/jmxrmi";
// ===================================================================
// NAMING STUFF
// ===================================================================
/**
* Parses an object name, softening any exceptions that result. Useful
* for when you know the object name is going to be valid, or you don't
* care about catching the resulting exception.
*
* @param name the name to parse
* @return the parsed version of the name
* @throws IllegalArgumentException if the name can't be parsed
*/
public static ObjectName parseName(String name) {
try {
return new ObjectName(name);
} catch (MalformedObjectNameException e) {
throw new IllegalArgumentException("bad name " + name, e);
}
}
/**
* Builds an object name, softening any exceptions that result. Useful
* for when you know the object name is going to be valid, or you don't
* care about catching the resulting exception.
*
* @param domain the domain of the resulting name
* @param props (String,Object) pairs representing key properties
* @return the parsed version of the name
* @throws IllegalArgumentException if the name can't be parsed
*/
public static ObjectName buildName(String domain, Object... props) {
StringBuilder str = new StringBuilder();
str.append(domain).append(":");
for (int i = 0; i < props.length; i += 2) {
if (i > 0) {
str.append(",");
}
str.append(props[i]).append("=").append(String.valueOf(props[i + 1]));
}
return parseName(str.toString());
}
/**
* Determines whether a name matches a pattern
*
* @param pattern the pattern to match (null to match anything)
* @param target the target object name
* @return does the target match the pattern?
*/
public static boolean match(ObjectName pattern, ObjectName target) {
return pattern == null || pattern.apply(target);
}
// ===================================================================
// REGISTERING
// ===================================================================
/*
* Maps objects we've registered to their object names (so we can do
* an unregister based on the object). We can have more than one object
* name per registered object, so we keep them in sets.
*/
static final Map> registered = new HashMap<>();
private static void putName(int code, ObjectName name) {
Set set = registered.get(code);
if (set == null) {
registered.put(code, set = new HashSet<>());
}
set.add(name);
}
/**
* Register an object which is already an MBean into JMX, softening
* any exceptions that occur
*
* @param name the JMX name of the object
* @param target the object to register
* @throws RuntimeException if the registration fails
*/
public static void register(ObjectName name, Object target) {
try {
ManagementFactory.getPlatformMBeanServer().registerMBean(target, name);
putName(System.identityHashCode(target), name);
} catch (JMException e) {
throw new RuntimeException("oops! couldn't register " + name, e);
}
}
/**
* Register an object which implements one or more interfaces into JMX,
* softening any exceptions that result
*
* @param name the JMX name of the object
* @param target the object to register
* @param metadata metadata about methods of the object that should be
* exposed; can either be Class> or String
* @throws RuntimeException if the registration fails
*/
public static void register(ObjectName name, Object target, Object... metadata) {
List> ifcs = new LinkedList<>();
List methods = new LinkedList<>();
for (Object o : metadata) {
if (o instanceof Class>) {
ifcs.add((Class>) o);
} else {
methods.add(o.toString());
}
}
try {
ManagementFactory.getPlatformMBeanServer().registerMBean(
createDynamicMBean(target, ifcs, methods),
name);
putName(System.identityHashCode(target), name);
} catch (JMException e) {
throw new RuntimeException("oops! couldn't register " + name, e);
}
}
/**
* Register an object which exposes one or more methods into JMX,
* softening any exceptions that result
*
* @param name the JMX name of the object
* @param obj the object to register
* @param methods methods exposed to JMX
* @throws RuntimeException if the registration fails
*/
public static void register(ObjectName name, Object obj, String... methods) {
try {
ManagementFactory.getPlatformMBeanServer().registerMBean(
createDynamicMBean(obj, null, Arrays.asList(methods)),
name);
putName(System.identityHashCode(obj), name);
} catch (JMException e) {
throw new RuntimeException("oops! couldn't register " + name, e);
}
}
/**
* Unregisters an MBean, softening any exceptions
*
* @param name the JMX name of the object
* @throws RuntimeException if the unregistration fails
*/
public static void unregister(ObjectName name) {
try {
ManagementFactory.getPlatformMBeanServer().unregisterMBean(name);
} catch (JMException e) {
throw new RuntimeException("oops! couldn't register " + name, e);
}
}
/**
* Unregisters an MBean, softening any exceptions
*
* @param obj the MBean that was registered
* @throws RuntimeException if the unregistration fails
*/
public static void unregister(Object obj) {
for (ObjectName name : registered.remove(System.identityHashCode(obj))) {
unregister(name);
}
}
// ===================================================================
// LISTENERS
// ===================================================================
static final Map listeners = new HashMap<>();
/**
* Adds a listener to be notified when MBeans matching the specified
* pattern get registered.
*
* @param listener the listener to add
* @param pattern the pattern for object names of interest (null for all)
* @throws RuntimeException if something prevents adding the listener
*/
public static void listen(final MBeanListener listener, final ObjectName pattern) {
listen(listener, pattern, System.identityHashCode(listener));
}
/**
* Creates a map which is tied to a JMX listener. The maps contents are
* concurrently updated as MBeans matching the name pattern come and go
* from JMX.
*/
public static Map listen(final ObjectName pattern, final Class type) {
final Map map = new ConcurrentHashMap<>();
final MBeanListener listener = new MBeanListener() {
@Override
public void mbeanAdded(ObjectName name) {
map.put(name, createProxy(name, type));
}
@Override
public void mbeanRemoved(ObjectName name) {
map.remove(name);
}
};
listen(listener, pattern, System.identityHashCode(map));
return map;
}
/*
* Registers a listener with JMX and scans current contents to look for
* matching MBeans that have already been registered. May or may not
* update the internal dictionary of listeners.
*/
protected static void listen(final MBeanListener listener, final ObjectName pattern, int key) {
try {
MBeanServer server = ManagementFactory.getPlatformMBeanServer();
NotificationListener jmxlisten = new NotificationListener() {
@Override
public void handleNotification(Notification notification, Object handback) {
MBeanServerNotification n = (MBeanServerNotification) notification;
if (match(pattern, n.getMBeanName())) {
if (MBEAN_ADD.equals(n.getType())) {
listener.mbeanAdded(n.getMBeanName());
}
if (MBEAN_DEL.equals(n.getType())) {
listener.mbeanRemoved(n.getMBeanName());
}
}
}
};
server.addNotificationListener(DELEGATE, jmxlisten, null, null);
listeners.put(key, jmxlisten);
for (ObjectName name : server.queryNames(pattern, null)) {
listener.mbeanAdded(name);
}
} catch (JMException e) {
throw new RuntimeException("error adding listener to " + pattern, e);
}
}
/**
* Removes a listener that was previously registered
*/
public static void unlisten(Object listener) {
NotificationListener jmxlisten = listeners.remove(System.identityHashCode(listener));
try {
ManagementFactory.getPlatformMBeanServer().removeNotificationListener(DELEGATE, jmxlisten);
} catch (JMException e) {
throw new RuntimeException("couldn't unlisten", e);
}
}
// ===================================================================
// PROXIES ETC.
// ===================================================================
/**
* Creates a dynamic MBean exposing the specified object with the
* specified management interfaces & methods
*/
public static DynamicMBean createDynamicMBean(Object target, Collection> ifcs, Collection methods)
throws JMException {
return new DynamicMBeanImpl(target, ifcs, methods);
}
/**
* Same as the three-arg version, but uses the classloader of the
* supplied management interface
*/
public static T createProxy(ObjectName name, Class ifc) {
return createProxy(ifc.getClassLoader(), name, ifc);
}
/**
* Creates a proxy on an MBean, allowing typesafe (and easier) access to
* its operations through a known interface.
*
* @param loader the classloader for the proxy
* @param name the name of the target MBean
* @param ifc the supported interface
* @return the proxy
* @throws IllegalArgumentException if the named MBean doesn't support the
* required interface
*/
public static T createProxy(ClassLoader loader, ObjectName name, Class ifc) {
return createProxy(ManagementFactory.getPlatformMBeanServer(), loader, name, ifc);
}
/**
* Creates a proxy on an MBean, allowing typesafe (and easier) access to
* its operations through a known interface. All operations go through
* the supplied MBeanConnection.
*
* @param conn the MBean server connection for the proxy
* @param loader the classloader for the proxy
* @param name the name of the target MBean
* @param ifc the supported interface
* @return the proxy
* @throws IllegalArgumentException if the named MBean doesn't support the
* required interface
*/
public static T createProxy(final MBeanServerConnection conn, ClassLoader loader, ObjectName name, Class ifc) {
final ObjectName target = name;
if (!supports(target, ifc)) {
throw new IllegalArgumentException(name + " doesn't support " + ifc.getName());
}
InvocationHandler handler = new InvocationHandler() {
MBeanInfo info = null;
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
if (info == null) {
info = conn.getMBeanInfo(target);
}
if (args == null) {
args = new Object[0];
}
String mname = method.getName();
String aname = getAttributeName(method);
if (aname != null) {
aname = aname.toLowerCase();
for (MBeanAttributeInfo att : info.getAttributes()) {
if (att.getName().equalsIgnoreCase(aname)) {
aname = att.getName();
break;
}
}
if (mname.startsWith("get")) {
return conn.getAttribute(target, aname);
} else {
conn.setAttribute(target, new Attribute(aname, args[0]));
return null;
}
} else {
String[] sig = new String[args.length];
for (int i = 0; i < sig.length; i++) {
sig[i] = method.getParameterTypes()[i].getName();
}
return conn.invoke(target, method.getName(), args, sig);
}
}
};
return ifc.cast(Proxy.newProxyInstance(loader, new Class>[]{ifc}, handler));
}
// ===================================================================
// REFLECTION
// ===================================================================
/**
* Examines an MBean to determine if it supports all the methods of the
* supplied interface (as determined by supports(MBeanInfo,Method))
*
* @param name the name of the MBean in question
* @param ifc the interface in question
* @return does the MBean support the interface (returns false if any
* exceptions prevent the operation)
*/
public static boolean supports(ObjectName name, Class> ifc) {
MBeanInfo info = null;
try {
info = ManagementFactory.getPlatformMBeanServer().getMBeanInfo(name);
} catch (JMException e) {
return false;
}
return supports(info, ifc);
}
/**
* Examines MBeanInfo to determine if the described MBean supports all
* the methods of the supplied interface (as determined by
* support(MBeanInfo,Method).
*
* @param info the MBean in question
* @param ifc the interface in question
* @return does the MBean support the interface?
*/
public static boolean supports(MBeanInfo info, Class> ifc) {
for (Method method : ifc.getMethods()) {
if (!supports(info, method)) {
return false;
}
}
return true;
}
/**
* Examines MBeanInfo to determine if the described MBean supports the
* method in question. Comparison is simple, based on the exact method
* name and number and type of arguments (no reasoning about parameter
* subclasses or overridden methods is done).
*
* @param info MBeanInfo for the MBean in question
* @param method the method in question
* @return true if the MBean supports the method
*/
public static boolean supports(MBeanInfo info, Method method) {
String mname = method.getName();
String aname = getAttributeName(method);
if (aname != null) {
for (MBeanAttributeInfo att : info.getAttributes()) {
if (att.getName().equalsIgnoreCase(aname)) {
if (mname.startsWith("get") && att.isReadable()) {
return true;
} else if (mname.startsWith("set") && att.isWritable()) {
return true;
}
return false;
}
}
return false;
} else {
List osig = new LinkedList<>();
List msig = new LinkedList<>();
for (Class> cls : method.getParameterTypes()) {
msig.add(cls.getName());
}
for (MBeanOperationInfo op : info.getOperations()) {
osig.clear();
for (MBeanParameterInfo param : op.getSignature()) {
osig.add(param.getType());
}
if (op.getName().equals(method.getName()) && osig.equals(msig)) {
return true;
}
}
return false;
}
}
/**
* @return the name of the attribute this method refers to, if it's a
* setter or getter; null otherwise
*/
public static String getAttributeName(Method method) {
String name = method.getName();
if (name.startsWith("set") &&
method.getReturnType() == void.class &&
method.getParameterTypes().length == 1) {
return name.substring(3);
}
if (name.startsWith("get") &&
method.getReturnType() != void.class &&
method.getParameterTypes().length == 0) {
return name.substring(3);
}
return null;
}
// ===================================================================
// REMOTENESS
// ===================================================================
/**
* @return an MBeanServerConnection to the specified host and port
*/
public static MBeanServerConnection connect(String host, int port) throws IOException {
String u = URL_PATTERN;
u = u.replace("{{host}}", host);
u = u.replace("{{port}}", Integer.toString(port));
return JMXConnectorFactory.connect(new JMXServiceURL(u)).getMBeanServerConnection();
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy