aboutsummaryrefslogtreecommitdiff
path: root/test/javax/management/namespace/LazyDomainTest.java
blob: eda9b66696d4e89a5b6208592696204fe5a6cc6d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
/*
 * 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 LazyDomainTest.java
 * @bug 5072476
 * @summary Basic test for Lazy Domains.
 * @author Daniel Fuchs
 * @run clean LazyDomainTest Wombat WombatMBean
 * @run build LazyDomainTest Wombat WombatMBean
 * @run main LazyDomainTest
 */


import java.lang.management.ClassLoadingMXBean;
import java.lang.management.ManagementFactory;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.Map;
import java.util.Set;
import javax.management.JMX;
import javax.management.ListenerNotFoundException;
import javax.management.MBeanServer;
import javax.management.MBeanServerBuilder;
import javax.management.MBeanServerDelegate;
import javax.management.MBeanServerFactory;
import javax.management.MBeanServerNotification;
import javax.management.Notification;
import javax.management.NotificationFilter;
import javax.management.NotificationListener;
import javax.management.ObjectName;
import javax.management.namespace.JMXDomain;
import javax.management.remote.MBeanServerForwarder;

/**
 * Test simple creation/registration of namespace.
 *
 */
public class LazyDomainTest {
    private static Map<String,Object> emptyEnvMap() {
        return Collections.emptyMap();
    }


    public static interface MBeanServerLoader {
        public MBeanServer loadMBeanServer();
    }


    public static class MBeanServerProxy implements InvocationHandler {

        private final static Map<Method,Method> localMap;
        static {
            localMap = new HashMap<Method, Method>();
            for (Method m : MBeanServerForwarder.class.getDeclaredMethods()) {
                try {
                    final Method loc = MBeanServerProxy.class.
                            getMethod(m.getName(), m.getParameterTypes());
                    localMap.put(m, loc);
                } catch (Exception x) {
                    // not defined...
                }
            }
            try {
                localMap.put(MBeanServer.class.
                        getMethod("getMBeanCount", (Class[]) null),
                        MBeanServerProxy.class.
                        getMethod("getMBeanCount", (Class[]) null));
            } catch (NoSuchMethodException x) {
                // OK.
            }
        }

        private final MBeanServerLoader loader;
        private MBeanServer server;
        private final Set<LazyDomain> domains;

        public MBeanServerProxy(MBeanServerLoader loader) {
            if (loader == null)
                throw new IllegalArgumentException("null loader");
            this.loader = loader;
            this.server = null;
            domains = new HashSet<LazyDomain>();
        }


        public Object invoke(Object proxy, Method method, Object[] args)
                throws Throwable {
            if (method.getDeclaringClass().equals(Object.class)) {
                return invokeMethod(this,method,args);
            }
            final Method local = localMap.get(method);
            if (local != null) {
                return invokeMethod(this,local,args);
            }
            if (method.getDeclaringClass().equals(MBeanServer.class)) {
                return invokeMethod(getMBeanServer(),method,args);
            }
            throw new NoSuchMethodException(method.getName());
        }

        private Object invokeMethod(Object on, Method method, Object[] args)
            throws Throwable {
            try {
                return method.invoke(on, args);
            } catch (InvocationTargetException ex) {
                throw ex.getTargetException();
            }
        }

        public synchronized MBeanServer getMBeanServer() {
            if (server == null) setMBeanServer(loader.loadMBeanServer());
            return server;
        }

        public synchronized void setMBeanServer(MBeanServer mbs) {
            this.server = mbs;
            if (mbs != null) {
                for (LazyDomain dom : domains) dom.loaded();
                domains.clear();
            }
        }

        public synchronized boolean isLoaded() {
            return server != null;
        }

        public synchronized void add(LazyDomain dom) {
            if (isLoaded()) dom.loaded();
            else domains.add(dom);
        }

        public synchronized boolean remove(LazyDomain dom) {
            return domains.remove(dom);
        }

        public Integer getMBeanCount() {
            if (isLoaded()) return server.getMBeanCount();
            else return Integer.valueOf(0);
        }
    }

