1. /*
  2. * @(#)Introspector.java 1.68 03/12/19
  3. *
  4. * Copyright 2004 Sun Microsystems, Inc. All rights reserved.
  5. * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
  6. */
  7. package com.sun.jmx.mbeanserver;
  8. // Java import
  9. import java.lang.reflect.Constructor;
  10. import java.lang.reflect.Method;
  11. import java.lang.reflect.Modifier;
  12. import java.util.ArrayList;
  13. import java.util.List;
  14. import java.util.Iterator;
  15. // RI Import
  16. import javax.management.IntrospectionException;
  17. import javax.management.MBeanAttributeInfo;
  18. import javax.management.MBeanConstructorInfo;
  19. import javax.management.MBeanInfo;
  20. import javax.management.MBeanOperationInfo;
  21. import javax.management.MBeanParameterInfo;
  22. import javax.management.NotCompliantMBeanException;
  23. /**
  24. * This class contains the methods for performing all the tests needed to verify
  25. * that a class represents a JMX compliant MBean.
  26. *
  27. * @since 1.5
  28. */
  29. public class Introspector {
  30. /*
  31. * ------------------------------------------
  32. * PRIVATE VARIABLES
  33. * ------------------------------------------
  34. */
  35. private static final String attributeDescription =
  36. "Attribute exposed for management";
  37. private static final String operationDescription =
  38. "Operation exposed for management";
  39. private static final String constructorDescription =
  40. "Public constructor of the MBean";
  41. private static final String mbeanInfoDescription =
  42. "Information on the management interface of the MBean";
  43. /*
  44. * ------------------------------------------
  45. * PRIVATE CONSTRUCTORS
  46. * ------------------------------------------
  47. */
  48. // private constructor defined to "hide" the default public constructor
  49. private Introspector() {
  50. // ------------------------------
  51. // ------------------------------
  52. }
  53. /*
  54. * ------------------------------------------
  55. * PUBLIC METHODS
  56. * ------------------------------------------
  57. */
  58. /**
  59. * Tell whether a MBean of the given class is a Dynamic MBean.
  60. * This method does nothing more than returning
  61. * <pre>
  62. * javax.management.DynamicMBean.class.isAssignableFrom(c)
  63. * </pre>
  64. * This method does not check for any JMX MBean compliance:
  65. * <ul><li>If <code>true</code> is returned, then instances of
  66. * <code>c</code> are DynamicMBean.</li>
  67. * <li>If <code>false</code> is returned, then no further
  68. * assumption can be made on instances of <code>c</code>.
  69. * In particular, instances of <code>c</code> may, or may not
  70. * be JMX standard MBeans.</li>
  71. * </ul>
  72. * @param c The class of the MBean under examination.
  73. * @return <code>true</code> if instances of <code>c</code> are
  74. * Dynamic MBeans, <code>false</code> otherwise.
  75. *
  76. * @since.unbundled JMX RI 1.2
  77. **/
  78. public static final boolean isDynamic(final Class c) {
  79. // Check if the MBean implements the DynamicMBean interface
  80. return javax.management.DynamicMBean.class.isAssignableFrom(c);
  81. }
  82. /**
  83. * Basic method for testing that a MBean of a given class can be
  84. * instantiated by the MBean server.<p>
  85. * This method checks that:
  86. * <ul><li>The given class is a concrete class.</li>
  87. * <li>The given class exposes at least one public constructor.</li>
  88. * </ul>
  89. * If these conditions are not met, throws a NotCompliantMBeanException.
  90. * @param c The class of the MBean we want to create.
  91. * @exception NotCompliantMBeanException if the MBean class makes it
  92. * impossible to instantiate the MBean from within the
  93. * MBeanServer.
  94. *
  95. * @since.unbundled JMX RI 1.2
  96. **/
  97. public static void testCreation(Class c)
  98. throws NotCompliantMBeanException {
  99. // Check if the class is a concrete class
  100. final int mods = c.getModifiers();
  101. if (Modifier.isAbstract(mods) || Modifier.isInterface(mods)) {
  102. throw new NotCompliantMBeanException("MBean class must be concrete");
  103. }
  104. // Check if the MBean has a public constructor
  105. final Constructor[] consList = c.getConstructors();
  106. if (consList.length == 0) {
  107. throw new NotCompliantMBeanException("MBean class must have public constructor");
  108. }
  109. }
  110. /**
  111. * Basic method for testing if a given class is a JMX compliant MBean.
  112. *
  113. * @param baseClass The class to be tested
  114. *
  115. * @return <code>null</code> if the MBean is a DynamicMBean,
  116. * the computed {@link javax.management.MBeanInfo} otherwise.
  117. * @exception NotCompliantMBeanException The specified class is not a
  118. * JMX compliant MBean
  119. */
  120. public static MBeanInfo testCompliance(Class baseClass)
  121. throws NotCompliantMBeanException {
  122. // ------------------------------
  123. // ------------------------------
  124. // Check if the MBean implements the MBean or the Dynamic
  125. // MBean interface
  126. if (isDynamic(baseClass))
  127. return null;
  128. return testCompliance(baseClass, null);
  129. }
  130. /**
  131. * Basic method for testing if a given class is a JMX compliant MBean.
  132. *
  133. * @param baseClass The class to be tested
  134. *
  135. * @return <code>null</code> if the MBean is a DynamicMBean,
  136. * the computed {@link javax.management.MBeanInfo} otherwise.
  137. * @exception NotCompliantMBeanException The specified class is not a
  138. * JMX compliant MBean
  139. */
  140. static MBeanInfo testCompliance(final Class baseClass,
  141. Class mbeanInterface)
  142. throws NotCompliantMBeanException {
  143. if (baseClass.isInterface())
  144. throw new NotCompliantMBeanException(baseClass.getName() +
  145. " must be a class.");
  146. // ------------------------------
  147. // ------------------------------
  148. if (mbeanInterface == null)
  149. // No interface specified: look for default MBean interface.
  150. mbeanInterface = getStandardMBeanInterface(baseClass);
  151. else if (! mbeanInterface.isAssignableFrom(baseClass)) {
  152. // specified interface not implemented by given class
  153. final String msg =
  154. baseClass.getName() + " does not implement the " +
  155. mbeanInterface.getName() + " interface";
  156. throw new NotCompliantMBeanException(msg);
  157. } else if (! mbeanInterface.isInterface()) {
  158. // Base class X, but XMBean is not an interface
  159. final String msg =
  160. baseClass.getName() + ": " + mbeanInterface.getName() +
  161. " is not an interface";
  162. throw new NotCompliantMBeanException(msg);
  163. }
  164. if (mbeanInterface == null) {
  165. // Error: MBean does not implement javax.management.DynamicMBean
  166. // nor MBean interface
  167. final String baseClassName = baseClass.getName();
  168. final String msg =
  169. baseClassName + " does not implement the " + baseClassName +
  170. "MBean interface or the DynamicMBean interface";
  171. throw new NotCompliantMBeanException(msg);
  172. }
  173. final int mods = mbeanInterface.getModifiers();
  174. if (!Modifier.isPublic(mods))
  175. throw new NotCompliantMBeanException(mbeanInterface.getName() +
  176. " implemented by " +
  177. baseClass.getName() +
  178. " must be public");
  179. return (introspect(baseClass, mbeanInterface));
  180. }
  181. /**
  182. * Get the MBean interface implemented by a JMX standard MBean
  183. * class.
  184. *
  185. * @param baseClass The class to be tested
  186. *
  187. * @return The MBean interface implemented by the MBean.
  188. * Return <code>null</code> if the MBean is a DynamicMBean,
  189. * or if no MBean interface is found.
  190. *
  191. */
  192. public static Class getMBeanInterface(Class baseClass) {
  193. // ------------------------------
  194. // ------------------------------
  195. // Check if the MBean implements the MBean or the Dynamic
  196. // MBean interface
  197. if (isDynamic(baseClass)) return null;
  198. return getStandardMBeanInterface(baseClass);
  199. }
  200. /**
  201. * Get the MBean interface implemented by a JMX standard MBean
  202. * class.
  203. *
  204. * @param baseClass The class to be tested
  205. *
  206. * @return The MBean interface implemented by the MBean.
  207. * Return <code>null</code> if no MBean interface is found.
  208. * Does not check whether the MBean is a DynamicMBean.
  209. *
  210. */
  211. static Class getStandardMBeanInterface(Class baseClass) {
  212. // ------------------------------
  213. // ------------------------------
  214. Class current = baseClass;
  215. Class mbeanInterface = null;
  216. while (current != null) {
  217. mbeanInterface =
  218. findMBeanInterface(current, current.getName());
  219. if (mbeanInterface != null) break;
  220. current = current.getSuperclass();
  221. }
  222. return mbeanInterface;
  223. }
  224. /*
  225. * ------------------------------------------
  226. * PRIVATE METHODS
  227. * ------------------------------------------
  228. */
  229. /**
  230. * Try to find the MBean interface corresponding to the class aName
  231. * - i.e. <i>aName</i>MBean, from within aClass and its superclasses.
  232. **/
  233. private static Class findMBeanInterface(Class aClass, String aName) {
  234. Class current = aClass;
  235. while (current != null) {
  236. final Class[] interfaces = current.getInterfaces();
  237. final int len = interfaces.length;
  238. for (int i=0;i<len;i++) {
  239. final Class inter =
  240. implementsMBean(interfaces[i], aName);
  241. if (inter != null) return inter;
  242. }
  243. current = current.getSuperclass();
  244. }
  245. return null;
  246. }
  247. /**
  248. * Discovers the getters, setters, operations of the class
  249. *
  250. * @param baseClass The XX base class.
  251. * @param beanClass The XXMBean interface implemented by the tested class.
  252. *
  253. * @exception NotCompliantMBeanException The tested class is not a
  254. * JMX compliant MBean
  255. */
  256. private static MBeanInfo introspect(Class baseClass, Class beanClass)
  257. throws NotCompliantMBeanException {
  258. // ------------------------------
  259. // ------------------------------
  260. List/*<MBeanAttributeInfo>*/ attributes =
  261. new ArrayList/*<MBeanAttributeInfo>*/();
  262. List/*<MBeanOperationInfo>*/ operations =
  263. new ArrayList/*<MBeanOperationInfo>*/();
  264. Method methodList[] = beanClass.getMethods();
  265. // Now analyze each method.
  266. for (int i = 0; i < methodList.length; i++) {
  267. Method method = methodList[i];
  268. String name = method.getName();
  269. Class argTypes[] = method.getParameterTypes();
  270. Class resultType = method.getReturnType();
  271. int argCount = argTypes.length;
  272. try {
  273. final MBeanAttributeInfo attr;
  274. if (name.startsWith("get") && !name.equals("get")
  275. && argCount == 0 && !resultType.equals(void.class)) {
  276. // if the method is "T getX()" it is a getter
  277. attr = new MBeanAttributeInfo(name.substring(3),
  278. attributeDescription,
  279. method, null);
  280. } else if (name.startsWith("set") && !name.equals("set")
  281. && argCount == 1 && resultType.equals(void.class)) {
  282. // if the method is "void setX(T x)" it is a setter
  283. attr = new MBeanAttributeInfo(name.substring(3),
  284. attributeDescription,
  285. null, method);
  286. } else if (name.startsWith("is") && !name.equals("is")
  287. && argCount == 0
  288. && resultType.equals(boolean.class)) {
  289. // if the method is "boolean isX()" it is a getter
  290. attr = new MBeanAttributeInfo(name.substring(2),
  291. attributeDescription,
  292. method, null);
  293. } else {
  294. // in all other cases it is an operation
  295. attr = null;
  296. }
  297. if (attr != null) {
  298. if (testConsistency(attributes, attr))
  299. attributes.add(attr);
  300. } else {
  301. final MBeanOperationInfo oper =
  302. new MBeanOperationInfo(operationDescription, method);
  303. operations.add(oper);
  304. }
  305. } catch (IntrospectionException e) {
  306. // Should not happen (MBeanAttributeInfo constructor)
  307. error("introspect", e);
  308. }
  309. }
  310. return constructResult(baseClass, attributes, operations);
  311. }
  312. /**
  313. * Checks if the types and the signatures of
  314. * getters/setters/operations are conform to the MBean design
  315. * patterns.
  316. *
  317. * Error cases:
  318. * - It exposes a method void Y getXX() AND a method void setXX(Z)
  319. * (parameter type mismatch)
  320. * - It exposes a method void setXX(Y) AND a method void setXX(Z)
  321. * (parameter type mismatch)
  322. * - It exposes a boolean isXX() method AND a YY getXX() or a void setXX(Y).
  323. * Returns false if the attribute is already in attributes List
  324. */
  325. private static boolean testConsistency(List/*<MBeanAttributeInfo>*/attributes,
  326. MBeanAttributeInfo attr)
  327. throws NotCompliantMBeanException {
  328. for (Iterator it = attributes.iterator(); it.hasNext(); ) {
  329. MBeanAttributeInfo mb = (MBeanAttributeInfo) it.next();
  330. if (mb.getName().equals(attr.getName())) {
  331. if ((attr.isReadable() && mb.isReadable()) &&
  332. (attr.isIs() != mb.isIs())) {
  333. final String msg =
  334. "Conflicting getters for attribute " + mb.getName();
  335. throw new NotCompliantMBeanException(msg);
  336. }
  337. if (!mb.getType().equals(attr.getType())) {
  338. if (mb.isWritable() && attr.isWritable()) {
  339. final String msg =
  340. "Type mismatch between parameters of set" +
  341. mb.getName() + " methods";
  342. throw new NotCompliantMBeanException(msg);
  343. } else {
  344. final String msg =
  345. "Type mismatch between parameters of get or is" +
  346. mb.getName() + ", set" + mb.getName() + " methods";
  347. throw new NotCompliantMBeanException(msg);
  348. }
  349. }
  350. if (attr.isReadable() && mb.isReadable()) {
  351. return false;
  352. }
  353. if (attr.isWritable() && mb.isWritable()) {
  354. return false;
  355. }
  356. }
  357. }
  358. return true;
  359. }
  360. /**
  361. * Discovers the constructors of the MBean
  362. */
  363. static MBeanConstructorInfo[] getConstructors(Class baseClass) {
  364. Constructor[] consList = baseClass.getConstructors();
  365. List constructors = new ArrayList();
  366. // Now analyze each Constructor.
  367. for (int i = 0; i < consList.length; i++) {
  368. Constructor constructor = consList[i];
  369. MBeanConstructorInfo mc = null;
  370. try {
  371. mc = new MBeanConstructorInfo(constructorDescription, constructor);
  372. } catch (Exception ex) {
  373. mc = null;
  374. }
  375. if (mc != null) {
  376. constructors.add(mc);
  377. }
  378. }
  379. // Allocate and populate the result array.
  380. MBeanConstructorInfo[] resultConstructors =
  381. new MBeanConstructorInfo[constructors.size()];
  382. constructors.toArray(resultConstructors);
  383. return resultConstructors;
  384. }
  385. /**
  386. * Constructs the MBeanInfo of the MBean.
  387. */
  388. private static MBeanInfo constructResult(Class baseClass,
  389. List/*<MBeanAttributeInfo>*/ attributes,
  390. List/*<MBeanOperationInfo>*/ operations) {
  391. final int len = attributes.size();
  392. final MBeanAttributeInfo[] attrlist = new MBeanAttributeInfo[len];
  393. attributes.toArray(attrlist);
  394. final ArrayList mergedAttributes = new ArrayList();
  395. for (int i=0;i<len;i++) {
  396. final MBeanAttributeInfo bi = attrlist[i];
  397. // bi can be null if it has already been eliminated
  398. // by the loop below at an earlier iteration
  399. // (cf. attrlist[j]=null;) In this case, just skip it.
  400. //
  401. if (bi == null) continue;
  402. // Placeholder for the final attribute info we're going to
  403. // keep.
  404. //
  405. MBeanAttributeInfo att = bi;
  406. // The loop below will try to find whether bi is also present
  407. // elsewhere further down the list.
  408. // If it is not, att will be left unchanged.
  409. // Otherwise, the found attribute info will be merged with
  410. // att and `removed' from the array by setting them to `null'
  411. //
  412. for (int j=i+1;j<len;j++) {
  413. MBeanAttributeInfo mi = attrlist[j];
  414. // mi can be null if it has already been eliminated
  415. // by this loop at an earlier iteration.
  416. // (cf. attrlist[j]=null;) In this case, just skip it.
  417. //
  418. if (mi == null) continue;
  419. if ((mi.getName().compareTo(bi.getName()) == 0)) {
  420. // mi and bi have the same name, which means that
  421. // that the attribute has been inserted twice in
  422. // the list, which means that it is a read-write
  423. // attribute.
  424. // So we're going to replace att with a new
  425. // attribute info with read-write mode.
  426. // We also set attrlist[j] to null in order to avoid
  427. // duplicates (attrlist[j] and attrlist[i] are now
  428. // merged into att).
  429. //
  430. attrlist[j]=null;
  431. att = new MBeanAttributeInfo(bi.getName(),
  432. bi.getType(),
  433. attributeDescription,
  434. true, true, bi.isIs());
  435. // I think we could break, but it is probably
  436. // safer not to...
  437. //
  438. // break;
  439. }
  440. }
  441. // Now all attributes info which had the same name than bi
  442. // have been merged together in att.
  443. // Simply add att to the merged list.
  444. //
  445. mergedAttributes.add(att);
  446. }
  447. final MBeanAttributeInfo[] resultAttributes =
  448. new MBeanAttributeInfo[mergedAttributes.size()];
  449. mergedAttributes.toArray(resultAttributes);
  450. final MBeanOperationInfo[] resultOperations =
  451. new MBeanOperationInfo[operations.size()];
  452. operations.toArray(resultOperations);
  453. final MBeanConstructorInfo[] resultConstructors =
  454. getConstructors(baseClass);
  455. final MBeanInfo resultMBeanInfo =
  456. new MBeanInfo(baseClass.getName(), mbeanInfoDescription,
  457. resultAttributes, resultConstructors,
  458. resultOperations, null);
  459. return resultMBeanInfo;
  460. }
  461. /**
  462. * Returns the XXMBean interface or null if no such interface exists
  463. *
  464. * @param c The interface to be tested
  465. * @param clName The name of the class implementing this interface
  466. */
  467. static Class implementsMBean(Class c, String clName) {
  468. if (c.getName().compareTo(clName + "MBean") == 0) {
  469. return c;
  470. }
  471. Class current = c;
  472. Class[] interfaces = c.getInterfaces();
  473. for (int i = 0;i < interfaces.length; i++) {
  474. try {
  475. if (interfaces[i].getName().compareTo(clName + "MBean") == 0) {
  476. return interfaces[i];
  477. }
  478. } catch (Exception e) {
  479. return null;
  480. }
  481. }
  482. return null;
  483. }
  484. private static void error(String method,Throwable t) {
  485. com.sun.jmx.trace.Trace.send(com.sun.jmx.trace.Trace.LEVEL_ERROR,
  486. com.sun.jmx.trace.Trace.INFO_MBEANSERVER,
  487. "Introspector",
  488. method,
  489. t);
  490. }
  491. }