1 /*
2  * Copyright (c) 2003, 2018, 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 /test/lib
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.test.lib.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 {
main(String[] args)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 
test(String proto, MBeanServer mbs, ObjectName on)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 
connect(JMXServiceURL addr)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 
delay(long ms)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 
noException(String what)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 
attrValue(AttributeList attrs)225     private static Object attrValue(AttributeList attrs) {
226         return ((Attribute) attrs.get(0)).getValue();
227     }
228 
checkType(String what, Object object, Class<?> wrongClass)229     private static boolean checkType(String what, Object object,
230                                      Class<?> wrongClass) {
231         return checkType(what, object, wrongClass, false);
232     }
233 
checkType(String what, Object object, Class<?> wrongClass, boolean isException)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 
checkExceptionType(String what, Exception exception, Class<?> wrongClass)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 
checkAttrs(String what, AttributeList attrs)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 {
Thing()289         public Thing() throws NotCompliantMBeanException {
290             super(ThingMBean.class);
291         }
292 
getExotic()293         public Exotic getExotic() throws ExoticException {
294             if (exception)
295                 throw new ExoticException();
296             return new Exotic();
297         }
298 
setExotic(Exotic x)299         public void setExotic(Exotic x) throws ExoticException {
300             if (exception)
301                 throw new ExoticException();
302         }
303 
anExotic()304         public Exotic anExotic() throws ExoticException {
305             if (exception)
306                 throw new ExoticException();
307             return new Exotic();
308         }
309 
cacheMBeanInfo(MBeanInfo mbi)310         public void cacheMBeanInfo(MBeanInfo mbi) {
311             if (mbi != null)
312                 mbi = new ExoticMBeanInfo(mbi);
313             super.cacheMBeanInfo(mbi);
314         }
315 
setException(boolean x)316         public void setException(boolean x) {
317             this.exception = x;
318         }
319 
320         private boolean exception;
321     }
322 
323     public static interface ThingMBean {
getExotic()324         public Exotic getExotic() throws ExoticException;
setExotic(Exotic x)325         public void setExotic(Exotic x) throws ExoticException;
anExotic()326         public Exotic anExotic() throws ExoticException;
setException(boolean x)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 {
ExoticMBeanInfo(MBeanInfo mbi)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 {
ShadowLoader(URL[] urls, ClassLoader realLoader, String[] shadowClassNames)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 
findClass(String name)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 }
364