001: //========================================================================
002: //Copyright 2004 Mort Bay Consulting Pty. Ltd.
003: //------------------------------------------------------------------------
004: //Licensed under the Apache License, Version 2.0 (the "License");
005: //you may not use this file except in compliance with the License.
006: //You may obtain a copy of the License at
007: //http://www.apache.org/licenses/LICENSE-2.0
008: //Unless required by applicable law or agreed to in writing, software
009: //distributed under the License is distributed on an "AS IS" BASIS,
010: //WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
011: //See the License for the specific language governing permissions and
012: //limitations under the License.
013: //========================================================================
014:
015: package org.mortbay.management;
016:
017: import java.lang.reflect.Array;
018: import java.lang.reflect.Constructor;
019: import java.lang.reflect.InvocationTargetException;
020: import java.lang.reflect.Method;
021: import java.lang.reflect.Modifier;
022: import java.util.Enumeration;
023: import java.util.HashMap;
024: import java.util.HashSet;
025: import java.util.Iterator;
026: import java.util.Locale;
027: import java.util.Map;
028: import java.util.MissingResourceException;
029: import java.util.ResourceBundle;
030: import java.util.Set;
031:
032: import javax.management.Attribute;
033: import javax.management.AttributeList;
034: import javax.management.AttributeNotFoundException;
035: import javax.management.DynamicMBean;
036: import javax.management.InvalidAttributeValueException;
037: import javax.management.MBeanAttributeInfo;
038: import javax.management.MBeanConstructorInfo;
039: import javax.management.MBeanException;
040: import javax.management.MBeanInfo;
041: import javax.management.MBeanNotificationInfo;
042: import javax.management.MBeanOperationInfo;
043: import javax.management.MBeanParameterInfo;
044: import javax.management.ObjectName;
045: import javax.management.ReflectionException;
046: import javax.management.modelmbean.ModelMBean;
047:
048: import org.mortbay.log.Log;
049: import org.mortbay.util.LazyList;
050: import org.mortbay.util.Loader;
051: import org.mortbay.util.TypeUtil;
052:
053: /* ------------------------------------------------------------ */
054: /** ObjectMBean.
055: * A dynamic MBean that can wrap an arbitary Object instance.
056: * the attributes and methods exposed by this bean are controlled by
057: * the merge of property bundles discovered by names related to all
058: * superclasses and all superinterfaces.
059: *
060: * Attributes and methods exported may be "Object" and must exist on the
061: * wrapped object, or "MBean" and must exist on a subclass of OBjectMBean
062: * or "MObject" which exists on the wrapped object, but whose values are
063: * converted to MBean object names.
064: *
065: */
066: public class ObjectMBean implements DynamicMBean {
067: private static Class[] OBJ_ARG = new Class[] { Object.class };
068:
069: private Object _managed;
070: private MBeanInfo _info;
071: private Map _getters = new HashMap();
072: private Map _setters = new HashMap();
073: private Map _methods = new HashMap();
074: private Set _convert = new HashSet();
075: private ClassLoader _loader;
076: private MBeanContainer _mbeanContainer;
077:
078: private static String OBJECT_NAME_CLASS = ObjectName.class
079: .getName();
080: private static String OBJECT_NAME_ARRAY_CLASS = ObjectName[].class
081: .getName();
082:
083: /* ------------------------------------------------------------ */
084: /**
085: * Create MBean for Object. Attempts to create an MBean for the object by searching the package
086: * and class name space. For example an object of the type
087: *
088: * <PRE>
089: * class com.acme.MyClass extends com.acme.util.BaseClass implements com.acme.Iface
090: * </PRE>
091: *
092: * Then this method would look for the following classes:
093: * <UL>
094: * <LI>com.acme.management.MyClassMBean
095: * <LI>com.acme.util.management.BaseClassMBean
096: * <LI>org.mortbay.management.ObjectMBean
097: * </UL>
098: *
099: * @param o The object
100: * @return A new instance of an MBean for the object or null.
101: */
102: public static Object mbeanFor(Object o) {
103: try {
104: Class oClass = o.getClass();
105: Object mbean = null;
106:
107: while (mbean == null && oClass != null) {
108: String pName = oClass.getPackage().getName();
109: String cName = oClass.getName().substring(
110: pName.length() + 1);
111: String mName = pName + ".management." + cName + "MBean";
112:
113: try {
114: Class mClass = (Object.class.equals(oClass)) ? oClass = ObjectMBean.class
115: : Loader.loadClass(oClass, mName, true);
116: if (Log.isDebugEnabled())
117: Log
118: .debug("mbeanFor " + o + " mClass="
119: + mClass);
120:
121: try {
122: Constructor constructor = mClass
123: .getConstructor(OBJ_ARG);
124: mbean = constructor
125: .newInstance(new Object[] { o });
126: } catch (Exception e) {
127: Log.ignore(e);
128: if (ModelMBean.class.isAssignableFrom(mClass)) {
129: mbean = mClass.newInstance();
130: ((ModelMBean) mbean).setManagedResource(o,
131: "objectReference");
132: }
133: }
134:
135: if (Log.isDebugEnabled())
136: Log.debug("mbeanFor " + o + " is " + mbean);
137: return mbean;
138: } catch (ClassNotFoundException e) {
139: if (e.toString().endsWith("MBean"))
140: Log.ignore(e);
141: else
142: Log.warn(e);
143: } catch (Error e) {
144: Log.warn(e);
145: mbean = null;
146: } catch (Exception e) {
147: Log.warn(e);
148: mbean = null;
149: }
150:
151: oClass = oClass.getSuperclass();
152: }
153: } catch (Exception e) {
154: Log.ignore(e);
155: }
156: return null;
157: }
158:
159: public ObjectMBean(Object managedObject) {
160: _managed = managedObject;
161: _loader = Thread.currentThread().getContextClassLoader();
162: }
163:
164: public ObjectName getObjectName() {
165: return null;
166: }
167:
168: protected void setMBeanContainer(MBeanContainer container) {
169: this ._mbeanContainer = container;
170: }
171:
172: public MBeanContainer getMBeanContainer() {
173: return this ._mbeanContainer;
174: }
175:
176: public MBeanInfo getMBeanInfo() {
177: try {
178: if (_info == null) {
179: // Start with blank lazy lists attributes etc.
180: String desc = null;
181: Object attributes = null;
182: Object constructors = null;
183: Object operations = null;
184: Object notifications = null;
185:
186: // Find list of classes that can influence the mbean
187: Class o_class = _managed.getClass();
188: Object influences = findInfluences(null, _managed
189: .getClass());
190:
191: // Set to record defined items
192: Set defined = new HashSet();
193:
194: // For each influence
195: for (int i = 0; i < LazyList.size(influences); i++) {
196: Class oClass = (Class) LazyList.get(influences, i);
197:
198: // look for a bundle defining methods
199: if (Object.class.equals(oClass))
200: oClass = ObjectMBean.class;
201: String pName = oClass.getPackage().getName();
202: String cName = oClass.getName().substring(
203: pName.length() + 1);
204: String rName = pName.replace('.', '/')
205: + "/management/" + cName + "-mbean";
206:
207: try {
208: Log.debug(rName);
209: ResourceBundle bundle = Loader
210: .getResourceBundle(o_class, rName,
211: true, Locale.getDefault());
212:
213: // Extract meta data from bundle
214: Enumeration e = bundle.getKeys();
215: while (e.hasMoreElements()) {
216: String key = (String) e.nextElement();
217: String value = bundle.getString(key);
218:
219: // Determin if key is for mbean , attribute or for operation
220: if (key.equals(cName)) {
221: // set the mbean description
222: if (desc == null)
223: desc = value;
224: } else if (key.indexOf('(') > 0) {
225: // define an operation
226: if (!defined.contains(key)
227: && key.indexOf('[') < 0) {
228: defined.add(key);
229: operations = LazyList.add(
230: operations,
231: defineOperation(key, value,
232: bundle));
233: }
234: } else {
235: // define an attribute
236: if (!defined.contains(key)) {
237: defined.add(key);
238: attributes = LazyList
239: .add(attributes,
240: defineAttribute(
241: key, value));
242: }
243: }
244: }
245:
246: } catch (MissingResourceException e) {
247: Log.ignore(e);
248: }
249: }
250:
251: _info = new MBeanInfo(o_class.getName(), desc,
252: (MBeanAttributeInfo[]) LazyList.toArray(
253: attributes, MBeanAttributeInfo.class),
254: (MBeanConstructorInfo[]) LazyList.toArray(
255: constructors,
256: MBeanConstructorInfo.class),
257: (MBeanOperationInfo[]) LazyList.toArray(
258: operations, MBeanOperationInfo.class),
259: (MBeanNotificationInfo[]) LazyList.toArray(
260: notifications,
261: MBeanNotificationInfo.class));
262: }
263: } catch (RuntimeException e) {
264: Log.warn(e);
265: throw e;
266: }
267: return _info;
268: }
269:
270: /* ------------------------------------------------------------ */
271: public Object getAttribute(String name)
272: throws AttributeNotFoundException, MBeanException,
273: ReflectionException {
274: if (Log.isDebugEnabled())
275: Log.debug("getAttribute " + name);
276: Method getter = (Method) _getters.get(name);
277: if (getter == null)
278: throw new AttributeNotFoundException(name);
279: try {
280: Object o = _managed;
281: if (getter.getDeclaringClass().isInstance(this ))
282: o = this ; // mbean method
283:
284: // get the attribute
285: Object r = getter.invoke(o, (java.lang.Object[]) null);
286:
287: // convert to ObjectName if need be.
288: if (r != null && _convert.contains(name)) {
289: if (r.getClass().isArray()) {
290: ObjectName[] on = new ObjectName[Array.getLength(r)];
291: for (int i = 0; i < on.length; i++)
292: on[i] = _mbeanContainer.findMBean(Array.get(r,
293: i));
294: r = on;
295: } else {
296: ObjectName mbean = _mbeanContainer.findMBean(r);
297: if (mbean == null)
298: return null;
299: r = mbean;
300: }
301: }
302: return r;
303: } catch (IllegalAccessException e) {
304: Log.warn(Log.EXCEPTION, e);
305: throw new AttributeNotFoundException(e.toString());
306: } catch (InvocationTargetException e) {
307: Log.warn(Log.EXCEPTION, e);
308: throw new ReflectionException((Exception) e
309: .getTargetException());
310: }
311: }
312:
313: /* ------------------------------------------------------------ */
314: public AttributeList getAttributes(String[] names) {
315: Log.debug("getAttributes");
316: AttributeList results = new AttributeList(names.length);
317: for (int i = 0; i < names.length; i++) {
318: try {
319: results.add(new Attribute(names[i],
320: getAttribute(names[i])));
321: } catch (Exception e) {
322: Log.warn(Log.EXCEPTION, e);
323: }
324: }
325: return results;
326: }
327:
328: /* ------------------------------------------------------------ */
329: public void setAttribute(Attribute attr)
330: throws AttributeNotFoundException,
331: InvalidAttributeValueException, MBeanException,
332: ReflectionException {
333: if (attr == null)
334: return;
335:
336: if (Log.isDebugEnabled())
337: Log.debug("setAttribute " + attr.getName() + "="
338: + attr.getValue());
339: Method setter = (Method) _setters.get(attr.getName());
340: if (setter == null)
341: throw new AttributeNotFoundException(attr.getName());
342: try {
343: Object o = _managed;
344: if (setter.getDeclaringClass().isInstance(this ))
345: o = this ;
346:
347: // get the value
348: Object value = attr.getValue();
349:
350: // convert from ObjectName if need be
351: if (value != null && _convert.contains(attr.getName())) {
352: if (value.getClass().isArray()) {
353: Class t = setter.getParameterTypes()[0]
354: .getComponentType();
355: Object na = Array.newInstance(t, Array
356: .getLength(value));
357: for (int i = Array.getLength(value); i-- > 0;)
358: Array.set(na, i, _mbeanContainer
359: .findBean((ObjectName) Array.get(value,
360: i)));
361: value = na;
362: } else
363: value = _mbeanContainer
364: .findBean((ObjectName) value);
365: }
366:
367: // do the setting
368: setter.invoke(o, new Object[] { value });
369: } catch (IllegalAccessException e) {
370: Log.warn(Log.EXCEPTION, e);
371: throw new AttributeNotFoundException(e.toString());
372: } catch (InvocationTargetException e) {
373: Log.warn(Log.EXCEPTION, e);
374: throw new ReflectionException((Exception) e
375: .getTargetException());
376: }
377: }
378:
379: /* ------------------------------------------------------------ */
380: public AttributeList setAttributes(AttributeList attrs) {
381: Log.debug("setAttributes");
382:
383: AttributeList results = new AttributeList(attrs.size());
384: Iterator iter = attrs.iterator();
385: while (iter.hasNext()) {
386: try {
387: Attribute attr = (Attribute) iter.next();
388: setAttribute(attr);
389: results.add(new Attribute(attr.getName(),
390: getAttribute(attr.getName())));
391: } catch (Exception e) {
392: Log.warn(Log.EXCEPTION, e);
393: }
394: }
395: return results;
396: }
397:
398: /* ------------------------------------------------------------ */
399: public Object invoke(String name, Object[] params,
400: String[] signature) throws MBeanException,
401: ReflectionException {
402: if (Log.isDebugEnabled())
403: Log.debug("invoke " + name);
404:
405: String methodKey = name + "(";
406: if (signature != null)
407: for (int i = 0; i < signature.length; i++)
408: methodKey += (i > 0 ? "," : "") + signature[i];
409: methodKey += ")";
410:
411: ClassLoader old_loader = Thread.currentThread()
412: .getContextClassLoader();
413: try {
414: Thread.currentThread().setContextClassLoader(_loader);
415: Method method = (Method) _methods.get(methodKey);
416: if (method == null)
417: throw new NoSuchMethodException(methodKey);
418:
419: Object o = _managed;
420: if (method.getDeclaringClass().isInstance(this ))
421: o = this ;
422: return method.invoke(o, params);
423: } catch (NoSuchMethodException e) {
424: Log.warn(Log.EXCEPTION, e);
425: throw new ReflectionException(e);
426: } catch (IllegalAccessException e) {
427: Log.warn(Log.EXCEPTION, e);
428: throw new MBeanException(e);
429: } catch (InvocationTargetException e) {
430: Log.warn(Log.EXCEPTION, e);
431: throw new ReflectionException((Exception) e
432: .getTargetException());
433: } finally {
434: Thread.currentThread().setContextClassLoader(old_loader);
435: }
436: }
437:
438: private static Object findInfluences(Object influences, Class aClass) {
439: if (aClass != null) {
440: // This class is an influence
441: influences = LazyList.add(influences, aClass);
442:
443: // So are the super classes
444: influences = findInfluences(influences, aClass
445: .getSuperclass());
446:
447: // So are the interfaces
448: Class[] ifs = aClass.getInterfaces();
449: for (int i = 0; ifs != null && i < ifs.length; i++)
450: influences = findInfluences(influences, ifs[i]);
451: }
452: return influences;
453: }
454:
455: /* ------------------------------------------------------------ */
456: /**
457: * Define an attribute on the managed object. The meta data is defined by looking for standard
458: * getter and setter methods. Descriptions are obtained with a call to findDescription with the
459: * attribute name.
460: *
461: * @param name
462: * @param metaData "description" or "access:description" or "type:access:description" where type is
463: * the "Object","MBean" or "MObject" to indicate the method is on the object, the MBean or on the object but converted to an MBean, access
464: * is either "RW" or "RO".
465: */
466: public MBeanAttributeInfo defineAttribute(String name,
467: String metaData) {
468: String description = "";
469: boolean writable = false;
470: boolean onMBean = false;
471: boolean convert = false;
472:
473: String[] tokens = metaData.split(":", 3);
474: if (tokens.length > 0) {
475: int index = tokens.length - 1;
476: description = tokens[index--];
477: if (index >= 0) {
478: writable = "RW".equalsIgnoreCase(tokens[index--]);
479: if (index >= 0) {
480: onMBean = "MMBean".equalsIgnoreCase(tokens[index])
481: || "MBean".equalsIgnoreCase(tokens[index]);
482: convert = "MMBean".equalsIgnoreCase(tokens[index])
483: || "MObject"
484: .equalsIgnoreCase(tokens[index]);
485: }
486: }
487: }
488:
489: String uName = name.substring(0, 1).toUpperCase()
490: + name.substring(1);
491: Class oClass = onMBean ? this .getClass() : _managed.getClass();
492:
493: if (Log.isDebugEnabled())
494: Log.debug("defineAttribute " + name + " " + onMBean + ":"
495: + writable + ":" + oClass + ":" + description);
496:
497: Class type = null;
498: Method getter = null;
499: Method setter = null;
500: Method[] methods = oClass.getMethods();
501: for (int m = 0; m < methods.length; m++) {
502: if ((methods[m].getModifiers() & Modifier.PUBLIC) == 0)
503: continue;
504:
505: // Look for a getter
506: if (methods[m].getName().equals("get" + uName)
507: && methods[m].getParameterTypes().length == 0) {
508: if (getter != null)
509: throw new IllegalArgumentException(
510: "Multiple getters for attr " + name
511: + " in " + oClass);
512: getter = methods[m];
513: if (type != null
514: && !type.equals(methods[m].getReturnType()))
515: throw new IllegalArgumentException(
516: "Type conflict for attr " + name + " in "
517: + oClass);
518: type = methods[m].getReturnType();
519: }
520:
521: // Look for an is getter
522: if (methods[m].getName().equals("is" + uName)
523: && methods[m].getParameterTypes().length == 0) {
524: if (getter != null)
525: throw new IllegalArgumentException(
526: "Multiple getters for attr " + name
527: + " in " + oClass);
528: getter = methods[m];
529: if (type != null
530: && !type.equals(methods[m].getReturnType()))
531: throw new IllegalArgumentException(
532: "Type conflict for attr " + name + " in "
533: + oClass);
534: type = methods[m].getReturnType();
535: }
536:
537: // look for a setter
538: if (writable && methods[m].getName().equals("set" + uName)
539: && methods[m].getParameterTypes().length == 1) {
540: if (setter != null)
541: throw new IllegalArgumentException(
542: "Multiple setters for attr " + name
543: + " in " + oClass);
544: setter = methods[m];
545: if (type != null
546: && !type
547: .equals(methods[m].getParameterTypes()[0]))
548: throw new IllegalArgumentException(
549: "Type conflict for attr " + name + " in "
550: + oClass);
551: type = methods[m].getParameterTypes()[0];
552: }
553: }
554:
555: if (convert && type.isPrimitive() && !type.isArray())
556: throw new IllegalArgumentException(
557: "Cannot convert primative " + name);
558:
559: if (getter == null && setter == null)
560: throw new IllegalArgumentException(
561: "No getter or setters found for " + name + " in "
562: + oClass);
563:
564: try {
565: // Remember the methods
566: _getters.put(name, getter);
567: _setters.put(name, setter);
568:
569: MBeanAttributeInfo info = null;
570: if (convert) {
571: _convert.add(name);
572: if (type.isArray())
573: info = new MBeanAttributeInfo(name,
574: OBJECT_NAME_ARRAY_CLASS, description,
575: getter != null, setter != null,
576: getter != null
577: && getter.getName()
578: .startsWith("is"));
579:
580: else
581: info = new MBeanAttributeInfo(name,
582: OBJECT_NAME_CLASS, description,
583: getter != null, setter != null,
584: getter != null
585: && getter.getName()
586: .startsWith("is"));
587: } else
588: info = new MBeanAttributeInfo(name, description,
589: getter, setter);
590:
591: return info;
592: } catch (Exception e) {
593: Log.warn(Log.EXCEPTION, e);
594: throw new IllegalArgumentException(e.toString());
595: }
596: }
597:
598: /* ------------------------------------------------------------ */
599: /**
600: * Define an operation on the managed object. Defines an operation with parameters. Refection is
601: * used to determine find the method and it's return type. The description of the method is
602: * found with a call to findDescription on "name(signature)". The name and description of each
603: * parameter is found with a call to findDescription with "name(partialSignature", the returned
604: * description is for the last parameter of the partial signature and is assumed to start with
605: * the parameter name, followed by a colon.
606: *
607: * @param metaData "description" or "impact:description" or "type:impact:description", type is
608: * the "Object","MBean" or "MObject" to indicate the method is on the object, the MBean or on the
609: * object but converted to an MBean, and impact is either "ACTION","INFO","ACTION_INFO" or "UNKNOWN".
610: */
611: private MBeanOperationInfo defineOperation(String signature,
612: String metaData, ResourceBundle bundle) {
613: String[] tokens = metaData.split(":", 3);
614: int i = tokens.length - 1;
615: String description = tokens[i--];
616: String impact_name = i < 0 ? "UNKNOWN" : tokens[i--];
617: boolean onMBean = i == 0 && "MBean".equalsIgnoreCase(tokens[0]);
618: boolean convert = i == 0
619: && "MObject".equalsIgnoreCase(tokens[0]);
620:
621: if (Log.isDebugEnabled())
622: Log.debug("defineOperation " + signature + " " + onMBean
623: + ":" + impact_name + ":" + description);
624:
625: Class oClass = onMBean ? this .getClass() : _managed.getClass();
626:
627: try {
628: // Resolve the impact
629: int impact = MBeanOperationInfo.UNKNOWN;
630: if (impact_name == null || impact_name.equals("UNKNOWN"))
631: impact = MBeanOperationInfo.UNKNOWN;
632: else if (impact_name.equals("ACTION"))
633: impact = MBeanOperationInfo.ACTION;
634: else if (impact_name.equals("INFO"))
635: impact = MBeanOperationInfo.INFO;
636: else if (impact_name.equals("ACTION_INFO"))
637: impact = MBeanOperationInfo.ACTION_INFO;
638: else
639: Log.warn("Unknown impact '" + impact_name + "' for "
640: + signature);
641:
642: // split the signature
643: String[] parts = signature.split("[\\(\\)]");
644: String method_name = parts[0];
645: String arguments = parts.length == 2 ? parts[1] : null;
646: String[] args = arguments == null ? new String[0]
647: : arguments.split(" *, *");
648:
649: // Check types and normalize signature.
650: Class[] types = new Class[args.length];
651: MBeanParameterInfo[] pInfo = new MBeanParameterInfo[args.length];
652: signature = method_name;
653: for (i = 0; i < args.length; i++) {
654: Class type = TypeUtil.fromName(args[i]);
655: if (type == null)
656: type = Thread.currentThread()
657: .getContextClassLoader().loadClass(args[i]);
658: types[i] = type;
659: args[i] = type.isPrimitive() ? TypeUtil.toName(type)
660: : args[i];
661: signature += (i > 0 ? "," : "(") + args[i];
662: }
663: signature += (i > 0 ? ")" : "()");
664:
665: // Build param infos
666: for (i = 0; i < args.length; i++) {
667: String param_desc = bundle.getString(signature + "["
668: + i + "]");
669: parts = param_desc.split(" *: *", 2);
670: if (Log.isDebugEnabled())
671: Log.debug(parts[0] + ": " + parts[1]);
672: pInfo[i] = new MBeanParameterInfo(parts[0].trim(),
673: args[i], parts[1].trim());
674: }
675:
676: // build the operation info
677: Method method = oClass.getMethod(method_name, types);
678: Class returnClass = method.getReturnType();
679: _methods.put(signature, method);
680: if (convert)
681: _convert.add(signature);
682:
683: return new MBeanOperationInfo(method_name, description,
684: pInfo, returnClass.isPrimitive() ? TypeUtil
685: .toName(returnClass) : (returnClass
686: .getName()), impact);
687: } catch (Exception e) {
688: Log.warn("Operation '" + signature + "'", e);
689: throw new IllegalArgumentException(e.toString());
690: }
691:
692: }
693:
694: }
|