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