net.lightbody.bmp.proxy.jetty.util.jmx.ModelMBeanImpl Maven / Gradle / Ivy
The newest version!
// ========================================================================
// $Id: ModelMBeanImpl.java,v 1.18 2005/08/13 00:01:28 gregwilkins Exp $
// Copyright 1999-2004 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// 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 net.lightbody.bmp.proxy.jetty.util.jmx;
import net.lightbody.bmp.proxy.jetty.log.LogFactory;
import net.lightbody.bmp.proxy.jetty.util.LogSupport;
import net.lightbody.bmp.proxy.jetty.util.TypeUtil;
import org.apache.commons.logging.Log;
import javax.management.*;
import javax.management.modelmbean.*;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.*;
/* ------------------------------------------------------------ */
/** Model MBean Implementation.
* This implementation of the JMX Model MBean API is designed to allow
* easy creation of Model MBeans. From minimal descriptions of
* operations and attributes, reflection is used to determine the full
* signature and ResourceBundles are used to determine other meta data.
*
* This class is normally used in one of the following patterns:
* - As a base class for a real MBean that contains the actual
* attributes and operations of the MBean. Such an Object is only
* usable in a JMX environment.
*
- As a proxy MBean to another non-JMX object. The attributes and
* operations of the proxied object are defined in the MBean. This
* pattern is used when an existing non-JMX objects API is to be
* exposed as an MBean.
*
- As a base class for a proxy MBean. The attributes and oepration
* of the MBean are implemented by the derived class but delegate to
* one or more other objects. This pattern is used if existing objects
* are to be managed by JMX, but a new management API needs to be
* defined.
*
*
* @version $Revision: 1.18 $
* @author Greg Wilkins (gregw)
*/
public class ModelMBeanImpl
implements ModelMBean,
MBeanRegistration
{
private static Log log = LogFactory.getLog(ModelMBeanImpl.class);
public final static int IMPACT_ACTION = MBeanOperationInfo.ACTION;
public final static int IMPACT_ACTION_INFO = MBeanOperationInfo.ACTION_INFO;
public final static int IMPACT_INFO = MBeanOperationInfo.INFO;
public final static int IMPACT_UNKOWN = MBeanOperationInfo.UNKNOWN;
public final static String STRING="java.lang.String";
public final static String OBJECT="java.lang.Object";
public final static String INT="int";
public final static String[] NO_PARAMS=new String[0];
public final static boolean READ_WRITE=true;
public final static boolean READ_ONLY=false;
public final static boolean ON_MBEAN=true;
public final static boolean ON_OBJECT=false;
private static HashMap __objectId = new HashMap();
private static String __defaultDomain="net.lightbody.bmp.proxy.jetty";
protected ModelMBeanInfoSupport _beanInfo;
private MBeanServer _mBeanServer;
private Object _object;
private ObjectName _objectName;
private boolean _dirty=false;
private HashMap _getter = new HashMap(4);
private HashMap _setter = new HashMap(4);
private HashMap _method = new HashMap(4);
private ArrayList _attributes = new ArrayList(4);
private ArrayList _operations = new ArrayList(4);
private ArrayList _notifications = new ArrayList(4);
private String _baseObjectName=null;
private Map _components = new HashMap(4);
/* ------------------------------------------------------------ */
/* ------------------------------------------------------------ */
/** 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
* class com.acme.MyClass extends com.acme.util.BaseClass
*
* Then this method would look for the following
* classes:
* - com.acme.MyClassMBean
*
- com.acme.jmx.MyClassMBean
*
- com.acme.util.BaseClassMBean
*
- com.acme.util.jmx.BaseClassMBean
*
* @param o The object
* @return A new instance of an MBean for the object or null.
*/
public static ModelMBean mbeanFor(Object o)
{
try
{
Class oClass = o.getClass();
ClassLoader loader =oClass.getClassLoader();
ModelMBean mbean = null;
boolean jmx=false;
Class[] interfaces=null;
int i=0;
while (mbean==null && oClass!=null)
{
Class focus=interfaces==null?oClass:interfaces[i];
String pName = focus.getPackage().getName();
String cName = focus.getName().substring(pName.length()+1);
String mName=pName+(jmx?".jmx.":".")+cName+"MBean";
try{
Class mClass=loader.loadClass(mName);
if(log.isTraceEnabled())log.trace("mbeanFor "+o+" mClass="+mClass);
mbean=(ModelMBean)mClass.newInstance();
mbean.setManagedResource(o,"objectReference");
if(log.isDebugEnabled())log.debug("mbeanFor "+o+" is "+mbean);
return mbean;
}
catch(ClassNotFoundException e)
{
if (e.toString().endsWith("MBean"))
{ if(log.isTraceEnabled())log.trace(e.toString());}
else
log.warn(LogSupport.EXCEPTION,e);
}
catch(Error e)
{
log.warn(LogSupport.EXCEPTION,e);
mbean=null;
}
catch(Exception e)
{
log.warn(LogSupport.EXCEPTION,e);
mbean=null;
}
if (jmx)
{
if (interfaces!=null)
{
i++;
if (i>=interfaces.length)
{
interfaces=null;
oClass=oClass.getSuperclass();
}
}
else
{
interfaces=oClass.getInterfaces();
i=0;
if (interfaces==null || interfaces.length==0)
{
interfaces=null;
oClass=oClass.getSuperclass();
}
}
}
jmx=!jmx;
}
}
catch(Exception e)
{
LogSupport.ignore(log,e);
}
return null;
}
/* ------------------------------------------------------------ */
/** MBean Constructor.
* No proxy object is defined. Attributes and operations are
* defined on this instance.
*/
public ModelMBeanImpl()
{}
/* ------------------------------------------------------------ */
/** Proxy MBean Constructor.
* @param proxyObject The actual object on which attributes and
* operations are to be defined and called.
*/
public ModelMBeanImpl(Object proxyObject)
{
try
{
setManagedResource(proxyObject,"objectReference");
}
catch(Exception e)
{
log.warn(LogSupport.EXCEPTION,e);
throw new IllegalArgumentException(e.toString());
}
}
/* ------------------------------------------------------------ */
public static String getDefaultDomain() { return __defaultDomain; }
/* ------------------------------------------------------------ */
public static void setDefaultDomain(String d) { __defaultDomain=d; }
/* ------------------------------------------------------------ */
public MBeanServer getMBeanServer() { return _mBeanServer; }
/* ------------------------------------------------------------ */
public ObjectName getObjectName() { return _objectName; }
/* ------------------------------------------------------------ */
public Object getManagedResource() { return _object; }
/* ------------------------------------------------------------ */
public void setManagedResource(Object proxyObject, String type)
throws MBeanException,
RuntimeOperationsException,
InstanceNotFoundException,
InvalidTargetObjectTypeException
{
if (proxyObject==null)
{
proxyObject=null;
return;
}
log.debug("setManagedResource");
if (!"objectreference".equalsIgnoreCase(type))
throw new InvalidTargetObjectTypeException(type);
if (_object==null)
{
// first set so define attributes etc.
_object=proxyObject;
defineManagedResource();
}
else
_object=proxyObject;
}
/* ------------------------------------------------------------ */
/** Define the Managed Resource.
* This method is called the first time setManagedResource is
* called with a non-null object. It should be implemented by a
* derived ModelMBean to define the attributes and operations
* after an initial object has been set.
*/
protected void defineManagedResource()
{}
/* ------------------------------------------------------------ */
/** Not Supported.
* Use RequiredModelMBean for this style of MBean creation.
*/
public void setModelMBeanInfo(ModelMBeanInfo info)
throws MBeanException,
RuntimeOperationsException
{
throw new Error("setModelMBeanInfo not supported");
}
/* ------------------------------------------------------------ */
/** 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 The name of the attribute. Normal java bean
* capitlization is enforced on this name.
*/
public synchronized void defineAttribute(String name)
{
defineAttribute(name,true,false);
}
/* ------------------------------------------------------------ */
/** 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 The name of the attribute. Normal java bean
* capitlization is enforced on this name.
* @param writable If false, do not look for a setter.
*/
public synchronized void defineAttribute(String name, boolean writable)
{
defineAttribute(name,writable,false);
}
/* ------------------------------------------------------------ */
/** 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 The name of the attribute. Normal java bean
* capitlization is enforced on this name.
* @param writable If false, do not look for a setter.
* @param onMBean .
*/
public synchronized void defineAttribute(String name,
boolean writable,
boolean onMBean)
{
_dirty=true;
String uName=name.substring(0,1).toUpperCase()+name.substring(1);
name=java.beans.Introspector.decapitalize(name);
Class oClass=onMBean?this.getClass():_object.getClass();
Class type=null;
Method getter=null;
Method setter=null;
Method[] methods=oClass.getMethods();
for (int m=0;m0?",":"")+signature[i];
}
methodKey+=")";
// Build param infos
for (int i=0;i0?",":"")+pInfo[i].getType();
}
method+=")";
_method.put(method,oClass.getMethod(opInfo.getName(),types));
_operations.add(opInfo);
}
catch(Exception e)
{
log.warn(LogSupport.EXCEPTION,e);
throw new IllegalArgumentException(e.toString());
}
}
/* ------------------------------------------------------------ */
public synchronized MBeanInfo getMBeanInfo()
{
log.debug("getMBeanInfo");
if (_dirty)
{
_dirty=false;
ModelMBeanAttributeInfo[] attributes = (ModelMBeanAttributeInfo[])
_attributes.toArray(new ModelMBeanAttributeInfo[_attributes.size()]);
ModelMBeanOperationInfo[] operations = (ModelMBeanOperationInfo[])
_operations.toArray(new ModelMBeanOperationInfo[_operations.size()]);
ModelMBeanNotificationInfo[] notifications =(ModelMBeanNotificationInfo[])
_notifications.toArray(new ModelMBeanNotificationInfo[_notifications.size()]);
_beanInfo =
new ModelMBeanInfoSupport(_object.getClass().getName(),
findDescription(null),
attributes,
null,
operations,
notifications);
}
return _beanInfo;
}
/* ------------------------------------------------------------ */
public Object getAttribute(String name)
throws AttributeNotFoundException,
MBeanException,
ReflectionException
{
if(log.isDebugEnabled())log.debug("getAttribute "+name);
Method getter = (Method)_getter.get(name);
if (getter==null)
throw new AttributeNotFoundException(name);
try
{
Object o=_object;
if (getter.getDeclaringClass().isInstance(this))
o=this;
return getter.invoke(o,(java.lang.Object[])null);
}
catch(IllegalAccessException e)
{
log.warn(LogSupport.EXCEPTION,e);
throw new AttributeNotFoundException(e.toString());
}
catch(InvocationTargetException e)
{
log.warn(LogSupport.EXCEPTION,e);
throw new ReflectionException((Exception)e.getTargetException());
}
}
/* ------------------------------------------------------------ */
public AttributeList getAttributes(String[] names)
{
log.debug("getAttributes");
AttributeList results=new AttributeList(names.length);
for (int i=0;i0?",":"")+signature[i];
methodKey+=")";
try
{
Method method = (Method)_method.get(methodKey);
if (method==null)
throw new NoSuchMethodException(methodKey);
Object o=_object;
if (method.getDeclaringClass().isInstance(this))
o=this;
return method.invoke(o,params);
}
catch(NoSuchMethodException e)
{
log.warn(LogSupport.EXCEPTION,e);
throw new ReflectionException(e);
}
catch(IllegalAccessException e)
{
log.warn(LogSupport.EXCEPTION,e);
throw new MBeanException(e);
}
catch(InvocationTargetException e)
{
log.warn(LogSupport.EXCEPTION,e);
throw new ReflectionException((Exception)e.getTargetException());
}
}
/* ------------------------------------------------------------ */
public void load()
throws MBeanException,
RuntimeOperationsException,
InstanceNotFoundException
{
log.debug("load");
}
/* ------------------------------------------------------------ */
public void store()
throws MBeanException,
RuntimeOperationsException,
InstanceNotFoundException
{
log.debug("store");
}
/* ------------------------------------------------------------ */
public void addNotificationListener(NotificationListener listener,
NotificationFilter filter,
Object handback)
throws IllegalArgumentException
{
log.debug("addNotificationListener");
}
/* ------------------------------------------------------------ */
public MBeanNotificationInfo[] getNotificationInfo()
{
log.debug("getNotificationInfo");
return null;
}
/* ------------------------------------------------------------ */
public void removeNotificationListener(NotificationListener listener)
throws ListenerNotFoundException
{
log.debug("removeNotificationListener");
}
/* ------------------------------------------------------------ */
public void addAttributeChangeNotificationListener(NotificationListener listener,
String name,
Object handback)
throws MBeanException,
RuntimeOperationsException,
IllegalArgumentException
{
log.debug("addAttributeChangeNotificationListener");
}
/* ------------------------------------------------------------ */
public void removeAttributeChangeNotificationListener(NotificationListener listener,
String name)
throws MBeanException,
RuntimeOperationsException,
ListenerNotFoundException
{
log.debug("removeAttributeChangeNotificationListener");
}
/* ------------------------------------------------------------ */
public void sendAttributeChangeNotification(Attribute oldAttr,
Attribute newAttr)
throws MBeanException,
RuntimeOperationsException
{
log.debug("sendAttributeChangeNotification");
}
/* ------------------------------------------------------------ */
public void sendAttributeChangeNotification(AttributeChangeNotification notify)
throws MBeanException,
RuntimeOperationsException
{
log.debug("sendAttributeChangeNotification");
}
/* ------------------------------------------------------------ */
public void sendNotification(String notify)
throws MBeanException,
RuntimeOperationsException
{
log.debug("sendNotification");
}
/* ------------------------------------------------------------ */
public void sendNotification(Notification notify)
throws MBeanException,
RuntimeOperationsException
{
log.debug("sendNotification");
}
/* ------------------------------------------------------------ */
/* Find MBean descriptions.
* MBean descriptions are searched for in ResourceBundles. Bundles
* are looked for in a mbean.property files within each package of
* the MBean class inheritance hierachy.
* Once a bundle is found, the key is added to object names in the
* following order: fully qualied managed resource class name, tail
* managed resource class name, tail mbean class name. The string
* "MBean" is stripped from the tail of any name.
* For example, if the class a.b.C is managed by a MBean
* p.q.RMBean which is derived from p.SMBean, then the seach order
* for a key x is as follows:
* bundle: p.q.mbean name: a.b.C.x
* bundle: p.q.mbean name: C.x
* bundle: p.q.mbean name: R.x
* bundle: p.mbean name: a.b.C.x
* bundle: p.mbean name: C.x
* bundle: p.mbean name: S.x
*
* The convention used for keys passed to this method are:
* null or empty - Object description
* xxx - Attribute xxx description
* xxx() - Simple operation xxx description
* xxx(type,..) - Operation xxx with signature desciption
* xxx(type,..)[n] - Param n of operation xxx description
*
* @param key
* @return Description string.
*/
private String findDescription(String key)
{
Class lookIn = this.getClass();
// Array of possible objectNames
String[] objectNames=new String[3];
objectNames[0]=_object.getClass().getName();
if (objectNames[0].indexOf(".")>=0)
objectNames[1]=objectNames[0].substring(objectNames[0].lastIndexOf(".")+1);
while(lookIn!=null)
{
String pkg=lookIn.getName();
int lastDot= pkg.lastIndexOf(".");
if (lastDot>0)
{
objectNames[2]=pkg.substring(lastDot+1);
pkg=pkg.substring(0,lastDot);
}
else
{
objectNames[2]=pkg;
pkg=null;
}
String resource=(pkg==null?"mbean":(pkg.replace('.','/')+"/mbean"));
if(log.isTraceEnabled())log.trace("Look for: "+resource);
try
{
ResourceBundle bundle=
ResourceBundle.getBundle(resource,
Locale.getDefault(),
_object.getClass().getClassLoader());
if(log.isTraceEnabled())log.trace("Bundle "+resource);
for (int i=0;i0)
name+="."+key;
try{
String description=bundle.getString(name);
if (description!=null && description.length()>0)
return description;
}
catch(Exception e) { if(log.isTraceEnabled())log.trace(e.toString()); }
}
}
catch(Exception e) { if(log.isTraceEnabled())log.trace(e.toString()); }
lookIn=lookIn.getSuperclass();
}
if (key==null || key.length()==0)
return objectNames[0];
return key;
}
/* ------------------------------------------------------------ */
/** Create a new ObjectName.
* Return a new object name. The default implementation is the
* results of uniqueObjectName(baseObjectName), if baseObjectName
* is not set, then the results of uniqueObjectName(defaultDomain+":");
* @return The Object name
*/
protected ObjectName newObjectName(MBeanServer server)
{
// Create own ObjectName of the form:
// package:class=id
if (_baseObjectName!=null)
{
if (_baseObjectName.indexOf(':')>=0)
return uniqueObjectName(server,_baseObjectName);
return uniqueObjectName(server,getDefaultDomain()+":"+
_baseObjectName);
}
return uniqueObjectName(server,getDefaultDomain()+":");
}
/* ------------------------------------------------------------ */
public void setBaseObjectName(String s)
{
_baseObjectName=s;
}
/* ------------------------------------------------------------ */
public String getBaseObjectName()
{
return _baseObjectName;
}
/* ------------------------------------------------------------ */
/** Pre registration notification.
* If this method is specialized by a derived class that may set
* the objectName, then it should call this implementation with
* the new objectName.
* @param server
* @param oName
* @return The ObjectName to use.
*/
public synchronized ObjectName preRegister(MBeanServer server, ObjectName oName)
{
_mBeanServer=server;
_objectName=oName;
if (_objectName==null)
{
try{oName=newObjectName(server);}
catch(Exception e){log.warn(LogSupport.EXCEPTION,e);}
}
if(log.isDebugEnabled())log.debug("preRegister "+_objectName+" -> "+oName);
_objectName=oName;
return _objectName;
}
/* ------------------------------------------------------------ */
public void postRegister(Boolean ok)
{
if (ok.booleanValue())
log.info("Registered "+_objectName);
else
{
_mBeanServer=null;
_objectName=null;
}
}
/* ------------------------------------------------------------ */
public void preDeregister()
{
log.info("Deregister "+_objectName);
getComponentMBeans(null,_components);
_components.clear();
}
/* ------------------------------------------------------------ */
/** Post Deregister.
* This implementation destroys this MBean and it cannot be used again.
*/
public void postDeregister()
{
_beanInfo=null;
_mBeanServer=null;
_object=null;
_objectName=null;
if (_getter!=null)
_getter.clear();
_getter=null;
if (_setter!=null)
_setter.clear();
_setter=null;
if (_method!=null)
_method.clear();
_method=null;
if (_attributes!=null)
_attributes.clear();
_attributes=null;
if (_operations!=null)
_operations.clear();
_operations=null;
if (_notifications!=null)
_notifications.clear();
_notifications=null;
}
/* ------------------------------------------------------------ */
/** Add an id clause to a JMX object name.
* Used to make unique objectnames when there are no other
* distinguishing attributes.
* If the passed object name ends with '=', just a unique ID is
* added. Otherwise and classname= clause is added.
* @param objectName
* @return objectName with id= class.
*/
public synchronized ObjectName uniqueObjectName(MBeanServer server,
String objectName)
{
return uniqueObjectName(server,_object,objectName);
}
/* ------------------------------------------------------------ */
public synchronized ObjectName uniqueObjectName(MBeanServer server,
Object object,
String objectName)
{
if (!objectName.endsWith("="))
{
String className = object.getClass().getName();
if (className.indexOf(".")>0)
className=className.substring(className.lastIndexOf(".")+1);
if (className.endsWith("MBean"))
className=className.substring(0,className.length()-5);
if (!objectName.endsWith(":"))
objectName+=",";
objectName+=className+"=";
}
ObjectName oName=null;
try
{
while(true)
{
Integer id=(Integer)__objectId.get(objectName);
if (id==null)
id=new Integer(0);
oName=new ObjectName(objectName+id);
id=new Integer(id.intValue()+1);
__objectId.put(objectName,id);
// If no server, this must be unique
if (server==null)
break;
// Otherwise let's check it is unique
// if not found then it is unique
if (!server.isRegistered(oName))
break;
}
}
catch(Exception e)
{
log.warn(LogSupport.EXCEPTION,e);
}
return oName;
}
/* ------------------------------------------------------------ */
/** Get Component MBeans.
* Creates, registers and deregisters MBeans for an array of components.
* On each call the passed map is used to determine components that have
* already been registers and those that need to be deregistered.
* @param components the components.
* @param map A map of previously registered components to object
* name. If null is passed, a default map for the mbean is used.
* @return An array of ObjectNames for each component.
*/
protected ObjectName[] getComponentMBeans(Object[] components, Map map)
{
if (map==null)
map=_components;
ObjectName[] beans=null;
if (components==null)
beans = new ObjectName[0];
else
{
beans = new ObjectName[components==null?0:components.length];
// Add new beans
for (int i=0;icomponents.length)
{
Object[] to_delete=new Object[map.size()-beans.length];
int d=0;
Iterator iter = map.keySet().iterator();
keys:
while(iter.hasNext())
{
Object bean = iter.next();
if (components!=null)
{
for(int i=0;i0;)
{
try{getMBeanServer().unregisterMBean((ObjectName)map.remove(to_delete[d]));}
catch (Exception e) {log.warn(LogSupport.EXCEPTION,e);}
}
}
return beans;
}
/** Unregister mbeans for already registered components
* @param map
*/
protected void destroyComponentMBeans (Map map)
{
//if no map of registered mbean names is passed,
//use the default map
if (null==map)
map = _components;
if (map==null)
return;
Iterator itor = map.values().iterator();
while (itor.hasNext())
{
try
{
ObjectName o = (ObjectName)itor.next();
getMBeanServer().unregisterMBean(o);
itor.remove();
}
catch (Exception e) {log.warn(LogSupport.EXCEPTION,e);}
}
}
}