    public static class LazyDomain extends JMXDomain {
        public static MBeanServer makeProxyFor(MBeanServerProxy proxy) {
            return (MBeanServer)
                    Proxy.newProxyInstance(LazyDomain.class.getClassLoader(),
                    new Class[] {MBeanServer.class, MBeanServerForwarder.class},
                    proxy);
        }

        private final MBeanServerProxy proxy;
        private volatile NotificationListener listener;
        private volatile NotificationFilter   filter;

        public LazyDomain(MBeanServerProxy proxy) {
            super(makeProxyFor(proxy));
            this.proxy = proxy;
        }

        @Override
        public Integer getMBeanCount() {
            if (proxy.isLoaded())
                return super.getMBeanCount();
            return 0;
        }


        @Override
        public synchronized void addMBeanServerNotificationListener(
                NotificationListener listener,
                NotificationFilter filter) {
            if (proxy.isLoaded()) {
                super.addMBeanServerNotificationListener(listener, filter);
            } else {
                this.listener = listener;
                this.filter   = filter;
                proxy.add(this);
            }
        }

        @Override
        public synchronized void removeMBeanServerNotificationListener(
                NotificationListener listener)
                throws ListenerNotFoundException {
            if (this.listener != listener)
                throw new ListenerNotFoundException();
            this.listener = null;
            this.filter   = null;
            if (proxy.isLoaded())
                super.removeMBeanServerNotificationListener(listener);
            proxy.remove(this);
        }

        public synchronized void loaded() {
            if (listener != null)
                addMBeanServerNotificationListener(listener, filter);
        }

    }

    /**
     * This is a use case for e.g GlassFish: the LazyStarterDomain MBean
     * is a place holder that will unregister itself and autoload a set
     * of MBeans in place of its own domain when that domain is
     * accessed.
     * This is an abstract class, where the only abstract method
     * is loadMBeans(MBeanServer).
     * Subclasses should implement that method to register whatever MBeans
     * in the domain previously held by that LazyStarterDomain object.
     * In other words: the LazyStarterDomain MBean is 'replaced' by the
     * MBeans loaded by loadMBeans();
     */
    public static abstract class LazyStarterDomain extends LazyDomain {

        /**
         * This is a loader that will unregister the JMXDomain that
         * created it, and register a bunch of MBeans in its place
         * by calling LazyStarterDomain.loadMBeans
         *
         * That one gave me "la migraine".
         */
        private static class HalfGrainLoader implements MBeanServerLoader {
            private volatile LazyStarterDomain domain;
            public MBeanServer loadMBeanServer() {
                if (domain == null)
                    throw new IllegalStateException(
                            "JMXDomain MBean not registered!");
                final MBeanServer server     = domain.getMBeanServer();
                final ObjectName  domainName = domain.getObjectName();
                try {
                    server.unregisterMBean(domainName);
                } catch (Exception x) {
                    throw new IllegalStateException("Can't unregister " +
                            "JMXDomain: "+x,x);
                }
                domain.loadMBeans(server,domainName.getDomain());
                return server;
            }
            public void setDomain(LazyStarterDomain domain) {
                this.domain = domain;
            }
        }

        /**
         * This is an MBeanServerProxy which create a loader for the
         * LazyStarterDomain MBean.
         */
        private static class DomainStarter extends MBeanServerProxy {

            public DomainStarter() {
                this(new HalfGrainLoader());
            }

            private final HalfGrainLoader loader;
            private DomainStarter(HalfGrainLoader loader) {
                super(loader);
                this.loader = loader;
            }

            public void setDomain(LazyStarterDomain domain) {
                loader.setDomain(domain);
            }
        }

        /**
         * A new LazyStarterDomain. When the domain monitored by this
         * MBean is accessed, this MBean will unregister itself and call
         * the abstract loadMBeans(MBeanServer) method.
         * Subclasses need only to implement loadMBeans().
         */
        public LazyStarterDomain() {
            this(new DomainStarter());
        }

        private LazyStarterDomain(DomainStarter starter) {
            super(starter);
            starter.setDomain(this);
        }

        // Contrarily to its LazyDomain superclass, this LazyDomain
        // doesn't wrapp another MBeanServer: it simply registers a bunch
        // of MBeans in its own MBeanServer.
        // Thus, there's no notifications to forward.
        //
        @Override
        public void addMBeanServerNotificationListener(
                NotificationListener listener, NotificationFilter filter) {
            // nothing to do.
        }

