1 /*
   2  * Copyright (c) 2008, 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 6730926
  27  * @summary Check behaviour of MBeanServer when postRegister and postDeregister
  28  *          throw exceptions.
  29  * @author Daniel Fuchs
  30  * @modules java.management
  31  * @compile PostExceptionTest.java
  32  * @run main PostExceptionTest
  33  */
  34 
  35 import javax.management.*;
  36 import java.io.Serializable;
  37 import java.net.URL;
  38 import java.util.EnumSet;
  39 import javax.management.loading.MLet;
  40 
  41 public class PostExceptionTest {
  42 
  43     /**
  44      * A test case where we instantiate an ExceptionalWombatMBean (or a
  45      * subclass of it) which will throw the exception {@code t} from within
  46      * the methods indicated by {@code where}
  47      */
  48     public static class Case {
  49         public final Throwable t;
  50         public final EnumSet<WHERE> where;
  51         public Case(Throwable t,EnumSet<WHERE> where) {
  52             this.t=t; this.where=where;
  53         }
  54     }
  55 
  56     // Various methods to create an instance of Case in a single line
  57     // --------------------------------------------------------------
  58 
  59     public static Case caze(Throwable t, WHERE w) {
  60         return new Case(t,EnumSet.of(w));
  61     }
  62     public static Case caze(Throwable t, EnumSet<WHERE> where) {
  63         return new Case(t,where);
  64     }
  65     public static Case caze(Throwable t, WHERE w, WHERE... rest) {
  66         return new Case(t,EnumSet.of(w,rest));
  67     }
  68 
  69     /**
  70      * Here is the list of our test cases:
  71      */
  72     public static Case[] cases ={
  73         caze(new RuntimeException(),WHERE.PREREGISTER),
  74         caze(new RuntimeException(),WHERE.POSTREGISTER),
  75         caze(new RuntimeException(),WHERE.POSTREGISTER, WHERE.PREDEREGISTER),
  76         caze(new RuntimeException(),WHERE.POSTREGISTER, WHERE.POSTDEREGISTER),
  77         caze(new Exception(),WHERE.PREREGISTER),
  78         caze(new Exception(),WHERE.POSTREGISTER),
  79         caze(new Exception(),WHERE.POSTREGISTER, WHERE.PREDEREGISTER),
  80         caze(new Exception(),WHERE.POSTREGISTER, WHERE.POSTDEREGISTER),
  81         caze(new Error(),WHERE.PREREGISTER),
  82         caze(new Error(),WHERE.POSTREGISTER),
  83         caze(new Error(),WHERE.POSTREGISTER, WHERE.PREDEREGISTER),
  84         caze(new Error(),WHERE.POSTREGISTER, WHERE.POSTDEREGISTER),
  85         caze(new RuntimeException(),EnumSet.allOf(WHERE.class)),
  86         caze(new Exception(),EnumSet.allOf(WHERE.class)),
  87         caze(new Error(),EnumSet.allOf(WHERE.class)),
  88     };
  89 
  90     public static void main(String[] args) throws Exception {
  91         System.out.println("Test behaviour of MBeanServer when postRegister " +
  92                 "or postDeregister throw exceptions");
  93         MBeanServer mbs = MBeanServerFactory.newMBeanServer();
  94         int failures = 0;
  95         final ObjectName n = new ObjectName("test:type=Wombat");
  96 
  97         // We're going to test each cases, using each of the 4 createMBean
  98         // forms + registerMBean in turn to create the MBean.
  99         // Wich method is used to create the MBean is indicated by "how"
 100         //
 101         for (Case caze:cases) {
 102             for (CREATE how : CREATE.values()) {
 103                 failures+=test(mbs,n,how,caze.t,caze.where);
 104             }
 105         }
 106         if (failures == 0)
 107             System.out.println("Test passed");
 108         else {
 109             System.out.println("TEST FAILED: " + failures + " failure(s)");
 110             System.exit(1);
 111         }
 112     }
 113 
 114     // Execute a test case composed of:
 115     // mbs:   The MBeanServer where the MBean will be registered,
 116     // name:  The name of that MBean
 117     // how:   How will the MBean be created/registered (which MBeanServer
 118     //        method)
 119     // t:     The exception/error that the MBean will throw
 120     // where: In which pre/post register/deregister method the exception/error
 121     //        will be thrown
 122     //
 123     private static int test(MBeanServer mbs, ObjectName name, CREATE how,
 124             Throwable t, EnumSet<WHERE> where)
 125             throws Exception {
 126         System.out.println("-------<"+how+"> / <"+t+"> / "+ where + "-------");
 127 
 128         int failures = 0;
 129         ObjectInstance oi = null;
 130         Exception reg = null;    // exception thrown by create/register
 131         Exception unreg = null;  // exception thrown by unregister
 132         try {
 133             // Create the MBean
 134             oi = how.create(t, where, mbs, name);
 135         } catch (Exception xx) {
 136             reg=xx;
 137         }
 138         final ObjectName n = (oi==null)?name:oi.getObjectName();
 139         final boolean isRegistered = mbs.isRegistered(n);
 140         try {
 141             // If the MBean is registered, unregister it
 142             if (isRegistered) mbs.unregisterMBean(n);
 143         } catch (Exception xxx) {
 144             unreg=xxx;
 145         }
 146         final boolean isUnregistered = !mbs.isRegistered(n);
 147         if (!isUnregistered) {
 148             // if the MBean is still registered (preDeregister threw an
 149             // exception) signify to the MBean that it now should stop
 150             // throwing anaything and unregister it.
 151             JMX.newMBeanProxy(mbs, n, ExceptionalWombatMBean.class).end();
 152             mbs.unregisterMBean(n);
 153         }
 154 
 155         // Now analyze the result. If we didn't ask the MBean to throw any
 156         // exception then reg should be null.
 157         if (where.isEmpty() && reg!=null) {
 158             System.out.println("Unexpected registration exception: "+
 159                     reg);
 160             throw new RuntimeException("Unexpected registration exception: "+
 161                     reg,reg);
 162         }
 163 
 164         // If we didn't ask the MBean to throw any exception then unreg should
 165         // also be null.
 166         if (where.isEmpty() && unreg!=null) {
 167             System.out.println("Unexpected unregistration exception: "+
 168                     unreg);
 169             throw new RuntimeException("Unexpected unregistration exception: "+
 170                     unreg,unreg);
 171         }
 172 
 173         // If we asked the MBean to throw an exception in either of preRegister
 174         // or postRegister, then reg should not be null.
 175         if ((where.contains(WHERE.PREREGISTER)
 176             || where.contains(WHERE.POSTREGISTER))&& reg==null) {
 177             System.out.println("Expected registration exception not " +
 178                     "thrown by "+where);
 179             throw new RuntimeException("Expected registration exception not " +
 180                     "thrown by "+where);
 181         }
 182 
 183         // If we asked the MBean not to throw any exception in preRegister
 184         // then the MBean should have been registered, unregisterMBean should
 185         // have been called.
 186         // If we asked the MBean to throw an exception in either of preDeregister
 187         // or postDeregister, then unreg should not be null.
 188         if ((where.contains(WHERE.PREDEREGISTER)
 189             || where.contains(WHERE.POSTDEREGISTER))&& unreg==null
 190             && !where.contains(WHERE.PREREGISTER)) {
 191             System.out.println("Expected unregistration exception not " +
 192                     "thrown by "+where);
 193             throw new RuntimeException("Expected unregistration exception not " +
 194                     "thrown by "+where);
 195         }
 196 
 197         // If we asked the MBean to throw an exception in preRegister
 198         // then the MBean should not have been registered.
 199         if (where.contains(WHERE.PREREGISTER)) {
 200             if (isRegistered) {
 201                 System.out.println("MBean is still registered [" +
 202                         where+
 203                         "]: "+name+" / "+reg);
 204                 throw new RuntimeException("MBean is still registered [" +
 205                         where+
 206                         "]: "+name+" / "+reg,reg);
 207             }
 208         }
 209 
 210         // If we asked the MBean not to throw an exception in preRegister,
 211         // but to throw an exception in postRegister, then the MBean should
 212         // have been registered.
 213         if (where.contains(WHERE.POSTREGISTER) &&
 214                 !where.contains(WHERE.PREREGISTER)) {
 215             if (!isRegistered) {
 216                 System.out.println("MBean is already unregistered [" +
 217                         where+
 218                         "]: "+name+" / "+reg);
 219                 throw new RuntimeException("MBean is already unregistered [" +
 220                         where+
 221                         "]: "+name+" / "+reg,reg);
 222             }
 223         }
 224 
 225         // If we asked the MBean to throw an exception in preRegister,
 226         // check that the exception we caught was as expected.
 227         //
 228         if (where.contains(WHERE.PREREGISTER)) {
 229             WHERE.PREREGISTER.check(reg, t);
 230         } else if (where.contains(WHERE.POSTREGISTER)) {
 231             // If we asked the MBean to throw an exception in postRegister,
 232             // check that the exception we caught was as expected.
 233             // We don't do this check if we asked the MBean to also throw an
 234             // exception in pre register, because postRegister will not have
 235             // been called.
 236             WHERE.POSTREGISTER.check(reg, t);
 237         }
 238 
 239         if (!isRegistered) return failures;
 240 
 241         // The MBean was registered, so unregisterMBean was called. Check
 242         // unregisterMBean exceptions...
 243         //
 244 
 245         // If we asked the MBean to throw an exception in preDeregister
 246         // then the MBean should not have been deregistered.
 247         if (where.contains(WHERE.PREDEREGISTER)) {
 248             if (isUnregistered) {
 249                 System.out.println("MBean is already unregistered [" +
 250                         where+
 251                         "]: "+name+" / "+unreg);
 252                 throw new RuntimeException("MBean is already unregistered [" +
 253                         where+
 254                         "]: "+name+" / "+unreg,unreg);
 255             }
 256         }
 257 
 258         // If we asked the MBean not to throw an exception in preDeregister,
 259         // but to throw an exception in postDeregister, then the MBean should
 260         // have been deregistered.
 261         if (where.contains(WHERE.POSTDEREGISTER) &&
 262                 !where.contains(WHERE.PREDEREGISTER)) {
 263             if (!isUnregistered) {
 264                 System.out.println("MBean is not unregistered [" +
 265                         where+
 266                         "]: "+name+" / "+unreg);
 267                 throw new RuntimeException("MBean is not unregistered [" +
 268                         where+
 269                         "]: "+name+" / "+unreg,unreg);
 270             }
 271         }
 272 
 273         // If we asked the MBean to throw an exception in preDeregister,
 274         // check that the exception we caught was as expected.
 275         //
 276         if (where.contains(WHERE.PREDEREGISTER)) {
 277             WHERE.PREDEREGISTER.check(unreg, t);
 278         } else if (where.contains(WHERE.POSTDEREGISTER)) {
 279             // If we asked the MBean to throw an exception in postDeregister,
 280             // check that the exception we caught was as expected.
 281             // We don't do this check if we asked the MBean to also throw an
 282             // exception in pre register, because postRegister will not have
 283             // been called.
 284             WHERE.POSTDEREGISTER.check(unreg, t);
 285         }
 286         return failures;
 287     }
 288 
 289     /**
 290      * This enum lists the 4 methods in MBeanRegistration.
 291      */
 292     public static enum WHERE {
 293 
 294         PREREGISTER, POSTREGISTER, PREDEREGISTER, POSTDEREGISTER;
 295 
 296         // Checks that an exception thrown by the MBeanServer correspond to
 297         // what is expected when an MBean throws an exception in this
 298         // MBeanRegistration method ("this" is one of the 4 enum values above)
 299         //
 300         public void check(Exception thrown, Throwable t)
 301                 throws Exception {
 302            if (t instanceof RuntimeException) {
 303                if (!(thrown instanceof RuntimeMBeanException)) {
 304                    System.out.println("Expected RuntimeMBeanException, got "+
 305                            thrown);
 306                    throw new Exception("Expected RuntimeMBeanException, got "+
 307                            thrown);
 308                }
 309            } else if (t instanceof Error) {
 310                if (!(thrown instanceof RuntimeErrorException)) {
 311                    System.out.println("Expected RuntimeErrorException, got "+
 312                            thrown);
 313                    throw new Exception("Expected RuntimeErrorException, got "+
 314                            thrown);
 315                }
 316            } else if (t instanceof Exception) {
 317                if (EnumSet.of(POSTDEREGISTER,POSTREGISTER).contains(this)) {
 318                    if (!(thrown instanceof RuntimeMBeanException)) {
 319                        System.out.println("Expected RuntimeMBeanException, got "+
 320                            thrown);
 321                        throw new Exception("Expected RuntimeMBeanException, got "+
 322                            thrown);
 323                    }
 324                    if (! (thrown.getCause() instanceof RuntimeException)) {
 325                        System.out.println("Bad cause: " +
 326                                "expected RuntimeException, " +
 327                            "got <"+thrown.getCause()+">");
 328                        throw new Exception("Bad cause: " +
 329                                "expected RuntimeException, " +
 330                            "got <"+thrown.getCause()+">");
 331                    }
 332                }
 333                if (EnumSet.of(PREDEREGISTER,PREREGISTER).contains(this)) {
 334                    if (!(thrown instanceof MBeanRegistrationException)) {
 335                        System.out.println("Expected " +
 336                                "MBeanRegistrationException, got "+
 337                            thrown);
 338                        throw new Exception("Expected " +
 339                                "MBeanRegistrationException, got "+
 340                            thrown);
 341                    }
 342                    if (! (thrown.getCause() instanceof Exception)) {
 343                        System.out.println("Bad cause: " +
 344                                "expected Exception, " +
 345                            "got <"+thrown.getCause()+">");
 346                        throw new Exception("Bad cause: " +
 347                                "expected Exception, " +
 348                            "got <"+thrown.getCause()+">");
 349                    }
 350                }
 351            }
 352 
 353         }
 354     }
 355 
 356     /**
 357      * This enum lists the 5 methods to create and register an
 358      * ExceptionalWombat MBean
 359      */
 360     public static enum CREATE {
 361 
 362         CREATE1() {
 363             // Creates an ExceptionalWombat MBean using createMBean form #1
 364             public ObjectInstance create(Throwable t, EnumSet<WHERE> where,
 365                     MBeanServer server, ObjectName name) throws Exception {
 366                 ExceptionallyHackyWombat.t = t;
 367                 ExceptionallyHackyWombat.w = where;
 368                 return server.createMBean(
 369                         ExceptionallyHackyWombat.class.getName(),
 370                         name);
 371             }
 372         },
 373         CREATE2() {
 374             // Creates an ExceptionalWombat MBean using createMBean form #2
 375             public ObjectInstance create(Throwable t, EnumSet<WHERE> where,
 376                     MBeanServer server, ObjectName name) throws Exception {
 377                 ExceptionallyHackyWombat.t = t;
 378                 ExceptionallyHackyWombat.w = where;
 379                 final ObjectName loaderName = registerMLet(server);
 380                 return server.createMBean(
 381                         ExceptionallyHackyWombat.class.getName(),
 382                         name, loaderName);
 383             }
 384         },
 385         CREATE3() {
 386             // Creates an ExceptionalWombat MBean using createMBean form #3
 387             public ObjectInstance create(Throwable t, EnumSet<WHERE> where,
 388                     MBeanServer server, ObjectName name) throws Exception {
 389                 final Object[] params = {t, where};
 390                 final String[] signature = {Throwable.class.getName(),
 391                     EnumSet.class.getName()
 392                 };
 393                 return server.createMBean(
 394                         ExceptionalWombat.class.getName(), name,
 395                         params, signature);
 396             }
 397         },
 398         CREATE4() {
 399             // Creates an ExceptionalWombat MBean using createMBean form #4
 400             public ObjectInstance create(Throwable t, EnumSet<WHERE> where,
 401                     MBeanServer server, ObjectName name) throws Exception {
 402                 final Object[] params = {t, where};
 403                 final String[] signature = {Throwable.class.getName(),
 404                     EnumSet.class.getName()
 405                 };
 406                 return server.createMBean(
 407                         ExceptionalWombat.class.getName(), name,
 408                         registerMLet(server), params, signature);
 409             }
 410         },
 411         REGISTER() {
 412             // Creates an ExceptionalWombat MBean using registerMBean
 413             public ObjectInstance create(Throwable t, EnumSet<WHERE> where,
 414                     MBeanServer server, ObjectName name) throws Exception {
 415                 final ExceptionalWombat wombat =
 416                         new ExceptionalWombat(t, where);
 417                 return server.registerMBean(wombat, name);
 418             }
 419         };
 420 
 421         // Creates an ExceptionalWombat MBean using the method denoted by this
 422         // Enum value - one of CREATE1, CREATE2, CREATE3, CREATE4, or REGISTER.
 423         public abstract ObjectInstance create(Throwable t, EnumSet<WHERE> where,
 424                 MBeanServer server, ObjectName name) throws Exception;
 425 
 426         // This is a bit of a hack - we use an MLet that delegates to the
 427         // System ClassLoader so that we can use createMBean form #2 and #3
 428         // while still using the same class loader (system).
 429         // This is necessary to make the ExceptionallyHackyWombatMBean work ;-)
 430         //
 431         public ObjectName registerMLet(MBeanServer server) throws Exception {
 432             final ObjectName name = new ObjectName("test:type=MLet");
 433             if (server.isRegistered(name)) {
 434                 return name;
 435             }
 436             final MLet mlet = new MLet(new URL[0],
 437                     ClassLoader.getSystemClassLoader());
 438             return server.registerMBean(mlet, name).getObjectName();
 439         }
 440     }
 441 
 442     /**
 443      *A Wombat MBean that can throw exceptions or errors in any of the
 444      * MBeanRegistration methods.
 445      */
 446     public static interface ExceptionalWombatMBean {
 447         // Tells the MBean to stop throwing exceptions - we sometime
 448         // need to call this at the end of the test so that we can
 449         // actually unregister the MBean.
 450         public void end();
 451     }
 452 
 453     /**
 454      *A Wombat MBean that can throw exceptions or errors in any of the
 455      * MBeanRegistration methods.
 456      */
 457     public static class ExceptionalWombat
 458             implements ExceptionalWombatMBean, MBeanRegistration {
 459 
 460         private final Throwable throwable;
 461         private final EnumSet<WHERE> where;
 462         private volatile boolean end=false;
 463 
 464         public ExceptionalWombat(Throwable t, EnumSet<WHERE> where) {
 465             this.throwable=t; this.where=where;
 466         }
 467         private Exception doThrow() {
 468             if (throwable instanceof Error)
 469                 throw (Error)throwable;
 470             if (throwable instanceof RuntimeException)
 471                 throw (RuntimeException)throwable;
 472             return (Exception)throwable;
 473         }
 474         public ObjectName preRegister(MBeanServer server, ObjectName name)
 475                 throws Exception {
 476             if (!end && where.contains(WHERE.PREREGISTER))
 477                 throw doThrow();
 478             return name;
 479         }
 480 
 481         public void postRegister(Boolean registrationDone) {
 482             if (!end && where.contains(WHERE.POSTREGISTER))
 483                 throw new RuntimeException(doThrow());
 484         }
 485 
 486         public void preDeregister() throws Exception {
 487             if (!end && where.contains(WHERE.PREDEREGISTER))
 488                 throw doThrow();
 489         }
 490 
 491         public void postDeregister() {
 492             if (!end && where.contains(WHERE.POSTREGISTER))
 493                 throw new RuntimeException(doThrow());
 494         }
 495 
 496         public void end() {
 497             this.end=true;
 498         }
 499     }
 500 
 501     /**
 502      * This is a big ugly hack to call createMBean form #1 and #2 - where
 503      * the empty constructor is used. Since we still want to supply parameters
 504      * to the ExceptionalWombat super class, we temporarily store these
 505      * parameter value in a static volatile before calling create MBean.
 506      * Of course this only works because our test is sequential and single
 507      * threaded, and nobody but our test uses this ExceptionallyHackyWombat.
 508      */
 509     public static class ExceptionallyHackyWombat extends ExceptionalWombat {
 510         public static volatile Throwable  t;
 511         public static volatile EnumSet<WHERE> w;
 512         public ExceptionallyHackyWombat() {
 513             super(t,w);
 514         }
 515     }
 516 
 517 }