6730926: Document that create/registerMBean can throw RuntimeMBeanException from postRegister
authordfuchs
Thu, 31 Jul 2008 12:41:35 +0200
changeset 1002 1e6a1b77f22a
parent 913 e3d549324eb4
child 1003 b2f6b7e00c29
6730926: Document that create/registerMBean can throw RuntimeMBeanException from postRegister Reviewed-by: emcmanus
jdk/src/share/classes/com/sun/jmx/interceptor/DefaultMBeanServerInterceptor.java
jdk/src/share/classes/javax/management/MBeanRegistration.java
jdk/src/share/classes/javax/management/MBeanServer.java
jdk/src/share/classes/javax/management/MBeanServerConnection.java
jdk/test/javax/management/MBeanServer/PostExceptionTest.java
--- a/jdk/src/share/classes/com/sun/jmx/interceptor/DefaultMBeanServerInterceptor.java	Tue Jul 29 16:57:09 2008 -0700
+++ b/jdk/src/share/classes/com/sun/jmx/interceptor/DefaultMBeanServerInterceptor.java	Thu Jul 31 12:41:35 2008 +0200
@@ -453,11 +453,12 @@
         final ResourceContext context =
                 unregisterFromRepository(resource, instance, name);
 
-
-        if (instance instanceof MBeanRegistration)
-            postDeregisterInvoke((MBeanRegistration) instance);
-
-        context.done();
+        try {
+            if (instance instanceof MBeanRegistration)
+                postDeregisterInvoke(name,(MBeanRegistration) instance);
+        } finally {
+            context.done();
+        }
     }
 
     public ObjectInstance getObjectInstance(ObjectName name)
@@ -989,10 +990,12 @@
             registerFailed = false;
             registered = true;
         } finally {
-            postRegister(mbean, registered, registerFailed);
+            try {
+                postRegister(logicalName, mbean, registered, registerFailed);
+            } finally {
+                if (registered) context.done();
+            }
         }
-
-        context.done();
         return new ObjectInstance(logicalName, classname);
     }
 
@@ -1051,7 +1054,8 @@
     }
 
     private static void postRegister(
-            DynamicMBean mbean, boolean registrationDone, boolean registerFailed) {
+            ObjectName logicalName, DynamicMBean mbean,
+            boolean registrationDone, boolean registerFailed) {
 
         if (registerFailed && mbean instanceof DynamicMBean2)
             ((DynamicMBean2) mbean).registerFailed();
@@ -1059,11 +1063,19 @@
             if (mbean instanceof MBeanRegistration)
                 ((MBeanRegistration) mbean).postRegister(registrationDone);
         } catch (RuntimeException e) {
+            MBEANSERVER_LOGGER.fine("While registering MBean ["+logicalName+
+                    "]: " + "Exception thrown by postRegister: " +
+                    "rethrowing <"+e+">, but keeping the MBean registered");
             throw new RuntimeMBeanException(e,
-                      "RuntimeException thrown in postRegister method");
+                      "RuntimeException thrown in postRegister method: "+
+                      "rethrowing <"+e+">, but keeping the MBean registered");
         } catch (Error er) {
+            MBEANSERVER_LOGGER.fine("While registering MBean ["+logicalName+
+                    "]: " + "Error thrown by postRegister: " +
+                    "rethrowing <"+er+">, but keeping the MBean registered");
             throw new RuntimeErrorException(er,
-                      "Error thrown in postRegister method");
+                      "Error thrown in postRegister method: "+
+                      "rethrowing <"+er+">, but keeping the MBean registered");
         }
     }
 
@@ -1076,15 +1088,28 @@
         }
     }
 
-    private static void postDeregisterInvoke(MBeanRegistration moi) {
+    private static void postDeregisterInvoke(ObjectName mbean,
+            MBeanRegistration moi) {
         try {
             moi.postDeregister();
         } catch (RuntimeException e) {
+            MBEANSERVER_LOGGER.fine("While unregistering MBean ["+mbean+
+                    "]: " + "Exception thrown by postDeregister: " +
+                    "rethrowing <"+e+">, although the MBean is succesfully " +
+                    "unregistered");
             throw new RuntimeMBeanException(e,
-                         "RuntimeException thrown in postDeregister method");
+                      "RuntimeException thrown in postDeregister method: "+
+                      "rethrowing <"+e+
+                      ">, although the MBean is sucessfully unregistered");
         } catch (Error er) {
+            MBEANSERVER_LOGGER.fine("While unregistering MBean ["+mbean+
+                    "]: " + "Error thrown by postDeregister: " +
+                    "rethrowing <"+er+">, although the MBean is succesfully " +
+                    "unregistered");
             throw new RuntimeErrorException(er,
-                         "Error thrown in postDeregister method");
+                      "Error thrown in postDeregister method: "+
+                      "rethrowing <"+er+
+                      ">, although the MBean is sucessfully unregistered");
         }
     }
 
