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   * A dynamic MBean that can wrap an arbitary Object instance.
65   * the attributes and methods exposed by this bean are controlled by
66   * the merge of property bundles discovered by names related to all
67   * superclasses and all superinterfaces.
68   *
69   * Attributes and methods exported may be "Object" and must exist on the
70   * wrapped object, or "MBean" and must exist on a subclass of OBjectMBean
71   * or "MObject" which exists on the wrapped object, but whose values are
72   * converted to MBean object names.
73   *
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
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      */
605     public MBeanAttributeInfo defineAttribute(Method method, ManagedAttribute attributeAnnotation)
606     {
607         // determine the name of the managed attribute
608         String name = attributeAnnotation.name();
609 
610         if ("".equals(name))
611         {
612             name = toVariableName(method.getName());
613         }
614 
615         if ( _attributes.contains(name))
616         {
617             return null; // we have an attribute named this already
618         }
619 
620         String description = attributeAnnotation.value();
621         boolean readonly = attributeAnnotation.readonly();
622         boolean onMBean = attributeAnnotation.proxied();
623 
624         boolean convert = false;
625 
626         // determine if we should convert
627         Class<?> return_type = method.getReturnType();
628 
629         // get the component type
630         Class<?> component_type = return_type;
631         while ( component_type.isArray() )
632         {
633             component_type = component_type.getComponentType();
634         }
635            
636         // Test to see if the returnType or any of its super classes are managed objects
637         convert = isAnnotationPresent(component_type, ManagedObject.class);       
638         
639         String uName = name.substring(0, 1).toUpperCase(Locale.ENGLISH) + name.substring(1);
640         Class<?> oClass = onMBean ? this.getClass() : _managed.getClass();
641 
642         if (LOG.isDebugEnabled())
643             LOG.debug("defineAttribute {} {}:{}:{}:{}",name,onMBean,readonly,oClass,description);
644 
645         Method setter = null;
646 
647         // dig out a setter if one exists
648         if (!readonly)
649         {
650             String declaredSetter = attributeAnnotation.setter();
651 
652             if (LOG.isDebugEnabled())
653                 LOG.debug("DeclaredSetter: {}", declaredSetter);
654 
655             Method[] methods = oClass.getMethods();
656             for (int m = 0; m < methods.length; m++)
657             {
658                 if ((methods[m].getModifiers() & Modifier.PUBLIC) == 0)
659                     continue;
660 
661                 if (!"".equals(declaredSetter))
662                 {
663 
664                     // look for a declared setter
665                     if (methods[m].getName().equals(declaredSetter) && methods[m].getParameterTypes().length == 1)
666                     {
667                         if (setter != null)
668                         {
669                             LOG.warn("Multiple setters for mbean attr {} in {}", name, oClass);
670                             continue;
671                         }
672                         setter = methods[m];
673                         if ( !component_type.equals(methods[m].getParameterTypes()[0]))
674                         {
675                             LOG.warn("Type conflict for mbean attr {} in {}", name, oClass);
676                             continue;
677                         }
678                         if (LOG.isDebugEnabled())
679                             LOG.debug("Declared Setter: " + declaredSetter);
680                     }
681                 }
682 
683                 // look for a setter
684                 if ( methods[m].getName().equals("set" + uName) && methods[m].getParameterTypes().length == 1)
685                 {
686                     if (setter != null)
687                     {
688                         LOG.warn("Multiple setters for mbean attr {} in {}", name, oClass);
689                         continue;
690                     }
691                     setter = methods[m];
692                     if ( !return_type.equals(methods[m].getParameterTypes()[0]))
693                     {                            
694                         LOG.warn("Type conflict for mbean attr {} in {}", name, oClass);
695                         continue;
696                     }
697                 }
698             }
699         }
700 
701         if (convert)
702         {
703             if (component_type==null)
704             {
705                 LOG.warn("No mbean type for {} on {}", name, _managed.getClass());
706                 return null;
707             }
708 
709             if (component_type.isPrimitive() && !component_type.isArray())
710             {
711                 LOG.warn("Cannot convert mbean primative {}", name);
712                 return null;
713             }
714             if (LOG.isDebugEnabled())
715                 LOG.debug("passed convert checks {} for type {}", name, component_type);
716         }
717 
718         try
719         {
720             // Remember the methods
721             _getters.put(name, method);
722             _setters.put(name, setter);
723 
724             MBeanAttributeInfo info=null;
725             if (convert)
726             {
727                 _convert.add(name);
728 
729                 if (component_type.isArray())
730                 {
731                     info= new MBeanAttributeInfo(name,OBJECT_NAME_ARRAY_CLASS,description,true,setter!=null,method.getName().startsWith("is"));
732                 }
733                 else
734                 {
735                     info= new MBeanAttributeInfo(name,OBJECT_NAME_CLASS,description,true,setter!=null,method.getName().startsWith("is"));
736                 }
737             }
738             else
739             {
740                 info= new MBeanAttributeInfo(name,description,method,setter);
741             }
742 
743             _attributes.add(name);
744             
745             return info;
746         }
747         catch (Exception e)
748         {
749             LOG.warn(e);
750             throw new IllegalArgumentException(e.toString());
751         }
752     }
753 
754 
755     /* ------------------------------------------------------------ */
756     /**
757      *  TODO update to new behavior
758      *
759      * Define an operation on the managed object. Defines an operation with parameters. Refection is
760      * used to determine find the method and it's return type. The description of the method is
761      * found with a call to findDescription on "name(signature)". The name and description of each
762      * parameter is found with a call to findDescription with "name(signature)[n]", the returned
763      * description is for the last parameter of the partial signature and is assumed to start with
764      * the parameter name, followed by a colon.
765      *
766      * @param metaData "description" or "impact:description" or "type:impact:description", type is
767      * the "Object","MBean", "MMBean" or "MObject" to indicate the method is on the object, the MBean or on the
768      * object but converted to an MBean reference, and impact is either "ACTION","INFO","ACTION_INFO" or "UNKNOWN".
769      */
770     private MBeanOperationInfo defineOperation(Method method, ManagedOperation methodAnnotation)
771     {
772         String description = methodAnnotation.value();
773         boolean onMBean = methodAnnotation.proxied();
774 
775         boolean convert = false;
776 
777         // determine if we should convert
778         Class<?> returnType = method.getReturnType();
779 
780         if ( returnType.isArray() )
781         {
782             if (LOG.isDebugEnabled())
783                 LOG.debug("returnType is array, get component type");
784             returnType = returnType.getComponentType();
785         }
786 
787         if ( returnType.isAnnotationPresent(ManagedObject.class))
788         {
789             convert = true;
790         }
791 
792         String impactName = methodAnnotation.impact();
793 
794         if (LOG.isDebugEnabled())
795             LOG.debug("defineOperation {} {}:{}:{}", method.getName(), onMBean, impactName, description);
796 
797         String signature = method.getName();
798 
799         try
800         {
801             // Resolve the impact
802             int impact=MBeanOperationInfo.UNKNOWN;
803             if (impactName==null || impactName.equals("UNKNOWN"))
804                 impact=MBeanOperationInfo.UNKNOWN;
805             else if (impactName.equals("ACTION"))
806                 impact=MBeanOperationInfo.ACTION;
807             else if (impactName.equals("INFO"))
808                 impact=MBeanOperationInfo.INFO;
809             else if (impactName.equals("ACTION_INFO"))
810                 impact=MBeanOperationInfo.ACTION_INFO;
811             else
812                 LOG.warn("Unknown impact '"+impactName+"' for "+signature);
813 
814 
815             Annotation[][] allParameterAnnotations = method.getParameterAnnotations();
816             Class<?>[] methodTypes = method.getParameterTypes();
817             MBeanParameterInfo[] pInfo = new MBeanParameterInfo[allParameterAnnotations.length];
818 
819             for ( int i = 0 ; i < allParameterAnnotations.length ; ++i )
820             {
821                 Annotation[] parameterAnnotations = allParameterAnnotations[i];
822 
823                 for ( Annotation anno : parameterAnnotations )
824                 {
825                     if ( anno instanceof Name )
826                     {
827                         Name nameAnnotation = (Name) anno;
828 
829                         pInfo[i] = new MBeanParameterInfo(nameAnnotation.value(),methodTypes[i].getName(),nameAnnotation.description());
830                     }
831                 }
832             }
833 
834             signature += "(";
835             for ( int i = 0 ; i < methodTypes.length ; ++i )
836             {
837                 signature += methodTypes[i].getName();
838 
839                 if ( i != methodTypes.length - 1 )
840                 {
841                     signature += ",";
842                 }
843             }
844             signature += ")";
845 
846             Class<?> returnClass = method.getReturnType();
847 
848             if (LOG.isDebugEnabled())
849                 LOG.debug("Method Cache: " + signature );
850 
851             if ( _methods.containsKey(signature) )
852             {
853                 return null; // we have an operation for this already
854             }
855 
856             _methods.put(signature, method);
857             if (convert)
858                 _convert.add(signature);
859 
860             return new MBeanOperationInfo(method.getName(), description, pInfo, returnClass.isPrimitive() ? TypeUtil.toName(returnClass) : (returnClass.getName()), impact);
861         }
862         catch (Exception e)
863         {
864             LOG.warn("Operation '"+signature+"'", e);
865             throw new IllegalArgumentException(e.toString());
866         }
867 
868     }
869 
870     protected String toVariableName( String methodName )
871     {
872         String variableName = methodName;
873 
874         if ( methodName.startsWith("get") || methodName.startsWith("set") )
875         {
876             variableName = variableName.substring(3);
877         }
878         else if ( methodName.startsWith("is") )
879         {
880             variableName = variableName.substring(2);
881         }
882 
883         variableName = variableName.substring(0,1).toLowerCase(Locale.ENGLISH) + variableName.substring(1);
884 
885         return variableName;
886     }
887     
888     protected boolean isAnnotationPresent(Class<?> clazz, Class<? extends Annotation> annotation)
889     {
890         Class<?> test = clazz;
891         
892         while (test != null )
893         {  
894             if ( test.isAnnotationPresent(annotation))
895             {
896                 return true;
897             }
898             else
899             {
900                 test = test.getSuperclass();
901             }
902         }
903         return false;
904     }
905 }