1 /* 2 * Copyright (c) 2004, 2008, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. 8 * 9 * This code is distributed in the hope that it will be useful, but WITHOUT 10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 12 * version 2 for more details (a copy is included in the LICENSE file that 13 * accompanied this code). 14 * 15 * You should have received a copy of the GNU General Public License version 16 * 2 along with this work; if not, write to the Free Software Foundation, 17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 18 * 19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 20 * or visit www.oracle.com if you need additional information or have any 21 * questions. 22 */ 23 24 /* 25 * @test 26 * @bug 5012634 27 * @summary Test that JMX classes use fully-qualified class names 28 * in MBeanNotificationInfo 29 * @author Eamonn McManus 30 * @run clean NotificationInfoTest 31 * @run build NotificationInfoTest 32 * @run main NotificationInfoTest 33 */ 34 35 import java.io.*; 36 import java.lang.management.*; 37 import java.lang.reflect.*; 38 import java.net.*; 39 import java.util.*; 40 import java.util.jar.*; 41 import javax.management.*; 42 import javax.management.relation.*; 43 import javax.management.remote.*; 44 import javax.management.remote.rmi.*; 45 46 /* 47 * This test finds all classes in the same code-base as the JMX 48 * classes that look like Standard MBeans, and checks that if they are 49 * NotificationBroadcasters they declare existent notification types. 50 * A class looks like a Standard MBean if both Thing and ThingMBean 51 * classes exist. So for example javax.management.timer.Timer looks 52 * like a Standard MBean because javax.management.timer.TimerMBean 53 * exists. Timer is instanceof NotificationBroadcaster, so we expect 54 * that ((NotificationBroadcaster) timer).getNotificationInfo() will 55 * return an array of MBeanNotificationInfo where each entry has a 56 * getName() that names an existent Java class that is a Notification. 57 * 58 * An MBean is "suspicious" if it is a NotificationBroadcaster but its 59 * MBeanNotificationInfo[] is empty. This is legal, but surprising. 60 * 61 * In order to call getNotificationInfo(), we need an instance of the 62 * class. We attempt to make one by calling a public no-arg 63 * constructor. But the "construct" method below can be extended to 64 * construct specific MBean classes for which the no-arg constructor 65 * doesn't exist. 66 * 67 * The test is obviously not exhaustive, but does catch the cases that 68 * failed in 5012634. 69 */ 70 public class NotificationInfoTest { 71 // class or object names where the test failed 72 private static final Set<String> failed = new TreeSet<String>(); 73 74 // class or object names where there were no MBeanNotificationInfo entries 75 private static final Set<String> suspicious = new TreeSet<String>(); 76 77 public static void main(String[] args) throws Exception { 78 System.out.println("Checking that all known MBeans that are " + 79 "NotificationBroadcasters have sane " + 80 "MBeanInfo.getNotifications()"); 81 82 System.out.println("Checking platform MBeans..."); 83 checkPlatformMBeans(); 84 85 URL codeBase = ClassLoader.getSystemResource("javax/management/MBeanServer.class"); 86 if (codeBase == null) { 87 throw new Exception("Could not determine codeBase for " + MBeanServer.class); 88 } 89 90 System.out.println(); 91 System.out.println("Looking for standard MBeans..."); 92 String[] classes = findStandardMBeans(codeBase); 93 94 System.out.println("Testing standard MBeans..."); 95 for (int i = 0; i < classes.length; i++) { 96 String name = classes[i]; 97 Class<?> c; 98 try { 99 c = Class.forName(name); 100 } catch (Throwable e) { 101 System.out.println(name + ": cannot load (not public?): " + e); 102 continue; 103 } 104 if (!NotificationBroadcaster.class.isAssignableFrom(c)) { 105 System.out.println(name + ": not a NotificationBroadcaster"); 106 continue; 107 } 108 if (Modifier.isAbstract(c.getModifiers())) { 109 System.out.println(name + ": abstract class"); 110 continue; 111 } 112 113 NotificationBroadcaster mbean; 114 Constructor<?> constr; 115 try { 116 constr = c.getConstructor(); 117 } catch (Exception e) { 118 System.out.println(name + ": no public no-arg constructor: " 119 + e); 120 continue; 121 } 122 try { 123 mbean = (NotificationBroadcaster) constr.newInstance(); 124 } catch (Exception e) { 125 System.out.println(name + ": no-arg constructor failed: " + e); 126 continue; 127 } 128 129 check(mbean); 130 } 131 132 System.out.println(); 133 System.out.println("Testing some explicit cases..."); 134 135 check(new RelationService(false)); 136 /* 137 We can't do this: 138 check(new RequiredModelMBean()); 139 because the Model MBean spec more or less forces us to use the 140 names GENERIC and ATTRIBUTE_CHANGE for its standard notifs. 141 */ 142 checkRMIConnectorServer(); 143 144 System.out.println(); 145 if (!suspicious.isEmpty()) 146 System.out.println("SUSPICIOUS CLASSES: " + suspicious); 147 148 if (failed.isEmpty()) 149 System.out.println("TEST PASSED"); 150 else { 151 System.out.println("TEST FAILED: " + failed); 152 System.exit(1); 153 } 154 } 155 156 private static void check(NotificationBroadcaster mbean) 157 throws Exception { 158 System.out.print(mbean.getClass().getName() + ": "); 159 160 check(mbean.getClass().getName(), mbean.getNotificationInfo()); 161 } 162 163 private static void checkPlatformMBeans() throws Exception { 164 MBeanServer mbs = ManagementFactory.getPlatformMBeanServer(); 165 Set<ObjectName> mbeanNames = mbs.queryNames(null, null); 166 for (ObjectName name : mbeanNames) { 167 if (!mbs.isInstanceOf(name, 168 NotificationBroadcaster.class.getName())) { 169 System.out.println(name + ": not a NotificationBroadcaster"); 170 } else { 171 MBeanInfo mbi = mbs.getMBeanInfo(name); 172 check(name.toString(), mbi.getNotifications()); 173 } 174 } 175 } 176 177 private static void checkRMIConnectorServer() throws Exception { 178 JMXServiceURL url = new JMXServiceURL("service:jmx:rmi://"); 179 RMIConnectorServer connector = new RMIConnectorServer(url, null); 180 check(connector); 181 } 182 183 private static void check(String what, MBeanNotificationInfo[] mbnis) { 184 System.out.print(what + ": checking notification info: "); 185 186 if (mbnis.length == 0) { 187 System.out.println("NONE (suspicious)"); 188 suspicious.add(what); 189 return; 190 } 191 192 // Each MBeanNotificationInfo.getName() should be an existent 193 // Java class that is Notification or a subclass of it 194 for (int j = 0; j < mbnis.length; j++) { 195 String notifClassName = mbnis[j].getName(); 196 Class notifClass; 197 try { 198 notifClass = Class.forName(notifClassName); 199 } catch (Exception e) { 200 System.out.print("FAILED(" + notifClassName + ": " + e + 201 ") "); 202 failed.add(what); 203 continue; 204 } 205 if (!Notification.class.isAssignableFrom(notifClass)) { 206 System.out.print("FAILED(" + notifClassName + 207 ": not a Notification) "); 208 failed.add(what); 209 continue; 210 } 211 System.out.print("OK(" + notifClassName + ") "); 212 } 213 System.out.println(); 214 } 215 216 private static String[] findStandardMBeans(URL codeBase) 217 throws Exception { 218 Set<String> names; 219 if (codeBase.getProtocol().equalsIgnoreCase("file") 220 && codeBase.toString().endsWith("/")) 221 names = findStandardMBeansFromDir(codeBase); 222 else 223 names = findStandardMBeansFromJar(codeBase); 224 225 Set<String> standardMBeanNames = new TreeSet<String>(); 226 for (String name : names) { 227 if (name.endsWith("MBean")) { 228 String prefix = name.substring(0, name.length() - 5); 229 if (names.contains(prefix)) 230 standardMBeanNames.add(prefix); 231 } 232 } 233 return standardMBeanNames.toArray(new String[0]); 234 } 235 236 private static Set<String> findStandardMBeansFromJar(URL codeBase) 237 throws Exception { 238 InputStream is = codeBase.openStream(); 239 JarInputStream jis = new JarInputStream(is); 240 Set<String> names = new TreeSet<String>(); 241 JarEntry entry; 242 while ((entry = jis.getNextJarEntry()) != null) { 243 String name = entry.getName(); 244 if (!name.endsWith(".class")) 245 continue; 246 name = name.substring(0, name.length() - 6); 247 name = name.replace('/', '.'); 248 names.add(name); 249 } 250 return names; 251 } 252 253 private static Set<String> findStandardMBeansFromDir(URL codeBase) 254 throws Exception { 255 File dir = new File(new URI(codeBase.toString())); 256 Set<String> names = new TreeSet<String>(); 257 scanDir(dir, "", names); 258 return names; 259 } 260 261 private static void scanDir(File dir, String prefix, Set<String> names) 262 throws Exception { 263 File[] files = dir.listFiles(); 264 if (files == null) 265 return; 266 for (int i = 0; i < files.length; i++) { 267 File f = files[i]; 268 String name = f.getName(); 269 String p = (prefix.equals("")) ? name : prefix + "." + name; 270 if (f.isDirectory()) 271 scanDir(f, p, names); 272 else if (name.endsWith(".class")) { 273 p = p.substring(0, p.length() - 6); 274 names.add(p); 275 } 276 } 277 } 278 }