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 }