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