        // Contrarily to its LazyDomain superclass, this LazyDomain
        // doesn't wrapp another MBeanServer: it simply registers a bunch
        // of MBeans in its own MBeanServer.
        // Thus, there's no notifications to forward.
        //
        @Override
        public void removeMBeanServerNotificationListener(
                NotificationListener listener) throws ListenerNotFoundException {
            // nothing to do
        }

        // If this domain is registered, it contains no MBean.
        // If it is not registered, then it no longer contain any MBean.
        // The MBeanCount is thus always 0.
        @Override
        public Integer getMBeanCount() {
            return 0;
        }

        /**
         * Called when the domain is first accessed.
         * {@code server} is the server in which this MBean was registered.
         * A subclass must override this method in order to register
         * the MBeans that should be contained in domain.
         *
         * @param server the server in which to load the MBeans.
         * @param domain the domain in which the MBeans should be registered.
         */
        protected abstract void loadMBeans(MBeanServer server, String domain);


    }

    private static MBeanServerNotification pop(
            BlockingQueue<Notification> queue,
                                    String type,
                                    ObjectName mbean,
                                    String test)
                                    throws InterruptedException {
        final Notification n = queue.poll(1, TimeUnit.SECONDS);
        if (!(n instanceof MBeanServerNotification))
            fail(test+"expected MBeanServerNotification, got "+n);
        final MBeanServerNotification msn = (MBeanServerNotification)n;
        if (!type.equals(msn.getType()))
            fail(test+"expected "+type+", got "+msn.getType());
        if (!mbean.apply(msn.getMBeanName()))
            fail(test+"expected "+mbean+", got "+msn.getMBeanName());
        System.out.println(test+" got: "+msn);
        return msn;
    }
    private static MBeanServerNotification popADD(
            BlockingQueue<Notification> queue,
                                    ObjectName mbean,
                                    String test)
                                    throws InterruptedException {
        return pop(queue, MBeanServerNotification.REGISTRATION_NOTIFICATION,
                mbean, test);
    }

    private static MBeanServerNotification popREM(
            BlockingQueue<Notification> queue,
                                    ObjectName mbean,
                                    String test)
                                    throws InterruptedException {
        return pop(queue, MBeanServerNotification.UNREGISTRATION_NOTIFICATION,
                mbean, test);
    }


    private static void fail(String msg) {
        raise(new RuntimeException(msg));
    }

    private static void fail(String msg, Throwable cause) {
        raise(new RuntimeException(msg,cause));
    }

    private static void raise(RuntimeException x) {
        lastException = x;
        exceptionCount++;
        throw x;
    }

    private static volatile Exception lastException = null;
    private static volatile int       exceptionCount = 0;

    // ZZZ need to add a test case with several LazyDomains, and
    // need to test that nothing is loaded until the lazy domains
    // are accessed...
    //

    private static void registerWombats(MBeanServer server, String domain,
            int count) {
        try {
            for (int i=0;i<count;i++) {
                final ObjectName name =
                        new ObjectName(domain+":type=Wombat,name=wombat#"+i);
                server.createMBean("Wombat", name);
            }
        } catch (RuntimeException x) {
            throw x;
        } catch(Exception x) {
            throw new RuntimeException(x.toString(),x);
        }
    }

    public static void checkSize(String test, MBeanServer server, int size) {
        System.out.println("We have now "+server.getMBeanCount()+
                " MBeans in "+Arrays.toString(server.getDomains()));
        if (server.getMBeanCount() != size)
            fail(test+"Expected "+size+
                    " MBeans, found " + server.getMBeanCount());
    }

    private static MBeanServer newMBeanServer() {
        return MBeanServerFactory.newMBeanServer();
    }

