org.jboss.mx.modelmbean.ModelMBeanInvoker Maven / Gradle / Ivy
/*
* JBoss, Home of Professional Open Source.
* Copyright 2008, Red Hat Middleware LLC, and individual contributors
* as indicated by the @author tags. See the copyright.txt file in the
* distribution for a full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.jboss.mx.modelmbean;
import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.beans.PropertyEditor;
import java.beans.PropertyEditorManager;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import javax.management.Attribute;
import javax.management.AttributeChangeNotification;
import javax.management.AttributeChangeNotificationFilter;
import javax.management.Descriptor;
import javax.management.InstanceNotFoundException;
import javax.management.JMException;
import javax.management.ListenerNotFoundException;
import javax.management.MBeanAttributeInfo;
import javax.management.MBeanException;
import javax.management.MBeanInfo;
import javax.management.MBeanNotificationInfo;
import javax.management.MBeanOperationInfo;
import javax.management.MBeanServer;
import javax.management.Notification;
import javax.management.NotificationFilter;
import javax.management.NotificationListener;
import javax.management.ObjectName;
import javax.management.RuntimeErrorException;
import javax.management.RuntimeOperationsException;
import javax.management.modelmbean.InvalidTargetObjectTypeException;
import javax.management.modelmbean.ModelMBean;
import javax.management.modelmbean.ModelMBeanAttributeInfo;
import javax.management.modelmbean.ModelMBeanInfo;
import javax.management.modelmbean.ModelMBeanInfoSupport;
import javax.management.modelmbean.ModelMBeanOperationInfo;
import org.jboss.logging.Logger;
import org.jboss.mx.interceptor.AbstractInterceptor;
import org.jboss.mx.interceptor.Interceptor;
import org.jboss.mx.interceptor.ModelMBeanAttributeInterceptor;
import org.jboss.mx.interceptor.ModelMBeanInfoInterceptor;
import org.jboss.mx.interceptor.ModelMBeanInterceptor;
import org.jboss.mx.interceptor.ModelMBeanOperationInterceptor;
import org.jboss.mx.interceptor.NullInterceptor;
import org.jboss.mx.interceptor.ObjectReferenceInterceptor;
import org.jboss.mx.interceptor.PersistenceInterceptor;
import org.jboss.mx.interceptor.PersistenceInterceptor2;
import org.jboss.mx.persistence.NullPersistence;
import org.jboss.mx.persistence.PersistenceManager;
import org.jboss.mx.server.AbstractMBeanInvoker;
import org.jboss.mx.server.Invocation;
import org.jboss.mx.server.InvocationContext;
import org.jboss.mx.server.MBeanInvoker;
import org.jboss.mx.util.JBossNotificationBroadcasterSupport;
/**
* An extension of the {@link org.jboss.mx.server.MBeanInvoker MBeanInvoker}
* that implements the base Model MBean functionality, essentially making the
* Model MBean just another invoker of managed resources.
*
* @see javax.management.modelmbean.ModelMBean
* @see org.jboss.mx.server.MBeanInvoker
*
* @author Juha Lindfors.
* @author Dimitris Andreadis.
* @author Matt Munz
* @version $Revision: 81026 $
*/
public abstract class ModelMBeanInvoker extends AbstractMBeanInvoker
implements ModelMBean, ModelMBeanConstants
{
Logger log = Logger.getLogger(ModelMBeanInvoker.class.getName());
// Attributes ----------------------------------------------------
/**
* The resource type string of the managed resource, such as
* {@link ModelMBeanConstants#OBJECT_REF} or
* {@link XMBeanConstants#STANDARD_INTERFACE}. This type string can be
* used by the invoker to determine the behavior implemented by the
* invocation chain and how the managed resource is exposed to the client
* programs.
*/
protected String resourceType = null;
/**
* Persistence manager.
*/
protected PersistenceManager persistence = new NullPersistence();
/**
* Notification broadcaster for this Model MBean.
*/
protected JBossNotificationBroadcasterSupport notifier = new JBossNotificationBroadcasterSupport();
/**
* Notification sequence number for generic Model MBean notifications.
*/
protected long notifierSequence = 1;
/**
* Notification sequence number for attribute change notifications.
*/
protected long attrNotifierSequence = 1;
// Constructors --------------------------------------------------
/**
* Default constructor.
*/
public ModelMBeanInvoker()
{
}
/**
* Creates a Model MBean instance and initializes it with the given
* Model MBean metadata.
*
* @param info Model MBean metadata
*/
public ModelMBeanInvoker(ModelMBeanInfo info) throws MBeanException
{
setModelMBeanInfo(info);
}
// ModelMBean implementation -------------------------------------
/**
* Sets the MBean metadata for this Model MBean instance.
*
* @param info Model MBean metadata
*/
public void setModelMBeanInfo(ModelMBeanInfo info)
throws MBeanException, RuntimeOperationsException
{
if (info == null)
throw new RuntimeOperationsException(new IllegalArgumentException("MBeanInfo cannot be null"));
// need to type to an instance of MBeanInfo -- therefore the extra copy here
this.info = new ModelMBeanInfoSupport(info);
// Apply the MBeanInfo injection if requested
ModelMBeanInfo minfo = info;
Descriptor mbeanDescriptor = null;
try
{
mbeanDescriptor = minfo.getDescriptor("",
ModelMBeanConstants.MBEAN_DESCRIPTOR);
}
catch (MBeanException e)
{
log.warn("Failed to obtain descriptor: "+ModelMBeanConstants.MBEAN_DESCRIPTOR, e);
return;
}
String type = (String) mbeanDescriptor.getFieldValue(
ModelMBeanConstants.MBEAN_INFO_INJECTION_TYPE);
if( type != null )
{
inject(ModelMBeanConstants.MBEAN_INFO_INJECTION_TYPE,
type, MBeanInfo.class, info);
}
}
/**
* Sets the managed resource for this Model MBean instance. The resource
* type must be known to the Model MBean implementation (see
* {@link #isSupportedResourceType} for more information).
*
* @param ref reference to the managed resource
* @param resourceType resource type identification string
*/
public void setManagedResource(Object ref, String resourceType)
throws MBeanException, InstanceNotFoundException, InvalidTargetObjectTypeException
{
if (!isSupportedResourceType(ref, resourceType))
throw new InvalidTargetObjectTypeException("Unsupported resource type: " + resourceType);
setResource(ref);
this.resourceType = resourceType;
if (getServer() != null)
{
try
{
this.init(getServer(), resourceEntry.getObjectName());
}
catch(Exception e)
{
throw new MBeanException(e, "Failed to init from resource");
}
}
}
// ModelMBeanNotificationBroadcaster implementation --------------
public void addNotificationListener(NotificationListener listener,
NotificationFilter filter,
Object handback)
{
notifier.addNotificationListener(listener, filter, handback);
}
public void removeNotificationListener(NotificationListener listener)
throws ListenerNotFoundException
{
notifier.removeNotificationListener(listener);
}
public void removeNotificationListener(NotificationListener listener,
NotificationFilter filter,
Object handback)
throws ListenerNotFoundException
{
notifier.removeNotificationListener(listener, filter, handback);
}
/**
* Sends a notification with a given string message. The notification
* type will be set as
* {@link ModelMBeanConstants#GENERIC_MODELMBEAN_NOTIFICATION GENERIC_MODELMBEAN_NOTIFICATION}.
*
* @param ntfyText notification message
*/
public void sendNotification(String ntfyText)
throws MBeanException, RuntimeOperationsException
{
if( ntfyText == null )
{
throw new RuntimeOperationsException(
new IllegalArgumentException("ntfyText cannot be null")
);
}
Notification notif = new Notification(
GENERIC_MODELMBEAN_NOTIFICATION, // type
this, // source
1, // always 1 - by spec
ntfyText // message
);
sendNotification(notif);
}
/**
* Sends a notification.
*
* @param ntfyObj notification to send
*/
public void sendNotification(Notification ntfyObj)
throws MBeanException, RuntimeOperationsException
{
if( ntfyObj == null )
{
throw new RuntimeOperationsException(
new IllegalArgumentException("ntfyText cannot be null")
);
}
notifier.sendNotification(ntfyObj);
}
/**
* Sends an attribute change notification.
*
* @param notification attribute change notification to send
*/
public void sendAttributeChangeNotification(AttributeChangeNotification notification)
throws MBeanException
{
if( notification == null )
{
throw new RuntimeOperationsException(
new IllegalArgumentException("notification cannot be null")
);
}
notifier.sendNotification(notification);
}
/**
* Sends an attribute change notification.
*
* @param oldValue attribute with the old value
* @param newValue attribute with the new value
* @throws IllegalArgumentException - An Attribute object passed in parameter
* is null or the names of the two Attribute objects in parameter are not
* the same.
*/
public void sendAttributeChangeNotification(Attribute oldValue, Attribute newValue)
throws MBeanException, RuntimeOperationsException
{
if( oldValue == null || newValue == null )
{
throw new RuntimeOperationsException(
new IllegalArgumentException("Attribute cannot be null")
);
}
if (!(oldValue.getName().equals(newValue.getName())))
{
throw new RuntimeOperationsException(
new IllegalArgumentException("Attribute name mismatch between oldvalue and newvalue")
);
}
String attr = oldValue.getName();
String type = ((ModelMBeanInfo) info).getAttribute(attr).getType();
AttributeChangeNotification notif = new AttributeChangeNotification(
this, // source
1, // always 1 - by spec
System.currentTimeMillis(), // time stamp
"" + attr + " changed from " + oldValue + " to " + newValue,
attr, type, // name & type
oldValue.getValue(),
newValue.getValue() // values
);
notifier.sendNotification(notif);
}
public MBeanNotificationInfo[] getNotificationInfo()
{
return info.getNotifications();
}
/**
*/
public void addAttributeChangeNotificationListener(
NotificationListener listener,
String attributeName,
Object handback) throws MBeanException
{
// Check the attribute info
ModelMBeanInfo minfo = (ModelMBeanInfo) info;
AttributeChangeNotificationFilter filter = null;
if (attributeName != null)
{
ModelMBeanAttributeInfo ainfo = minfo.getAttribute(attributeName);
if( ainfo == null )
{
throw new RuntimeOperationsException(
new IllegalArgumentException("Attribute does not exist: "+attributeName));
}
filter = new AttributeChangeNotificationFilter();
filter.enableAttribute(attributeName);
}
else
{
filter = new AttributeChangeNotificationFilter();
MBeanAttributeInfo[] allAttributes = minfo.getAttributes();
for (int i = 0; i < allAttributes.length; ++i)
filter.enableAttribute(allAttributes[i].getName());
}
notifier.addNotificationListener(listener, filter, handback);
}
/**
*/
public void removeAttributeChangeNotificationListener(
NotificationListener listener,
String attributeName) throws MBeanException, ListenerNotFoundException
{
if( attributeName != null )
{
// Check the attribute info
ModelMBeanInfo minfo = (ModelMBeanInfo) info;
ModelMBeanAttributeInfo ainfo = minfo.getAttribute(attributeName);
if( ainfo == null )
{
throw new RuntimeOperationsException(
new IllegalArgumentException("Attribute does not exist: "+attributeName));
}
}
notifier.removeNotificationListener(listener);
}
// PersistentMBean implementation --------------------------------
public void load() throws MBeanException, InstanceNotFoundException
{
if (info == null)
return;
persistence.load(this, info);
}
public void store() throws MBeanException, InstanceNotFoundException
{
persistence.store(info);
}
// MBeanRegistration implementation ------------------------------
/**
* The default implementation of preRegister invokes the
* {@link #configureInterceptorStack} method which sets up the interceptors
* for this Model MBean instance. Subclasses may override the
* configureInterceptorStack() method to implement their own
* interceptor stack configurations. See the JavaDoc for
* configureInterceptorStack() for more information.
*
* After the interceptor configuration, this implementation invokes the
* {@link #load} method on this Model MBean instance. This will attempt
* to load a pre-existing management attribute state for this Model MBean
* instance. See the Javadoc for load() for more information.
*/
public ObjectName invokePreRegister(MBeanServer server, ObjectName name)
throws Exception
{
// Check for null metadata and prevent registration if metadata
// has not been set
if (info == null)
{
throw new RuntimeErrorException(
new Error("MBeanInfo has not been set."));
}
// Set the mbean descriptor on the info context for use by interceptor config
final ModelMBeanInfo minfo = (ModelMBeanInfo) info;
Descriptor mbeanDescriptor = minfo.getMBeanDescriptor();
getMBeanInfoCtx = new InvocationContext();
getMBeanInfoCtx.setInvoker(this);
getMBeanInfoCtx.setDescriptor(mbeanDescriptor);
getMBeanInfoCtx.setDispatcher(new AbstractInterceptor("MBeanInfo Dispatcher")
{
public Object invoke(Invocation invocation) throws Throwable
{
return minfo;
}
});
// JBAS-33 - No need to register the "getMBeanInfo" context to the operationsContextMap,
// this is only accessible through AbstractMBeanInvoker.getMBeanInfo().
// Registering it will result in duplicate interceptor construction.
// Need to install the setManagedResource op
// TODO, this is probably uneccessary now so revisit this
String[] signature = new String[]{"java.lang.Object", "java.lang.String"};
OperationKey opKey = new OperationKey("setManagedResource", signature);
InvocationContext ctx = new InvocationContext();
ctx.setInvoker(this);
ctx.setDispatcher(new AbstractInterceptor("SetMangedResource Dispatcher")
{
public Object invoke(Invocation invocation) throws Throwable
{
Object[] args = invocation.getArgs();
setManagedResource(args[0], (String) args[1]);
return null;
}
});
operationContextMap.put(opKey, ctx);
if (getResource() == null )
{
return name;
}
else
{
init(server, name);
}
return super.invokePreRegister(server, name);
}
// Protected ---------------------------------------------------
/**
*
* @param server
* @param name
* @throws Exception
*/
protected void init(MBeanServer server, ObjectName name)
throws Exception
{
ModelMBeanInfo minfo = (ModelMBeanInfo) info;
configureInterceptorStack(minfo, server, name);
initDispatchers();
// add the resource classname to the MBean info
Object resource = getResource();
if (resource != null)
{
Descriptor mbeanDescriptor = minfo.getMBeanDescriptor();
String resClassName = getResource().getClass().getName();
mbeanDescriptor.setField(ModelMBeanConstants.RESOURCE_CLASS, resClassName);
minfo.setMBeanDescriptor(mbeanDescriptor);
}
//Set initial values provided in descriptors
setValuesFromMBeanInfo();
initPersistence(server, name);
//Set (and override) values from mbean persistence store.
load();
}
/**
* initializes the persistence manager based on the info for this bean.
* If this is successful, loads the bean from the persistence store.
*/
protected void initPersistence(MBeanServer server, ObjectName name)
throws MBeanException, InstanceNotFoundException
{
Descriptor[] descriptors;
ModelMBeanInfo minfo = (ModelMBeanInfo) getMetaData();
try
{
descriptors = minfo.getDescriptors(MBEAN_DESCRIPTOR);
}
catch (MBeanException e)
{
log.error("Failed to obtain MBEAN_DESCRIPTORs", e);
return;
}
if (descriptors == null)
{
return;
}
String persistMgrName = null;
for (int i = 0; ((i < descriptors.length) && (persistMgrName == null)); i++)
{
persistMgrName = (String) descriptors[i].getFieldValue(PERSISTENCE_MANAGER);
}
if (persistMgrName == null)
{
log.trace("No " + PERSISTENCE_MANAGER
+ " descriptor found, null persistence will be used");
return;
}
try
{
persistence = (PersistenceManager) server.instantiate(persistMgrName);
log.debug("Loaded persistence mgr: " + persistMgrName);
// Add the ObjectName to the ModelMBean Descriptor
// so that it can be used by the PersistentManager (if needed)
Descriptor descriptor = minfo.getMBeanDescriptor();
descriptor.setField(ModelMBeanConstants.OBJECT_NAME, name);
minfo.setMBeanDescriptor(descriptor);
}
catch (Exception cause)
{
log.error("Unable to instantiate the persistence manager:"
+ persistMgrName, cause);
}
}
protected void initOperationContexts(MBeanOperationInfo[] operations)
{
// make sure we invoke the super class initialization sequence first
super.initOperationContexts(operations);
for (int i = 0; i < operations.length; ++i)
{
OperationKey key = new OperationKey(operations[i]);
InvocationContext ctx = (InvocationContext) operationContextMap.get(key);
ModelMBeanOperationInfo info = (ModelMBeanOperationInfo) operations[i];
ctx.setDescriptor(info.getDescriptor());
}
}
protected void initAttributeContexts(MBeanAttributeInfo[] attributes)
{
super.initAttributeContexts(attributes);
for (int i = 0; i < attributes.length; ++i)
{
ModelMBeanAttributeInfo info = (ModelMBeanAttributeInfo) attributes[i];
String name = info.getName();
InvocationContext ctx = (InvocationContext) attributeContextMap.get(name);
ctx.setDescriptor(info.getDescriptor());
ctx.setReadable(info.isReadable());
ctx.setWritable(info.isWritable());
}
}
/**
* Build the getMBeanInfo, operation, and attribute interceptor stacks
* and associated these with the corresponding InvocationContexts.
*
* @param info - the ModelMBean metadata
* @param server - the MBeanServer the ModelMBean is registering with
* @param name - the ModelMBean name
* @throws Exception
*/
protected void configureInterceptorStack(ModelMBeanInfo info, MBeanServer server, ObjectName name)
throws Exception
{
// Get the MBeanInfo accessor interceptor stack. This is the interceptor
// stack declared at the model mbean level. In 3.2.3 and earlier this was
// the interceptor stack for all operation and attribute access so we
// use this as the default interceptor stack, for all attributes/operations.
List defaultInterceptors = getInterceptors(getMBeanInfoCtx.getDescriptor());
List interceptors = null;
if (defaultInterceptors != null)
{
interceptors = new ArrayList(defaultInterceptors);
}
if (interceptors == null)
{
// Set the default interceptor stack
interceptors = getMBeanInfoCtx.getInterceptors();
}
// We always add the ModelMBeanInfoInterceptor as we expect that
// users are specifying additional interceptors, not overriding the
// source of the ModelMBeanInfo.
String mbeanName = name != null ? name.toString() : info.getClassName();
interceptors.add(new ModelMBeanInfoInterceptor(mbeanName));
getMBeanInfoCtx.setInterceptors(interceptors);
// Get any custom interceptors specified at the attribute level
for (Iterator it = attributeContextMap.entrySet().iterator(); it.hasNext();)
{
Map.Entry entry = (Map.Entry) it.next();
InvocationContext ctx = (InvocationContext) entry.getValue();
List list = getInterceptors(ctx.getDescriptor());
if (list == null)
{
// Use the mbean inteceptors if sepecified
if (defaultInterceptors != null)
{
list = new ArrayList(defaultInterceptors);
}
else
{
list = new ArrayList();
}
}
// Add the attribute accessor semantic interceptors
list.add(new PersistenceInterceptor());
list.add(new ModelMBeanAttributeInterceptor());
ctx.setInterceptors(list);
}
// Get any custom interceptors specified at the operation level
for (Iterator it = operationContextMap.entrySet().iterator(); it.hasNext();)
{
Map.Entry entry = (Map.Entry) it.next();
InvocationContext ctx = (InvocationContext) entry.getValue();
List list = getInterceptors(ctx.getDescriptor());
if (list == null && defaultInterceptors != null)
list = new ArrayList(defaultInterceptors);
// Add operation caching (not for standard mbeans)
if (dynamicResource)
{
if (list == null)
{
list = new ArrayList();
}
list.add(new ModelMBeanOperationInterceptor());
}
if (list != null)
{
// Add a noop interceptor since the 3.2.3- interceptors always had
// to delegate to the next in order to dispatch the operation. Now
// there is no interceptor for this so this prevents NPEs.
list.add(new NullInterceptor());
ctx.setInterceptors(list);
}
}
}
/**
*
* @param d
* @return
* @throws Exception
*/
protected List getInterceptors(Descriptor d) throws Exception
{
if (d == null)
return null;
Descriptor[] interceptorDescriptors = (Descriptor[]) d.getFieldValue(INTERCEPTORS);
if (interceptorDescriptors == null)
return null;
ArrayList interceptors = new ArrayList();
ClassLoader loader = Thread.currentThread().getContextClassLoader();
for (int i = 0; i < interceptorDescriptors.length; i++)
{
Descriptor desc = interceptorDescriptors[i];
String code = (String) desc.getFieldValue("code");
// Ignore the legacy required interceptors
if (code.equals(ModelMBeanInterceptor.class.getName()) ||
code.equals(ObjectReferenceInterceptor.class.getName()) ||
code.equals(PersistenceInterceptor2.class.getName()))
{
log.debug("Ignoring obsolete legacy interceptor: " + code);
continue;
}
Class interceptorClass = loader.loadClass(code);
Interceptor interceptor = null;
// Check for a ctor(MBeanInvoker)
Class[] ctorSig = {MBeanInvoker.class};
try
{
Constructor ctor = interceptorClass.getConstructor(ctorSig);
Object[] ctorArgs = {this};
interceptor = (Interceptor) ctor.newInstance(ctorArgs);
}
catch (Throwable t)
{
log.debug("Could not invoke CTOR(MBeanInvoker) for '"
+ interceptorClass + "', trying default CTOR: " + t.getMessage());
// Try the default ctor
interceptor = (Interceptor) interceptorClass.newInstance();
}
interceptors.add(interceptor);
// Apply any interceptor attributes
String[] names = desc.getFieldNames();
HashMap propertyMap = new HashMap();
if (names.length > 1)
{
BeanInfo beanInfo = Introspector.getBeanInfo(interceptorClass);
PropertyDescriptor[] props = beanInfo.getPropertyDescriptors();
for (int p = 0; p < props.length; p++)
{
String fieldName = props[p].getName();
propertyMap.put(fieldName, props[p]);
}
// Map each attribute to the corresponding interceptor property
for (int n = 0; n < names.length; n++)
{
String name = names[n];
if (name.equals("code"))
continue;
String text = (String) desc.getFieldValue(name);
PropertyDescriptor pd = (PropertyDescriptor) propertyMap.get(name);
if (pd == null)
throw new IntrospectionException("No PropertyDescriptor for attribute:" + name);
Method setter = pd.getWriteMethod();
if (setter != null)
{
Class ptype = pd.getPropertyType();
PropertyEditor editor = PropertyEditorManager.findEditor(ptype);
if (editor == null)
throw new IntrospectionException("Cannot convert string to interceptor attribute:" + name);
editor.setAsText(text);
Object args[] = {editor.getValue()};
setter.invoke(interceptor, args);
}
}
}
}
if (interceptors.size() == 0)
interceptors = null;
return interceptors;
}
protected void setValuesFromMBeanInfo() throws JMException
{
for (Iterator it = attributeContextMap.entrySet().iterator(); it.hasNext();)
{
Map.Entry entry = (Map.Entry) it.next();
String key = (String) entry.getKey();
InvocationContext ctx = (InvocationContext) entry.getValue();
//Initialize value from descriptor.
Object value = ctx.getDescriptor().getFieldValue(XMBeanConstants.CACHED_VALUE);
if (value != null)
{
setAttribute(new Attribute(key, value));
} // end of if ()
}
}
protected boolean isSupportedResourceType(Object resource, String resourceType)
{
if (resourceType.equalsIgnoreCase(OBJECT_REF))
return true;
return false;
}
protected void override(Invocation invocation) throws MBeanException
{
// Do we allow for dynamic descriptor changes
if (dynamicResource && info != null)
{
Descriptor current = invocation.getDescriptor();
if (current != null)
{
ModelMBeanInfo mminfo = (ModelMBeanInfo) info;
Descriptor descriptor = mminfo.getDescriptor((String) current.getFieldValue(NAME), (String) current.getFieldValue(DESCRIPTOR_TYPE));
if (descriptor != null)
invocation.setDescriptor(descriptor);
}
}
}
}