diff options
Diffstat (limited to 'jetty-jmx/src/main/java/org/eclipse/jetty/jmx/ObjectMBean.java')
-rw-r--r-- | jetty-jmx/src/main/java/org/eclipse/jetty/jmx/ObjectMBean.java | 719 |
1 files changed, 719 insertions, 0 deletions
diff --git a/jetty-jmx/src/main/java/org/eclipse/jetty/jmx/ObjectMBean.java b/jetty-jmx/src/main/java/org/eclipse/jetty/jmx/ObjectMBean.java new file mode 100644 index 0000000000..a1f3d82450 --- /dev/null +++ b/jetty-jmx/src/main/java/org/eclipse/jetty/jmx/ObjectMBean.java @@ -0,0 +1,719 @@ +// ======================================================================== +// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.jmx; + +import java.lang.reflect.Array; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Locale; +import java.util.Map; +import java.util.MissingResourceException; +import java.util.ResourceBundle; +import java.util.Set; + +import javax.management.Attribute; +import javax.management.AttributeList; +import javax.management.AttributeNotFoundException; +import javax.management.DynamicMBean; +import javax.management.InvalidAttributeValueException; +import javax.management.MBeanAttributeInfo; +import javax.management.MBeanConstructorInfo; +import javax.management.MBeanException; +import javax.management.MBeanInfo; +import javax.management.MBeanNotificationInfo; +import javax.management.MBeanOperationInfo; +import javax.management.MBeanParameterInfo; +import javax.management.ObjectName; +import javax.management.ReflectionException; +import javax.management.modelmbean.ModelMBean; + +import org.eclipse.jetty.util.LazyList; +import org.eclipse.jetty.util.Loader; +import org.eclipse.jetty.util.TypeUtil; +import org.eclipse.jetty.util.log.Log; + +/* ------------------------------------------------------------ */ +/** ObjectMBean. + * A dynamic MBean that can wrap an arbitary Object instance. + * the attributes and methods exposed by this bean are controlled by + * the merge of property bundles discovered by names related to all + * superclasses and all superinterfaces. + * + * Attributes and methods exported may be "Object" and must exist on the + * wrapped object, or "MBean" and must exist on a subclass of OBjectMBean + * or "MObject" which exists on the wrapped object, but whose values are + * converted to MBean object names. + * + */ +public class ObjectMBean implements DynamicMBean +{ + private static Class[] OBJ_ARG = new Class[]{Object.class}; + + protected Object _managed; + private MBeanInfo _info; + private Map _getters=new HashMap(); + private Map _setters=new HashMap(); + private Map _methods=new HashMap(); + private Set _convert=new HashSet(); + private ClassLoader _loader; + private MBeanContainer _mbeanContainer; + + private static String OBJECT_NAME_CLASS = ObjectName.class.getName(); + private static String OBJECT_NAME_ARRAY_CLASS = ObjectName[].class.getName(); + + /* ------------------------------------------------------------ */ + /** + * Create MBean for Object. Attempts to create an MBean for the object by searching the package + * and class name space. For example an object of the type + * + * <PRE> + * class com.acme.MyClass extends com.acme.util.BaseClass implements com.acme.Iface + * </PRE> + * + * Then this method would look for the following classes: + * <UL> + * <LI>com.acme.management.MyClassMBean + * <LI>com.acme.util.management.BaseClassMBean + * <LI>org.eclipse.jetty.jmx.ObjectMBean + * </UL> + * + * @param o The object + * @return A new instance of an MBean for the object or null. + */ + public static Object mbeanFor(Object o) + { + try + { + Class oClass = o.getClass(); + Object mbean = null; + + while (mbean == null && oClass != null) + { + String pName = oClass.getPackage().getName(); + String cName = oClass.getName().substring(pName.length() + 1); + String mName = pName + ".management." + cName + "MBean"; + + + try + { + Class mClass = (Object.class.equals(oClass))?oClass=ObjectMBean.class:Loader.loadClass(oClass,mName,true); + if (Log.isDebugEnabled()) + Log.debug("mbeanFor " + o + " mClass=" + mClass); + + try + { + Constructor constructor = mClass.getConstructor(OBJ_ARG); + mbean=constructor.newInstance(new Object[]{o}); + } + catch(Exception e) + { + Log.ignore(e); + if (ModelMBean.class.isAssignableFrom(mClass)) + { + mbean=mClass.newInstance(); + ((ModelMBean)mbean).setManagedResource(o, "objectReference"); + } + } + + if (Log.isDebugEnabled()) + Log.debug("mbeanFor " + o + " is " + mbean); + return mbean; + } + catch (ClassNotFoundException e) + { + if (e.toString().endsWith("MBean")) + Log.ignore(e); + else + Log.warn(e); + } + catch (Error e) + { + Log.warn(e); + mbean = null; + } + catch (Exception e) + { + Log.warn(e); + mbean = null; + } + + oClass = oClass.getSuperclass(); + } + } + catch (Exception e) + { + Log.ignore(e); + } + return null; + } + + + public ObjectMBean(Object managedObject) + { + _managed = managedObject; + _loader = Thread.currentThread().getContextClassLoader(); + } + + public Object getManagedObject() + { + return _managed; + } + + public ObjectName getObjectName() + { + return null; + } + + public String getObjectNameBasis() + { + return null; + } + + protected void setMBeanContainer(MBeanContainer container) + { + this._mbeanContainer = container; + } + + public MBeanContainer getMBeanContainer () + { + return this._mbeanContainer; + } + + + public MBeanInfo getMBeanInfo() + { + try + { + if (_info==null) + { + // Start with blank lazy lists attributes etc. + String desc=null; + Object attributes=null; + Object constructors=null; + Object operations=null; + Object notifications=null; + + // Find list of classes that can influence the mbean + Class o_class=_managed.getClass(); + Object influences = findInfluences(null, _managed.getClass()); + + // Set to record defined items + Set defined=new HashSet(); + + // For each influence + for (int i=0;i<LazyList.size(influences);i++) + { + Class oClass = (Class)LazyList.get(influences, i); + + // look for a bundle defining methods + if (Object.class.equals(oClass)) + oClass=ObjectMBean.class; + String pName = oClass.getPackage().getName(); + String cName = oClass.getName().substring(pName.length() + 1); + String rName = pName.replace('.', '/') + "/management/" + cName+"-mbean"; + + try + { + Log.debug(rName); + ResourceBundle bundle = Loader.getResourceBundle(o_class, rName,true,Locale.getDefault()); + + + // Extract meta data from bundle + Enumeration e = bundle.getKeys(); + while (e.hasMoreElements()) + { + String key = (String)e.nextElement(); + String value = bundle.getString(key); + + // Determin if key is for mbean , attribute or for operation + if (key.equals(cName)) + { + // set the mbean description + if (desc==null) + desc=value; + } + else if (key.indexOf('(')>0) + { + // define an operation + if (!defined.contains(key) && key.indexOf('[')<0) + { + defined.add(key); + operations=LazyList.add(operations,defineOperation(key, value, bundle)); + } + } + else + { + // define an attribute + if (!defined.contains(key)) + { + defined.add(key); + attributes=LazyList.add(attributes,defineAttribute(key, value)); + } + } + } + + } + catch(MissingResourceException e) + { + Log.ignore(e); + } + } + + _info = new MBeanInfo(o_class.getName(), + desc, + (MBeanAttributeInfo[])LazyList.toArray(attributes, MBeanAttributeInfo.class), + (MBeanConstructorInfo[])LazyList.toArray(constructors, MBeanConstructorInfo.class), + (MBeanOperationInfo[])LazyList.toArray(operations, MBeanOperationInfo.class), + (MBeanNotificationInfo[])LazyList.toArray(notifications, MBeanNotificationInfo.class)); + } + } + catch(RuntimeException e) + { + Log.warn(e); + throw e; + } + return _info; + } + + + /* ------------------------------------------------------------ */ + public Object getAttribute(String name) throws AttributeNotFoundException, MBeanException, ReflectionException + { + Method getter = (Method) _getters.get(name); + if (getter == null) + throw new AttributeNotFoundException(name); + try + { + Object o = _managed; + if (getter.getDeclaringClass().isInstance(this)) + o = this; // mbean method + + // get the attribute + Object r=getter.invoke(o, (java.lang.Object[]) null); + + // convert to ObjectName if need be. + if (r!=null && _convert.contains(name)) + { + if (r.getClass().isArray()) + { + ObjectName[] on = new ObjectName[Array.getLength(r)]; + for (int i=0;i<on.length;i++) + on[i]=_mbeanContainer.findMBean(Array.get(r, i)); + r=on; + } + else + { + ObjectName mbean = _mbeanContainer.findMBean(r); + if (mbean==null) + return null; + r=mbean; + } + } + return r; + } + catch (IllegalAccessException e) + { + Log.warn(Log.EXCEPTION, e); + throw new AttributeNotFoundException(e.toString()); + } + catch (InvocationTargetException e) + { + Log.warn(Log.EXCEPTION, e); + throw new ReflectionException((Exception) e.getTargetException()); + } + } + + /* ------------------------------------------------------------ */ + public AttributeList getAttributes(String[] names) + { + AttributeList results = new AttributeList(names.length); + for (int i = 0; i < names.length; i++) + { + try + { + results.add(new Attribute(names[i], getAttribute(names[i]))); + } + catch (Exception e) + { + Log.warn(Log.EXCEPTION, e); + } + } + return results; + } + + /* ------------------------------------------------------------ */ + public void setAttribute(Attribute attr) throws AttributeNotFoundException, InvalidAttributeValueException, MBeanException, ReflectionException + { + if (attr == null) + return; + + if (Log.isDebugEnabled()) + Log.debug("setAttribute " + _managed + ":" +attr.getName() + "=" + attr.getValue()); + Method setter = (Method) _setters.get(attr.getName()); + if (setter == null) + throw new AttributeNotFoundException(attr.getName()); + try + { + Object o = _managed; + if (setter.getDeclaringClass().isInstance(this)) + o = this; + + // get the value + Object value = attr.getValue(); + + // convert from ObjectName if need be + if (value!=null && _convert.contains(attr.getName())) + { + if (value.getClass().isArray()) + { + Class t=setter.getParameterTypes()[0].getComponentType(); + Object na = Array.newInstance(t,Array.getLength(value)); + for (int i=Array.getLength(value);i-->0;) + Array.set(na, i, _mbeanContainer.findBean((ObjectName)Array.get(value, i))); + value=na; + } + else + value=_mbeanContainer.findBean((ObjectName)value); + } + + // do the setting + setter.invoke(o, new Object[]{ value }); + } + catch (IllegalAccessException e) + { + Log.warn(Log.EXCEPTION, e); + throw new AttributeNotFoundException(e.toString()); + } + catch (InvocationTargetException e) + { + Log.warn(Log.EXCEPTION, e); + throw new ReflectionException((Exception) e.getTargetException()); + } + } + + /* ------------------------------------------------------------ */ + public AttributeList setAttributes(AttributeList attrs) + { + Log.debug("setAttributes"); + + AttributeList results = new AttributeList(attrs.size()); + Iterator iter = attrs.iterator(); + while (iter.hasNext()) + { + try + { + Attribute attr = (Attribute) iter.next(); + setAttribute(attr); + results.add(new Attribute(attr.getName(), getAttribute(attr.getName()))); + } + catch (Exception e) + { + Log.warn(Log.EXCEPTION, e); + } + } + return results; + } + + /* ------------------------------------------------------------ */ + public Object invoke(String name, Object[] params, String[] signature) throws MBeanException, ReflectionException + { + if (Log.isDebugEnabled()) + Log.debug("invoke " + name); + + String methodKey = name + "("; + if (signature != null) + for (int i = 0; i < signature.length; i++) + methodKey += (i > 0 ? "," : "") + signature[i]; + methodKey += ")"; + + ClassLoader old_loader=Thread.currentThread().getContextClassLoader(); + try + { + Thread.currentThread().setContextClassLoader(_loader); + Method method = (Method) _methods.get(methodKey); + if (method == null) + throw new NoSuchMethodException(methodKey); + + Object o = _managed; + if (method.getDeclaringClass().isInstance(this)) + o = this; + return method.invoke(o, params); + } + catch (NoSuchMethodException e) + { + Log.warn(Log.EXCEPTION, e); + throw new ReflectionException(e); + } + catch (IllegalAccessException e) + { + Log.warn(Log.EXCEPTION, e); + throw new MBeanException(e); + } + catch (InvocationTargetException e) + { + Log.warn(Log.EXCEPTION, e); + throw new ReflectionException((Exception) e.getTargetException()); + } + finally + { + Thread.currentThread().setContextClassLoader(old_loader); + } + } + + private static Object findInfluences(Object influences, Class aClass) + { + if (aClass!=null) + { + // This class is an influence + influences=LazyList.add(influences,aClass); + + // So are the super classes + influences=findInfluences(influences,aClass.getSuperclass()); + + // So are the interfaces + Class[] ifs = aClass.getInterfaces(); + for (int i=0;ifs!=null && i<ifs.length;i++) + influences=findInfluences(influences,ifs[i]); + } + return influences; + } + + /* ------------------------------------------------------------ */ + /** + * Define an attribute on the managed object. The meta data is defined by looking for standard + * getter and setter methods. Descriptions are obtained with a call to findDescription with the + * attribute name. + * + * @param name + * @param metaData "description" or "access:description" or "type:access:description" where type is + * one of: <ul> + * <li>"Object" The field/method is on the managed object. + * <li>"MBean" The field/method is on the mbean proxy object + * <li>"MObject" The field/method is on the managed object and value should be converted to MBean reference + * <li>"MMBean" The field/method is on the mbean proxy object and value should be converted to MBean reference + * </ul> + * the access is either "RW" or "RO". + */ + public MBeanAttributeInfo defineAttribute(String name, String metaData) + { + String description = ""; + boolean writable = true; + boolean onMBean = false; + boolean convert = false; + + if (metaData!= null) + { + String[] tokens = metaData.split(":", 3); + for (int t=0;t<tokens.length-1;t++) + { + tokens[t]=tokens[t].trim(); + if ("RO".equals(tokens[t])) + writable=false; + else + { + onMBean=("MMBean".equalsIgnoreCase(tokens[t]) || "MBean".equalsIgnoreCase(tokens[t])); + convert=("MMBean".equalsIgnoreCase(tokens[t]) || "MObject".equalsIgnoreCase(tokens[t])); + } + } + description=tokens[tokens.length-1]; + } + + + String uName = name.substring(0, 1).toUpperCase() + name.substring(1); + Class oClass = onMBean ? this.getClass() : _managed.getClass(); + + if (Log.isDebugEnabled()) + Log.debug("defineAttribute "+name+" "+onMBean+":"+writable+":"+oClass+":"+description); + + Class type = null; + Method getter = null; + Method setter = null; + Method[] methods = oClass.getMethods(); + for (int m = 0; m < methods.length; m++) + { + if ((methods[m].getModifiers() & Modifier.PUBLIC) == 0) + continue; + + // Look for a getter + if (methods[m].getName().equals("get" + uName) && methods[m].getParameterTypes().length == 0) + { + if (getter != null) + throw new IllegalArgumentException("Multiple getters for attr " + name+ " in "+oClass); + getter = methods[m]; + if (type != null && !type.equals(methods[m].getReturnType())) + throw new IllegalArgumentException("Type conflict for attr " + name+ " in "+oClass); + type = methods[m].getReturnType(); + } + + // Look for an is getter + if (methods[m].getName().equals("is" + uName) && methods[m].getParameterTypes().length == 0) + { + if (getter != null) + throw new IllegalArgumentException("Multiple getters for attr " + name+ " in "+oClass); + getter = methods[m]; + if (type != null && !type.equals(methods[m].getReturnType())) + throw new IllegalArgumentException("Type conflict for attr " + name+ " in "+oClass); + type = methods[m].getReturnType(); + } + + // look for a setter + if (writable && methods[m].getName().equals("set" + uName) && methods[m].getParameterTypes().length == 1) + { + if (setter != null) + throw new IllegalArgumentException("Multiple setters for attr " + name+ " in "+oClass); + setter = methods[m]; + if (type != null && !type.equals(methods[m].getParameterTypes()[0])) + throw new IllegalArgumentException("Type conflict for attr " + name+ " in "+oClass); + type = methods[m].getParameterTypes()[0]; + } + } + + if (convert && type.isPrimitive() && !type.isArray()) + throw new IllegalArgumentException("Cannot convert primative " + name); + + + if (getter == null && setter == null) + throw new IllegalArgumentException("No getter or setters found for " + name+ " in "+oClass); + + try + { + // Remember the methods + _getters.put(name, getter); + _setters.put(name, setter); + + + + MBeanAttributeInfo info=null; + if (convert) + { + _convert.add(name); + if (type.isArray()) + info= new MBeanAttributeInfo(name,OBJECT_NAME_ARRAY_CLASS,description,getter!=null,setter!=null,getter!=null&&getter.getName().startsWith("is")); + + else + info= new MBeanAttributeInfo(name,OBJECT_NAME_CLASS,description,getter!=null,setter!=null,getter!=null&&getter.getName().startsWith("is")); + } + else + info= new MBeanAttributeInfo(name,description,getter,setter); + + return info; + } + catch (Exception e) + { + Log.warn(Log.EXCEPTION, e); + throw new IllegalArgumentException(e.toString()); + } + } + + + /* ------------------------------------------------------------ */ + /** + * Define an operation on the managed object. Defines an operation with parameters. Refection is + * used to determine find the method and it's return type. The description of the method is + * found with a call to findDescription on "name(signature)". The name and description of each + * parameter is found with a call to findDescription with "name(signature)[n]", the returned + * description is for the last parameter of the partial signature and is assumed to start with + * the parameter name, followed by a colon. + * + * @param metaData "description" or "impact:description" or "type:impact:description", type is + * the "Object","MBean", "MMBean" or "MObject" to indicate the method is on the object, the MBean or on the + * object but converted to an MBean reference, and impact is either "ACTION","INFO","ACTION_INFO" or "UNKNOWN". + */ + private MBeanOperationInfo defineOperation(String signature, String metaData, ResourceBundle bundle) + { + String[] tokens=metaData.split(":",3); + int i=tokens.length-1; + String description=tokens[i--]; + String impact_name = i<0?"UNKNOWN":tokens[i--].trim(); + if (i==0) + tokens[0]=tokens[0].trim(); + boolean onMBean= i==0 && ("MBean".equalsIgnoreCase(tokens[0])||"MMBean".equalsIgnoreCase(tokens[0])); + boolean convert= i==0 && ("MObject".equalsIgnoreCase(tokens[0])||"MMBean".equalsIgnoreCase(tokens[0])); + + if (Log.isDebugEnabled()) + Log.debug("defineOperation "+signature+" "+onMBean+":"+impact_name+":"+description); + + Class oClass = onMBean ? this.getClass() : _managed.getClass(); + + try + { + // Resolve the impact + int impact=MBeanOperationInfo.UNKNOWN; + if (impact_name==null || impact_name.equals("UNKNOWN")) + impact=MBeanOperationInfo.UNKNOWN; + else if (impact_name.equals("ACTION")) + impact=MBeanOperationInfo.ACTION; + else if (impact_name.equals("INFO")) + impact=MBeanOperationInfo.INFO; + else if (impact_name.equals("ACTION_INFO")) + impact=MBeanOperationInfo.ACTION_INFO; + else + Log.warn("Unknown impact '"+impact_name+"' for "+signature); + + + // split the signature + String[] parts=signature.split("[\\(\\)]"); + String method_name=parts[0]; + String arguments=parts.length==2?parts[1]:null; + String[] args=arguments==null?new String[0]:arguments.split(" *, *"); + + // Check types and normalize signature. + Class[] types = new Class[args.length]; + MBeanParameterInfo[] pInfo = new MBeanParameterInfo[args.length]; + signature=method_name; + for (i = 0; i < args.length; i++) + { + Class type = TypeUtil.fromName(args[i]); + if (type == null) + type = Thread.currentThread().getContextClassLoader().loadClass(args[i]); + types[i] = type; + args[i] = type.isPrimitive() ? TypeUtil.toName(type) : args[i]; + signature+=(i>0?",":"(")+args[i]; + } + signature+=(i>0?")":"()"); + + // Build param infos + for (i = 0; i < args.length; i++) + { + String param_desc = bundle.getString(signature + "[" + i + "]"); + parts=param_desc.split(" *: *",2); + if (Log.isDebugEnabled()) + Log.debug(parts[0]+": "+parts[1]); + pInfo[i] = new MBeanParameterInfo(parts[0].trim(), args[i], parts[1].trim()); + } + + // build the operation info + Method method = oClass.getMethod(method_name, types); + Class returnClass = method.getReturnType(); + _methods.put(signature, method); + if (convert) + _convert.add(signature); + + return new MBeanOperationInfo(method_name, description, pInfo, returnClass.isPrimitive() ? TypeUtil.toName(returnClass) : (returnClass.getName()), impact); + } + catch (Exception e) + { + Log.warn("Operation '"+signature+"'", e); + throw new IllegalArgumentException(e.toString()); + } + + } + +} |