    public static void lazyTest() throws Exception {
        final String test = "lazyTest: ";
        System.out.println("" +
                "\nThis test checks that it is possible to perform lazy loading" +
                "\nof MBeans in a given domain by using a JMXDomain subclass" +
                "\nfor that domain.");

        System.out.println(test + " START");

        // The "global" MBeanServer...
        final MBeanServer server = newMBeanServer();

        // An MBeanServer proxy which makes it possible to `lazy load'
        // the platform MBeanServer domains inside the global MBeanServer.
        //
        final MBeanServerProxy  platform =
                new MBeanServerProxy(new MBeanServerLoader() {

            public MBeanServer loadMBeanServer() {
                return ManagementFactory.getPlatformMBeanServer();
            }
        });


        // The list of domain from the platform MBeanServer that will be
        // lazily loaded in the global MBeanServer
        //
        final String[] platformDomains = {
              "java.lang", "com.sun.management",
              "java.util.logging", "java.nio"
        };

        // We create a second MBeanServer, in which we will store some
        // custom MBeans. We will use this server to perform lazy loading
        // of two domains: custom.awomb and custom.bwomb.
        //
        // We use an MBeanServerBuilder here so that the MBeans registered
        // in our custom domain see all the MBeans in the global MBeanServer.
        // We do this by saying that the 'outer' MBeanServer is our global
        // servers. This means that the MBeans registered in the global
        // MBeanServer will see the MBeans from custom.awomb and custom.bwomb,
        // and the MBeans from custom.awomb and custom.bwomb will also see
        // the MBeans from the global MBeanServer, including those from
        // the platform domains.
        //
        final MBeanServerBuilder builder = new MBeanServerBuilder();
        final MBeanServerDelegate delegate = builder.newMBeanServerDelegate();
        final MBeanServer custom = builder.newMBeanServer("custom",
                server, delegate);

        // Number of MBean that we will put in each of the custom domain.
        //
        final int customCount = 10;

        // We use one MBeanServer proxy for each of the custom domains.
        // This makes it possible to load custom.awomb independently of
        // custom.bwomb.
        //
        // Here, the logic of the loader is to register MBeans in the loaded
        // domain as soon as the domain is loaded.
        //
        final MBeanServerProxy customa =
                new MBeanServerProxy(new MBeanServerLoader() {
            // A loader to register awomb MBeans in the custom MBeanServer.
            public MBeanServer loadMBeanServer() {
                registerWombats(custom, "custom.awomb", customCount);
                return custom;
            }
        });
        final MBeanServerProxy customb =
                new MBeanServerProxy(new MBeanServerLoader() {
            // A loader to register bwomb MBeans in the custom MBeanServer.
            public MBeanServer loadMBeanServer() {
                registerWombats(custom, "custom.bwomb", customCount);
                return custom;
            }
        });

        // A notification queue.
        final BlockingQueue<Notification> queue =
                new ArrayBlockingQueue<Notification>(100);

        // A listener that puts notifs in the queue.
        final NotificationListener l = new NotificationListener() {

            public void handleNotification(Notification notification,
                    Object handback) {
                try {
                    if (!queue.offer(notification, 5, TimeUnit.SECONDS)) {
                        throw new RuntimeException("timeout exceeded");
                    }
                } catch (Exception x) {
                    fail(test + "failed to handle notif", x);
                }
            }
        };

        // Create a LazyDomain for each of the platform domain.
        // All platform domain share the same MBeanServer proxy, which means
        // that loading one domain will also load all the others.
        //
        Map<String,LazyDomain> domainsMap = new HashMap<String,LazyDomain>();
        for (String dom : platformDomains) {
            domainsMap.put(dom, new LazyDomain(platform));
        }
        domainsMap.put("custom.awomb", new LazyDomain(customa));
        domainsMap.put("custom.bwomb", new LazyDomain(customb));

        for (Map.Entry<String,LazyDomain> e : domainsMap.entrySet()) {
            server.registerMBean(e.getValue(),
                    JMXDomain.getDomainObjectName(e.getKey()));
        }

        // check that lazy MBeans are not there...
        checkSize(test,server,domainsMap.size()+1);

        System.out.println(test+" registering listener with delegate.");
        server.addNotificationListener(MBeanServerDelegate.DELEGATE_NAME, l,
                null, null);

        // check that lazy MBeans are not there...
        checkSize(test,server,domainsMap.size()+1);

        // force loading of custom.awomb.
        final ObjectName awombat = new ObjectName(
                "custom.awomb:type=Wombat,name=wombat#"+customCount/2);
        if (!server.isRegistered(awombat))
            fail(test+"Expected "+awombat+" to be reggistered!");

        final int oldCount = domainsMap.size()+1+customCount;
        checkSize(test,server,oldCount);

        if (queue.peek() != null)
            fail(test+"Received unexpected notifications: "+queue);


        System.out.println(test+"creating a proxy for ClassLoadingMXBean.");
        final ClassLoadingMXBean cl =
                JMX.newMXBeanProxy(server,
                new ObjectName(ManagementFactory.CLASS_LOADING_MXBEAN_NAME),
                ClassLoadingMXBean.class);

        checkSize(test,server,oldCount);

        System.out.println(test+"Loaded classes: "+cl.getLoadedClassCount());

        final int newCount = server.getMBeanCount();
        if (newCount < oldCount+6)
            fail(test+"Expected at least "+(oldCount+6)+
                    " MBeans. Found "+newCount);

        final ObjectName jwombat = new ObjectName("java.lang:type=Wombat");
        server.createMBean("Wombat", jwombat);
        System.out.println(test+"Created "+jwombat);
        checkSize(test,server,newCount+1);

        popADD(queue, jwombat, test);
        if (queue.peek() != null)
            fail(test+"Received unexpected notifications: "+queue);


        int platcount = 0;
        for (String dom : platformDomains) {
            final Set<ObjectName> found =
                    server.queryNames(new ObjectName(dom+":*"),null);
            final int jcount = found.size();
            System.out.println(test+"Found "+jcount+" MBeans in "+dom+
                    ": "+found);
            checkSize(test,server,newCount+1);
            platcount += (jcount-1);
        }
        checkSize(test,server,oldCount+platcount);

        final ObjectName owombat = new ObjectName("custom:type=Wombat");
        server.createMBean("Wombat", owombat);
        System.out.println(test+"Created "+owombat);
        checkSize(test,server,newCount+2);
        popADD(queue, owombat, test);
        if (queue.peek() != null)
            fail(test+"Received unexpected notifications: "+queue);

        final Set<ObjectName> jwombatView = (Set<ObjectName>)
                server.invoke(jwombat, "listMatching", new Object[] {null},
                new String[] {ObjectName.class.getName()});
        System.out.println(test+jwombat+" sees: "+jwombatView);
        checkSize(test, server, newCount+2);
        if (jwombatView.size() != (platcount+1))
            fail(test+jwombat+" sees "+jwombatView.size()+" MBeans - should" +
                    " have seen "+(platcount+1));

        final Set<ObjectName> platformMBeans =
                ManagementFactory.getPlatformMBeanServer().
                queryNames(null, null);
        if (!platformMBeans.equals(jwombatView))
            fail(test+jwombat+" should have seen "+platformMBeans);

        // check that awombat triggers loading of bwombats
        final Set<ObjectName> awombatView = (Set<ObjectName>)
                server.invoke(awombat, "listMatching", new Object[] {null},
                new String[] {ObjectName.class.getName()});
        System.out.println(test+awombat+" sees: "+awombatView);
        final int totalCount = newCount+2+customCount;
        checkSize(test, server, totalCount);
        if (awombatView.size() != totalCount)
            fail(test+jwombat+" sees "+jwombatView.size()+" MBeans - should" +
                    " have seen "+totalCount);

        final Set<ObjectName> allMBeans = server.
                queryNames(null, null);
        if (!allMBeans.equals(awombatView))
            fail(test+awombat+" should have seen "+allMBeans);

        System.out.println(test + " PASSED");

    }


