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  *
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;
Case(Throwable t,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 
caze(Throwable t, WHERE w)58     public static Case caze(Throwable t, WHERE w) {
59         return new Case(t,EnumSet.of(w));
60     }
caze(Throwable t, EnumSet<WHERE> where)61     public static Case caze(Throwable t, EnumSet<WHERE> where) {
62         return new Case(t,where);
63     }
caze(Throwable t, WHERE w, WHERE... rest)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 
main(String[] args)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     //
test(MBeanServer mbs, ObjectName name, CREATE how, Throwable t, EnumSet<WHERE> where)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         //
check(Exception thrown, Throwable t)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 
CREATE1()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         },
CREATE2()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         },
CREATE3()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         },
CREATE4()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         },
REGISTER()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.
create(Throwable t, EnumSet<WHERE> where, MBeanServer server, ObjectName name)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         //
registerMLet(MBeanServer server)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.
end()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 
ExceptionalWombat(Throwable t, EnumSet<WHERE> where)463         public ExceptionalWombat(Throwable t, EnumSet<WHERE> where) {
464             this.throwable=t; this.where=where;
465         }
doThrow()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         }
preRegister(MBeanServer server, ObjectName name)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 
postRegister(Boolean registrationDone)480         public void postRegister(Boolean registrationDone) {
481             if (!end && where.contains(WHERE.POSTREGISTER))
482                 throw new RuntimeException(doThrow());
483         }
484 
preDeregister()485         public void preDeregister() throws Exception {
486             if (!end && where.contains(WHERE.PREDEREGISTER))
487                 throw doThrow();
488         }
489 
postDeregister()490         public void postDeregister() {
491             if (!end && where.contains(WHERE.POSTREGISTER))
492                 throw new RuntimeException(doThrow());
493         }
494 
end()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;
ExceptionallyHackyWombat()511         public ExceptionallyHackyWombat() {
512             super(t,w);
513         }
514     }
515 
516 }
517