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