MethodResultTest.java revision 2362:00cd9dc3c2b5
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
34import java.io.*;
35import java.net.*;
36import java.util.*;
37import javax.management.*;
38import 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 */
57public 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}
334