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 }