/*
* Copyright 2008-2009 LinkedIn, Inc
*
* 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 voldemort.utils;
import java.lang.annotation.Annotation;
import java.lang.management.ManagementFactory;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.management.Descriptor;
import javax.management.InstanceNotFoundException;
import javax.management.IntrospectionException;
import javax.management.MBeanException;
import javax.management.MBeanOperationInfo;
import javax.management.MBeanParameterInfo;
import javax.management.MBeanServer;
import javax.management.MalformedObjectNameException;
import javax.management.ObjectName;
import javax.management.modelmbean.InvalidTargetObjectTypeException;
import javax.management.modelmbean.ModelMBean;
import javax.management.modelmbean.ModelMBeanAttributeInfo;
import javax.management.modelmbean.ModelMBeanConstructorInfo;
import javax.management.modelmbean.ModelMBeanInfo;
import javax.management.modelmbean.ModelMBeanInfoSupport;
import javax.management.modelmbean.ModelMBeanNotificationInfo;
import javax.management.modelmbean.ModelMBeanOperationInfo;
import javax.management.modelmbean.RequiredModelMBean;
import org.apache.log4j.Logger;
import voldemort.VoldemortException;
import voldemort.annotations.jmx.JmxGetter;
import voldemort.annotations.jmx.JmxManaged;
import voldemort.annotations.jmx.JmxOperation;
import voldemort.annotations.jmx.JmxParam;
import voldemort.annotations.jmx.JmxSetter;
/**
* JMX helper functions
*
* These rely on annotations to create MBeans. Would be easier to just use the
* annotations in Java 6, but we don't want to require Java 6 just for our own
* convenience. Hence reinventing the wheel.
*
*
*/
public class JmxUtils {
private static final Object LOCK = new Object();
private static final Logger logger = Logger.getLogger(JmxUtils.class);
public static final String MBEAN_NAME_SEPARATOR = "-";
/**
* Create a model mbean from an object using the description given in the
* Jmx annotation if present. Only operations are supported so far, no
* attributes, constructors, or notifications
*
* @param o The object to create an MBean for
* @return The ModelMBean for the given object
*/
public static ModelMBean createModelMBean(Object o) {
try {
ModelMBean mbean = new RequiredModelMBean();
JmxManaged annotation = o.getClass().getAnnotation(JmxManaged.class);
String description = annotation == null ? "" : annotation.description();
ModelMBeanInfo info = new ModelMBeanInfoSupport(o.getClass().getName(),
description,
extractAttributeInfo(o),
new ModelMBeanConstructorInfo[0],
extractOperationInfo(o),
new ModelMBeanNotificationInfo[0]);
mbean.setModelMBeanInfo(info);
mbean.setManagedResource(o, "ObjectReference");
return mbean;
} catch(MBeanException e) {
throw new VoldemortException(e);
} catch(InvalidTargetObjectTypeException e) {
throw new VoldemortException(e);
} catch(InstanceNotFoundException e) {
throw new VoldemortException(e);
}
}
/**
* Extract all operations and attributes from the given object that have
* been annotated with the Jmx annotation. Operations are all methods that
* are marked with the JmxOperation annotation.
*
* @param object The object to process
* @return An array of operations taken from the object
*/
public static ModelMBeanOperationInfo[] extractOperationInfo(Object object) {
ArrayList<ModelMBeanOperationInfo> infos = new ArrayList<ModelMBeanOperationInfo>();
for(Method m: object.getClass().getMethods()) {
JmxOperation jmxOperation = m.getAnnotation(JmxOperation.class);
JmxGetter jmxGetter = m.getAnnotation(JmxGetter.class);
JmxSetter jmxSetter = m.getAnnotation(JmxSetter.class);
if(jmxOperation != null || jmxGetter != null || jmxSetter != null) {
String description = "";
int visibility = 1;
int impact = MBeanOperationInfo.UNKNOWN;
if(jmxOperation != null) {
description = jmxOperation.description();
impact = jmxOperation.impact();
} else if(jmxGetter != null) {
description = jmxGetter.description();
impact = MBeanOperationInfo.INFO;
visibility = 4;
} else if(jmxSetter != null) {
description = jmxSetter.description();
impact = MBeanOperationInfo.ACTION;
visibility = 4;
}
ModelMBeanOperationInfo info = new ModelMBeanOperationInfo(m.getName(),
description,
extractParameterInfo(m),
m.getReturnType()
.getName(), impact);
info.getDescriptor().setField("visibility", Integer.toString(visibility));
infos.add(info);
}
}
return infos.toArray(new ModelMBeanOperationInfo[infos.size()]);
}
/**
* Extract all operations from the given object that have been annotated
* with the Jmx annotation. Operations are all methods that are marked with
* the JMX annotation and are not getters and setters (which are extracted
* as attributes).
*
* @param object The object to process
* @return An array of attributes taken from the object
*/
public static ModelMBeanAttributeInfo[] extractAttributeInfo(Object object) {
Map<String, Method> getters = new HashMap<String, Method>();
Map<String, Method> setters = new HashMap<String, Method>();
Map<String, String> descriptions = new HashMap<String, String>();
for(Method m: object.getClass().getMethods()) {
JmxGetter getter = m.getAnnotation(JmxGetter.class);
if(getter != null) {
getters.put(getter.name(), m);
descriptions.put(getter.name(), getter.description());
}
JmxSetter setter = m.getAnnotation(JmxSetter.class);
if(setter != null) {
setters.put(setter.name(), m);
descriptions.put(setter.name(), setter.description());
}
}
Set<String> attributes = new HashSet<String>(getters.keySet());
attributes.addAll(setters.keySet());
List<ModelMBeanAttributeInfo> infos = new ArrayList<ModelMBeanAttributeInfo>();
for(String name: attributes) {
try {
Method getter = getters.get(name);
Method setter = setters.get(name);
ModelMBeanAttributeInfo info = new ModelMBeanAttributeInfo(name,
descriptions.get(name),
getter,
setter);
Descriptor descriptor = info.getDescriptor();
if(getter != null)
descriptor.setField("getMethod", getter.getName());
if(setter != null)
descriptor.setField("setMethod", setter.getName());
info.setDescriptor(descriptor);
infos.add(info);
} catch(IntrospectionException e) {
throw new VoldemortException(e);
}
}
return infos.toArray(new ModelMBeanAttributeInfo[infos.size()]);
}
/**
* Extract the parameters from a method using the Jmx annotation if present,
* or just the raw types otherwise
*
* @param m The method to extract parameters from
* @return An array of parameter infos
*/
public static MBeanParameterInfo[] extractParameterInfo(Method m) {
Class<?>[] types = m.getParameterTypes();
Annotation[][] annotations = m.getParameterAnnotations();
MBeanParameterInfo[] params = new MBeanParameterInfo[types.length];
for(int i = 0; i < params.length; i++) {
boolean hasAnnotation = false;
for(int j = 0; j < annotations[i].length; j++) {
if(annotations[i][j] instanceof JmxParam) {
JmxParam param = (JmxParam) annotations[i][j];
params[i] = new MBeanParameterInfo(param.name(),
types[i].getName(),
param.description());
hasAnnotation = true;
break;
}
}
if(!hasAnnotation) {
params[i] = new MBeanParameterInfo("", types[i].getName(), "");
}
}
return params;
}
/**
* Create a JMX ObjectName
*
* @param domain The domain of the object
* @param type The type of the object
* @return An ObjectName representing the name
*/
public static ObjectName createObjectName(String domain, String type) {
try {
return new ObjectName(domain + ":type=" + type);
} catch(MalformedObjectNameException e) {
throw new VoldemortException(e);
}
}
/**
* Create an ObjectName from a class
*
* @param c The class
* @return The created object name
*/
public static ObjectName createObjectName(Class<?> c) {
return createObjectName(getPackageName(c), getClassName(c));
}
/**
* Get the package for this class
*
* @param c The class
* @return The package name as a String
*/
public static String getPackageName(Class<?> c) {
String name = c.getName();
return name.substring(0, name.lastIndexOf('.'));
}
/**
* Get the class name without the package
*
* @param c The class name with package
* @return the class name without the package
*/
public static String getClassName(Class<?> c) {
String name = c.getName();
return name.substring(name.lastIndexOf('.') + 1, name.length());
}
/**
* Register the given mbean with the platform mbean server
*
* @param mbean The mbean to register
* @param name The name to register under
*/
public static void registerMbean(Object mbean, ObjectName name) {
registerMbean(ManagementFactory.getPlatformMBeanServer(),
JmxUtils.createModelMBean(mbean),
name);
}
/**
* Register the given object under the package name of the object's class
* with the given type name.
*
* this method using the platform mbean server as returned by
* ManagementFactory.getPlatformMBeanServer()
*
* @param typeName The name of the type to register
* @param obj The object to register as an mbean
*/
public static ObjectName registerMbean(String typeName, Object obj) {
MBeanServer server = ManagementFactory.getPlatformMBeanServer();
ObjectName name = JmxUtils.createObjectName(JmxUtils.getPackageName(obj.getClass()),
typeName);
registerMbean(server, JmxUtils.createModelMBean(obj), name);
return name;
}
/**
* Register the given mbean with the server
*
* @param server The server to register with
* @param mbean The mbean to register
* @param name The name to register under
*/
public static void registerMbean(MBeanServer server, ModelMBean mbean, ObjectName name) {
try {
synchronized(LOCK) {
if(server.isRegistered(name))
JmxUtils.unregisterMbean(server, name);
server.registerMBean(mbean, name);
}
} catch(Exception e) {
logger.error("Error registering mbean:", e);
}
}
/**
* Unregister the mbean with the given name
*
* @param server The server to unregister from
* @param name The name of the mbean to unregister
*/
public static void unregisterMbean(MBeanServer server, ObjectName name) {
try {
server.unregisterMBean(name);
} catch(Exception e) {
logger.error("Error unregistering mbean", e);
}
}
/**
* Unregister the mbean with the given name from the platform mbean server
*
* @param name The name of the mbean to unregister
*/
public static void unregisterMbean(ObjectName name) {
try {
ManagementFactory.getPlatformMBeanServer().unregisterMBean(name);
} catch(Exception e) {
logger.error("Error unregistering mbean", e);
}
}
/**
* Return the string representation of jmxId
*
* @param jmxId
* @return string representation of jmx id
*/
public static String getJmxId(int jmxId) {
return jmxId == 0 ? "" : Integer.toString(jmxId);
}
}