View Javadoc

1   //
2   //  ========================================================================
3   //  Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
4   //  ------------------------------------------------------------------------
5   //  All rights reserved. This program and the accompanying materials
6   //  are made available under the terms of the Eclipse Public License v1.0
7   //  and Apache License v2.0 which accompanies this distribution.
8   //
9   //      The Eclipse Public License is available at
10  //      http://www.eclipse.org/legal/epl-v10.html
11  //
12  //      The Apache License v2.0 is available at
13  //      http://www.opensource.org/licenses/apache2.0.php
14  //
15  //  You may elect to redistribute this code under either of these licenses.
16  //  ========================================================================
17  //
18  
19  package org.eclipse.jetty.jmx;
20  
21  import java.lang.reflect.Array;
22  import java.lang.reflect.Constructor;
23  import java.lang.reflect.InvocationTargetException;
24  import java.lang.reflect.Method;
25  import java.lang.reflect.Modifier;
26  import java.util.Collection;
27  import java.util.Enumeration;
28  import java.util.HashMap;
29  import java.util.HashSet;
30  import java.util.Iterator;
31  import java.util.Locale;
32  import java.util.Map;
33  import java.util.MissingResourceException;
34  import java.util.ResourceBundle;
35  import java.util.Set;
36  
37  import javax.management.Attribute;
38  import javax.management.AttributeList;
39  import javax.management.AttributeNotFoundException;
40  import javax.management.DynamicMBean;
41  import javax.management.InvalidAttributeValueException;
42  import javax.management.MBeanAttributeInfo;
43  import javax.management.MBeanConstructorInfo;
44  import javax.management.MBeanException;
45  import javax.management.MBeanInfo;
46  import javax.management.MBeanNotificationInfo;
47  import javax.management.MBeanOperationInfo;
48  import javax.management.MBeanParameterInfo;
49  import javax.management.ObjectName;
50  import javax.management.ReflectionException;
51  import javax.management.modelmbean.ModelMBean;
52  
53  import org.eclipse.jetty.util.LazyList;
54  import org.eclipse.jetty.util.Loader;
55  import org.eclipse.jetty.util.TypeUtil;
56  import org.eclipse.jetty.util.log.Log;
57  import org.eclipse.jetty.util.log.Logger;
58  
59  /* ------------------------------------------------------------ */
60  /** ObjectMBean.
61   * A dynamic MBean that can wrap an arbitary Object instance.
62   * the attributes and methods exposed by this bean are controlled by
63   * the merge of property bundles discovered by names related to all
64   * superclasses and all superinterfaces.
65   *
66   * Attributes and methods exported may be "Object" and must exist on the
67   * wrapped object, or "MBean" and must exist on a subclass of OBjectMBean
68   * or "MObject" which exists on the wrapped object, but whose values are
69   * converted to MBean object names.
70   *
71   */
72  public class ObjectMBean implements DynamicMBean
73  {
74      private static final Logger LOG = Log.getLogger(ObjectMBean.class);
75  
76      private static Class[] OBJ_ARG = new Class[]{Object.class};
77  
78      protected Object _managed;
79      private MBeanInfo _info;
80      private Map _getters=new HashMap();
81      private Map _setters=new HashMap();
82      private Map _methods=new HashMap();
83      private Set _convert=new HashSet();
84      private ClassLoader _loader;
85      private MBeanContainer _mbeanContainer;
86  
87      private static String OBJECT_NAME_CLASS = ObjectName.class.getName();
88      private static String OBJECT_NAME_ARRAY_CLASS = ObjectName[].class.getName();
89  
90      /* ------------------------------------------------------------ */
91      /**
92       * Create MBean for Object. Attempts to create an MBean for the object by searching the package
93       * and class name space. For example an object of the type
94       *
95       * <PRE>
96       * class com.acme.MyClass extends com.acme.util.BaseClass implements com.acme.Iface
97       * </PRE>
98       *
99       * Then this method would look for the following classes:
100      * <UL>
101      * <LI>com.acme.jmx.MyClassMBean
102      * <LI>com.acme.util.jmx.BaseClassMBean
103      * <LI>org.eclipse.jetty.jmx.ObjectMBean
104      * </UL>
105      *
106      * @param o The object
107      * @return A new instance of an MBean for the object or null.
108      */
109     public static Object mbeanFor(Object o)
110     {
111         try
112         {
113             Class oClass = o.getClass();
114             Object mbean = null;
115 
116             while (mbean == null && oClass != null)
117             {
118                 String pName = oClass.getPackage().getName();
119                 String cName = oClass.getName().substring(pName.length() + 1);
120                 String mName = pName + ".jmx." + cName + "MBean";
121                 
122 
123                 try
124                 {
125                     Class mClass = (Object.class.equals(oClass))?oClass=ObjectMBean.class:Loader.loadClass(oClass,mName,true);
126                     if (LOG.isDebugEnabled())
127                         LOG.debug("mbeanFor " + o + " mClass=" + mClass);
128 
129                     try
130                     {
131                         Constructor constructor = mClass.getConstructor(OBJ_ARG);
132                         mbean=constructor.newInstance(new Object[]{o});
133                     }
134                     catch(Exception e)
135                     {
136                         LOG.ignore(e);
137                         if (ModelMBean.class.isAssignableFrom(mClass))
138                         {
139                             mbean=mClass.newInstance();
140                             ((ModelMBean)mbean).setManagedResource(o, "objectReference");
141                         }
142                     }
143 
144                     if (LOG.isDebugEnabled())
145                         LOG.debug("mbeanFor " + o + " is " + mbean);
146                     return mbean;
147                 }
148                 catch (ClassNotFoundException e)
149                 {
150                     // The code below was modified to fix bugs 332200 and JETTY-1416 
151                     // The issue was caused by additional information added to the 
152                     // message after the class name when running in Apache Felix,
153                     // as well as before the class name when running in JBoss.
154                     if (e.getMessage().contains(mName))
155                         LOG.ignore(e);
156                     else
157                         LOG.warn(e);
158                 }
159                 catch (Error e)
160                 {
161                     LOG.warn(e);
162                     mbean = null;
163                 }
164                 catch (Exception e)
165                 {
166                     LOG.warn(e);
167                     mbean = null;
168                 }
169 
170                 oClass = oClass.getSuperclass();
171             }
172         }
173         catch (Exception e)
174         {
175             LOG.ignore(e);
176         }
177         return null;
178     }
179 
180 
181     public ObjectMBean(Object managedObject)
182     {
183         _managed = managedObject;
184         _loader = Thread.currentThread().getContextClassLoader();
185     }
186     
187     public Object getManagedObject()
188     {
189         return _managed;
190     }
191     
192     public ObjectName getObjectName()
193     {
194         return null;
195     }
196     
197     public String getObjectContextBasis()
198     {
199         return null;
200     }
201     
202     public String getObjectNameBasis()
203     {
204         return null;
205     }
206 
207     protected void setMBeanContainer(MBeanContainer container)
208     {
209        this._mbeanContainer = container;
210     }
211 
212     public MBeanContainer getMBeanContainer ()
213     {
214         return this._mbeanContainer;
215     }
216     
217     
218     public MBeanInfo getMBeanInfo()
219     {
220         try
221         {
222             if (_info==null)
223             {
224                 // Start with blank lazy lists attributes etc.
225                 String desc=null;
226                 Object attributes=null;
227                 Object constructors=null;
228                 Object operations=null;
229                 Object notifications=null;
230 
231                 // Find list of classes that can influence the mbean
232                 Class o_class=_managed.getClass();
233                 Object influences = findInfluences(null, _managed.getClass());
234 
235                 // Set to record defined items
236                 Set defined=new HashSet();
237 
238                 // For each influence
239                 for (int i=0;i<LazyList.size(influences);i++)
240                 {
241                     Class oClass = (Class)LazyList.get(influences, i);
242 
243                     // look for a bundle defining methods
244                     if (Object.class.equals(oClass))
245                         oClass=ObjectMBean.class;
246                     String pName = oClass.getPackage().getName();
247                     String cName = oClass.getName().substring(pName.length() + 1);
248                     String rName = pName.replace('.', '/') + "/jmx/" + cName+"-mbean";
249 
250                     try
251                     {
252                         LOG.debug(rName);
253                         ResourceBundle bundle = Loader.getResourceBundle(o_class, rName,true,Locale.getDefault());
254 
255                         
256                         // Extract meta data from bundle
257                         Enumeration e = bundle.getKeys();
258                         while (e.hasMoreElements())
259                         {
260                             String key = (String)e.nextElement();
261                             String value = bundle.getString(key);
262 
263                             // Determin if key is for mbean , attribute or for operation
264                             if (key.equals(cName))
265                             {
266                                 // set the mbean description
267                                 if (desc==null)
268                                     desc=value;
269                             }
270                             else if (key.indexOf('(')>0)
271                             {
272                                 // define an operation
273                                 if (!defined.contains(key) && key.indexOf('[')<0)
274                                 {
275                                     defined.add(key);
276                                     operations=LazyList.add(operations,defineOperation(key, value, bundle));
277                                 }
278                             }
279                             else
280                             {
281                                 // define an attribute
282                                 if (!defined.contains(key))
283                                 {
284                                     defined.add(key);
285                                     MBeanAttributeInfo info=defineAttribute(key, value);
286                                     if (info!=null)
287                                         attributes=LazyList.add(attributes,info);
288                                 }
289                             }
290                         }
291                     }
292                     catch(MissingResourceException e)
293                     {
294                         LOG.ignore(e);
295                     }
296                 }
297 
298                 _info = new MBeanInfo(o_class.getName(),
299                                 desc,
300                                 (MBeanAttributeInfo[])LazyList.toArray(attributes, MBeanAttributeInfo.class),
301                                 (MBeanConstructorInfo[])LazyList.toArray(constructors, MBeanConstructorInfo.class),
302                                 (MBeanOperationInfo[])LazyList.toArray(operations, MBeanOperationInfo.class),
303                                 (MBeanNotificationInfo[])LazyList.toArray(notifications, MBeanNotificationInfo.class));
304             }
305         }
306         catch(RuntimeException e)
307         {
308             LOG.warn(e);
309             throw e;
310         }
311         return _info;
312     }
313 
314 
315     /* ------------------------------------------------------------ */
316     public Object getAttribute(String name) throws AttributeNotFoundException, MBeanException, ReflectionException
317     {
318         Method getter = (Method) _getters.get(name);
319         if (getter == null)
320             throw new AttributeNotFoundException(name);
321         try
322         {
323             Object o = _managed;
324             if (getter.getDeclaringClass().isInstance(this))
325                 o = this; // mbean method
326 
327             // get the attribute
328             Object r=getter.invoke(o, (java.lang.Object[]) null);
329 
330             // convert to ObjectName if need be.
331             if (r!=null && _convert.contains(name))
332             {
333                 if (r.getClass().isArray())
334                 {
335                     ObjectName[] on = new ObjectName[Array.getLength(r)];
336                     for (int i=0;i<on.length;i++)
337                         on[i]=_mbeanContainer.findMBean(Array.get(r, i));
338                     r=on;
339                 }
340                 else if (r instanceof Collection<?>)
341                 {
342                     Collection<Object> c = (Collection<Object>)r;
343                     ObjectName[] on = new ObjectName[c.size()];
344                     int i=0;
345                     for (Object obj :c)
346                         on[i++]=_mbeanContainer.findMBean(obj);
347                     r=on;
348                 }
349                 else
350                 {
351                     ObjectName mbean = _mbeanContainer.findMBean(r);
352                     if (mbean==null)
353                         return null;
354                     r=mbean;
355                 }
356             }
357             return r;
358         }
359         catch (IllegalAccessException e)
360         {
361             LOG.warn(Log.EXCEPTION, e);
362             throw new AttributeNotFoundException(e.toString());
363         }
364         catch (InvocationTargetException e)
365         {
366             LOG.warn(Log.EXCEPTION, e);
367             throw new ReflectionException(new Exception(e.getCause()));
368         }
369     }
370 
371     /* ------------------------------------------------------------ */
372     public AttributeList getAttributes(String[] names)
373     {
374         AttributeList results = new AttributeList(names.length);
375         for (int i = 0; i < names.length; i++)
376         {
377             try
378             {
379                 results.add(new Attribute(names[i], getAttribute(names[i])));
380             }
381             catch (Exception e)
382             {
383                 LOG.warn(Log.EXCEPTION, e);
384             }
385         }
386         return results;
387     }
388 
389     /* ------------------------------------------------------------ */
390     public void setAttribute(Attribute attr) throws AttributeNotFoundException, InvalidAttributeValueException, MBeanException, ReflectionException
391     {
392         if (attr == null)
393             return;
394 
395         if (LOG.isDebugEnabled())
396             LOG.debug("setAttribute " + _managed + ":" +attr.getName() + "=" + attr.getValue());
397         Method setter = (Method) _setters.get(attr.getName());
398         if (setter == null)
399             throw new AttributeNotFoundException(attr.getName());
400         try
401         {
402             Object o = _managed;
403             if (setter.getDeclaringClass().isInstance(this))
404                 o = this;
405 
406             // get the value
407             Object value = attr.getValue();
408 
409             // convert from ObjectName if need be
410             if (value!=null && _convert.contains(attr.getName()))
411             {
412                 if (value.getClass().isArray())
413                 {
414                     Class t=setter.getParameterTypes()[0].getComponentType();
415                     Object na = Array.newInstance(t,Array.getLength(value));
416                     for (int i=Array.getLength(value);i-->0;)
417                         Array.set(na, i, _mbeanContainer.findBean((ObjectName)Array.get(value, i)));
418                     value=na;
419                 }
420                 else
421                     value=_mbeanContainer.findBean((ObjectName)value);
422             }
423 
424             // do the setting
425             setter.invoke(o, new Object[]{ value });
426         }
427         catch (IllegalAccessException e)
428         {
429             LOG.warn(Log.EXCEPTION, e);
430             throw new AttributeNotFoundException(e.toString());
431         }
432         catch (InvocationTargetException e)
433         {
434             LOG.warn(Log.EXCEPTION, e);
435             throw new ReflectionException(new Exception(e.getCause()));
436         }
437     }
438 
439     /* ------------------------------------------------------------ */
440     public AttributeList setAttributes(AttributeList attrs)
441     {
442         LOG.debug("setAttributes");
443 
444         AttributeList results = new AttributeList(attrs.size());
445         Iterator iter = attrs.iterator();
446         while (iter.hasNext())
447         {
448             try
449             {
450                 Attribute attr = (Attribute) iter.next();
451                 setAttribute(attr);
452                 results.add(new Attribute(attr.getName(), getAttribute(attr.getName())));
453             }
454             catch (Exception e)
455             {
456                 LOG.warn(Log.EXCEPTION, e);
457             }
458         }
459         return results;
460     }
461 
462     /* ------------------------------------------------------------ */
463     public Object invoke(String name, Object[] params, String[] signature) throws MBeanException, ReflectionException
464     {
465         if (LOG.isDebugEnabled())
466             LOG.debug("invoke " + name);
467 
468         String methodKey = name + "(";
469         if (signature != null)
470             for (int i = 0; i < signature.length; i++)
471                 methodKey += (i > 0 ? "," : "") + signature[i];
472         methodKey += ")";
473 
474         ClassLoader old_loader=Thread.currentThread().getContextClassLoader();
475         try
476         {
477             Thread.currentThread().setContextClassLoader(_loader);
478             Method method = (Method) _methods.get(methodKey);
479             if (method == null)
480                 throw new NoSuchMethodException(methodKey);
481 
482             Object o = _managed;
483             if (method.getDeclaringClass().isInstance(this))
484                 o = this;
485             return method.invoke(o, params);
486         }
487         catch (NoSuchMethodException e)
488         {
489             LOG.warn(Log.EXCEPTION, e);
490             throw new ReflectionException(e);
491         }
492         catch (IllegalAccessException e)
493         {
494             LOG.warn(Log.EXCEPTION, e);
495             throw new MBeanException(e);
496         }
497         catch (InvocationTargetException e)
498         {
499             LOG.warn(Log.EXCEPTION, e);
500             throw new ReflectionException(new Exception(e.getCause()));
501         }
502         finally
503         {
504             Thread.currentThread().setContextClassLoader(old_loader);
505         }
506     }
507 
508     private static Object findInfluences(Object influences, Class aClass)
509     {
510         if (aClass!=null)
511         {
512             // This class is an influence
513             influences=LazyList.add(influences,aClass);
514 
515             // So are the super classes
516             influences=findInfluences(influences,aClass.getSuperclass());
517 
518             // So are the interfaces
519             Class[] ifs = aClass.getInterfaces();
520             for (int i=0;ifs!=null && i<ifs.length;i++)
521                 influences=findInfluences(influences,ifs[i]);
522         }
523         return influences;
524     }
525 
526     /* ------------------------------------------------------------ */
527     /**
528      * Define an attribute on the managed object. The meta data is defined by looking for standard
529      * getter and setter methods. Descriptions are obtained with a call to findDescription with the
530      * attribute name.
531      *
532      * @param name
533      * @param metaData "description" or "access:description" or "type:access:description"  where type is
534      * one of: <ul>
535      * <li>"Object" The field/method is on the managed object.
536      * <li>"MBean" The field/method is on the mbean proxy object
537      * <li>"MObject" The field/method is on the managed object and value should be converted to MBean reference
538      * <li>"MMBean" The field/method is on the mbean proxy object and value should be converted to MBean reference
539      * </ul>
540      * the access is either "RW" or "RO".
541      */
542     public MBeanAttributeInfo defineAttribute(String name, String metaData)
543     {
544         String description = "";
545         boolean writable = true;
546         boolean onMBean = false;
547         boolean convert = false;
548 
549         if (metaData!= null)
550         {
551             String[] tokens = metaData.split(":", 3);
552             for (int t=0;t<tokens.length-1;t++)
553             {
554                 tokens[t]=tokens[t].trim();
555                 if ("RO".equals(tokens[t]))
556                     writable=false;
557                 else 
558                 {
559                     onMBean=("MMBean".equalsIgnoreCase(tokens[t]) || "MBean".equalsIgnoreCase(tokens[t]));
560                     convert=("MMBean".equalsIgnoreCase(tokens[t]) || "MObject".equalsIgnoreCase(tokens[t]));
561                 }
562             }
563             description=tokens[tokens.length-1];
564         }
565         
566 
567         String uName = name.substring(0, 1).toUpperCase(Locale.ENGLISH) + name.substring(1);
568         Class oClass = onMBean ? this.getClass() : _managed.getClass();
569 
570         if (LOG.isDebugEnabled())
571             LOG.debug("defineAttribute "+name+" "+onMBean+":"+writable+":"+oClass+":"+description);
572 
573         Class type = null;
574         Method getter = null;
575         Method setter = null;
576         Method[] methods = oClass.getMethods();
577         for (int m = 0; m < methods.length; m++)
578         {
579             if ((methods[m].getModifiers() & Modifier.PUBLIC) == 0)
580                 continue;
581 
582             // Look for a getter
583             if (methods[m].getName().equals("get" + uName) && methods[m].getParameterTypes().length == 0)
584             {
585                 if (getter != null)
586                 {
587 		    LOG.warn("Multiple mbean getters for attr " + name+ " in "+oClass);
588 		    continue;
589 		}
590                 getter = methods[m];
591                 if (type != null && !type.equals(methods[m].getReturnType()))
592                 {
593 		    LOG.warn("Type conflict for mbean attr " + name+ " in "+oClass);
594 		    continue;
595 		}
596                 type = methods[m].getReturnType();
597             }
598 
599             // Look for an is getter
600             if (methods[m].getName().equals("is" + uName) && methods[m].getParameterTypes().length == 0)
601             {
602                 if (getter != null)
603                 {
604 		    LOG.warn("Multiple mbean getters for attr " + name+ " in "+oClass);
605 		    continue;
606 		}
607                 getter = methods[m];
608                 if (type != null && !type.equals(methods[m].getReturnType()))
609                 {
610 		    LOG.warn("Type conflict for mbean attr " + name+ " in "+oClass);
611 		    continue;
612 		}
613                 type = methods[m].getReturnType();
614             }
615 
616             // look for a setter
617             if (writable && methods[m].getName().equals("set" + uName) && methods[m].getParameterTypes().length == 1)
618             {
619                 if (setter != null)
620                 {
621 		    LOG.warn("Multiple setters for mbean attr " + name+ " in "+oClass);
622 		    continue;
623 		}
624                 setter = methods[m];
625                 if (type != null && !type.equals(methods[m].getParameterTypes()[0]))
626                 {
627 		    LOG.warn("Type conflict for mbean attr " + name+ " in "+oClass);
628 		    continue;
629 		}
630                 type = methods[m].getParameterTypes()[0];
631             }
632         }
633         
634         if (convert)
635         {
636             if (type==null)
637             {
638 	        LOG.warn("No mbean type for " + name+" on "+_managed.getClass());
639 		return null;
640 	    }
641                 
642             if (type.isPrimitive() && !type.isArray())
643             {
644 	        LOG.warn("Cannot convert mbean primative " + name);
645 		return null;
646 	    }
647         }
648 
649         if (getter == null && setter == null)
650         {
651 	    LOG.warn("No mbean getter or setters found for " + name+ " in "+oClass);
652 	    return null;
653 	}
654 
655         try
656         {
657             // Remember the methods
658             _getters.put(name, getter);
659             _setters.put(name, setter);
660 
661             MBeanAttributeInfo info=null;
662             if (convert)
663             {
664                 _convert.add(name);
665                 if (type.isArray())
666                     info= new MBeanAttributeInfo(name,OBJECT_NAME_ARRAY_CLASS,description,getter!=null,setter!=null,getter!=null&&getter.getName().startsWith("is"));
667 
668                 else
669                     info= new MBeanAttributeInfo(name,OBJECT_NAME_CLASS,description,getter!=null,setter!=null,getter!=null&&getter.getName().startsWith("is"));
670             }
671             else
672                 info= new MBeanAttributeInfo(name,description,getter,setter);
673 
674             return info;
675         }
676         catch (Exception e)
677         {
678             LOG.warn(name+": "+metaData, e);
679             throw new IllegalArgumentException(e.toString());
680         }
681     }
682 
683 
684     /* ------------------------------------------------------------ */
685     /**
686      * Define an operation on the managed object. Defines an operation with parameters. Refection is
687      * used to determine find the method and it's return type. The description of the method is
688      * found with a call to findDescription on "name(signature)". The name and description of each
689      * parameter is found with a call to findDescription with "name(signature)[n]", the returned
690      * description is for the last parameter of the partial signature and is assumed to start with
691      * the parameter name, followed by a colon.
692      *
693      * @param metaData "description" or "impact:description" or "type:impact:description", type is
694      * the "Object","MBean", "MMBean" or "MObject" to indicate the method is on the object, the MBean or on the
695      * object but converted to an MBean reference, and impact is either "ACTION","INFO","ACTION_INFO" or "UNKNOWN".
696      */
697     private MBeanOperationInfo defineOperation(String signature, String metaData, ResourceBundle bundle)
698     {
699         String[] tokens=metaData.split(":",3);
700         int i=tokens.length-1;
701         String description=tokens[i--];
702         String impact_name = i<0?"UNKNOWN":tokens[i--].trim();
703         if (i==0)
704             tokens[0]=tokens[0].trim();
705         boolean onMBean= i==0 && ("MBean".equalsIgnoreCase(tokens[0])||"MMBean".equalsIgnoreCase(tokens[0]));
706         boolean convert= i==0 && ("MObject".equalsIgnoreCase(tokens[0])||"MMBean".equalsIgnoreCase(tokens[0]));
707 
708         if (LOG.isDebugEnabled())
709             LOG.debug("defineOperation "+signature+" "+onMBean+":"+impact_name+":"+description);
710 
711         Class oClass = onMBean ? this.getClass() : _managed.getClass();
712 
713         try
714         {
715             // Resolve the impact
716             int impact=MBeanOperationInfo.UNKNOWN;
717             if (impact_name==null || impact_name.equals("UNKNOWN"))
718                 impact=MBeanOperationInfo.UNKNOWN;
719             else if (impact_name.equals("ACTION"))
720                 impact=MBeanOperationInfo.ACTION;
721             else if (impact_name.equals("INFO"))
722                 impact=MBeanOperationInfo.INFO;
723             else if (impact_name.equals("ACTION_INFO"))
724                 impact=MBeanOperationInfo.ACTION_INFO;
725             else
726                 LOG.warn("Unknown impact '"+impact_name+"' for "+signature);
727 
728 
729             // split the signature
730             String[] parts=signature.split("[\\(\\)]");
731             String method_name=parts[0];
732             String arguments=parts.length==2?parts[1]:null;
733             String[] args=arguments==null?new String[0]:arguments.split(" *, *");
734 
735             // Check types and normalize signature.
736             Class[] types = new Class[args.length];
737             MBeanParameterInfo[] pInfo = new MBeanParameterInfo[args.length];
738             signature=method_name;
739             for (i = 0; i < args.length; i++)
740             {
741                 Class type = TypeUtil.fromName(args[i]);
742                 if (type == null)
743                     type = Thread.currentThread().getContextClassLoader().loadClass(args[i]);
744                 types[i] = type;
745                 args[i] = type.isPrimitive() ? TypeUtil.toName(type) : args[i];
746                 signature+=(i>0?",":"(")+args[i];
747             }
748             signature+=(i>0?")":"()");
749 
750             // Build param infos
751             for (i = 0; i < args.length; i++)
752             {
753                 String param_desc = bundle.getString(signature + "[" + i + "]");
754                 parts=param_desc.split(" *: *",2);
755                 if (LOG.isDebugEnabled())
756                     LOG.debug(parts[0]+": "+parts[1]);
757                 pInfo[i] = new MBeanParameterInfo(parts[0].trim(), args[i], parts[1].trim());
758             }
759 
760             // build the operation info
761             Method method = oClass.getMethod(method_name, types);
762             Class returnClass = method.getReturnType();
763             _methods.put(signature, method);
764             if (convert)
765                 _convert.add(signature);
766 
767             return new MBeanOperationInfo(method_name, description, pInfo, returnClass.isPrimitive() ? TypeUtil.toName(returnClass) : (returnClass.getName()), impact);
768         }
769         catch (Exception e)
770         {
771             LOG.warn("Operation '"+signature+"'", e);
772             throw new IllegalArgumentException(e.toString());
773         }
774 
775     }
776 
777 }