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