    public static void lazyStarterTest() throws Exception {
        final String test = "lazyStarterTest: ";
        System.out.println("" +
                "\nThis test checks that it is possible to perform lazy loading" +
                "\nof MBeans in a given domain by using a transient JMXDomain" +
                "\nsubclass for that domain. ");

        System.out.println(test + " START");

        // The "global" MBeanServer...
        final MBeanServer platform =
                ManagementFactory.getPlatformMBeanServer();

        // A notification queue.
        final BlockingQueue<Notification> queue =
                new ArrayBlockingQueue<Notification>(100);

        // A listener that puts notifs in the queue.
        final NotificationListener l = new NotificationListener() {

            public void handleNotification(Notification notification,
                    Object handback) {
                try {
                    if (!queue.offer(notification, 5, TimeUnit.SECONDS)) {
                        throw new RuntimeException("timeout exceeded");
                    }
                } catch (Exception x) {
                    fail(test + "failed to handle notif", x);
                }
            }
        };

        System.out.println(test+" registering listener with delegate.");
        platform.addNotificationListener(MBeanServerDelegate.DELEGATE_NAME, l,
                null, null);

        final String ld1 = "lazy1";
        final String ld2 = "lazy2";
        final int wCount = 5;
        final LazyStarterDomain lazy1 = new LazyStarterDomain() {
            @Override
            protected void loadMBeans(MBeanServer server, String domain) {
                registerWombats(server, ld1, wCount);
            }
        };
        final LazyStarterDomain lazy2 = new LazyStarterDomain() {
            @Override
            protected void loadMBeans(MBeanServer server, String domain) {
                registerWombats(server, ld2, wCount);
            }
        };
        final ObjectName lo1 = JMXDomain.getDomainObjectName(ld1);
        final ObjectName lo2 = JMXDomain.getDomainObjectName(ld2);

        final int initial = platform.getMBeanCount();

        platform.registerMBean(lazy1, lo1);
        System.out.println(test+"registered "+lo1);
        checkSize(test, platform, initial+1);
        popADD(queue, lo1, test);

        platform.registerMBean(lazy2, lo2);
        System.out.println(test+"registered "+lo2);
        checkSize(test, platform, initial+2);
        popADD(queue, lo2, test);


        final ObjectName awombat = new ObjectName(
                ld1+":type=Wombat,name=wombat#"+wCount/2);
        if (!platform.isRegistered(awombat))
            fail(test+"Expected "+awombat+" to be reggistered!");
        checkSize(test,platform,initial+wCount+1);
        popREM(queue, lo1, test);
        final ObjectName pat1 =
                new ObjectName(ld1+":type=Wombat,name=wombat#*");
        for (int i=0;i<wCount;i++) {
            popADD(queue,pat1,test);
        }
        System.out.println(test+"Found: "+
                platform.queryNames(pat1,null));
        checkSize(test,platform,initial+wCount+1);

        final Set<ObjectName> all = platform.queryNames(null, null);
        popREM(queue, lo2, test);
        System.out.println(test+"Now found: "+all);
        checkSize(test,platform,initial+wCount+wCount);
        final ObjectName pat2 =
                new ObjectName(ld2+":type=Wombat,name=wombat#*");
        for (int i=0;i<wCount;i++) {
            popADD(queue,pat2,test);
        }

        System.out.println(test+"check concurrent modification " +
                "of the DomainDispatcher.");
        System.out.println(test+"This will fail if the DomainDispatcher" +
                " doesn't allow concurrent modifications.");
        final HashMap<String,LazyStarterDomain> testConcurrent =
                new HashMap<String,LazyStarterDomain>();
        for (int i=0;i<(100/wCount);i++) {
            final String ld = "concurrent.lazy"+i;
            final LazyStarterDomain lazy = new LazyStarterDomain() {
                @Override
                protected void loadMBeans(MBeanServer server, String domain) {
                    registerWombats(server, ld, wCount-1);
                }
            };
            testConcurrent.put(ld, lazy);
            final ObjectName lo = JMXDomain.getDomainObjectName(ld);
            platform.registerMBean(lazy, lo);
            popADD(queue, lo, test);
        }

        System.out.println(test+"Big autoload: "+
                platform.queryNames(null,null));
        System.out.println(test+"Big after load: "+
                platform.queryNames(null,null));
        if (!platform.queryNames(JMXDomain.getDomainObjectName("*"), null).
                isEmpty()) {
            fail(test+" some domains are still here: "+
                    platform.queryNames(
                    JMXDomain.getDomainObjectName("*"), null));
        }
        queue.clear();
        System.out.println(test+"PASSED: The DomainDispatcher appears to be " +
                "resilient to concurrent modifications.");
    }

    public static void main(String... args) throws Exception {

        lazyTest();
        lazyStarterTest();

        if (lastException != null)
            throw lastException;
    }
}