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 }