1 /*
   2  * Copyright (c) 2003, 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 4898478
  27  * @summary Tests client default class loader used before JSR 160 loader
  28  * @author Eamonn McManus
  29  * @modules java.management
  30  * @library /lib/testlibrary
  31  * @run clean MethodResultTest
  32  * @run build MethodResultTest
  33  * @run main MethodResultTest
  34  */
  35 
  36 import java.io.*;
  37 import java.nio.file.Paths;
  38 import java.net.*;
  39 import java.util.*;
  40 import javax.management.*;
  41 import javax.management.remote.*;
  42 import jdk.testlibrary.Utils;
  43 
  44 /*
  45    This test checks that the class loader that is used to deserialize
  46    the return values from remote MBean server operations is indeed the
  47    one specified by the user.  The only MBean server operations that
  48    matter are those than can return an arbitrary Object.  We don't
  49    care about getMBeanCount or queryNames or whatever because their
  50    return values are always of classes loaded by the bootstrap loader.
  51    But for the operations getAttribute, getAttributes, setAttributes,
  52    and invoke, the return value can include any Java class.  This is
  53    also true of getMBeanInfo, since the return value can be an exotic
  54    subclass of MBeanInfo, or a ModelMBeanInfo that refers to an
  55    arbitrary Object.  The JMX Remote API spec requires that these
  56    return values be deserialized using the class loader supplied by
  57    the user (default is context class loader).  In particular it must
  58    not be deserialized using the system class loader, which it will be
  59    with RMI unless special precautions are taken.
  60  */
  61 public class MethodResultTest {
  62     public static void main(String[] args) throws Exception {
  63         Class<?> thisClass = MethodResultTest.class;
  64         Class<?> exoticClass = Exotic.class;
  65         String exoticClassName = Exotic.class.getName();
  66 
  67         String[] cpaths = System.getProperty("test.classes", ".")
  68                                 .split(File.pathSeparator);
  69         URL[] urls = new URL[cpaths.length];
  70         for (int i=0; i < cpaths.length; i++) {
  71             urls[i] = Paths.get(cpaths[i]).toUri().toURL();
  72         }
  73 
  74         ClassLoader shadowLoader =
  75             new ShadowLoader(urls, thisClass.getClassLoader(),
  76                              new String[] {exoticClassName,
  77                                            ExoticMBeanInfo.class.getName(),
  78                                            ExoticException.class.getName()});
  79         Class<?> cl = shadowLoader.loadClass(exoticClassName);
  80         if (cl == exoticClass) {
  81             System.out.println("TEST INVALID: Shadow class loader loaded " +
  82                                "same class as test class loader");
  83             System.exit(1);
  84         }
  85         Thread.currentThread().setContextClassLoader(shadowLoader);
  86 
  87         ObjectName on = new ObjectName("a:b=c");
  88         MBeanServer mbs = MBeanServerFactory.newMBeanServer();
  89         mbs.createMBean(Thing.class.getName(), on);
  90 
  91         final String[] protos = {"rmi", "iiop", "jmxmp"};
  92 
  93         boolean ok = true;
  94         for (int i = 0; i < protos.length; i++) {
  95             try {
  96                 ok &= test(protos[i], mbs, on);
  97                 System.out.println();
  98             } catch (Exception e) {
  99                 System.out.println("TEST FAILED WITH EXCEPTION:");
 100                 e.printStackTrace(System.out);
 101                 ok = false;
 102             }
 103         }
 104 
 105         if (ok)
 106             System.out.println("Test passed");
 107         else {
 108             System.out.println("TEST FAILED");
 109             System.exit(1);
 110         }
 111     }
 112 
 113     private static boolean test(String proto, MBeanServer mbs, ObjectName on)
 114             throws Exception {
 115         System.out.println("Testing for protocol " + proto);
 116 
 117         JMXConnectorServer cs;
 118         JMXServiceURL url = new JMXServiceURL(proto, null, 0);
 119         try {
 120             cs = JMXConnectorServerFactory.newJMXConnectorServer(url, null,
 121                                                                  mbs);
 122         } catch (MalformedURLException e) {
 123             System.out.println("System does not recognize URL: " + url +
 124                                "; ignoring");
 125             return true;
 126         }
 127         cs.start();
 128         JMXServiceURL addr = cs.getAddress();
 129         JMXConnector client = connect(addr);
 130         MBeanServerConnection mbsc = client.getMBeanServerConnection();
 131         Object getAttributeExotic = mbsc.getAttribute(on, "Exotic");
 132         AttributeList getAttrs =
 133             mbsc.getAttributes(on, new String[] {"Exotic"});
 134         AttributeList setAttrs = new AttributeList();
 135         setAttrs.add(new Attribute("Exotic", new Exotic()));
 136         setAttrs = mbsc.setAttributes(on, setAttrs);
 137         Object invokeExotic =
 138             mbsc.invoke(on, "anExotic", new Object[] {}, new String[] {});
 139         MBeanInfo exoticMBI = mbsc.getMBeanInfo(on);
 140 
 141         mbsc.setAttribute(on, new Attribute("Exception", Boolean.TRUE));
 142         Exception
 143             getAttributeException, setAttributeException, invokeException;
 144         try {
 145             try {
 146                 mbsc.getAttribute(on, "Exotic");
 147                 throw noException("getAttribute");
 148             } catch (Exception e) {
 149                 getAttributeException = e;
 150             }
 151             try {
 152                 mbsc.setAttribute(on, new Attribute("Exotic", new Exotic()));
 153                 throw noException("setAttribute");
 154             } catch (Exception e) {
 155                 setAttributeException = e;
 156             }
 157             try {
 158                 mbsc.invoke(on, "anExotic", new Object[] {}, new String[] {});
 159                 throw noException("invoke");
 160             } catch (Exception e) {
 161                 invokeException = e;
 162             }
 163         } finally {
 164             mbsc.setAttribute(on, new Attribute("Exception", Boolean.FALSE));
 165         }
 166         client.close();
 167         cs.stop();
 168 
 169         boolean ok = true;
 170 
 171         ok &= checkAttrs("getAttributes", getAttrs);
 172         ok &= checkAttrs("setAttributes", setAttrs);
 173 
 174         ok &= checkType("getAttribute", getAttributeExotic, Exotic.class);
 175         ok &= checkType("getAttributes", attrValue(getAttrs), Exotic.class);
 176         ok &= checkType("setAttributes", attrValue(setAttrs), Exotic.class);
 177         ok &= checkType("invoke", invokeExotic, Exotic.class);
 178         ok &= checkType("getMBeanInfo", exoticMBI, ExoticMBeanInfo.class);
 179 
 180         ok &= checkExceptionType("getAttribute", getAttributeException,
 181                                  ExoticException.class);
 182         ok &= checkExceptionType("setAttribute", setAttributeException,
 183                                  ExoticException.class);
 184         ok &= checkExceptionType("invoke", invokeException,
 185                                  ExoticException.class);
 186 
 187         if (ok)
 188             System.out.println("Test passes for protocol " + proto);
 189         return ok;
 190     }
 191 
 192     private static JMXConnector connect(JMXServiceURL addr) {
 193         final long timeout = Utils.adjustTimeout(100);
 194 
 195         JMXConnector connector = null;
 196         while (connector == null) {
 197             try {
 198                 connector = JMXConnectorFactory.connect(addr);
 199             } catch (IOException e) {
 200                 System.out.println("Connection error. Retrying after delay...");
 201                 delay(timeout);
 202             } catch (Exception otherException) {
 203                 System.out.println("Unexpected exception while connecting " + otherException);
 204                 throw new RuntimeException(otherException);
 205             }
 206         }
 207         return connector;
 208     }
 209 
 210     private static void delay(long ms) {
 211         try {
 212             Thread.sleep(ms);
 213         } catch (InterruptedException e) {
 214             throw new RuntimeException(e);
 215         }
 216     }
 217 
 218     private static Exception noException(String what) {
 219         final String msg =
 220             "Operation " + what + " returned when exception expected";
 221         return new IllegalStateException(msg);
 222     }
 223 
 224     private static Object attrValue(AttributeList attrs) {
 225         return ((Attribute) attrs.get(0)).getValue();
 226     }
 227 
 228     private static boolean checkType(String what, Object object,
 229                                      Class<?> wrongClass) {
 230         return checkType(what, object, wrongClass, false);
 231     }
 232 
 233     private static boolean checkType(String what, Object object,
 234                                      Class<?> wrongClass, boolean isException) {
 235         final String type = isException ? "exception" : "object";
 236         final String rendered = isException ? "thrown" : "returned";
 237         System.out.println("For " + type + " " + rendered + " by " + what +
 238                            ":");
 239         if (wrongClass.isInstance(object)) {
 240             System.out.println("TEST FAILS: " + type + " loaded by test " +
 241                                "classloader");
 242             return false;
 243         }
 244         String className = object.getClass().getName();
 245         if (!className.equals(wrongClass.getName())) {
 246             System.out.println("TEST FAILS: " + rendered + " " + type +
 247                                " has wrong class name: " + className);
 248             return false;
 249         }
 250         System.out.println("Test passes: " + rendered + " " + type +
 251                            " has same class name but is not same class");
 252         return true;
 253     }
 254 
 255     private static boolean checkExceptionType(String what, Exception exception,
 256                                               Class<?> wrongClass) {
 257         if (!(exception instanceof MBeanException)) {
 258             System.out.println("Exception thrown by " + what + " is not an " +
 259                                MBeanException.class.getName() +
 260                                ":");
 261             exception.printStackTrace(System.out);
 262             return false;
 263         }
 264 
 265         exception = ((MBeanException) exception).getTargetException();
 266 
 267         return checkType(what, exception, wrongClass, true);
 268     }
 269 
 270     private static boolean checkAttrs(String what, AttributeList attrs) {
 271         if (attrs.size() != 1) {
 272             System.out.println("TEST FAILS: list returned by " + what +
 273                                " does not have size 1: " + attrs);
 274             return false;
 275         }
 276         Attribute attr = (Attribute) attrs.get(0);
 277         if (!"Exotic".equals(attr.getName())) {
 278             System.out.println("TEST FAILS: " + what + " returned wrong " +
 279                                "attribute: " + attr);
 280             return false;
 281         }
 282 
 283         return true;
 284     }
 285 
 286     public static class Thing
 287             extends StandardMBean implements ThingMBean {
 288         public Thing() throws NotCompliantMBeanException {
 289             super(ThingMBean.class);
 290         }
 291 
 292         public Exotic getExotic() throws ExoticException {
 293             if (exception)
 294                 throw new ExoticException();
 295             return new Exotic();
 296         }
 297 
 298         public void setExotic(Exotic x) throws ExoticException {
 299             if (exception)
 300                 throw new ExoticException();
 301         }
 302 
 303         public Exotic anExotic() throws ExoticException {
 304             if (exception)
 305                 throw new ExoticException();
 306             return new Exotic();
 307         }
 308 
 309         public void cacheMBeanInfo(MBeanInfo mbi) {
 310             if (mbi != null)
 311                 mbi = new ExoticMBeanInfo(mbi);
 312             super.cacheMBeanInfo(mbi);
 313         }
 314 
 315         public void setException(boolean x) {
 316             this.exception = x;
 317         }
 318 
 319         private boolean exception;
 320     }
 321 
 322     public static interface ThingMBean {
 323         public Exotic getExotic() throws ExoticException;
 324         public void setExotic(Exotic x) throws ExoticException;
 325         public Exotic anExotic() throws ExoticException;
 326         public void setException(boolean x);
 327     }
 328 
 329     public static class Exotic implements Serializable {}
 330 
 331     public static class ExoticException extends Exception {}
 332 
 333     public static class ExoticMBeanInfo extends MBeanInfo {
 334         public ExoticMBeanInfo(MBeanInfo mbi) {
 335             super(mbi.getClassName(),
 336                   mbi.getDescription(),
 337                   mbi.getAttributes(),
 338                   mbi.getConstructors(),
 339                   mbi.getOperations(),
 340                   mbi.getNotifications());
 341         }
 342     }
 343 
 344     private static class ShadowLoader extends URLClassLoader {
 345         ShadowLoader(URL[] urls, ClassLoader realLoader,
 346                      String[] shadowClassNames) {
 347             super(urls, null);
 348             this.realLoader = realLoader;
 349             this.shadowClassNames = Arrays.asList(shadowClassNames);
 350         }
 351 
 352         protected Class<?> findClass(String name) throws ClassNotFoundException {
 353             if (shadowClassNames.contains(name))
 354                 return super.findClass(name);
 355             else
 356                 return realLoader.loadClass(name);
 357         }
 358 
 359         private final ClassLoader realLoader;
 360         private final List shadowClassNames;
 361     }
 362 }