--- a/jdk/src/share/classes/javax/management/MBeanRegistration.java	Tue Jul 29 16:57:09 2008 -0700
+++ b/jdk/src/share/classes/javax/management/MBeanRegistration.java	Thu Jul 31 12:41:35 2008 +0200
@@ -158,7 +158,19 @@
     /**
      * Allows the MBean to perform any operations needed after having been
      * registered in the MBean server or after the registration has failed.
-     *
+     * <p>If the implementation of this method throws a {@link RuntimeException}
+     * or an {@link Error}, the MBean Server will rethrow those inside
+     * a {@link RuntimeMBeanException} or {@link RuntimeErrorException},
+     * respectively. However, throwing an exception in {@code postRegister}
+     * will not change the state of the MBean:
+     * if the MBean was already registered ({@code registrationDone} is
+     * {@code true}), the MBean will remain registered. </p>
+     * <p>This might be confusing for the code calling {@code createMBean()}
+     * or {@code registerMBean()}, as such code might assume that MBean
+     * registration has failed when such an exception is raised.
+     * Therefore it is recommended that implementations of
+     * {@code postRegister} do not throw Runtime Exceptions or Errors if it
+     * can be avoided.</p>
      * @param registrationDone Indicates whether or not the MBean has
      * been successfully registered in the MBean server. The value
      * false means that the registration phase has failed.
@@ -178,6 +190,17 @@
     /**
      * Allows the MBean to perform any operations needed after having been
      * unregistered in the MBean server.
+     * <p>If the implementation of this method throws a {@link RuntimeException}
+     * or an {@link Error}, the MBean Server will rethrow those inside
+     * a {@link RuntimeMBeanException} or {@link RuntimeErrorException},
+     * respectively. However, throwing an excepption in {@code postDeregister}
+     * will not change the state of the MBean:
+     * the MBean was already successfully deregistered and will remain so. </p>
+     * <p>This might be confusing for the code calling
+     * {@code unregisterMBean()}, as it might assume that MBean deregistration
+     * has failed. Therefore it is recommended that implementations of
+     * {@code postDeregister} do not throw Runtime Exceptions or Errors if it
+     * can be avoided.</p>
      */
     public void postDeregister();
 
--- a/jdk/src/share/classes/javax/management/MBeanServer.java	Tue Jul 29 16:57:09 2008 -0700
+++ b/jdk/src/share/classes/javax/management/MBeanServer.java	Thu Jul 31 12:41:35 2008 +0200
@@ -328,11 +328,30 @@
      * <CODE>preRegister</CODE> (<CODE>MBeanRegistration</CODE>
      * interface) method of the MBean has thrown an exception. The
      * MBean will not be registered.
+     * @exception RuntimeMBeanException If the <CODE>postRegister</CODE>
+     * (<CODE>MBeanRegistration</CODE> interface) method of the MBean throws a
+     * <CODE>RuntimeException</CODE>, the <CODE>registerMBean<CODE> method will
+     * throw a <CODE>RuntimeMBeanException</CODE>, although the MBean
+     * registration succeeded. In such a case, the MBean will be actually
+     * registered even though the <CODE>registerMBean<CODE> method
+     * threw an exception.  Note that <CODE>RuntimeMBeanException</CODE> can
+     * also be thrown by <CODE>preRegister</CODE>, in which case the MBean
+     * will not be registered.
+     * @exception RuntimeErrorException If the <CODE>postRegister</CODE>
+     * (<CODE>MBeanRegistration</CODE> interface) method of the MBean throws an
+     * <CODE>Error</CODE>, the <CODE>registerMBean<CODE> method will
+     * throw a <CODE>RuntimeErrorException</CODE>, although the MBean
+     * registration succeeded. In such a case, the MBean will be actually
+     * registered even though the <CODE>registerMBean<CODE> method
+     * threw an exception.  Note that <CODE>RuntimeErrorException</CODE> can
+     * also be thrown by <CODE>preRegister</CODE>, in which case the MBean
+     * will not be registered.
      * @exception NotCompliantMBeanException This object is not a JMX
      * compliant MBean
      * @exception RuntimeOperationsException Wraps a
      * <CODE>java.lang.IllegalArgumentException</CODE>: The object
      * passed in parameter is null or no object name is specified.
+     * @see javax.management.MBeanRegistration
      */
     public ObjectInstance registerMBean(Object object, ObjectName name)
             throws InstanceAlreadyExistsException, MBeanRegistrationException,
--- a/jdk/src/share/classes/javax/management/MBeanServerConnection.java	Tue Jul 29 16:57:09 2008 -0700
+++ b/jdk/src/share/classes/javax/management/MBeanServerConnection.java	Thu Jul 31 12:41:35 2008 +0200
@@ -75,6 +75,24 @@
      * <CODE>preRegister</CODE> (<CODE>MBeanRegistration</CODE>
      * interface) method of the MBean has thrown an exception. The
      * MBean will not be registered.
