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 4910428
27 * @summary Tests target MBean class loader used before JSR 160 loader
28 * @author Eamonn McManus
29 *
30 * @run clean TargetMBeanTest
31 * @run build TargetMBeanTest
32 * @run main TargetMBeanTest
33 */
34
35/*
36  The JSR 160 spec says that, when invoking a method (or setting an
37  attribute or creating) on a target MBean, that MBean's class loader
38  is used to deserialize parameters.  The problem is that the RMI
39  connector protocol wraps these parameters as MarshalledObjects.
40  When you call get() on a MarshalledObject, the context class loader
41  is used to deserialize, so if we set this to the target MBean's
42  class loader everything should work.  EXCEPT that MarshalledObject
43  first tries to load classes using the first class loader it finds in
44  the caller's stack.  If our JSR 160 implementation is part of J2SE,
45  it will not find any such class loader (only the system class
46  loader).  But if it's standalone, then it will find the class loader
47  of the JSR 160 implementation.  If the class name of a parameter is
48  known to both the 160 loader and the target MBean loader, then we
49  will use the wrong loader for deserialization and the attempt to
50  invoke the target MBean with the deserialized object will fail.
51
52  We test this as follows.  We fabricate an MLet that has the same set
53  of URLs as the 160 class loader, which we assume is the system class
54  loader (or at least, it is a URLClassLoader).  This MLet is
55  therefore a "shadow class loader" -- for every class name known to
56  the 160 class loader, it can load the same name, but the result is
57  not the same class, since it has not been loaded by the same loader.
58  Then, we use the MLet to create an RMIConnectorServer MBean.  This
59  MBean is an instance of "shadow RMIConnectorServer", and its
60  constructor has a parameter of type "shadow JMXServiceURL".  If the
61  constructor is invoked with "real JMXServiceURL" it will fail.
62
63  While we are at it, we also test that the behaviour is correct for
64  the JMXMP protocol, if that optional protocol is present.
65 */
66import java.lang.reflect.*;
67import java.net.*;
68import java.util.*;
69import javax.management.*;
70import javax.management.loading.*;
71import javax.management.remote.*;
72import javax.management.remote.rmi.RMIConnectorServer;
73
74public class TargetMBeanTest {
75    private static final ObjectName mletName;
76    static {
77        try {
78            mletName = new ObjectName("x:type=mlet");
79        } catch (Exception e) {
80            e.printStackTrace();
81            throw new Error();
82        }
83    }
84
85    public static void main(String[] args) throws Exception {
86        System.out.println("Test that target MBean class loader is used " +
87                           "before JMX Remote API class loader");
88
89        ClassLoader jmxRemoteClassLoader =
90            JMXServiceURL.class.getClassLoader();
91        if (jmxRemoteClassLoader == null) {
92            System.out.println("JMX Remote API loaded by bootstrap " +
93                               "class loader, this test is irrelevant");
94            return;
95        }
96        if (!(jmxRemoteClassLoader instanceof URLClassLoader)) {
97            System.out.println("TEST INVALID: JMX Remote API not loaded by " +
98                               "URLClassLoader");
99            System.exit(1);
100        }
101
102        URLClassLoader jrcl = (URLClassLoader) jmxRemoteClassLoader;
103        URL[] urls = jrcl.getURLs();
104        PrivateMLet mlet = new PrivateMLet(urls, null, false);
105        Class shadowClass = mlet.loadClass(JMXServiceURL.class.getName());
106        if (shadowClass == JMXServiceURL.class) {
107            System.out.println("TEST INVALID: MLet got original " +
108                               "JMXServiceURL not shadow");
109            System.exit(1);
110        }
111
112        MBeanServer mbs = MBeanServerFactory.newMBeanServer();
113        mbs.registerMBean(mlet, mletName);
114
115        final String[] protos = {"rmi", "iiop", "jmxmp"};
116        boolean ok = true;
117        for (int i = 0; i < protos.length; i++) {
118            try {
119                ok &= test(protos[i], mbs);
120            } catch (Exception e) {
121                System.out.println("TEST FAILED WITH EXCEPTION:");
122                e.printStackTrace(System.out);
123                ok = false;
124            }
125        }
126
127        if (ok)
128            System.out.println("Test passed");
129        else {
130            System.out.println("TEST FAILED");
131            System.exit(1);
132        }
133    }
134
135    private static boolean test(String proto, MBeanServer mbs)
136            throws Exception {
137        System.out.println("Testing for proto " + proto);
138
139        JMXConnectorServer cs;
140        JMXServiceURL url = new JMXServiceURL(proto, null, 0);
141        try {
142            cs = JMXConnectorServerFactory.newJMXConnectorServer(url, null,
143                                                                 mbs);
144        } catch (MalformedURLException e) {
145            System.out.println("System does not recognize URL: " + url +
146                               "; ignoring");
147            return true;
148        }
149        cs.start();
150        JMXServiceURL addr = cs.getAddress();
151        JMXServiceURL rmiurl = new JMXServiceURL("rmi", null, 0);
152        JMXConnector client = JMXConnectorFactory.connect(addr);
153        MBeanServerConnection mbsc = client.getMBeanServerConnection();
154        ObjectName on = new ObjectName("x:proto=" + proto + ",ok=yes");
155        mbsc.createMBean(RMIConnectorServer.class.getName(),
156                         on,
157                         mletName,
158                         new Object[] {rmiurl, null},
159                         new String[] {JMXServiceURL.class.getName(),
160                                       Map.class.getName()});
161        System.out.println("Successfully deserialized with " + proto);
162        mbsc.unregisterMBean(on);
163
164        client.close();
165        cs.stop();
166        return true;
167    }
168}
169