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
34import javax.management.*;
35import java.io.Serializable;
36import java.net.URL;
37import java.util.EnumSet;
38import javax.management.loading.MLet;
39
40public 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}
517