+     * @exception RuntimeMBeanException If the <CODE>postRegister</CODE>
+     * (<CODE>MBeanRegistration</CODE> interface) method of the MBean throws a
+     * <CODE>RuntimeException</CODE>, the <CODE>createMBean<CODE> method will
+     * throw a <CODE>RuntimeMBeanException</CODE>, although the MBean creation
+     * and registration succeeded. In such a case, the MBean will be actually
+     * registered even though the <CODE>createMBean<CODE> method
+     * threw an exception. Note that <CODE>RuntimeMBeanException</CODE> can
+     * also be thrown by <CODE>preRegister</CODE>, in which case the MBean
+     * will not be registered.
+     * @exception RuntimeErrorException If the <CODE>postRegister</CODE>
+     * (<CODE>MBeanRegistration</CODE> interface) method of the MBean throws an
+     * <CODE>Error</CODE>, the <CODE>createMBean<CODE> method will
+     * throw a <CODE>RuntimeErrorException</CODE>, although the MBean creation
+     * and registration succeeded. In such a case, the MBean will be actually
+     * registered even though the <CODE>createMBean<CODE> method
+     * threw an exception.  Note that <CODE>RuntimeErrorException</CODE> can
+     * also be thrown by <CODE>preRegister</CODE>, in which case the MBean
+     * will not be registered.
      * @exception MBeanException The constructor of the MBean has
      * thrown an exception
      * @exception NotCompliantMBeanException This class is not a JMX
@@ -86,7 +104,7 @@
      * is specified for the MBean.
      * @exception IOException A communication problem occurred when
      * talking to the MBean server.
-     *
+     * @see javax.management.MBeanRegistration
      */
     public ObjectInstance createMBean(String className, ObjectName name)
             throws ReflectionException, InstanceAlreadyExistsException,
@@ -129,6 +147,24 @@
      * <CODE>preRegister</CODE> (<CODE>MBeanRegistration</CODE>
      * interface) method of the MBean has thrown an exception. The
      * MBean will not be registered.
+     * @exception RuntimeMBeanException If the <CODE>postRegister</CODE>
+     * (<CODE>MBeanRegistration</CODE> interface) method of the MBean throws a
+     * <CODE>RuntimeException</CODE>, the <CODE>createMBean<CODE> method will
+     * throw a <CODE>RuntimeMBeanException</CODE>, although the MBean creation
+     * and registration succeeded. In such a case, the MBean will be actually
+     * registered even though the <CODE>createMBean<CODE> method
+     * threw an exception.  Note that <CODE>RuntimeMBeanException</CODE> can
+     * also be thrown by <CODE>preRegister</CODE>, in which case the MBean
+     * will not be registered.
+     * @exception RuntimeErrorException If the <CODE>postRegister</CODE>
+     * (<CODE>MBeanRegistration</CODE> interface) method of the MBean throws an
+     * <CODE>Error</CODE>, the <CODE>createMBean<CODE> method will
+     * throw a <CODE>RuntimeErrorException</CODE>, although the MBean creation
+     * and registration succeeded. In such a case, the MBean will be actually
+     * registered even though the <CODE>createMBean<CODE> method
+     * threw an exception.  Note that <CODE>RuntimeErrorException</CODE> can
+     * also be thrown by <CODE>preRegister</CODE>, in which case the MBean
+     * will not be registered.
      * @exception MBeanException The constructor of the MBean has
      * thrown an exception
      * @exception NotCompliantMBeanException This class is not a JMX
@@ -142,6 +178,7 @@
      * is specified for the MBean.
      * @exception IOException A communication problem occurred when
      * talking to the MBean server.
+     * @see javax.management.MBeanRegistration
      */
     public ObjectInstance createMBean(String className, ObjectName name,
                                       ObjectName loaderName)
@@ -185,6 +222,24 @@
      * <CODE>preRegister</CODE> (<CODE>MBeanRegistration</CODE>
      * interface) method of the MBean has thrown an exception. The
      * MBean will not be registered.
+     * @exception RuntimeMBeanException If the <CODE>postRegister</CODE>
+     * (<CODE>MBeanRegistration</CODE> interface) method of the MBean throws a
+     * <CODE>RuntimeException</CODE>, the <CODE>createMBean<CODE> method will
+     * throw a <CODE>RuntimeMBeanException</CODE>, although the MBean creation
+     * and registration succeeded. In such a case, the MBean will be actually
+     * registered even though the <CODE>createMBean<CODE> method
+     * threw an exception.  Note that <CODE>RuntimeMBeanException</CODE> can
+     * also be thrown by <CODE>preRegister</CODE>, in which case the MBean
+     * will not be registered.
+     * @exception RuntimeErrorException If the <CODE>postRegister</CODE>
+     * (<CODE>MBeanRegistration</CODE> interface) method of the MBean throws an
+     * <CODE>Error</CODE>, the <CODE>createMBean<CODE> method will
+     * throw a <CODE>RuntimeErrorException</CODE>, although the MBean creation
+     * and registration succeeded. In such a case, the MBean will be actually
+     * registered even though the <CODE>createMBean<CODE> method
+     * threw an exception.  Note that <CODE>RuntimeErrorException</CODE> can
+     * also be thrown by <CODE>preRegister</CODE>, in which case the MBean
+     * will not be registered.
      * @exception MBeanException The constructor of the MBean has
      * thrown an exception
      * @exception NotCompliantMBeanException This class is not a JMX
