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  */
  66 import java.lang.reflect.*;
  67 import java.net.*;
  68 import java.util.*;
  69 import javax.management.*;
  70 import javax.management.loading.*;
  71 import javax.management.remote.*;
  72 import javax.management.remote.rmi.RMIConnectorServer;
  73 
  74 public 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 }