Package com.addthis.basis.jmx

Source Code of com.addthis.basis.jmx.MBeanUtils

/*
* 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<Integer, Set<ObjectName>> registered = new HashMap<>();

    private static void putName(int code, ObjectName name) {
        Set<ObjectName> 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<Class<?>> ifcs = new LinkedList<>();
        List<String> 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<Integer, NotificationListener> 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 <T> Map<ObjectName, T> listen(final ObjectName pattern, final Class<T> type) {
        final Map<ObjectName, T> 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<Class<?>> ifcs, Collection<String> 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> T createProxy(ObjectName name, Class<T> 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> T createProxy(ClassLoader loader, ObjectName name, Class<T> 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> T createProxy(final MBeanServerConnection conn, ClassLoader loader, ObjectName name, Class<T> 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<String> osig = new LinkedList<>();
            List<String> 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();
    }
}
TOP

Related Classes of com.addthis.basis.jmx.MBeanUtils

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.