1 /* 2 * Copyright (c) 2004, 2015, 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 * @modules java.management.rmi 31 * @run clean NotificationInfoTest 32 * @run build NotificationInfoTest 33 * @run main NotificationInfoTest 34 */ 35 36 import java.io.*; 37 import java.lang.management.*; 38 import java.lang.reflect.*; 39 import java.net.*; 40 import java.nio.file.FileSystem; 41 import java.nio.file.FileSystems; 42 import java.nio.file.Files; 43 import java.nio.file.Path; 44 import java.nio.file.Paths; 45 import java.util.*; 46 import java.util.stream.Collectors; 47 import javax.management.*; 48 import javax.management.relation.*; 49 import javax.management.remote.*; 50 import javax.management.remote.rmi.*; 51 52 /* 53 * This test finds all classes in the same code-base as the JMX 54 * classes that look like Standard MBeans, and checks that if they are 55 * NotificationBroadcasters they declare existent notification types. 56 * A class looks like a Standard MBean if both Thing and ThingMBean 57 * classes exist. So for example javax.management.timer.Timer looks 58 * like a Standard MBean because javax.management.timer.TimerMBean 59 * exists. Timer is instanceof NotificationBroadcaster, so we expect 60 * that ((NotificationBroadcaster) timer).getNotificationInfo() will 61 * return an array of MBeanNotificationInfo where each entry has a 62 * getName() that names an existent Java class that is a Notification. 63 * 64 * An MBean is "suspicious" if it is a NotificationBroadcaster but its 65 * MBeanNotificationInfo[] is empty. This is legal, but surprising. 66 * 67 * In order to call getNotificationInfo(), we need an instance of the 68 * class. We attempt to make one by calling a public no-arg 69 * constructor. But the "construct" method below can be extended to 70 * construct specific MBean classes for which the no-arg constructor 71 * doesn't exist. 72 * 73 * The test is obviously not exhaustive, but does catch the cases that 74 * failed in 5012634. 75 */ 76 public class NotificationInfoTest { 77 // class or object names where the test failed 78 private static final Set<String> failed = new TreeSet<String>(); 79 80 // class or object names where there were no MBeanNotificationInfo entries 81 private static final Set<String> suspicious = new TreeSet<String>(); 82 83 public static void main(String[] args) throws Exception { 84 System.out.println("Checking that all known MBeans that are " + 85 "NotificationBroadcasters have sane " + 86 "MBeanInfo.getNotifications()"); 87 88 System.out.println("Checking platform MBeans..."); 89 checkPlatformMBeans(); 90 91 URL codeBase; 92 String home = System.getProperty("java.home"); 93 Path classFile = Paths.get(home, "modules", "java.management"); 94 if (Files.isDirectory(classFile)) { 95 codeBase = classFile.toUri().toURL(); 96 } else { 97 codeBase = URI.create("jrt:/java.management").toURL(); 98 } 99 100 System.out.println(); 101 System.out.println("Looking for standard MBeans..."); 102 String[] classes = findStandardMBeans(codeBase); 103 104 System.out.println("Testing standard MBeans..."); 105 for (int i = 0; i < classes.length; i++) { 106 String name = classes[i]; 107 Class<?> c; 108 try { 109 c = Class.forName(name); 110 } catch (Throwable e) { 111 System.out.println(name + ": cannot load (not public?): " + e); 112 continue; 113 } 114 if (!NotificationBroadcaster.class.isAssignableFrom(c)) { 115 System.out.println(name + ": not a NotificationBroadcaster"); 116 continue; 117 } 118 if (Modifier.isAbstract(c.getModifiers())) { 119 System.out.println(name + ": abstract class"); 120 continue; 121 } 122 123 NotificationBroadcaster mbean; 124 Constructor<?> constr; 125 try { 126 constr = c.getConstructor(); 127 } catch (Exception e) { 128 System.out.println(name + ": no public no-arg constructor: " 129 + e); 130 continue; 131 } 132 try { 133 mbean = (NotificationBroadcaster) constr.newInstance(); 134 } catch (Exception e) { 135 System.out.println(name + ": no-arg constructor failed: " + e); 136 continue; 137 } 138 139 check(mbean); 140 } 141 142 System.out.println(); 143 System.out.println("Testing some explicit cases..."); 144 145 check(new RelationService(false)); 146 /* 147 We can't do this: 148 check(new RequiredModelMBean()); 149 because the Model MBean spec more or less forces us to use the 150 names GENERIC and ATTRIBUTE_CHANGE for its standard notifs. 151 */ 152 checkRMIConnectorServer(); 153 154 System.out.println(); 155 if (!suspicious.isEmpty()) 156 System.out.println("SUSPICIOUS CLASSES: " + suspicious); 157 158 if (failed.isEmpty()) 159 System.out.println("TEST PASSED"); 160 else { 161 System.out.println("TEST FAILED: " + failed); 162 System.exit(1); 163 } 164 } 165 166 private static void check(NotificationBroadcaster mbean) 167 throws Exception { 168 System.out.print(mbean.getClass().getName() + ": "); 169 170 check(mbean.getClass().getName(), mbean.getNotificationInfo()); 171 } 172 173 private static void checkPlatformMBeans() throws Exception { 174 MBeanServer mbs = ManagementFactory.getPlatformMBeanServer(); 175 Set<ObjectName> mbeanNames = mbs.queryNames(null, null); 176 for (ObjectName name : mbeanNames) { 177 if (!mbs.isInstanceOf(name, 178 NotificationBroadcaster.class.getName())) { 179 System.out.println(name + ": not a NotificationBroadcaster"); 180 } else { 181 MBeanInfo mbi = mbs.getMBeanInfo(name); 182 check(name.toString(), mbi.getNotifications()); 183 } 184 } 185 } 186 187 private static void checkRMIConnectorServer() throws Exception { 188 JMXServiceURL url = new JMXServiceURL("service:jmx:rmi://"); 189 RMIConnectorServer connector = new RMIConnectorServer(url, null); 190 check(connector); 191 } 192 193 private static void check(String what, MBeanNotificationInfo[] mbnis) { 194 System.out.print(what + ": checking notification info: "); 195 196 if (mbnis.length == 0) { 197 System.out.println("NONE (suspicious)"); 198 suspicious.add(what); 199 return; 200 } 201 202 // Each MBeanNotificationInfo.getName() should be an existent 203 // Java class that is Notification or a subclass of it 204 for (int j = 0; j < mbnis.length; j++) { 205 String notifClassName = mbnis[j].getName(); 206 Class notifClass; 207 try { 208 notifClass = Class.forName(notifClassName); 209 } catch (Exception e) { 210 System.out.print("FAILED(" + notifClassName + ": " + e + 211 ") "); 212 failed.add(what); 213 continue; 214 } 215 if (!Notification.class.isAssignableFrom(notifClass)) { 216 System.out.print("FAILED(" + notifClassName + 217 ": not a Notification) "); 218 failed.add(what); 219 continue; 220 } 221 System.out.print("OK(" + notifClassName + ") "); 222 } 223 System.out.println(); 224 } 225 226 private static String[] findStandardMBeans(URL codeBase) throws Exception { 227 Set<String> names; 228 if (codeBase.getProtocol().equalsIgnoreCase("jrt")) { 229 names = findStandardMBeansFromRuntime(); 230 } else { 231 names = findStandardMBeansFromDir(codeBase); 232 } 233 234 Set<String> standardMBeanNames = new TreeSet<String>(); 235 for (String name : names) { 236 if (name.endsWith("MBean")) { 237 String prefix = name.substring(0, name.length() - 5); 238 if (names.contains(prefix)) 239 standardMBeanNames.add(prefix); 240 } 241 } 242 return standardMBeanNames.toArray(new String[0]); 243 } 244 245 private static Set<String> findStandardMBeansFromRuntime() throws Exception { 246 FileSystem fs = FileSystems.getFileSystem(URI.create("jrt:/")); 247 Path modules = fs.getPath("/modules"); 248 return Files.walk(modules) 249 .filter(path -> path.toString().endsWith(".class")) 250 .map(path -> path.subpath(2, path.getNameCount())) 251 .map(Path::toString) 252 .map(s -> s.substring(0, s.length() - 6)) // drop .class 253 .filter(s -> !s.equals("module-info")) 254 .map(s -> s.replace('/', '.')) 255 .collect(Collectors.toSet()); 256 } 257 258 private static Set<String> findStandardMBeansFromDir(URL codeBase) 259 throws Exception { 260 File dir = new File(new URI(codeBase.toString())); 261 Set<String> names = new TreeSet<String>(); 262 scanDir(dir, "", names); 263 return names; 264 } 265 266 private static void scanDir(File dir, String prefix, Set<String> names) 267 throws Exception { 268 File[] files = dir.listFiles(); 269 if (files == null) 270 return; 271 for (int i = 0; i < files.length; i++) { 272 File f = files[i]; 273 String name = f.getName(); 274 String p = (prefix.equals("")) ? name : prefix + "." + name; 275 if (f.isDirectory()) 276 scanDir(f, p, names); 277 else if (name.endsWith(".class")) { 278 p = p.substring(0, p.length() - 6); 279 names.add(p); 280 } 281 } 282 } 283 }