001: /*
002: * Copyright (c) 1998-2008 Caucho Technology -- all rights reserved
003: *
004: * This file is part of Resin(R) Open Source
005: *
006: * Each copy or derived work must preserve the copyright notice and this
007: * notice unmodified.
008: *
009: * Resin Open Source is free software; you can redistribute it and/or modify
010: * it under the terms of the GNU General Public License as published by
011: * the Free Software Foundation; either version 2 of the License, or
012: * (at your option) any later version.
013: *
014: * Resin Open Source is distributed in the hope that it will be useful,
015: * but WITHOUT ANY WARRANTY; without even the implied warranty of
016: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, or any warranty
017: * of NON-INFRINGEMENT. See the GNU General Public License for more
018: * details.
019: *
020: * You should have received a copy of the GNU General Public License
021: * along with Resin Open Source; if not, write to the
022: *
023: * Free Software Foundation, Inc.
024: * 59 Temple Place, Suite 330
025: * Boston, MA 02111-1307 USA
026: *
027: * @author Scott Ferguson
028: */
029:
030: package com.caucho.jmx;
031:
032: import javax.management.*;
033: import javax.management.openmbean.ArrayType;
034: import javax.management.openmbean.OpenType;
035: import javax.management.openmbean.SimpleType;
036: import java.lang.annotation.Annotation;
037: import java.lang.ref.SoftReference;
038: import java.lang.reflect.InvocationTargetException;
039: import java.lang.reflect.Method;
040: import java.lang.reflect.Modifier;
041: import java.util.ArrayList;
042: import java.util.Arrays;
043: import java.util.Collections;
044: import java.util.Comparator;
045: import java.util.HashMap;
046: import java.util.WeakHashMap;
047: import java.util.logging.Level;
048: import java.util.logging.Logger;
049:
050: import com.caucho.util.L10N;
051:
052: /**
053: * Resin implementation of StandardMBean.
054: */
055: public class IntrospectionMBean implements DynamicMBean {
056: private static final L10N L = new L10N(IntrospectionMBean.class);
057: private static final Logger log = Logger
058: .getLogger(IntrospectionMBean.class.getName());
059:
060: private static final Class[] NULL_ARG = new Class[0];
061:
062: private static final Class _descriptionAnn;
063: private static final Class _nameAnn;
064:
065: private static final WeakHashMap<Class, SoftReference<MBeanInfo>> _cachedInfo = new WeakHashMap<Class, SoftReference<MBeanInfo>>();
066:
067: private final Object _impl;
068: private final Class _mbeanInterface;
069: private final boolean _isLowercaseAttributeNames;
070:
071: private final MBeanInfo _mbeanInfo;
072:
073: private final HashMap<String, OpenModelMethod> _attrGetMap = new HashMap<String, OpenModelMethod>();
074:
075: /**
076: * Makes a DynamicMBean.
077: */
078: public IntrospectionMBean(Object impl, Class mbeanInterface)
079: throws NotCompliantMBeanException {
080: this (impl, mbeanInterface, false);
081: }
082:
083: /**
084: * Makes a DynamicMBean.
085: *
086: * @param isLowercaseAttributeNames true if attributes should have first
087: * letter lowercased
088: */
089: public IntrospectionMBean(Object impl, Class mbeanInterface,
090: boolean isLowercaseAttributeNames)
091: throws NotCompliantMBeanException {
092: if (impl == null)
093: throw new NullPointerException();
094:
095: _mbeanInterface = mbeanInterface;
096: _isLowercaseAttributeNames = isLowercaseAttributeNames;
097:
098: _mbeanInfo = introspect(impl, mbeanInterface,
099: isLowercaseAttributeNames);
100: _impl = impl;
101: }
102:
103: /**
104: * Returns the implementation.
105: */
106: public Object getImplementation() {
107: return _impl;
108: }
109:
110: /**
111: * Returns an attribute value.
112: */
113: public Object getAttribute(String attribute)
114: throws AttributeNotFoundException, MBeanException,
115: ReflectionException {
116: try {
117: OpenModelMethod method = getGetMethod(attribute);
118:
119: if (method != null)
120: return method.invoke(_impl, (Object[]) null);
121: else
122: throw new AttributeNotFoundException(L.l(
123: "'{0}' is an unknown attribute in '{1}'",
124: attribute, _mbeanInterface.getName()));
125: } catch (IllegalAccessException e) {
126: throw new MBeanException(e);
127: } catch (InvocationTargetException e) {
128: if (e.getCause() instanceof Exception)
129: throw new ReflectionException((Exception) e.getCause());
130: else
131: throw (Error) e.getCause();
132: } catch (Throwable e) {
133: throw new RuntimeException(e);
134: }
135: }
136:
137: /**
138: * Sets an attribute value.
139: */
140: public void setAttribute(Attribute attribute)
141: throws AttributeNotFoundException,
142: InvalidAttributeValueException, MBeanException,
143: ReflectionException {
144: try {
145: Method method = getSetMethod(attribute.getName(), attribute
146: .getValue());
147:
148: if (method != null)
149: method.invoke(_impl, new Object[] { attribute
150: .getValue() });
151: else
152: throw new AttributeNotFoundException(attribute
153: .getName());
154: } catch (IllegalAccessException e) {
155: throw new MBeanException(e);
156: } catch (InvocationTargetException e) {
157: throw new MBeanException(e);
158: } catch (Throwable e) {
159: throw new RuntimeException(e.toString());
160: }
161: }
162:
163: /**
164: * Returns matching attribute values.
165: */
166: public AttributeList getAttributes(String[] attributes) {
167: AttributeList list = new AttributeList();
168:
169: for (int i = 0; i < attributes.length; i++) {
170: try {
171: OpenModelMethod method = getGetMethod(attributes[i]);
172:
173: if (method != null) {
174: Object value = method
175: .invoke(_impl, (Object[]) null);
176:
177: list.add(new Attribute(attributes[i], value));
178: }
179: } catch (Throwable e) {
180: log.log(Level.WARNING, e.toString(), e);
181: }
182: }
183:
184: return list;
185: }
186:
187: /**
188: * Sets attribute values.
189: */
190: public AttributeList setAttributes(AttributeList attributes) {
191: AttributeList list = new AttributeList();
192:
193: for (int i = 0; i < attributes.size(); i++) {
194: try {
195: Attribute attr = (Attribute) attributes.get(i);
196: Method method = getSetMethod(attr.getName(), attr
197: .getValue());
198:
199: if (method != null) {
200: method.invoke(_impl,
201: new Object[] { attr.getValue() });
202: list.add(new Attribute(attr.getName(), attr
203: .getValue()));
204: }
205: } catch (Throwable e) {
206: log.log(Level.WARNING, e.toString(), e);
207: }
208: }
209:
210: return list;
211: }
212:
213: /**
214: * Returns the set method matching the name.
215: */
216: private OpenModelMethod getGetMethod(String name) {
217: OpenModelMethod method = _attrGetMap.get(name);
218:
219: if (method != null)
220: return method;
221:
222: method = createGetMethod(name);
223:
224: if (method != null)
225: _attrGetMap.put(name, method);
226:
227: return method;
228: }
229:
230: /**
231: * Returns the get or is method matching the name.
232: */
233: private OpenModelMethod createGetMethod(String name) {
234: String methodName;
235:
236: if (_isLowercaseAttributeNames) {
237: StringBuilder builder = new StringBuilder(name);
238: builder.setCharAt(0, Character.toUpperCase(builder
239: .charAt(0)));
240: methodName = builder.toString();
241: } else
242: methodName = name;
243:
244: String getName = "get" + methodName;
245: String isName = "is" + methodName;
246:
247: Method[] methods = _mbeanInterface.getMethods();
248:
249: for (int i = 0; i < methods.length; i++) {
250: if (!methods[i].getName().equals(getName)
251: && !methods[i].getName().equals(isName))
252: continue;
253:
254: Class[] args = methods[i].getParameterTypes();
255:
256: if (args.length == 0
257: && !methods[i].getReturnType().equals(void.class)) {
258: Class retType = methods[i].getReturnType();
259:
260: return new OpenModelMethod(methods[i],
261: createUnmarshall(retType));
262: }
263: }
264:
265: return null;
266: }
267:
268: /**
269: v * Returns the open mbean unmarshaller for the given return type.
270: */
271: private Unmarshall createUnmarshall(Class cl) {
272: Unmarshall mbean = getMBeanObjectName(cl);
273:
274: if (mbean != null)
275: return mbean;
276:
277: if (cl.isArray()) {
278: Class componentType = cl.getComponentType();
279:
280: mbean = getMBeanObjectName(componentType);
281:
282: if (mbean != null)
283: return new UnmarshallArray(ObjectName.class, mbean);
284: }
285:
286: return Unmarshall.IDENTITY;
287: }
288:
289: private Unmarshall getMBeanObjectName(Class cl) {
290: try {
291: Method method = cl.getMethod("getObjectName");
292:
293: if (method != null
294: && ObjectName.class.equals(method.getReturnType()))
295: return new UnmarshallMBean(method);
296: } catch (NoSuchMethodException e) {
297: } catch (Exception e) {
298: log.log(Level.FINER, e.toString(), e);
299: }
300:
301: return null;
302: }
303:
304: /**
305: * Returns the set method matching the name.
306: */
307: private Method getSetMethod(String name, Object value) {
308: String methodName;
309:
310: if (_isLowercaseAttributeNames) {
311: StringBuilder builder = new StringBuilder(name);
312: builder.setCharAt(0, Character.toUpperCase(builder
313: .charAt(0)));
314: methodName = builder.toString();
315: } else
316: methodName = name;
317:
318: String setName = "set" + methodName;
319:
320: Method[] methods = _mbeanInterface.getMethods();
321:
322: for (int i = 0; i < methods.length; i++) {
323: if (!methods[i].getName().equals(setName))
324: continue;
325:
326: Class[] args = methods[i].getParameterTypes();
327:
328: if (args.length != 1)
329: continue;
330:
331: /*
332: if (value != null && ! args[0].isAssignableFrom(value.getClass()))
333: continue;
334: */
335:
336: return methods[i];
337: }
338:
339: return null;
340: }
341:
342: /**
343: * Invokes a method on the bean.
344: */
345: public Object invoke(String actionName, Object[] params,
346: String[] signature) throws MBeanException,
347: ReflectionException {
348: try {
349: Method[] methods = _mbeanInterface.getMethods();
350:
351: int length = 0;
352: if (signature != null)
353: length = signature.length;
354: if (params != null)
355: length = params.length;
356:
357: for (int i = 0; i < methods.length; i++) {
358: if (!methods[i].getName().equals(actionName))
359: continue;
360:
361: Class[] args = methods[i].getParameterTypes();
362:
363: if (args.length != length)
364: continue;
365:
366: boolean isMatch = true;
367: for (int j = length - 1; j >= 0; j--) {
368: if (signature != null
369: && !args[j].getName().equals(signature[j]))
370: isMatch = false;
371: }
372:
373: if (isMatch) {
374: return methods[i].invoke(_impl, params);
375: }
376: }
377:
378: if (actionName.equals("hashCode")
379: && (signature == null || signature.length == 0))
380: return _impl.hashCode();
381: else if (actionName.equals("toString")
382: && (signature == null || signature.length == 0))
383: return _impl.toString();
384: else
385: return null;
386: } catch (IllegalAccessException e) {
387: throw new MBeanException(e);
388: } catch (InvocationTargetException e) {
389: if (e.getCause() instanceof Exception)
390: throw new ReflectionException((Exception) e.getCause());
391: else
392: throw (Error) e.getCause();
393: }
394: }
395:
396: /**
397: * Returns the introspection information for the MBean.
398: */
399: public MBeanInfo getMBeanInfo() {
400: return _mbeanInfo;
401: }
402:
403: static MBeanInfo introspect(Object obj, Class cl,
404: boolean isLowercaseAttributeNames)
405: throws NotCompliantMBeanException {
406: try {
407: SoftReference<MBeanInfo> infoRef = _cachedInfo.get(cl);
408: MBeanInfo info = null;
409:
410: if (infoRef != null && (info = infoRef.get()) != null)
411: return info;
412:
413: String className = cl.getName();
414:
415: HashMap<String, MBeanAttributeInfo> attributes = new HashMap<String, MBeanAttributeInfo>();
416:
417: ArrayList<MBeanConstructorInfo> constructors = new ArrayList<MBeanConstructorInfo>();
418:
419: ArrayList<MBeanOperationInfo> operations = new ArrayList<MBeanOperationInfo>();
420:
421: Method[] methods = cl.getMethods();
422: for (int i = 0; i < methods.length; i++) {
423: Method method = methods[i];
424:
425: if (method.getDeclaringClass() == Object.class)
426: continue;
427:
428: if (Modifier.isStatic(method.getModifiers()))
429: continue;
430:
431: String methodName = method.getName();
432: Class[] args = method.getParameterTypes();
433: Class retType = method.getReturnType();
434:
435: if (methodName.startsWith("get") && args.length == 0
436: && !retType.equals(void.class)) {
437: Method getter = method;
438: String name = methodName.substring(3);
439:
440: Method setter = getSetter(methods, name, retType);
441:
442: String attributeName;
443:
444: if (isLowercaseAttributeNames) {
445: StringBuilder builder = new StringBuilder(name);
446: builder.setCharAt(0, Character
447: .toLowerCase(builder.charAt(0)));
448: attributeName = builder.toString();
449: } else
450: attributeName = name;
451:
452: Class type = method.getReturnType();
453:
454: MBeanAttributeInfo attr;
455:
456: attr = new MBeanAttributeInfo(attributeName,
457: getDescription(method), getter, setter);
458:
459: /*
460: Descriptor descriptor = attr.getDescriptor();
461:
462: if (descriptor != null) {
463: Object openType = getOpenType(type);
464:
465: if (openType != null)
466: descriptor.setField("openType", openType);
467:
468: descriptor.setField("originalType", getTypeName(type));
469:
470: attr.setDescriptor(descriptor);
471: }
472: */
473:
474: if (attributes.get(attributeName) == null)
475: attributes.put(attributeName, attr);
476: } else if (methodName.startsWith("is")
477: && args.length == 0
478: && (retType.equals(boolean.class) || retType
479: .equals(Boolean.class))) {
480: Method getter = method;
481: String name = methodName.substring(2);
482:
483: Method setter = getSetter(methods, name, retType);
484:
485: String attributeName;
486:
487: if (isLowercaseAttributeNames) {
488: StringBuilder builder = new StringBuilder(name);
489: builder.setCharAt(0, Character
490: .toLowerCase(builder.charAt(0)));
491: attributeName = builder.toString();
492: } else
493: attributeName = name;
494:
495: if (attributes.get(attributeName) == null) {
496: attributes.put(attributeName,
497: new MBeanAttributeInfo(attributeName,
498: getDescription(method), getter,
499: setter));
500: }
501: } else if (methodName.startsWith("set")
502: && args.length == 1) {
503: Method setter = method;
504: String name = methodName.substring(3);
505:
506: Method getter = getGetter(methods, name, args[0]);
507:
508: if (getter == null) {
509: String attributeName;
510:
511: if (isLowercaseAttributeNames) {
512: StringBuilder builder = new StringBuilder(
513: name);
514: builder.setCharAt(0, Character
515: .toLowerCase(builder.charAt(0)));
516: attributeName = builder.toString();
517: } else
518: attributeName = name;
519:
520: if (attributes.get(attributeName) == null) {
521: attributes.put(attributeName,
522: new MBeanAttributeInfo(
523: attributeName,
524: getDescription(method),
525: null, setter));
526: }
527: }
528: } else {
529: operations.add(new MBeanOperationInfo(
530: getName(method), getDescription(method),
531: getSignature(method), method
532: .getReturnType().getName(),
533: MBeanOperationInfo.UNKNOWN));
534: }
535: }
536:
537: ArrayList<MBeanNotificationInfo> notifications = new ArrayList<MBeanNotificationInfo>();
538:
539: if (obj instanceof NotificationBroadcaster) {
540: NotificationBroadcaster broadcaster;
541: broadcaster = (NotificationBroadcaster) obj;
542:
543: MBeanNotificationInfo[] notifs = broadcaster
544: .getNotificationInfo();
545:
546: if (notifs != null) {
547: for (int i = 0; i < notifs.length; i++) {
548: MBeanNotificationInfo notif = notifs[i];
549:
550: notifications
551: .add((MBeanNotificationInfo) notifs[i]
552: .clone());
553: }
554: }
555: }
556:
557: Collections.sort(notifications,
558: MBEAN_FEATURE_INFO_COMPARATOR);
559:
560: MBeanAttributeInfo[] attrArray = new MBeanAttributeInfo[attributes
561: .size()];
562: attributes.values().toArray(attrArray);
563: Arrays.sort(attrArray, MBEAN_FEATURE_INFO_COMPARATOR);
564:
565: MBeanConstructorInfo[] conArray = new MBeanConstructorInfo[constructors
566: .size()];
567: constructors.toArray(conArray);
568:
569: MBeanOperationInfo[] opArray = new MBeanOperationInfo[operations
570: .size()];
571: operations.toArray(opArray);
572: Arrays.sort(opArray, MBEAN_FEATURE_INFO_COMPARATOR);
573: MBeanNotificationInfo[] notifArray = new MBeanNotificationInfo[notifications
574: .size()];
575: notifications.toArray(notifArray);
576: Arrays.sort(notifArray, MBEAN_FEATURE_INFO_COMPARATOR);
577:
578: MBeanInfo modelInfo;
579:
580: modelInfo = new MBeanInfo(cl.getName(), getDescription(cl),
581: attrArray, conArray, opArray, notifArray);
582: /*
583: Descriptor descriptor = modelInfo.getMBeanDescriptor();
584: if (descriptor != null) {
585: descriptor.setField("mxbean", "true");
586: modelInfo.setMBeanDescriptor(descriptor);
587: }
588: */
589:
590: info = modelInfo;
591:
592: _cachedInfo.put(cl, new SoftReference<MBeanInfo>(info));
593:
594: return info;
595: } catch (Exception e) {
596: NotCompliantMBeanException exn;
597: exn = new NotCompliantMBeanException(String.valueOf(e));
598:
599: exn.initCause(e);
600:
601: throw exn;
602: }
603: }
604:
605: /**
606: * Returns the matching setter.
607: */
608: static Method getSetter(Method[] methods, String property,
609: Class type) {
610: String name = "set" + property;
611:
612: for (int i = 0; i < methods.length; i++) {
613: if (!methods[i].getName().equals(name))
614: continue;
615:
616: Class[] args = methods[i].getParameterTypes();
617:
618: if (args.length != 1 || !args[0].equals(type))
619: continue;
620:
621: return methods[i];
622: }
623:
624: return null;
625: }
626:
627: /**
628: * Returns the matching getter.
629: */
630: static Method getGetter(Method[] methods, String property,
631: Class type) {
632: String getName = "get" + property;
633: String isName = "is" + property;
634:
635: for (int i = 0; i < methods.length; i++) {
636: if (!methods[i].getName().equals(getName)
637: && !methods[i].getName().equals(isName))
638: continue;
639:
640: Class[] args = methods[i].getParameterTypes();
641:
642: if (args.length != 0)
643: continue;
644:
645: Class retType = methods[i].getReturnType();
646:
647: if (!retType.equals(type))
648: continue;
649:
650: return methods[i];
651: }
652:
653: return null;
654: }
655:
656: /**
657: * Returns the class's description.
658: */
659: static String getDescription(Class cl) {
660: try {
661: Description desc = (Description) cl
662: .getAnnotation(_descriptionAnn);
663:
664: if (desc != null)
665: return desc.value();
666: else
667: return "";
668: } catch (Throwable e) {
669: return "";
670: }
671: }
672:
673: /**
674: * Returns the method's description.
675: */
676: static String getDescription(Method method) {
677: try {
678: Description desc = (Description) method
679: .getAnnotation(_descriptionAnn);
680:
681: if (desc != null)
682: return desc.value();
683: else
684: return "";
685: } catch (Throwable e) {
686: return "";
687: }
688: }
689:
690: /**
691: * Returns the method's name, the optional {@link Name} annotation overrides.
692: */
693: static String getName(Method method) {
694: try {
695: Name name = (Name) method.getAnnotation(_nameAnn);
696:
697: if (name != null)
698: return name.value();
699: else
700: return method.getName();
701: } catch (Throwable e) {
702: return method.getName();
703: }
704: }
705:
706: private static MBeanParameterInfo[] getSignature(Method method) {
707: Class[] params = method.getParameterTypes();
708: MBeanParameterInfo[] paramInfos = new MBeanParameterInfo[params.length];
709:
710: for (int i = 0; i < params.length; i++) {
711: Class cl = params[i];
712:
713: String name = getName(method, i);
714: String description = getDescription(method, i);
715:
716: paramInfos[i] = new MBeanParameterInfo(name, cl.getName(),
717: description);
718: }
719:
720: return paramInfos;
721: }
722:
723: private static String getName(Method method, int i) {
724: try {
725: for (Annotation ann : method.getParameterAnnotations()[i]) {
726: if (ann instanceof Name)
727: return ((Name) ann).value();
728: }
729: } catch (Throwable e) {
730: log.log(Level.FINER, e.toString(), e);
731: }
732:
733: return "p" + i;
734: }
735:
736: private static String getDescription(Method method, int i) {
737: try {
738: for (Annotation ann : method.getParameterAnnotations()[i]) {
739: if (ann instanceof Description)
740: return ((Description) ann).value();
741: }
742: } catch (Throwable e) {
743: log.log(Level.FINER, e.toString(), e);
744: }
745:
746: return "";
747: }
748:
749: private static String getTypeName(Class type) {
750: if (type.isArray())
751: return getTypeName(type.getComponentType()) + "[]";
752: else
753: return type.getName();
754: }
755:
756: private static OpenType getOpenType(Class type) {
757: try {
758: if (type.isArray()) {
759: OpenType component = getOpenType(type
760: .getComponentType());
761:
762: if (component != null)
763: return new ArrayType(1, component);
764: else
765: return null;
766: } else if (type.getName().endsWith("MXBean")
767: || type.getName().endsWith("MBean"))
768: return SimpleType.OBJECTNAME;
769: else if (void.class.equals(type))
770: return SimpleType.VOID;
771: else if (boolean.class.equals(type)
772: || Boolean.class.equals(type))
773: return SimpleType.BOOLEAN;
774: else if (byte.class.equals(type) || Byte.class.equals(type))
775: return SimpleType.BYTE;
776: else if (short.class.equals(type)
777: || Short.class.equals(type))
778: return SimpleType.SHORT;
779: else if (int.class.equals(type)
780: || Integer.class.equals(type))
781: return SimpleType.INTEGER;
782: else if (long.class.equals(type) || Long.class.equals(type))
783: return SimpleType.LONG;
784: else if (float.class.equals(type)
785: || Float.class.equals(type))
786: return SimpleType.FLOAT;
787: else if (double.class.equals(type)
788: || Double.class.equals(type))
789: return SimpleType.DOUBLE;
790: else if (String.class.equals(type))
791: return SimpleType.STRING;
792: else if (char.class.equals(type)
793: || Character.class.equals(type))
794: return SimpleType.CHARACTER;
795: else if (java.util.Date.class.equals(type))
796: return SimpleType.DATE;
797: else if (java.util.Calendar.class.equals(type))
798: return SimpleType.DATE;
799: else
800: return null; // can't deal with more complex at the moment
801: } catch (Exception e) {
802: log.log(Level.FINER, e.toString(), e);
803:
804: return null;
805: }
806: }
807:
808: private static Class findClass(String name) {
809: try {
810: return Class.forName(name);
811: } catch (Throwable e) {
812: return null;
813: }
814: }
815:
816: private static final Comparator<MBeanFeatureInfo> MBEAN_FEATURE_INFO_COMPARATOR = new Comparator<MBeanFeatureInfo>() {
817:
818: public int compare(MBeanFeatureInfo o1, MBeanFeatureInfo o2) {
819: return o1.getName().compareTo(o2.getName());
820: }
821: };
822:
823: static {
824: _descriptionAnn = findClass("com.caucho.jmx.Description");
825: _nameAnn = findClass("com.caucho.jmx.Name");
826: }
827: }
|