/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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 org.apache.tomcat.util.modeler; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.Iterator; import javax.management.Attribute; import javax.management.AttributeChangeNotification; import javax.management.AttributeList; import javax.management.AttributeNotFoundException; import javax.management.DynamicMBean; import javax.management.InstanceNotFoundException; import javax.management.InvalidAttributeValueException; import javax.management.ListenerNotFoundException; import javax.management.MBeanException; import javax.management.MBeanInfo; import javax.management.MBeanNotificationInfo; import javax.management.MBeanRegistration; import javax.management.MBeanServer; import javax.management.Notification; import javax.management.NotificationFilter; import javax.management.NotificationListener; import javax.management.ObjectName; import javax.management.ReflectionException; import javax.management.RuntimeErrorException; import javax.management.RuntimeOperationsException; import javax.management.modelmbean.InvalidTargetObjectTypeException; import javax.management.modelmbean.ModelMBeanNotificationBroadcaster; import org.apache.juli.logging.Log; import org.apache.juli.logging.LogFactory; /* * Changes from commons.modeler: * * - use DynamicMBean * - remove methods not used in tomcat and redundant/not very generic * - must be created from the ManagedBean - I don't think there were any direct * uses, but now it is required. * - some of the gratuitous flexibility removed - instead this is more predictive and * strict with the use cases. * - all Method and metadata is stored in ManagedBean. BaseModelBMean and ManagedBean act * like Object and Class. * - setModelMBean is no longer called on resources ( not used in tomcat ) * - no caching of Methods for now - operations and setters are not called repeatedly in most * management use cases. Getters shouldn't be called very frequently either - and even if they * are, the overhead of getting the method should be small compared with other JMX costs ( RMI, etc ). * We can add getter cache if needed. * - removed unused constructor, fields * * TODO: * - clean up catalina.mbeans, stop using weird inheritance */ /** *
Basic implementation of the DynamicMBean interface, which
* supports the minimal requirements of the interface contract.
This can be used directly to wrap an existing java bean, or inside * an mlet or anywhere an MBean would be used. * * Limitations: *
objectReference are
* supported.invoke() are immediately executed.void.MBeanInfo object for this MBean.
*/
@Override
public MBeanInfo getMBeanInfo() {
return managedBean.getMBeanInfo();
}
/**
* Invoke a particular method on this MBean, and return any returned
* value.
*
* IMPLEMENTATION NOTE - This implementation will * attempt to invoke this method on the MBean itself, or (if not * available) on the managed resource object associated with this * MBean.
* * @param name Name of the operation to be invoked * @param params Array containing the method parameters of this operation * @param signature Array containing the class names representing * the signature of this operation * * @exception MBeanException if the initializer of an object * throws an exception * @exception ReflectionException if a Java reflection exception * occurs when invoking a method */ @Override public Object invoke(String name, Object params[], String signature[]) throws MBeanException, ReflectionException { if( (resource instanceof DynamicMBean) && ! ( resource instanceof BaseModelMBean )) { return ((DynamicMBean)resource).invoke(name, params, signature); } // Validate the input parameters if (name == null) throw new RuntimeOperationsException (new IllegalArgumentException("Method name is null"), "Method name is null"); if( log.isDebugEnabled()) log.debug("Invoke " + name); Method method= managedBean.getInvoke(name, params, signature, this, resource); // Invoke the selected method on the appropriate object Object result = null; try { if( method.getDeclaringClass().isAssignableFrom( this.getClass()) ) { result = method.invoke(this, params ); } else { result = method.invoke(resource, params); } } catch (InvocationTargetException e) { Throwable t = e.getTargetException(); log.error("Exception invoking method " + name , t ); if (t == null) t = e; if (t instanceof RuntimeException) throw new RuntimeOperationsException ((RuntimeException) t, "Exception invoking method " + name); else if (t instanceof Error) throw new RuntimeErrorException ((Error) t, "Error invoking method " + name); else throw new MBeanException ((Exception)t, "Exception invoking method " + name); } catch (Exception e) { log.error("Exception invoking method " + name , e ); throw new MBeanException (e, "Exception invoking method " + name); } // Return the results of this method invocation // FIXME - should we validate the return type? return result; } static Class> getAttributeClass(String signature) throws ReflectionException { if (signature.equals(Boolean.TYPE.getName())) return Boolean.TYPE; else if (signature.equals(Byte.TYPE.getName())) return Byte.TYPE; else if (signature.equals(Character.TYPE.getName())) return Character.TYPE; else if (signature.equals(Double.TYPE.getName())) return Double.TYPE; else if (signature.equals(Float.TYPE.getName())) return Float.TYPE; else if (signature.equals(Integer.TYPE.getName())) return Integer.TYPE; else if (signature.equals(Long.TYPE.getName())) return Long.TYPE; else if (signature.equals(Short.TYPE.getName())) return Short.TYPE; else { try { ClassLoader cl=Thread.currentThread().getContextClassLoader(); if( cl!=null ) return cl.loadClass(signature); } catch( ClassNotFoundException e ) { } try { return Class.forName(signature); } catch (ClassNotFoundException e) { throw new ReflectionException (e, "Cannot find Class for " + signature); } } } /** * Set the value of a specific attribute of this MBean. * * @param attribute The identification of the attribute to be set * and the new value * * @exception AttributeNotFoundException if this attribute is not * supported by this MBean * @exception MBeanException if the initializer of an object * throws an exception * @exception ReflectionException if a Java reflection exception * occurs when invoking the getter */ @Override public void setAttribute(Attribute attribute) throws AttributeNotFoundException, MBeanException, ReflectionException { if( log.isDebugEnabled() ) log.debug("Setting attribute " + this + " " + attribute ); if( (resource instanceof DynamicMBean) && ! ( resource instanceof BaseModelMBean )) { try { ((DynamicMBean)resource).setAttribute(attribute); } catch (InvalidAttributeValueException e) { throw new MBeanException(e); } return; } // Validate the input parameters if (attribute == null) throw new RuntimeOperationsException (new IllegalArgumentException("Attribute is null"), "Attribute is null"); String name = attribute.getName(); Object value = attribute.getValue(); if (name == null) throw new RuntimeOperationsException (new IllegalArgumentException("Attribute name is null"), "Attribute name is null"); Object oldValue=null; //if( getAttMap.get(name) != null ) // oldValue=getAttribute( name ); Method m=managedBean.getSetter(name,this,resource); try { if( m.getDeclaringClass().isAssignableFrom( this.getClass()) ) { m.invoke(this, new Object[] { value }); } else { m.invoke(resource, new Object[] { value }); } } catch (InvocationTargetException e) { Throwable t = e.getTargetException(); if (t == null) t = e; if (t instanceof RuntimeException) throw new RuntimeOperationsException ((RuntimeException) t, "Exception invoking method " + name); else if (t instanceof Error) throw new RuntimeErrorException ((Error) t, "Error invoking method " + name); else throw new MBeanException (e, "Exception invoking method " + name); } catch (Exception e) { log.error("Exception invoking method " + name , e ); throw new MBeanException (e, "Exception invoking method " + name); } try { sendAttributeChangeNotification(new Attribute( name, oldValue), attribute); } catch(Exception ex) { log.error("Error sending notification " + name, ex); } //attributes.put( name, value ); // if( source != null ) { // // this mbean is associated with a source - maybe we want to persist // source.updateField(oname, name, value); // } } @Override public String toString() { if( resource==null ) return "BaseModelMbean[" + resourceType + "]"; return resource.toString(); } /** * Set the values of several attributes of this MBean. * * @param attributes THe names and values to be set * * @return The list of attributes that were set and their new values */ @Override public AttributeList setAttributes(AttributeList attributes) { AttributeList response = new AttributeList(); // Validate the input parameters if (attributes == null) return response; // Prepare and return our response, eating all exceptions String names[] = new String[attributes.size()]; int n = 0; Iterator> items = attributes.iterator(); while (items.hasNext()) { Attribute item = (Attribute) items.next(); names[n++] = item.getName(); try { setAttribute(item); } catch (Exception e) { // Ignore all exceptions } } return getAttributes(names); } // ----------------------------------------------------- ModelMBean Methods /** * Get the instance handle of the object against which we execute * all methods in this ModelMBean management interface. * * @return the backend managed object * @exception InstanceNotFoundException if the managed resource object * cannot be found * @exception InvalidTargetObjectTypeException if the managed resource * object is of the wrong type * @exception MBeanException if the initializer of the object throws * an exception * @exception RuntimeOperationsException if the managed resource or the * resource type isnull or invalid
*/
public Object getManagedResource()
throws InstanceNotFoundException, InvalidTargetObjectTypeException,
MBeanException, RuntimeOperationsException {
if (resource == null)
throw new RuntimeOperationsException
(new IllegalArgumentException("Managed resource is null"),
"Managed resource is null");
return resource;
}
/**
* Set the instance handle of the object against which we will execute
* all methods in this ModelMBean management interface.
*
* The caller can provide the mbean instance or the object name to
* the resource, if needed.
*
* @param resource The resource object to be managed
* @param type The type of reference for the managed resource
* ("ObjectReference", "Handle", "IOR", "EJBHandle", or
* "RMIReference")
*
* @exception InstanceNotFoundException if the managed resource object
* cannot be found
* @exception MBeanException if the initializer of the object throws
* an exception
* @exception RuntimeOperationsException if the managed resource or the
* resource type is null or invalid
*/
public void setManagedResource(Object resource, String type)
throws InstanceNotFoundException,
MBeanException, RuntimeOperationsException
{
if (resource == null)
throw new RuntimeOperationsException
(new IllegalArgumentException("Managed resource is null"),
"Managed resource is null");
// if (!"objectreference".equalsIgnoreCase(type))
// throw new InvalidTargetObjectTypeException(type);
this.resource = resource;
this.resourceType = resource.getClass().getName();
// // Make the resource aware of the model mbean.
// try {
// Method m=resource.getClass().getMethod("setModelMBean",
// new Class[] {ModelMBean.class});
// if( m!= null ) {
// m.invoke(resource, new Object[] {this});
// }
// } catch( NoSuchMethodException t ) {
// // ignore
// } catch( Throwable t ) {
// log.error( "Can't set model mbean ", t );
// }
}
// ------------------------------ ModelMBeanNotificationBroadcaster Methods
/**
* Add an attribute change notification event listener to this MBean.
*
* @param listener Listener that will receive event notifications
* @param name Name of the attribute of interest, or null
* to indicate interest in all attributes
* @param handback Handback object to be sent along with event
* notifications
*
* @exception IllegalArgumentException if the listener parameter is null
*/
@Override
public void addAttributeChangeNotificationListener
(NotificationListener listener, String name, Object handback)
throws IllegalArgumentException {
if (listener == null)
throw new IllegalArgumentException("Listener is null");
if (attributeBroadcaster == null)
attributeBroadcaster = new BaseNotificationBroadcaster();
if( log.isDebugEnabled() )
log.debug("addAttributeNotificationListener " + listener);
BaseAttributeFilter filter = new BaseAttributeFilter(name);
attributeBroadcaster.addNotificationListener
(listener, filter, handback);
}
/**
* Remove an attribute change notification event listener from
* this MBean.
*
* @param listener The listener to be removed
* @param name The attribute name for which no more events are required
*
*
* @exception ListenerNotFoundException if this listener is not
* registered in the MBean
*/
@Override
public void removeAttributeChangeNotificationListener
(NotificationListener listener, String name)
throws ListenerNotFoundException {
if (listener == null)
throw new IllegalArgumentException("Listener is null");
// FIXME - currently this removes *all* notifications for this listener
if (attributeBroadcaster != null) {
attributeBroadcaster.removeNotificationListener(listener);
}
}
/**
* Send an AttributeChangeNotification to all registered
* listeners.
*
* @param notification The AttributeChangeNotification
* that will be passed
*
* @exception MBeanException if an object initializer throws an
* exception
* @exception RuntimeOperationsException wraps IllegalArgumentException
* when the specified notification is null or invalid
*/
@Override
public void sendAttributeChangeNotification
(AttributeChangeNotification notification)
throws MBeanException, RuntimeOperationsException {
if (notification == null)
throw new RuntimeOperationsException
(new IllegalArgumentException("Notification is null"),
"Notification is null");
if (attributeBroadcaster == null)
return; // This means there are no registered listeners
if( log.isDebugEnabled() )
log.debug( "AttributeChangeNotification " + notification );
attributeBroadcaster.sendNotification(notification);
}
/**
* Send an AttributeChangeNotification to all registered
* listeners.
*
* @param oldValue The original value of the Attribute
* @param newValue The new value of the Attribute
*
* @exception MBeanException if an object initializer throws an
* exception
* @exception RuntimeOperationsException wraps IllegalArgumentException
* when the specified notification is null or invalid
*/
@Override
public void sendAttributeChangeNotification
(Attribute oldValue, Attribute newValue)
throws MBeanException, RuntimeOperationsException {
// Calculate the class name for the change notification
String type = null;
if (newValue.getValue() != null)
type = newValue.getValue().getClass().getName();
else if (oldValue.getValue() != null)
type = oldValue.getValue().getClass().getName();
else
return; // Old and new are both null == no change
AttributeChangeNotification notification =
new AttributeChangeNotification
(this, 1, System.currentTimeMillis(),
"Attribute value has changed",
oldValue.getName(), type,
oldValue.getValue(), newValue.getValue());
sendAttributeChangeNotification(notification);
}
/**
* Send a Notification to all registered listeners as a
* jmx.modelmbean.general notification.
*
* @param notification The Notification that will be passed
*
* @exception MBeanException if an object initializer throws an
* exception
* @exception RuntimeOperationsException wraps IllegalArgumentException
* when the specified notification is null or invalid
*/
@Override
public void sendNotification(Notification notification)
throws MBeanException, RuntimeOperationsException {
if (notification == null)
throw new RuntimeOperationsException
(new IllegalArgumentException("Notification is null"),
"Notification is null");
if (generalBroadcaster == null)
return; // This means there are no registered listeners
generalBroadcaster.sendNotification(notification);
}
/**
* Send a Notification which contains the specified string
* as a jmx.modelmbean.generic notification.
*
* @param message The message string to be passed
*
* @exception MBeanException if an object initializer throws an
* exception
* @exception RuntimeOperationsException wraps IllegalArgumentException
* when the specified notification is null or invalid
*/
@Override
public void sendNotification(String message)
throws MBeanException, RuntimeOperationsException {
if (message == null)
throw new RuntimeOperationsException
(new IllegalArgumentException("Message is null"),
"Message is null");
Notification notification = new Notification
("jmx.modelmbean.generic", this, 1, message);
sendNotification(notification);
}
// ---------------------------------------- NotificationBroadcaster Methods
/**
* Add a notification event listener to this MBean.
*
* @param listener Listener that will receive event notifications
* @param filter Filter object used to filter event notifications
* actually delivered, or null for no filtering
* @param handback Handback object to be sent along with event
* notifications
*
* @exception IllegalArgumentException if the listener parameter is null
*/
@Override
public void addNotificationListener(NotificationListener listener,
NotificationFilter filter,
Object handback)
throws IllegalArgumentException {
if (listener == null)
throw new IllegalArgumentException("Listener is null");
if( log.isDebugEnabled() ) log.debug("addNotificationListener " + listener);
if (generalBroadcaster == null)
generalBroadcaster = new BaseNotificationBroadcaster();
generalBroadcaster.addNotificationListener
(listener, filter, handback);
// We'll send the attribute change notifications to all listeners ( who care )
// The normal filtering can be used.
// The problem is that there is no other way to add attribute change listeners
// to a model mbean ( AFAIK ). I suppose the spec should be fixed.
if (attributeBroadcaster == null)
attributeBroadcaster = new BaseNotificationBroadcaster();
if( log.isDebugEnabled() )
log.debug("addAttributeNotificationListener " + listener);
attributeBroadcaster.addNotificationListener
(listener, filter, handback);
}
/**
* Return an MBeanNotificationInfo object describing the
* notifications sent by this MBean.
*/
@Override
public MBeanNotificationInfo[] getNotificationInfo() {
// Acquire the set of application notifications
MBeanNotificationInfo current[] = getMBeanInfo().getNotifications();
MBeanNotificationInfo response[] =
new MBeanNotificationInfo[current.length + 2];
// Descriptor descriptor = null;
// Fill in entry for general notifications
// descriptor = new DescriptorSupport
// (new String[] { "name=GENERIC",
// "descriptorType=notification",
// "log=T",
// "severity=5",
// "displayName=jmx.modelmbean.generic" });
response[0] = new MBeanNotificationInfo
(new String[] { "jmx.modelmbean.generic" },
"GENERIC",
"Text message notification from the managed resource");
//descriptor);
// Fill in entry for attribute change notifications
// descriptor = new DescriptorSupport
// (new String[] { "name=ATTRIBUTE_CHANGE",
// "descriptorType=notification",
// "log=T",
// "severity=5",
// "displayName=jmx.attribute.change" });
response[1] = new MBeanNotificationInfo
(new String[] { "jmx.attribute.change" },
"ATTRIBUTE_CHANGE",
"Observed MBean attribute value has changed");
//descriptor);
// Copy remaining notifications as reported by the application
System.arraycopy(current, 0, response, 2, current.length);
return response;
}
/**
* Remove a notification event listener from this MBean.
*
* @param listener The listener to be removed (any and all registrations
* for this listener will be eliminated)
*
* @exception ListenerNotFoundException if this listener is not
* registered in the MBean
*/
@Override
public void removeNotificationListener(NotificationListener listener)
throws ListenerNotFoundException {
if (listener == null)
throw new IllegalArgumentException("Listener is null");
if (generalBroadcaster != null) {
generalBroadcaster.removeNotificationListener(listener);
}
if (attributeBroadcaster != null) {
attributeBroadcaster.removeNotificationListener(listener);
}
}
public String getModelerType() {
return resourceType;
}
public String getClassName() {
return getModelerType();
}
public ObjectName getJmxName() {
return oname;
}
public String getObjectName() {
if (oname != null) {
return oname.toString();
} else {
return null;
}
}
// -------------------- Registration --------------------
// XXX We can add some method patterns here- like setName() and
// setDomain() for code that doesn't implement the Registration
@Override
public ObjectName preRegister(MBeanServer server,
ObjectName name)
throws Exception
{
if( log.isDebugEnabled())
log.debug("preRegister " + resource + " " + name );
oname=name;
if( resource instanceof MBeanRegistration ) {
oname = ((MBeanRegistration)resource).preRegister(server, name );
}
return oname;
}
@Override
public void postRegister(Boolean registrationDone) {
if( resource instanceof MBeanRegistration ) {
((MBeanRegistration)resource).postRegister(registrationDone);
}
}
@Override
public void preDeregister() throws Exception {
if( resource instanceof MBeanRegistration ) {
((MBeanRegistration)resource).preDeregister();
}
}
@Override
public void postDeregister() {
if( resource instanceof MBeanRegistration ) {
((MBeanRegistration)resource).postDeregister();
}
}
}