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 }