@@ -196,7 +251,7 @@
      * is specified for the MBean.
      * @exception IOException A communication problem occurred when
      * talking to the MBean server.
-     *
+     * @see javax.management.MBeanRegistration
      */
     public ObjectInstance createMBean(String className, ObjectName name,
                                       Object params[], String signature[])
@@ -239,6 +294,24 @@
      * <CODE>preRegister</CODE> (<CODE>MBeanRegistration</CODE>
      * interface) method of the MBean has thrown an exception. The
      * MBean will not be registered.
+     * @exception RuntimeMBeanException If the <CODE>postRegister</CODE>
+     * (<CODE>MBeanRegistration</CODE> interface) method of the MBean throws a
+     * <CODE>RuntimeException</CODE>, the <CODE>createMBean<CODE> method will
+     * throw a <CODE>RuntimeMBeanException</CODE>, although the MBean creation
+     * and registration succeeded. In such a case, the MBean will be actually
+     * registered even though the <CODE>createMBean<CODE> method
+     * threw an exception.  Note that <CODE>RuntimeMBeanException</CODE> can
+     * also be thrown by <CODE>preRegister</CODE>, in which case the MBean
+     * will not be registered.
+     * @exception RuntimeErrorException If the <CODE>postRegister</CODE> method
+     * (<CODE>MBeanRegistration</CODE> interface) method of the MBean throws an
+     * <CODE>Error</CODE>, the <CODE>createMBean<CODE> method will
+     * throw a <CODE>RuntimeErrorException</CODE>, although the MBean creation
+     * and registration succeeded. In such a case, the MBean will be actually
+     * registered even though the <CODE>createMBean<CODE> method
+     * threw an exception.  Note that <CODE>RuntimeErrorException</CODE> can
+     * also be thrown by <CODE>preRegister</CODE>, in which case the MBean
+     * will not be registered.
      * @exception MBeanException The constructor of the MBean has
      * thrown an exception
      * @exception NotCompliantMBeanException This class is not a JMX
@@ -252,7 +325,7 @@
      * is specified for the MBean.
      * @exception IOException A communication problem occurred when
      * talking to the MBean server.
