Path: blob/master/test/jdk/javax/management/remote/mandatory/loading/MissingClassTest.java
41159 views
/*1* Copyright (c) 2003, 2018, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation.7*8* This code is distributed in the hope that it will be useful, but WITHOUT9* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or10* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License11* version 2 for more details (a copy is included in the LICENSE file that12* accompanied this code).13*14* You should have received a copy of the GNU General Public License version15* 2 along with this work; if not, write to the Free Software Foundation,16* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.17*18* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA19* or visit www.oracle.com if you need additional information or have any20* questions.21*/2223/*24* @test25* @bug 4915825 4921009 4934965 4977469 801958426* @key randomness27* @summary Tests behavior when client or server gets object of unknown class28* @author Eamonn McManus29*30* @run clean MissingClassTest SingleClassLoader31* @run build MissingClassTest SingleClassLoader32* @run main MissingClassTest33*/3435/*36Tests that clients and servers react correctly when they receive37objects of unknown classes. This can happen easily due to version38skew or missing jar files on one end or the other. The default39behaviour of causing a connection to die because of the resultant40IOException is not acceptable! We try sending attributes and invoke41parameters to the server of classes it doesn't know, and we try42sending attributes, exceptions and notifications to the client of43classes it doesn't know.4445We also test objects that are of known class but not serializable.46The test cases are similar.47*/4849import java.io.ByteArrayOutputStream;50import java.io.IOException;51import java.io.NotSerializableException;52import java.io.ObjectOutputStream;53import java.net.MalformedURLException;54import java.util.HashMap;55import java.util.Map;56import java.util.Random;57import java.util.Set;58import javax.management.Attribute;59import javax.management.MBeanServer;60import javax.management.MBeanServerConnection;61import javax.management.MBeanServerFactory;62import javax.management.Notification;63import javax.management.NotificationBroadcasterSupport;64import javax.management.NotificationFilter;65import javax.management.NotificationListener;66import javax.management.ObjectName;67import javax.management.remote.JMXConnectionNotification;68import javax.management.remote.JMXConnector;69import javax.management.remote.JMXConnectorFactory;70import javax.management.remote.JMXConnectorServer;71import javax.management.remote.JMXConnectorServerFactory;72import javax.management.remote.JMXServiceURL;73import javax.management.remote.rmi.RMIConnectorServer;7475public class MissingClassTest {76private static final int NNOTIFS = 50;7778private static ClassLoader clientLoader, serverLoader;79private static Object serverUnknown;80private static Exception clientUnknown;81private static ObjectName on;82private static final Object[] NO_OBJECTS = new Object[0];83private static final String[] NO_STRINGS = new String[0];8485private static final Object unserializableObject = Thread.currentThread();8687private static boolean isInstance(Object o, String cn) {88try {89Class<?> c = Class.forName(cn);90return c.isInstance(o);91} catch (ClassNotFoundException x) {92return false;93}94}9596public static void main(String[] args) throws Exception {97System.out.println("Test that the client or server end of a " +98"connection does not fail if sent an object " +99"it cannot deserialize");100101on = new ObjectName("test:type=Test");102103ClassLoader testLoader = MissingClassTest.class.getClassLoader();104clientLoader =105new SingleClassLoader("$ServerUnknown$", HashMap.class,106testLoader);107serverLoader =108new SingleClassLoader("$ClientUnknown$", Exception.class,109testLoader);110serverUnknown =111clientLoader.loadClass("$ServerUnknown$").newInstance();112clientUnknown = (Exception)113serverLoader.loadClass("$ClientUnknown$").newInstance();114115final String[] protos = {"rmi", /*"iiop",*/ "jmxmp"};116boolean ok = true;117for (int i = 0; i < protos.length; i++) {118try {119ok &= test(protos[i]);120} catch (Exception e) {121System.out.println("TEST FAILED WITH EXCEPTION:");122e.printStackTrace(System.out);123ok = false;124}125}126127if (ok)128System.out.println("Test passed");129else {130throw new RuntimeException("TEST FAILED");131}132}133134private static boolean test(String proto) throws Exception {135System.out.println("Testing for proto " + proto);136137boolean ok = true;138139MBeanServer mbs = MBeanServerFactory.newMBeanServer();140mbs.createMBean(Test.class.getName(), on);141142JMXConnectorServer cs;143JMXServiceURL url = new JMXServiceURL(proto, null, 0);144Map<String,Object> serverMap = new HashMap<>();145serverMap.put(JMXConnectorServerFactory.DEFAULT_CLASS_LOADER,146serverLoader);147148// make sure no auto-close at server side149serverMap.put("jmx.remote.x.server.connection.timeout", "888888888");150151try {152cs = JMXConnectorServerFactory.newJMXConnectorServer(url,153serverMap,154mbs);155} catch (MalformedURLException e) {156System.out.println("System does not recognize URL: " + url +157"; ignoring");158return true;159}160cs.start();161JMXServiceURL addr = cs.getAddress();162Map<String,Object> clientMap = new HashMap<>();163clientMap.put(JMXConnectorFactory.DEFAULT_CLASS_LOADER,164clientLoader);165166System.out.println("Connecting for client-unknown test");167168JMXConnector client = JMXConnectorFactory.connect(addr, clientMap);169170// add a listener to verify no failed notif171CNListener cnListener = new CNListener();172client.addConnectionNotificationListener(cnListener, null, null);173174MBeanServerConnection mbsc = client.getMBeanServerConnection();175176System.out.println("Getting attribute with class unknown to client");177try {178Object result = mbsc.getAttribute(on, "ClientUnknown");179System.out.println("TEST FAILS: getAttribute for class " +180"unknown to client should fail, returned: " +181result);182ok = false;183} catch (IOException e) {184Throwable cause = e.getCause();185if (cause instanceof ClassNotFoundException) {186System.out.println("Success: got an IOException wrapping " +187"a ClassNotFoundException");188} else {189System.out.println("TEST FAILS: Caught IOException (" + e +190") but cause should be " +191"ClassNotFoundException: " + cause);192ok = false;193}194}195196System.out.println("Doing queryNames to ensure connection alive");197Set<ObjectName> names = mbsc.queryNames(null, null);198System.out.println("queryNames returned " + names);199200System.out.println("Provoke exception of unknown class");201try {202mbsc.invoke(on, "throwClientUnknown", NO_OBJECTS, NO_STRINGS);203System.out.println("TEST FAILS: did not get exception");204ok = false;205} catch (IOException e) {206Throwable wrapped = e.getCause();207if (wrapped instanceof ClassNotFoundException) {208System.out.println("Success: got an IOException wrapping " +209"a ClassNotFoundException: " +210wrapped);211} else {212System.out.println("TEST FAILS: Got IOException but cause " +213"should be ClassNotFoundException: ");214if (wrapped == null)215System.out.println("(null)");216else217wrapped.printStackTrace(System.out);218ok = false;219}220} catch (Exception e) {221System.out.println("TEST FAILS: Got wrong exception: " +222"should be IOException with cause " +223"ClassNotFoundException:");224e.printStackTrace(System.out);225ok = false;226}227228System.out.println("Doing queryNames to ensure connection alive");229names = mbsc.queryNames(null, null);230System.out.println("queryNames returned " + names);231232ok &= notifyTest(client, mbsc);233234System.out.println("Doing queryNames to ensure connection alive");235names = mbsc.queryNames(null, null);236System.out.println("queryNames returned " + names);237238for (int i = 0; i < 2; i++) {239boolean setAttribute = (i == 0); // else invoke240String what = setAttribute ? "setAttribute" : "invoke";241System.out.println("Trying " + what +242" with class unknown to server");243try {244if (setAttribute) {245mbsc.setAttribute(on, new Attribute("ServerUnknown",246serverUnknown));247} else {248mbsc.invoke(on, "useServerUnknown",249new Object[] {serverUnknown},250new String[] {"java.lang.Object"});251}252System.out.println("TEST FAILS: " + what + " with " +253"class unknown to server should fail " +254"but did not");255ok = false;256} catch (IOException e) {257Throwable cause = e.getCause();258if (cause instanceof ClassNotFoundException) {259System.out.println("Success: got an IOException " +260"wrapping a ClassNotFoundException");261} else {262System.out.println("TEST FAILS: Caught IOException (" + e +263") but cause should be " +264"ClassNotFoundException: " + cause);265e.printStackTrace(System.out); // XXX266ok = false;267}268}269}270271System.out.println("Doing queryNames to ensure connection alive");272names = mbsc.queryNames(null, null);273System.out.println("queryNames returned " + names);274275System.out.println("Trying to get unserializable attribute");276try {277mbsc.getAttribute(on, "Unserializable");278System.out.println("TEST FAILS: get unserializable worked " +279"but should not");280ok = false;281} catch (IOException e) {282System.out.println("Success: got an IOException: " + e +283" (cause: " + e.getCause() + ")");284}285286System.out.println("Doing queryNames to ensure connection alive");287names = mbsc.queryNames(null, null);288System.out.println("queryNames returned " + names);289290System.out.println("Trying to set unserializable attribute");291try {292Attribute attr =293new Attribute("Unserializable", unserializableObject);294mbsc.setAttribute(on, attr);295System.out.println("TEST FAILS: set unserializable worked " +296"but should not");297ok = false;298} catch (IOException e) {299System.out.println("Success: got an IOException: " + e +300" (cause: " + e.getCause() + ")");301}302303System.out.println("Doing queryNames to ensure connection alive");304names = mbsc.queryNames(null, null);305System.out.println("queryNames returned " + names);306307System.out.println("Trying to throw unserializable exception");308try {309mbsc.invoke(on, "throwUnserializable", NO_OBJECTS, NO_STRINGS);310System.out.println("TEST FAILS: throw unserializable worked " +311"but should not");312ok = false;313} catch (IOException e) {314System.out.println("Success: got an IOException: " + e +315" (cause: " + e.getCause() + ")");316}317318client.removeConnectionNotificationListener(cnListener);319ok = ok && !cnListener.failed;320321client.close();322cs.stop();323324if (ok)325System.out.println("Test passed for protocol " + proto);326327System.out.println();328return ok;329}330331private static class TestListener implements NotificationListener {332TestListener(LostListener ll) {333this.lostListener = ll;334}335336public void handleNotification(Notification n, Object h) {337/* Connectors can handle unserializable notifications in338one of two ways. Either they can arrange for the339client to get a NotSerializableException from its340fetchNotifications call (RMI connector), or they can341replace the unserializable notification by a342JMXConnectionNotification.NOTIFS_LOST (JMXMP343connector). The former case is handled by code within344the connector client which will end up sending a345NOTIFS_LOST to our LostListener. The logic here346handles the latter case by converting it into the347former.348*/349if (n instanceof JMXConnectionNotification350&& n.getType().equals(JMXConnectionNotification.NOTIFS_LOST)) {351lostListener.handleNotification(n, h);352return;353}354355synchronized (result) {356if (!n.getType().equals("interesting")357|| !n.getUserData().equals("known")) {358System.out.println("TestListener received strange notif: "359+ notificationString(n));360result.failed = true;361result.notifyAll();362} else {363result.knownCount++;364if (result.knownCount == NNOTIFS)365result.notifyAll();366}367}368}369370private LostListener lostListener;371}372373private static class LostListener implements NotificationListener {374public void handleNotification(Notification n, Object h) {375synchronized (result) {376handle(n, h);377}378}379380private void handle(Notification n, Object h) {381if (!(n instanceof JMXConnectionNotification)) {382System.out.println("LostListener received strange notif: " +383notificationString(n));384result.failed = true;385result.notifyAll();386return;387}388389JMXConnectionNotification jn = (JMXConnectionNotification) n;390if (!jn.getType().equals(jn.NOTIFS_LOST)) {391System.out.println("Ignoring JMXConnectionNotification: " +392notificationString(jn));393return;394}395final String msg = jn.getMessage();396if ((!msg.startsWith("Dropped ")397|| !msg.endsWith("classes were missing locally"))398&& (!msg.startsWith("Not serializable: "))) {399System.out.println("Surprising NOTIFS_LOST getMessage: " +400msg);401}402if (!(jn.getUserData() instanceof Long)) {403System.out.println("JMXConnectionNotification userData " +404"not a Long: " + jn.getUserData());405result.failed = true;406} else {407int lost = ((Long) jn.getUserData()).intValue();408result.lostCount += lost;409if (result.lostCount == NNOTIFS*2)410result.notifyAll();411}412}413}414415private static class TestFilter implements NotificationFilter {416public boolean isNotificationEnabled(Notification n) {417return (n.getType().equals("interesting"));418}419}420421private static class Result {422int knownCount, lostCount;423boolean failed;424}425private static Result result;426427/* Send a bunch of notifications to exercise the logic to recover428from unknown notification classes. We have four kinds of429notifications: "known" ones are of a class known to the client430and which match its filters; "unknown" ones are of a class that431match the client's filters but that the client can't load;432"tricky" ones are unserializable; and "boring" notifications433are of a class that the client knows but that doesn't match its434filters. We emit NNOTIFS notifications of each kind. We do a435random shuffle on these 4*NNOTIFS notifications so it is likely436that we will cover the various different cases in the logic.437438Specifically, what we are testing here is the logic that439recovers from a fetchNotifications request that gets a440ClassNotFoundException. Because the request can contain441several notifications, the client doesn't know which of them442generated the exception. So it redoes a request that asks for443just one notification. We need to be sure that this works when444that one notification is of an unknown class and when it is of445a known class, and in both cases when there are filtered446notifications that are skipped.447448We are also testing the behaviour in the server when it tries449to include an unserializable notification in the response to a450fetchNotifications, and in the client when that happens.451452If the test succeeds, the listener should receive the NNOTIFS453"known" notifications, and the connection listener should454receive an indication of NNOTIFS lost notifications455representing the "unknown" notifications.456457We depend on some implementation-specific features here:4584591. The buffer size is sufficient to contain the 4*NNOTIFS460notifications which are all sent at once, before the client461gets a chance to start receiving them.4624632. When one or more notifications are dropped because they are464of unknown classes, the NOTIFS_LOST notification contains a465userData that is a Long with a count of the number dropped.4664673. If a notification is not serializable on the server, the468client gets told about it somehow, rather than having it just469dropped on the floor. The somehow might be through an RMI470exception, or it might be by the server replacing the471unserializable notif by a JMXConnectionNotification.NOTIFS_LOST.472*/473private static boolean notifyTest(JMXConnector client,474MBeanServerConnection mbsc)475throws Exception {476System.out.println("Send notifications including unknown ones");477result = new Result();478LostListener ll = new LostListener();479client.addConnectionNotificationListener(ll, null, null);480TestListener nl = new TestListener(ll);481mbsc.addNotificationListener(on, nl, new TestFilter(), null);482mbsc.invoke(on, "sendNotifs", NO_OBJECTS, NO_STRINGS);483484// wait for the listeners to receive all their notifs485// or to fail486long deadline = System.currentTimeMillis() + 60000;487long remain;488while ((remain = deadline - System.currentTimeMillis()) >= 0) {489synchronized (result) {490if (result.failed491|| (result.knownCount >= NNOTIFS492&& result.lostCount >= NNOTIFS*2))493break;494result.wait(remain);495}496}497Thread.sleep(2); // allow any spurious extra notifs to arrive498if (result.failed) {499System.out.println("TEST FAILS: Notification strangeness");500return false;501} else if (result.knownCount == NNOTIFS502&& result.lostCount == NNOTIFS*2) {503System.out.println("Success: received known notifications and " +504"got NOTIFS_LOST for unknown and " +505"unserializable ones");506return true;507} else if (result.knownCount >= NNOTIFS508|| result.lostCount >= NNOTIFS*2) {509System.out.println("TEST FAILS: Received too many notifs: " +510"known=" + result.knownCount + "; lost=" + result.lostCount);511return false;512} else {513System.out.println("TEST FAILS: Timed out without receiving " +514"all notifs: known=" + result.knownCount +515"; lost=" + result.lostCount);516return false;517}518}519520public static interface TestMBean {521public Object getClientUnknown() throws Exception;522public void throwClientUnknown() throws Exception;523public void setServerUnknown(Object o) throws Exception;524public void useServerUnknown(Object o) throws Exception;525public Object getUnserializable() throws Exception;526public void setUnserializable(Object un) throws Exception;527public void throwUnserializable() throws Exception;528public void sendNotifs() throws Exception;529}530531public static class Test extends NotificationBroadcasterSupport532implements TestMBean {533534public Object getClientUnknown() {535return clientUnknown;536}537538public void throwClientUnknown() throws Exception {539throw clientUnknown;540}541542public void setServerUnknown(Object o) {543throw new IllegalArgumentException("setServerUnknown succeeded "+544"but should not have");545}546547public void useServerUnknown(Object o) {548throw new IllegalArgumentException("useServerUnknown succeeded "+549"but should not have");550}551552public Object getUnserializable() {553return unserializableObject;554}555556public void setUnserializable(Object un) {557throw new IllegalArgumentException("setUnserializable succeeded " +558"but should not have");559}560561public void throwUnserializable() throws Exception {562throw new Exception() {563private Object unserializable = unserializableObject;564};565}566567public void sendNotifs() {568/* We actually send the same four notification objects569NNOTIFS times each. This doesn't particularly matter,570but note that the MBeanServer will replace "this" by571the sender's ObjectName the first time. Since that's572always the same, no problem. */573Notification known =574new Notification("interesting", this, 1L, 1L, "known");575known.setUserData("known");576Notification unknown =577new Notification("interesting", this, 1L, 1L, "unknown");578unknown.setUserData(clientUnknown);579Notification boring =580new Notification("boring", this, 1L, 1L, "boring");581Notification tricky =582new Notification("interesting", this, 1L, 1L, "tricky");583tricky.setUserData(unserializableObject);584585// check that the tricky notif is indeed unserializable586try {587new ObjectOutputStream(new ByteArrayOutputStream())588.writeObject(tricky);589throw new RuntimeException("TEST INCORRECT: tricky notif is " +590"serializable");591} catch (NotSerializableException e) {592// OK: tricky notif is not serializable593} catch (IOException e) {594throw new RuntimeException("TEST INCORRECT: tricky notif " +595"serialization check failed");596}597598/* Now shuffle an imaginary deck of cards where K, U, T, and599B (known, unknown, tricky, boring) each appear NNOTIFS times.600We explicitly seed the random number generator so we601can reproduce rare test failures if necessary. We only602use a StringBuffer so we can print the shuffled deck --603otherwise we could just emit the notifications as the604cards are placed. */605long seed = System.currentTimeMillis();606System.out.println("Random number seed is " + seed);607Random r = new Random(seed);608int knownCount = NNOTIFS; // remaining K cards609int unknownCount = NNOTIFS; // remaining U cards610int trickyCount = NNOTIFS; // remaining T cards611int boringCount = NNOTIFS; // remaining B cards612StringBuffer notifList = new StringBuffer();613for (int i = NNOTIFS * 4; i > 0; i--) {614int rand = r.nextInt(i);615// use rand to pick a card from the remaining ones616if ((rand -= knownCount) < 0) {617notifList.append('k');618knownCount--;619} else if ((rand -= unknownCount) < 0) {620notifList.append('u');621unknownCount--;622} else if ((rand -= trickyCount) < 0) {623notifList.append('t');624trickyCount--;625} else {626notifList.append('b');627boringCount--;628}629}630if (knownCount != 0 || unknownCount != 0631|| trickyCount != 0 || boringCount != 0) {632throw new RuntimeException("TEST INCORRECT: Shuffle failed: " +633"known=" + knownCount +" unknown=" +634unknownCount + " tricky=" + trickyCount +635" boring=" + boringCount +636" deal=" + notifList);637}638String notifs = notifList.toString();639System.out.println("Shuffle: " + notifs);640for (int i = 0; i < NNOTIFS * 4; i++) {641Notification n;642switch (notifs.charAt(i)) {643case 'k': n = known; break;644case 'u': n = unknown; break;645case 't': n = tricky; break;646case 'b': n = boring; break;647default:648throw new RuntimeException("TEST INCORRECT: Bad shuffle char: " +649notifs.charAt(i));650}651sendNotification(n);652}653}654}655656private static String notificationString(Notification n) {657return n.getClass().getName() + "/" + n.getType() + " \"" +658n.getMessage() + "\" <" + n.getUserData() + ">";659}660661//662private static class CNListener implements NotificationListener {663public void handleNotification(Notification n, Object o) {664if (n instanceof JMXConnectionNotification) {665JMXConnectionNotification jn = (JMXConnectionNotification)n;666if (JMXConnectionNotification.FAILED.equals(jn.getType())) {667failed = true;668}669}670}671672public boolean failed = false;673}674}675676677