View Javadoc

1   //
2   //  ========================================================================
3   //  Copyright (c) 1995-2016 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.annotation.Annotation;
22  import java.lang.reflect.Array;
23  import java.lang.reflect.Constructor;
24  import java.lang.reflect.InvocationTargetException;
25  import java.lang.reflect.Method;
26  import java.lang.reflect.Modifier;
27  import java.util.ArrayList;
28  import java.util.Collection;
29  import java.util.HashMap;
30  import java.util.HashSet;
31  import java.util.Iterator;
32  import java.util.List;
33  import java.util.Locale;
34  import java.util.Map;
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.Loader;
54  import org.eclipse.jetty.util.TypeUtil;
55  import org.eclipse.jetty.util.annotation.ManagedAttribute;
56  import org.eclipse.jetty.util.annotation.ManagedObject;
57  import org.eclipse.jetty.util.annotation.ManagedOperation;
58  import org.eclipse.jetty.util.annotation.Name;
59  import org.eclipse.jetty.util.log.Log;
60  import org.eclipse.jetty.util.log.Logger;
61  
62  /** 
63   * ObjectMBean.
64   * <p>
65   * A dynamic MBean that can wrap an arbitary Object instance.
66   * the attributes and methods exposed by this bean are controlled by
67   * the merge of property bundles discovered by names related to all
68   * superclasses and all superinterfaces.
69   * <p>
70   * Attributes and methods exported may be "Object" and must exist on the
71   * wrapped object, or "MBean" and must exist on a subclass of OBjectMBean
72   * or "MObject" which exists on the wrapped object, but whose values are
73   * converted to MBean object names.
74   */
75  public class ObjectMBean implements DynamicMBean
76  {
77      private static final Logger LOG = Log.getLogger(ObjectMBean.class);
78  
79      private static Class<?>[] OBJ_ARG = new Class[]{Object.class};
80  
81      protected Object _managed;
82      private MBeanInfo _info;
83      private Map<String, Method> _getters=new HashMap<String, Method>();
84      private Map<String, Method> _setters=new HashMap<String, Method>();
85      private Map<String, Method> _methods=new HashMap<String, Method>();
86  
87      // set of attributes mined from influence hierarchy
88      private Set<String> _attributes = new HashSet<String>();
89  
90      // set of attributes that are automatically converted to ObjectName
91      // as they represent other managed beans which can be linked to
92      private Set<String> _convert=new HashSet<String>();
93      private ClassLoader _loader;
94      private MBeanContainer _mbeanContainer;
95  
96      private static String OBJECT_NAME_CLASS = ObjectName.class.getName();
97      private static String OBJECT_NAME_ARRAY_CLASS = ObjectName[].class.getName();
98  
99      /* ------------------------------------------------------------ */
100     /**
101      * Create MBean for Object. Attempts to create an MBean for the object by searching the package
102      * and class name space. For example an object of the type
103      *
104      * <PRE>
105      * class com.acme.MyClass extends com.acme.util.BaseClass implements com.acme.Iface
106      * </PRE>
107      *
108      * Then this method would look for the following classes:
109      * <UL>
110      * <LI>com.acme.jmx.MyClassMBean
111      * <LI>com.acme.util.jmx.BaseClassMBean
112      * <LI>org.eclipse.jetty.jmx.ObjectMBean
113      * </UL>
114      *
115      * @param o The object
116      * @return A new instance of an MBean for the object or null.
117      */
118     public static Object mbeanFor(Object o)
119     {
120         try
121         {
122             Class<?> oClass = o.getClass();
123             Object mbean = null;
124 
125             while ( mbean == null && oClass != null )
126             {
127                 String pName = oClass.getPackage().getName();
128                 String cName = oClass.getName().substring(pName.length() + 1);
129                 String mName = pName + ".jmx." + cName + "MBean";
130 
131                 try
132                 {
133                     Class<?> mClass = (Object.class.equals(oClass))?oClass=ObjectMBean.class:Loader.loadClass(oClass,mName);
134 
135                     if (LOG.isDebugEnabled())
136                         LOG.debug("ObjectMbean: mbeanFor {} mClass={}", o, mClass);
137 
138                     try
139                     {
140                         Constructor<?> constructor = mClass.getConstructor(OBJ_ARG);
141                         mbean=constructor.newInstance(new Object[]{o});
142                     }
143                     catch(Exception e)
144                     {
145                         LOG.ignore(e);
146                         if (ModelMBean.class.isAssignableFrom(mClass))
147                         {
148                             mbean=mClass.newInstance();
149                             ((ModelMBean)mbean).setManagedResource(o, "objectReference");
150                         }
151                     }
152 
153                     if (LOG.isDebugEnabled())
154                         LOG.debug("mbeanFor {} is {}", o, mbean);
155 
156                     return mbean;
157                 }
158                 catch (ClassNotFoundException e)
159                 {
160                     // The code below was modified to fix bugs 332200 and JETTY-1416
161                     // The issue was caused by additional information added to the
162                     // message after the class name when running in Apache Felix,
163                     // as well as before the class name when running in JBoss.
164                     if (e.getMessage().contains(mName))
165                         LOG.ignore(e);
166                     else
167                         LOG.warn(e);
168                 }
169                 catch (Error e)
170                 {
171                     LOG.warn(e);
172                     mbean = null;
173                 }
174                 catch (Exception e)
175                 {
176                     LOG.warn(e);
177                     mbean = null;
178                 }
179 
180                 oClass = oClass.getSuperclass();
181             }
182         }
183         catch (Exception e)
184         {
185             LOG.ignore(e);
186         }
187 
188         return null;
189     }
190 
191 
192     public ObjectMBean(Object managedObject)
193     {
194         _managed = managedObject;
195         _loader = Thread.currentThread().getContextClassLoader();
196     }
197 
198     public Object getManagedObject()
199     {
200         return _managed;
201     }
202 
203     public ObjectName getObjectName()
204     {
205         return null;
206     }
207 
208     public String getObjectContextBasis()
209     {
210         return null;
211     }
212 
213     public String getObjectNameBasis()
214     {
215         return null;
216     }
217 
218     protected void setMBeanContainer(MBeanContainer container)
219     {
220        this._mbeanContainer = container;
221     }
222 
223     public MBeanContainer getMBeanContainer ()
224     {
225         return this._mbeanContainer;
226     }
227 
228 
229     public MBeanInfo getMBeanInfo()
230     {
231         try
232         {
233             if (_info==null)
234             {
235                 // Start with blank lazy lists attributes etc.
236                 String desc=null;
237                 List<MBeanAttributeInfo> attributes = new ArrayList<MBeanAttributeInfo>();
238                 List<MBeanConstructorInfo> constructors = new ArrayList<MBeanConstructorInfo>();
239                 List<MBeanOperationInfo> operations = new ArrayList<MBeanOperationInfo>();
240                 List<MBeanNotificationInfo> notifications = new ArrayList<MBeanNotificationInfo>();
241 
242                 // Find list of classes that can influence the mbean
243                 Class<?> o_class=_managed.getClass();
244                 List<Class<?>> influences = new ArrayList<Class<?>>();
245                 influences.add(this.getClass()); // always add MBean itself
246                 influences = findInfluences(influences, _managed.getClass());
247 
248                 if (LOG.isDebugEnabled())
249                     LOG.debug("Influence Count: {}", influences.size() );
250 
251                 // Process Type Annotations
252                 ManagedObject primary = o_class.getAnnotation( ManagedObject.class);
253 
254                 if ( primary != null )
255                 {
256                     desc = primary.value();
257                 }
258                 else
259                 {
260                     if (LOG.isDebugEnabled())
261                         LOG.debug("No @ManagedObject declared on {}", _managed.getClass());
262                 }
263 
264 
265                 // For each influence
266                 for (int i=0;i<influences.size();i++)
267                 {
268                     Class<?> oClass = influences.get(i);
269 
270                     ManagedObject typeAnnotation = oClass.getAnnotation( ManagedObject.class );
271 
272                     if (LOG.isDebugEnabled())
273                         LOG.debug("Influenced by: " + oClass.getCanonicalName() );
274 
275                     if ( typeAnnotation == null )
276                     {
277                         if (LOG.isDebugEnabled())
278                             LOG.debug("Annotations not found for: {}", oClass.getCanonicalName() );
279                         continue;
280                     }
281 
282                     // Process Method Annotations
283 
284                     for (Method method : oClass.getDeclaredMethods())
285                     {
286                         ManagedAttribute methodAttributeAnnotation = method.getAnnotation(ManagedAttribute.class);
287 
288                         if (methodAttributeAnnotation != null)
289                         {
290                             // TODO sort out how a proper name could get here, its a method name as an attribute at this point.
291                             if (LOG.isDebugEnabled())
292                                 LOG.debug("Attribute Annotation found for: {}", method.getName());
293                             MBeanAttributeInfo mai = defineAttribute(method,methodAttributeAnnotation);
294                             if ( mai != null )
295                             {
296                                 attributes.add(mai);
297                             }
298                         }
299 
300                         ManagedOperation methodOperationAnnotation = method.getAnnotation(ManagedOperation.class);
301 
302                         if (methodOperationAnnotation != null)
303                         {
304                             if (LOG.isDebugEnabled())
305                                 LOG.debug("Method Annotation found for: {}", method.getName());
306                             MBeanOperationInfo oi = defineOperation(method,methodOperationAnnotation);
307                             if (oi != null)
308                             {
309                                 operations.add(oi);
310                             }
311                         }
312                     }
313 
314                 }
315 
316                 _info = new MBeanInfo(o_class.getName(),
317                                 desc,
318                                 (MBeanAttributeInfo[])attributes.toArray(new MBeanAttributeInfo[attributes.size()]),
319                                 (MBeanConstructorInfo[])constructors.toArray(new MBeanConstructorInfo[constructors.size()]),
320                                 (MBeanOperationInfo[])operations.toArray(new MBeanOperationInfo[operations.size()]),
321                                 (MBeanNotificationInfo[])notifications.toArray(new MBeanNotificationInfo[notifications.size()]));
322             }
323         }
324         catch(RuntimeException e)
325         {
326             LOG.warn(e);
327             throw e;
328         }
329         return _info;
330     }
331 
332 
333     /* ------------------------------------------------------------ */
334     public Object getAttribute(String name) throws AttributeNotFoundException, MBeanException, ReflectionException
335     {
336         Method getter = (Method) _getters.get(name);
337         if (getter == null)
338         {
339             throw new AttributeNotFoundException(name);
340         }
341 
342         try
343         {
344             Object o = _managed;
345             if (getter.getDeclaringClass().isInstance(this))
346                 o = this; // mbean method
347 
348             // get the attribute
349             Object r=getter.invoke(o, (java.lang.Object[]) null);
350 
351             // convert to ObjectName if the type has the @ManagedObject annotation
352             if (r!=null )
353             {
354                 if (r.getClass().isArray())
355                 {
356                     if (r.getClass().getComponentType().isAnnotationPresent(ManagedObject.class))
357                     {
358                         ObjectName[] on = new ObjectName[Array.getLength(r)];
359                         for (int i = 0; i < on.length; i++)
360                         {
361                             on[i] = _mbeanContainer.findMBean(Array.get(r,i));
362                         }
363                         r = on;
364                     }
365                 }
366                 else if (r instanceof Collection<?>)
367                 {
368                     @SuppressWarnings("unchecked")
369                     Collection<Object> c = (Collection<Object>)r;
370 
371                     if (!c.isEmpty() && c.iterator().next().getClass().isAnnotationPresent(ManagedObject.class))
372                     {
373                         // check the first thing out
374 
375                         ObjectName[] on = new ObjectName[c.size()];
376                         int i = 0;
377                         for (Object obj : c)
378                         {
379                             on[i++] = _mbeanContainer.findMBean(obj);
380                         }
381                         r = on;
382                     }
383                 }
384                 else
385                 {
386                     Class<?> clazz = r.getClass();
387                     
388                     while (clazz != null)
389                     {
390                         if (clazz.isAnnotationPresent(ManagedObject.class))
391                         {
392                             ObjectName mbean = _mbeanContainer.findMBean(r);
393 
394                             if (mbean != null)
395                             {    
396                                 return mbean;
397                             }
398                             else
399                             {
400                                 return null;
401                             }
402                         }                   
403                         clazz = clazz.getSuperclass();
404                     }              
405                 }
406             }
407 
408             return r;
409         }
410         catch (IllegalAccessException e)
411         {
412             LOG.warn(Log.EXCEPTION, e);
413             throw new AttributeNotFoundException(e.toString());
414         }
415         catch (InvocationTargetException e)
416         {
417             LOG.warn(Log.EXCEPTION, e);
418             throw new ReflectionException(new Exception(e.getCause()));
419         }
420     }
421 
422     /* ------------------------------------------------------------ */
423     public AttributeList getAttributes(String[] names)
424     {
425         AttributeList results = new AttributeList(names.length);
426         for (int i = 0; i < names.length; i++)
427         {
428             try
429             {
430                 results.add(new Attribute(names[i], getAttribute(names[i])));
431             }
432             catch (Exception e)
433             {
434                 LOG.warn(Log.EXCEPTION, e);
435             }
436         }
437         return results;
438     }
439 
440     /* ------------------------------------------------------------ */
441     public void setAttribute(Attribute attr) throws AttributeNotFoundException, InvalidAttributeValueException, MBeanException, ReflectionException
442     {
443         if (attr == null)
444             return;
445 
446         if (LOG.isDebugEnabled())
447             LOG.debug("setAttribute " + _managed + ":" +attr.getName() + "=" + attr.getValue());
448         Method setter = (Method) _setters.get(attr.getName());
449         if (setter == null)
450             throw new AttributeNotFoundException(attr.getName());
451         try
452         {
453             Object o = _managed;
454             if (setter.getDeclaringClass().isInstance(this))
455                 o = this;
456 
457             // get the value
458             Object value = attr.getValue();
459 
460             // convert from ObjectName if need be
461             if (value!=null && _convert.contains(attr.getName()))
462             {
463                 if (value.getClass().isArray())
464                 {
465                     Class<?> t=setter.getParameterTypes()[0].getComponentType();
466                     Object na = Array.newInstance(t,Array.getLength(value));
467                     for (int i=Array.getLength(value);i-->0;)
468                         Array.set(na, i, _mbeanContainer.findBean((ObjectName)Array.get(value, i)));
469                     value=na;
470                 }
471                 else
472                     value=_mbeanContainer.findBean((ObjectName)value);
473             }
474 
475             // do the setting
476             setter.invoke(o, new Object[]{ value });
477         }
478         catch (IllegalAccessException e)
479         {
480             LOG.warn(Log.EXCEPTION, e);
481             throw new AttributeNotFoundException(e.toString());
482         }
483         catch (InvocationTargetException e)
484         {
485             LOG.warn(Log.EXCEPTION, e);
486             throw new ReflectionException(new Exception(e.getCause()));
487         }
488     }
489 
490     /* ------------------------------------------------------------ */
491     public AttributeList setAttributes(AttributeList attrs)
492     {
493         if (LOG.isDebugEnabled())
494             LOG.debug("setAttributes");
495 
496         AttributeList results = new AttributeList(attrs.size());
497         Iterator<Object> iter = attrs.iterator();
498         while (iter.hasNext())
499         {
500             try
501             {
502                 Attribute attr = (Attribute) iter.next();
503                 setAttribute(attr);
504                 results.add(new Attribute(attr.getName(), getAttribute(attr.getName())));
505             }
506             catch (Exception e)
507             {
508                 LOG.warn(Log.EXCEPTION, e);
509             }
510         }
511         return results;
512     }
513 
514     /* ------------------------------------------------------------ */
515     public Object invoke(String name, Object[] params, String[] signature) throws MBeanException, ReflectionException
516     {
517         if (LOG.isDebugEnabled())
518             LOG.debug("ObjectMBean:invoke " + name);
519 
520         String methodKey = name + "(";
521         if (signature != null)
522             for (int i = 0; i < signature.length; i++)
523                 methodKey += (i > 0 ? "," : "") + signature[i];
524         methodKey += ")";
525 
526         ClassLoader old_loader=Thread.currentThread().getContextClassLoader();
527         try
528         {
529             Thread.currentThread().setContextClassLoader(_loader);
530             Method method = (Method) _methods.get(methodKey);
531             if (method == null)
532                 throw new NoSuchMethodException(methodKey);
533 
534             Object o = _managed;
535 
536             if (method.getDeclaringClass().isInstance(this))
537             {
538                 o = this;
539             }
540             return method.invoke(o, params);
541         }
542         catch (NoSuchMethodException e)
543         {
544             LOG.warn(Log.EXCEPTION, e);
545             throw new ReflectionException(e);
546         }
547         catch (IllegalAccessException e)
548         {
549             LOG.warn(Log.EXCEPTION, e);
550             throw new MBeanException(e);
551         }
552         catch (InvocationTargetException e)
553         {
554             LOG.warn(Log.EXCEPTION, e);
555             throw new ReflectionException(new Exception(e.getCause()));
556         }
557         finally
558         {
559             Thread.currentThread().setContextClassLoader(old_loader);
560         }
561     }
562 
563     private static List<Class<?>> findInfluences(List<Class<?>> influences, Class<?> aClass)
564     {
565         if (aClass != null)
566         {
567             if (!influences.contains(aClass))
568             {
569                 // This class is a new influence
570                 influences.add(aClass);
571             }
572 
573             // So are the super classes
574             influences = findInfluences(influences,aClass.getSuperclass());
575 
576             // So are the interfaces
577             Class<?>[] ifs = aClass.getInterfaces();
578             for (int i = 0; ifs != null && i < ifs.length; i++)
579             {
580                 influences = findInfluences(influences,ifs[i]);
581             }
582         }
583 
584         return influences;
585     }
586 
587     /* ------------------------------------------------------------ */
588     /**
589      * TODO update to new behavior
590      *
591      * Define an attribute on the managed object. The meta data is defined by looking for standard
592      * getter and setter methods. Descriptions are obtained with a call to findDescription with the
593      * attribute name.
594      *
595      * @param method the method to define
596      * @param attributeAnnotation "description" or "access:description" or "type:access:description"  where type is
597      * one of: <ul>
598      * <li>"Object" The field/method is on the managed object.
599      * <li>"MBean" The field/method is on the mbean proxy object
600      * <li>"MObject" The field/method is on the managed object and value should be converted to MBean reference
601      * <li>"MMBean" The field/method is on the mbean proxy object and value should be converted to MBean reference
602      * </ul>
603      * the access is either "RW" or "RO".
604      * @return the mbean attribute info for the method
605      */
606     public MBeanAttributeInfo defineAttribute(Method method, ManagedAttribute attributeAnnotation)
607     {
608         // determine the name of the managed attribute
609         String name = attributeAnnotation.name();
610 
611         if ("".equals(name))
612         {
613             name = toVariableName(method.getName());
614         }
615 
616         if ( _attributes.contains(name))
617         {
618             return null; // we have an attribute named this already
619         }
620 
621         String description = attributeAnnotation.value();
622         boolean readonly = attributeAnnotation.readonly();
623         boolean onMBean = attributeAnnotation.proxied();
624 
625         boolean convert = false;
626 
627         // determine if we should convert
628         Class<?> return_type = method.getReturnType();
629 
630         // get the component type
631         Class<?> component_type = return_type;
632         while ( component_type.isArray() )
633         {
634             component_type = component_type.getComponentType();
635         }
636            
637         // Test to see if the returnType or any of its super classes are managed objects
638         convert = isAnnotationPresent(component_type, ManagedObject.class);       
639         
640         String uName = name.substring(0, 1).toUpperCase(Locale.ENGLISH) + name.substring(1);
641         Class<?> oClass = onMBean ? this.getClass() : _managed.getClass();
642 
643         if (LOG.isDebugEnabled())
644             LOG.debug("defineAttribute {} {}:{}:{}:{}",name,onMBean,readonly,oClass,description);
645 
646         Method setter = null;
647 
648         // dig out a setter if one exists
649         if (!readonly)
650         {
651             String declaredSetter = attributeAnnotation.setter();
652 
653             if (LOG.isDebugEnabled())
654                 LOG.debug("DeclaredSetter: {}", declaredSetter);
655 
656             Method[] methods = oClass.getMethods();
657             for (int m = 0; m < methods.length; m++)
658             {
659                 if ((methods[m].getModifiers() & Modifier.PUBLIC) == 0)
660                     continue;
661 
662                 if (!"".equals(declaredSetter))
663                 {
664 
665                     // look for a declared setter
666                     if (methods[m].getName().equals(declaredSetter) && methods[m].getParameterTypes().length == 1)
667                     {
668                         if (setter != null)
669                         {
670                             LOG.warn("Multiple setters for mbean attr {} in {}", name, oClass);
671                             continue;
672                         }
673                         setter = methods[m];
674                         if ( !component_type.equals(methods[m].getParameterTypes()[0]))
675                         {
676                             LOG.warn("Type conflict for mbean attr {} in {}", name, oClass);
677                             continue;
678                         }
679                         if (LOG.isDebugEnabled())
680                             LOG.debug("Declared Setter: " + declaredSetter);
681                     }
682                 }
683 
684                 // look for a setter
685                 if ( methods[m].getName().equals("set" + uName) && methods[m].getParameterTypes().length == 1)
686                 {
687                     if (setter != null)
688                     {
689                         LOG.warn("Multiple setters for mbean attr {} in {}", name, oClass);
690                         continue;
691                     }
692                     setter = methods[m];
693                     if ( !return_type.equals(methods[m].getParameterTypes()[0]))
694                     {                            
695                         LOG.warn("Type conflict for mbean attr {} in {}", name, oClass);
696                         continue;
697                     }
698                 }
699             }
700         }
701 
702         if (convert)
703         {
704             if (component_type==null)
705             {
706                 LOG.warn("No mbean type for {} on {}", name, _managed.getClass());
707                 return null;
708             }
709 
710             if (component_type.isPrimitive() && !component_type.isArray())
711             {
712                 LOG.warn("Cannot convert mbean primative {}", name);
713                 return null;
714             }
715             if (LOG.isDebugEnabled())
716                 LOG.debug("passed convert checks {} for type {}", name, component_type);
717         }
718 
719         try
720         {
721             // Remember the methods
722             _getters.put(name, method);
723             _setters.put(name, setter);
724 
725             MBeanAttributeInfo info=null;
726             if (convert)
727             {
728                 _convert.add(name);
729 
730                 if (component_type.isArray())
731                 {
732                     info= new MBeanAttributeInfo(name,OBJECT_NAME_ARRAY_CLASS,description,true,setter!=null,method.getName().startsWith("is"));
733                 }
734                 else
735                 {
736                     info= new MBeanAttributeInfo(name,OBJECT_NAME_CLASS,description,true,setter!=null,method.getName().startsWith("is"));
737                 }
738             }
739             else
740             {
741                 info= new MBeanAttributeInfo(name,description,method,setter);
742             }
743 
744             _attributes.add(name);
745             
746             return info;
747         }
748         catch (Exception e)
749         {
750             LOG.warn(e);
751             throw new IllegalArgumentException(e.toString());
752         }
753     }
754 
755 
756     /* ------------------------------------------------------------ */
757     /**
758      *  TODO update to new behavior
759      *
760      * Define an operation on the managed object. Defines an operation with parameters. Refection is
761      * used to determine find the method and it's return type. The description of the method is
762      * found with a call to findDescription on "name(signature)". The name and description of each
763      * parameter is found with a call to findDescription with "name(signature)[n]", the returned
764      * description is for the last parameter of the partial signature and is assumed to start with
765      * the parameter name, followed by a colon.
766      *
767      * @param metaData "description" or "impact:description" or "type:impact:description", type is
768      * the "Object","MBean", "MMBean" or "MObject" to indicate the method is on the object, the MBean or on the
769      * object but converted to an MBean reference, and impact is either "ACTION","INFO","ACTION_INFO" or "UNKNOWN".
770      */
771     private MBeanOperationInfo defineOperation(Method method, ManagedOperation methodAnnotation)
772     {
773         String description = methodAnnotation.value();
774         boolean onMBean = methodAnnotation.proxied();
775 
776         boolean convert = false;
777 
778         // determine if we should convert
779         Class<?> returnType = method.getReturnType();
780 
781         if ( returnType.isArray() )
782         {
783             if (LOG.isDebugEnabled())
784                 LOG.debug("returnType is array, get component type");
785             returnType = returnType.getComponentType();
786         }
787 
788         if ( returnType.isAnnotationPresent(ManagedObject.class))
789         {
790             convert = true;
791         }
792 
793         String impactName = methodAnnotation.impact();
794 
795         if (LOG.isDebugEnabled())
796             LOG.debug("defineOperation {} {}:{}:{}", method.getName(), onMBean, impactName, description);
797 
798         String signature = method.getName();
799 
800         try
801         {
802             // Resolve the impact
803             int impact=MBeanOperationInfo.UNKNOWN;
804             if (impactName==null || impactName.equals("UNKNOWN"))
805                 impact=MBeanOperationInfo.UNKNOWN;
806             else if (impactName.equals("ACTION"))
807                 impact=MBeanOperationInfo.ACTION;
808             else if (impactName.equals("INFO"))
809                 impact=MBeanOperationInfo.INFO;
810             else if (impactName.equals("ACTION_INFO"))
811                 impact=MBeanOperationInfo.ACTION_INFO;
812             else
813                 LOG.warn("Unknown impact '"+impactName+"' for "+signature);
814 
815 
816             Annotation[][] allParameterAnnotations = method.getParameterAnnotations();
817             Class<?>[] methodTypes = method.getParameterTypes();
818             MBeanParameterInfo[] pInfo = new MBeanParameterInfo[allParameterAnnotations.length];
819 
820             for ( int i = 0 ; i < allParameterAnnotations.length ; ++i )
821             {
822                 Annotation[] parameterAnnotations = allParameterAnnotations[i];
823 
824                 for ( Annotation anno : parameterAnnotations )
825                 {
826                     if ( anno instanceof Name )
827                     {
828                         Name nameAnnotation = (Name) anno;
829 
830                         pInfo[i] = new MBeanParameterInfo(nameAnnotation.value(),methodTypes[i].getName(),nameAnnotation.description());
831                     }
832                 }
833             }
834 
835             signature += "(";
836             for ( int i = 0 ; i < methodTypes.length ; ++i )
837             {
838                 signature += methodTypes[i].getName();
839 
840                 if ( i != methodTypes.length - 1 )
841                 {
842                     signature += ",";
843                 }
844             }
845             signature += ")";
846 
847             Class<?> returnClass = method.getReturnType();
848 
849             if (LOG.isDebugEnabled())
850                 LOG.debug("Method Cache: " + signature );
851 
852             if ( _methods.containsKey(signature) )
853             {
854                 return null; // we have an operation for this already
855             }
856 
857             _methods.put(signature, method);
858             if (convert)
859                 _convert.add(signature);
860 
861             return new MBeanOperationInfo(method.getName(), description, pInfo, returnClass.isPrimitive() ? TypeUtil.toName(returnClass) : (returnClass.getName()), impact);
862         }
863         catch (Exception e)
864         {
865             LOG.warn("Operation '"+signature+"'", e);
866             throw new IllegalArgumentException(e.toString());
867         }
868 
869     }
870 
871     protected String toVariableName( String methodName )
872     {
873         String variableName = methodName;
874 
875         if ( methodName.startsWith("get") || methodName.startsWith("set") )
876         {
877             variableName = variableName.substring(3);
878         }
879         else if ( methodName.startsWith("is") )
880         {
881             variableName = variableName.substring(2);
882         }
883 
884         variableName = variableName.substring(0,1).toLowerCase(Locale.ENGLISH) + variableName.substring(1);
885 
886         return variableName;
887     }
888     
889     protected boolean isAnnotationPresent(Class<?> clazz, Class<? extends Annotation> annotation)
890     {
891         Class<?> test = clazz;
892         
893         while (test != null )
894         {  
895             if ( test.isAnnotationPresent(annotation))
896             {
897                 return true;
898             }
899             else
900             {
901                 test = test.getSuperclass();
902             }
903         }
904         return false;
905     }
906 }