-     *
+     * @see javax.management.MBeanRegistration
      */
     public ObjectInstance createMBean(String className, ObjectName name,
                                       ObjectName loaderName, Object params[],
@@ -275,6 +348,24 @@
      * @exception MBeanRegistrationException The preDeregister
      * ((<CODE>MBeanRegistration</CODE> interface) method of the MBean
      * has thrown an exception.
+     * @exception RuntimeMBeanException If the <CODE>postDeregister</CODE>
+     * (<CODE>MBeanRegistration</CODE> interface) method of the MBean throws a
+     * <CODE>RuntimeException</CODE>, the <CODE>unregisterMBean<CODE> method
+     * will throw a <CODE>RuntimeMBeanException</CODE>, although the MBean
+     * unregistration succeeded. In such a case, the MBean will be actually
+     * unregistered even though the <CODE>unregisterMBean<CODE> method
+     * threw an exception.  Note that <CODE>RuntimeMBeanException</CODE> can
+     * also be thrown by <CODE>preDeregister</CODE>, in which case the MBean
+     * will remain registered.
+     * @exception RuntimeErrorException If the <CODE>postDeregister</CODE>
+     * (<CODE>MBeanRegistration</CODE> interface) method of the MBean throws an
+     * <CODE>Error</CODE>, the <CODE>unregisterMBean<CODE> method will
+     * throw a <CODE>RuntimeErrorException</CODE>, although the MBean
+     * unregistration succeeded. In such a case, the MBean will be actually
+     * unregistered even though the <CODE>unregisterMBean<CODE> method
+     * threw an exception.  Note that <CODE>RuntimeMBeanException</CODE> can
+     * also be thrown by <CODE>preDeregister</CODE>, in which case the MBean
+     * will remain registered.
      * @exception RuntimeOperationsException Wraps a
      * <CODE>java.lang.IllegalArgumentException</CODE>: The object
      * name in parameter is null or the MBean you are when trying to
@@ -282,7 +373,7 @@
      * MBeanServerDelegate} MBean.
      * @exception IOException A communication problem occurred when
      * talking to the MBean server.
-     *
+     * @see javax.management.MBeanRegistration
      */
     public void unregisterMBean(ObjectName name)
             throws InstanceNotFoundException, MBeanRegistrationException,
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/javax/management/MBeanServer/PostExceptionTest.java	Thu Jul 31 12:41:35 2008 +0200
@@ -0,0 +1,516 @@
+/*
+ * Copyright 2008 Sun Microsystems, Inc.  All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+/*
+ * @test
+ * @bug 6730926
+ * @summary Check behaviour of MBeanServer when postRegister and postDeregister
+ *          throw exceptions.
+ * @author Daniel Fuchs
+ * @compile PostExceptionTest.java
+ * @run main PostExceptionTest
+ */
+
+import javax.management.*;
+import java.io.Serializable;
+import java.net.URL;
+import java.util.EnumSet;
+import javax.management.loading.MLet;
+
+public class PostExceptionTest {
+
+    /**
+     * A test case where we instantiate an ExceptionalWombatMBean (or a
+     * subclass of it) which will throw the exception {@code t} from within
+     * the methods indicated by {@code where}
+     */
+    public static class Case {
+        public final Throwable t;
+        public final EnumSet<WHERE> where;
+        public Case(Throwable t,EnumSet<WHERE> where) {
+            this.t=t; this.where=where;
+        }
+    }
+
+    // Various methods to create an instance of Case in a single line
+    // --------------------------------------------------------------
+
+    public static Case caze(Throwable t, WHERE w) {
+        return new Case(t,EnumSet.of(w));
+    }
+    public static Case caze(Throwable t, EnumSet<WHERE> where) {
+        return new Case(t,where);
+    }
+    public static Case caze(Throwable t, WHERE w, WHERE... rest) {
+        return new Case(t,EnumSet.of(w,rest));
+    }
+
+    /**
+     * Here is the list of our test cases:
+     */
+    public static Case[] cases ={
+        caze(new RuntimeException(),WHERE.PREREGISTER),
+        caze(new RuntimeException(),WHERE.POSTREGISTER),
+        caze(new RuntimeException(),WHERE.POSTREGISTER, WHERE.PREDEREGISTER),
+        caze(new RuntimeException(),WHERE.POSTREGISTER, WHERE.POSTDEREGISTER),
+        caze(new Exception(),WHERE.PREREGISTER),
+        caze(new Exception(),WHERE.POSTREGISTER),
+        caze(new Exception(),WHERE.POSTREGISTER, WHERE.PREDEREGISTER),
+        caze(new Exception(),WHERE.POSTREGISTER, WHERE.POSTDEREGISTER),
+        caze(new Error(),WHERE.PREREGISTER),
+        caze(new Error(),WHERE.POSTREGISTER),
+        caze(new Error(),WHERE.POSTREGISTER, WHERE.PREDEREGISTER),
+        caze(new Error(),WHERE.POSTREGISTER, WHERE.POSTDEREGISTER),
+        caze(new RuntimeException(),EnumSet.allOf(WHERE.class)),
+        caze(new Exception(),EnumSet.allOf(WHERE.class)),
+        caze(new Error(),EnumSet.allOf(WHERE.class)),
+    };
+
+    public static void main(String[] args) throws Exception {
+        System.out.println("Test behaviour of MBeanServer when postRegister " +
+                "or postDeregister throw exceptions");
+        MBeanServer mbs = MBeanServerFactory.newMBeanServer();
+        int failures = 0;
+        final ObjectName n = new ObjectName("test:type=Wombat");
+
+        // We're going to test each cases, using each of the 4 createMBean
+        // forms + registerMBean in turn to create the MBean.
+        // Wich method is used to create the MBean is indicated by "how"
+        //
+        for (Case caze:cases) {
+            for (CREATE how : CREATE.values()) {
+                failures+=test(mbs,n,how,caze.t,caze.where);
+            }
+        }
+        if (failures == 0)
+            System.out.println("Test passed");
+        else {
+            System.out.println("TEST FAILED: " + failures + " failure(s)");
+            System.exit(1);
+        }
+    }
+
+    // Execute a test case composed of:
+    // mbs:   The MBeanServer where the MBean will be registered,
+    // name:  The name of that MBean
+    // how:   How will the MBean be created/registered (which MBeanServer
+    //        method)
+    // t:     The exception/error that the MBean will throw
+    // where: In which pre/post register/deregister method the exception/error
+    //        will be thrown
+    //
+    private static int test(MBeanServer mbs, ObjectName name, CREATE how,
+            Throwable t, EnumSet<WHERE> where)
+            throws Exception {
+        System.out.println("-------<"+how+"> / <"+t+"> / "+ where + "-------");
+
+        int failures = 0;
+        ObjectInstance oi = null;
+        Exception reg = null;    // exception thrown by create/register
+        Exception unreg = null;  // exception thrown by unregister
+        try {
+            // Create the MBean
+            oi = how.create(t, where, mbs, name);
+        } catch (Exception xx) {
+            reg=xx;
+        }
+        final ObjectName n = (oi==null)?name:oi.getObjectName();
+        final boolean isRegistered = mbs.isRegistered(n);
+        try {
+            // If the MBean is registered, unregister it
+            if (isRegistered) mbs.unregisterMBean(n);
+        } catch (Exception xxx) {
+            unreg=xxx;
+        }
+        final boolean isUnregistered = !mbs.isRegistered(n);
+        if (!isUnregistered) {
+            // if the MBean is still registered (preDeregister threw an
+            // exception) signify to the MBean that it now should stop
+            // throwing anaything and unregister it.
+            JMX.newMBeanProxy(mbs, n, ExceptionalWombatMBean.class).end();
+            mbs.unregisterMBean(n);
+        }
+
+        // Now analyze the result. If we didn't ask the MBean to throw any
+        // exception then reg should be null.
+        if (where.isEmpty() && reg!=null) {
+            System.out.println("Unexpected registration exception: "+
+                    reg);
+            throw new RuntimeException("Unexpected registration exception: "+
+                    reg,reg);
+        }
+
+        // If we didn't ask the MBean to throw any exception then unreg should
+        // also be null.
+        if (where.isEmpty() && unreg!=null) {
+            System.out.println("Unexpected unregistration exception: "+
+                    unreg);
+            throw new RuntimeException("Unexpected unregistration exception: "+
+                    unreg,unreg);
+        }
+
+        // If we asked the MBean to throw an exception in either of preRegister
+        // or postRegister, then reg should not be null.
+        if ((where.contains(WHERE.PREREGISTER)
+            || where.contains(WHERE.POSTREGISTER))&& reg==null) {
+            System.out.println("Expected registration exception not " +
+                    "thrown by "+where);
+            throw new RuntimeException("Expected registration exception not " +
+                    "thrown by "+where);
+        }
+
+        // If we asked the MBean not to throw any exception in preRegister
+        // then the MBean should have been registered, unregisterMBean should
+        // have been called.
+        // If we asked the MBean to throw an exception in either of preDeregister
+        // or postDeregister, then unreg should not be null.
+        if ((where.contains(WHERE.PREDEREGISTER)
+            || where.contains(WHERE.POSTDEREGISTER))&& unreg==null
+            && !where.contains(WHERE.PREREGISTER)) {
+            System.out.println("Expected unregistration exception not " +
+                    "thrown by "+where);
+            throw new RuntimeException("Expected unregistration exception not " +
+                    "thrown by "+where);
+        }
+
+        // If we asked the MBean to throw an exception in preRegister
+        // then the MBean should not have been registered.
+        if (where.contains(WHERE.PREREGISTER)) {
+            if (isRegistered) {
+                System.out.println("MBean is still registered [" +
+                        where+
+                        "]: "+name+" / "+reg);
+                throw new RuntimeException("MBean is still registered [" +
+                        where+
+                        "]: "+name+" / "+reg,reg);
+            }
+        }
+
+        // If we asked the MBean not to throw an exception in preRegister,
+        // but to throw an exception in postRegister, then the MBean should
+        // have been registered.
+        if (where.contains(WHERE.POSTREGISTER) &&
+                !where.contains(WHERE.PREREGISTER)) {
+            if (!isRegistered) {
+                System.out.println("MBean is already unregistered [" +
+                        where+
+                        "]: "+name+" / "+reg);
+                throw new RuntimeException("MBean is already unregistered [" +
+                        where+
+                        "]: "+name+" / "+reg,reg);
+            }
+        }
+
+        // If we asked the MBean to throw an exception in preRegister,
+        // check that the exception we caught was as expected.
+        //
+        if (where.contains(WHERE.PREREGISTER)) {
+            WHERE.PREREGISTER.check(reg, t);
+        } else if (where.contains(WHERE.POSTREGISTER)) {
+            // If we asked the MBean to throw an exception in postRegister,
+            // check that the exception we caught was as expected.
+            // We don't do this check if we asked the MBean to also throw an
+            // exception in pre register, because postRegister will not have
+            // been called.
+            WHERE.POSTREGISTER.check(reg, t);
+        }
+
+        if (!isRegistered) return failures;
+
+        // The MBean was registered, so unregisterMBean was called. Check
+        // unregisterMBean exceptions...
+        //
+
+        // If we asked the MBean to throw an exception in preDeregister
+        // then the MBean should not have been deregistered.
+        if (where.contains(WHERE.PREDEREGISTER)) {
+            if (isUnregistered) {
+                System.out.println("MBean is already unregistered [" +
+                        where+
+                        "]: "+name+" / "+unreg);
+                throw new RuntimeException("MBean is already unregistered [" +
+                        where+
+                        "]: "+name+" / "+unreg,unreg);
+            }
+        }
+
+        // If we asked the MBean not to throw an exception in preDeregister,
+        // but to throw an exception in postDeregister, then the MBean should
+        // have been deregistered.
+        if (where.contains(WHERE.POSTDEREGISTER) &&
+                !where.contains(WHERE.PREDEREGISTER)) {
+            if (!isUnregistered) {
+                System.out.println("MBean is not unregistered [" +
+                        where+
+                        "]: "+name+" / "+unreg);
+                throw new RuntimeException("MBean is not unregistered [" +
+                        where+
+                        "]: "+name+" / "+unreg,unreg);
+            }
+        }
+
+        // If we asked the MBean to throw an exception in preDeregister,
+        // check that the exception we caught was as expected.
+        //
+        if (where.contains(WHERE.PREDEREGISTER)) {
+            WHERE.PREDEREGISTER.check(unreg, t);
+        } else if (where.contains(WHERE.POSTDEREGISTER)) {
+            // If we asked the MBean to throw an exception in postDeregister,
+            // check that the exception we caught was as expected.
+            // We don't do this check if we asked the MBean to also throw an
+            // exception in pre register, because postRegister will not have
+            // been called.
+            WHERE.POSTDEREGISTER.check(unreg, t);
+        }
+        return failures;
+    }
+
+    /**
+     * This enum lists the 4 methods in MBeanRegistration.
+     */
+    public static enum WHERE {
+
+        PREREGISTER, POSTREGISTER, PREDEREGISTER, POSTDEREGISTER;
+
+        // Checks that an exception thrown by the MBeanServer correspond to
+        // what is expected when an MBean throws an exception in this
+        // MBeanRegistration method ("this" is one of the 4 enum values above)
+        //
+        public void check(Exception thrown, Throwable t)
+                throws Exception {
+           if (t instanceof RuntimeException) {
+               if (!(thrown instanceof RuntimeMBeanException)) {
+                   System.out.println("Expected RuntimeMBeanException, got "+
+                           thrown);
+                   throw new Exception("Expected RuntimeMBeanException, got "+
+                           thrown);
+               }
+           } else if (t instanceof Error) {
+               if (!(thrown instanceof RuntimeErrorException)) {
+                   System.out.println("Expected RuntimeErrorException, got "+
+                           thrown);
+                   throw new Exception("Expected RuntimeErrorException, got "+
+                           thrown);
+               }
+           } else if (t instanceof Exception) {
+               if (EnumSet.of(POSTDEREGISTER,POSTREGISTER).contains(this)) {
+                   if (!(thrown instanceof RuntimeMBeanException)) {
+                       System.out.println("Expected RuntimeMBeanException, got "+
+                           thrown);
+                       throw new Exception("Expected RuntimeMBeanException, got "+
+                           thrown);
+                   }
+                   if (! (thrown.getCause() instanceof RuntimeException)) {
+                       System.out.println("Bad cause: " +
+                               "expected RuntimeException, " +
+                           "got <"+thrown.getCause()+">");
+                       throw new Exception("Bad cause: " +
+                               "expected RuntimeException, " +
+                           "got <"+thrown.getCause()+">");
+                   }
+               }
+               if (EnumSet.of(PREDEREGISTER,PREREGISTER).contains(this)) {
+                   if (!(thrown instanceof MBeanRegistrationException)) {
+                       System.out.println("Expected " +
+                               "MBeanRegistrationException, got "+
+                           thrown);
+                       throw new Exception("Expected " +
+                               "MBeanRegistrationException, got "+
+                           thrown);
+                   }
+                   if (! (thrown.getCause() instanceof Exception)) {
+                       System.out.println("Bad cause: " +
+                               "expected Exception, " +
+                           "got <"+thrown.getCause()+">");
+                       throw new Exception("Bad cause: " +
+                               "expected Exception, " +
+                           "got <"+thrown.getCause()+">");
+                   }
+               }
+           }
+
+        }
+    }
+
+    /**
+     * This enum lists the 5 methods to create and register an
+     * ExceptionalWombat MBean
+     */
+    public static enum CREATE {
+
+        CREATE1() {
+            // Creates an ExceptionalWombat MBean using createMBean form #1
+            public ObjectInstance create(Throwable t, EnumSet<WHERE> where,
+                    MBeanServer server, ObjectName name) throws Exception {
+                ExceptionallyHackyWombat.t = t;
+                ExceptionallyHackyWombat.w = where;
+                return server.createMBean(
+                        ExceptionallyHackyWombat.class.getName(),
+                        name);
+            }
+        },
+        CREATE2() {
+            // Creates an ExceptionalWombat MBean using createMBean form #2
+            public ObjectInstance create(Throwable t, EnumSet<WHERE> where,
+                    MBeanServer server, ObjectName name) throws Exception {
+                ExceptionallyHackyWombat.t = t;
+                ExceptionallyHackyWombat.w = where;
+                final ObjectName loaderName = registerMLet(server);
+                return server.createMBean(
+                        ExceptionallyHackyWombat.class.getName(),
+                        name, loaderName);
+            }
+        },
+        CREATE3() {
+            // Creates an ExceptionalWombat MBean using createMBean form #3
+            public ObjectInstance create(Throwable t, EnumSet<WHERE> where,
+                    MBeanServer server, ObjectName name) throws Exception {
+                final Object[] params = {t, where};
+                final String[] signature = {Throwable.class.getName(),
+                    EnumSet.class.getName()
+                };
+                return server.createMBean(
+                        ExceptionalWombat.class.getName(), name,
+                        params, signature);
+            }
+        },
+        CREATE4() {
+            // Creates an ExceptionalWombat MBean using createMBean form #4
+            public ObjectInstance create(Throwable t, EnumSet<WHERE> where,
+                    MBeanServer server, ObjectName name) throws Exception {
+                final Object[] params = {t, where};
+                final String[] signature = {Throwable.class.getName(),
+                    EnumSet.class.getName()
+                };
+                return server.createMBean(
+                        ExceptionalWombat.class.getName(), name,
+                        registerMLet(server), params, signature);
+            }
+        },
+        REGISTER() {
+            // Creates an ExceptionalWombat MBean using registerMBean
+            public ObjectInstance create(Throwable t, EnumSet<WHERE> where,
+                    MBeanServer server, ObjectName name) throws Exception {
+                final ExceptionalWombat wombat =
+                        new ExceptionalWombat(t, where);
+                return server.registerMBean(wombat, name);
+            }
+        };
+
+        // Creates an ExceptionalWombat MBean using the method denoted by this
+        // Enum value - one of CREATE1, CREATE2, CREATE3, CREATE4, or REGISTER.
+        public abstract ObjectInstance create(Throwable t, EnumSet<WHERE> where,
+                MBeanServer server, ObjectName name) throws Exception;
+
+        // This is a bit of a hack - we use an MLet that delegates to the
+        // System ClassLoader so that we can use createMBean form #2 and #3
+        // while still using the same class loader (system).
+        // This is necessary to make the ExceptionallyHackyWombatMBean work ;-)
+        //
+        public ObjectName registerMLet(MBeanServer server) throws Exception {
+            final ObjectName name = new ObjectName("test:type=MLet");
+            if (server.isRegistered(name)) {
+                return name;
+            }
+            final MLet mlet = new MLet(new URL[0],
+                    ClassLoader.getSystemClassLoader());
+            return server.registerMBean(mlet, name).getObjectName();
+        }
+    }
+
+    /**
+     *A Wombat MBean that can throw exceptions or errors in any of the
+     * MBeanRegistration methods.
+     */
+    public static interface ExceptionalWombatMBean {
+        // Tells the MBean to stop throwing exceptions - we sometime
+        // need to call this at the end of the test so that we can
+        // actually unregister the MBean.
+        public void end();
+    }
+
+    /**
+     *A Wombat MBean that can throw exceptions or errors in any of the
+     * MBeanRegistration methods.
+     */
+    public static class ExceptionalWombat
+            implements ExceptionalWombatMBean, MBeanRegistration {
+
+        private final Throwable throwable;
+        private final EnumSet<WHERE> where;
+        private volatile boolean end=false;
+
+        public ExceptionalWombat(Throwable t, EnumSet<WHERE> where) {
+            this.throwable=t; this.where=where;
+        }
+        private Exception doThrow() {
+            if (throwable instanceof Error)
+                throw (Error)throwable;
+            if (throwable instanceof RuntimeException)
+                throw (RuntimeException)throwable;
+            return (Exception)throwable;
+        }
+        public ObjectName preRegister(MBeanServer server, ObjectName name)
+                throws Exception {
+            if (!end && where.contains(WHERE.PREREGISTER))
+                throw doThrow();
+            return name;
+        }
+
+        public void postRegister(Boolean registrationDone) {
+            if (!end && where.contains(WHERE.POSTREGISTER))
+                throw new RuntimeException(doThrow());
+        }
+
+        public void preDeregister() throws Exception {
+            if (!end && where.contains(WHERE.PREDEREGISTER))
+                throw doThrow();
+        }
+
+        public void postDeregister() {
+            if (!end && where.contains(WHERE.POSTREGISTER))
+                throw new RuntimeException(doThrow());
+        }
+
+        public void end() {
+            this.end=true;
+        }
+    }
+
+    /**
+     * This is a big ugly hack to call createMBean form #1 and #2 - where
+     * the empty constructor is used. Since we still want to supply parameters
+     * to the ExceptionalWombat super class, we temporarily store these
+     * parameter value in a static volatile before calling create MBean.
+     * Of course this only works because our test is sequential and single
+     * threaded, and nobody but our test uses this ExceptionallyHackyWombat.
+     */
+    public static class ExceptionallyHackyWombat extends ExceptionalWombat {
+        public static volatile Throwable  t;
+        public static volatile EnumSet<WHERE> w;
+        public ExceptionallyHackyWombat() {
+            super(t,w);
+        }
+    }
+
+}