1/*
2 * Copyright (c) 2003, 2013, 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.  Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26package com.sun.jmx.remote.internal;
27
28import java.security.AccessController;
29import java.security.PrivilegedAction;
30import java.security.PrivilegedActionException;
31import java.security.PrivilegedExceptionAction;
32import java.util.ArrayList;
33import java.util.Collection;
34import java.util.Collections;
35import java.util.HashSet;
36import java.util.List;
37import java.util.Set;
38import java.util.HashMap;
39import java.util.Map;
40
41import javax.management.InstanceNotFoundException;
42import javax.management.MBeanServer;
43import javax.management.MBeanServerDelegate;
44import javax.management.MBeanServerNotification;
45import javax.management.Notification;
46import javax.management.NotificationBroadcaster;
47import javax.management.NotificationFilter;
48import javax.management.NotificationFilterSupport;
49import javax.management.NotificationListener;
50import javax.management.ObjectName;
51import javax.management.QueryEval;
52import javax.management.QueryExp;
53
54import javax.management.remote.NotificationResult;
55import javax.management.remote.TargetedNotification;
56
57import com.sun.jmx.remote.util.EnvHelp;
58import com.sun.jmx.remote.util.ClassLogger;
59
60/** A circular buffer of notifications received from an MBean server. */
61/*
62  There is one instance of ArrayNotificationBuffer for every
63  MBeanServer object that has an attached ConnectorServer.  Then, for
64  every ConnectorServer attached to a given MBeanServer, there is an
65  instance of the inner class ShareBuffer.  So for example with two
66  ConnectorServers it looks like this:
67
68  ConnectorServer1 -> ShareBuffer1 -\
69                                     }-> ArrayNotificationBuffer
70  ConnectorServer2 -> ShareBuffer2 -/              |
71                                                   |
72                                                   v
73                                              MBeanServer
74
75  The ArrayNotificationBuffer has a circular buffer of
76  NamedNotification objects.  Each ConnectorServer defines a
77  notification buffer size, and this size is recorded by the
78  corresponding ShareBuffer.  The buffer size of the
79  ArrayNotificationBuffer is the maximum of all of its ShareBuffers.
80  When a ShareBuffer is added or removed, the ArrayNotificationBuffer
81  size is adjusted accordingly.
82
83  An ArrayNotificationBuffer also has a BufferListener (which is a
84  NotificationListener) registered on every NotificationBroadcaster
85  MBean in the MBeanServer to which it is attached.  The cost of this
86  potentially large set of listeners is the principal motivation for
87  sharing the ArrayNotificationBuffer between ConnectorServers, and
88  also the reason that we are careful to discard the
89  ArrayNotificationBuffer (and its BufferListeners) when there are no
90  longer any ConnectorServers using it.
91
92  The synchronization of this class is inherently complex.  In an attempt
93  to limit the complexity, we use just two locks:
94
95  - globalLock controls access to the mapping between an MBeanServer
96    and its ArrayNotificationBuffer and to the set of ShareBuffers for
97    each ArrayNotificationBuffer.
98
99  - the instance lock of each ArrayNotificationBuffer controls access
100    to the array of notifications, including its size, and to the
101    dispose flag of the ArrayNotificationBuffer.  The wait/notify
102    mechanism is used to indicate changes to the array.
103
104  If both locks are held at the same time, the globalLock must be
105  taken first.
106
107  Since adding or removing a BufferListener to an MBean can involve
108  calling user code, we are careful not to hold any locks while it is
109  done.
110 */
111public class ArrayNotificationBuffer implements NotificationBuffer {
112    private boolean disposed = false;
113
114    // FACTORY STUFF, INCLUDING SHARING
115
116    private static final Object globalLock = new Object();
117    private static final
118        HashMap<MBeanServer,ArrayNotificationBuffer> mbsToBuffer =
119        new HashMap<MBeanServer,ArrayNotificationBuffer>(1);
120    private final Collection<ShareBuffer> sharers = new HashSet<ShareBuffer>(1);
121
122    public static NotificationBuffer getNotificationBuffer(
123            MBeanServer mbs, Map<String, ?> env) {
124
125        if (env == null)
126            env = Collections.emptyMap();
127
128        //Find out queue size
129        int queueSize = EnvHelp.getNotifBufferSize(env);
130
131        ArrayNotificationBuffer buf;
132        boolean create;
133        NotificationBuffer sharer;
134        synchronized (globalLock) {
135            buf = mbsToBuffer.get(mbs);
136            create = (buf == null);
137            if (create) {
138                buf = new ArrayNotificationBuffer(mbs, queueSize);
139                mbsToBuffer.put(mbs, buf);
140            }
141            sharer = buf.new ShareBuffer(queueSize);
142        }
143        /* We avoid holding any locks while calling createListeners.
144         * This prevents possible deadlocks involving user code, but
145         * does mean that a second ConnectorServer created and started
146         * in this window will return before all the listeners are ready,
147         * which could lead to surprising behaviour.  The alternative
148         * would be to block the second ConnectorServer until the first
149         * one has finished adding all the listeners, but that would then
150         * be subject to deadlock.
151         */
152        if (create)
153            buf.createListeners();
154        return sharer;
155    }
156
157    /* Ensure that this buffer is no longer the one that will be returned by
158     * getNotificationBuffer.  This method is idempotent - calling it more
159     * than once has no effect beyond that of calling it once.
160     */
161    static void removeNotificationBuffer(MBeanServer mbs) {
162        synchronized (globalLock) {
163            mbsToBuffer.remove(mbs);
164        }
165    }
166
167    void addSharer(ShareBuffer sharer) {
168        synchronized (globalLock) {
169            synchronized (this) {
170                if (sharer.getSize() > queueSize)
171                    resize(sharer.getSize());
172            }
173            sharers.add(sharer);
174        }
175    }
176
177    private void removeSharer(ShareBuffer sharer) {
178        boolean empty;
179        synchronized (globalLock) {
180            sharers.remove(sharer);
181            empty = sharers.isEmpty();
182            if (empty)
183                removeNotificationBuffer(mBeanServer);
184            else {
185                int max = 0;
186                for (ShareBuffer buf : sharers) {
187                    int bufsize = buf.getSize();
188                    if (bufsize > max)
189                        max = bufsize;
190                }
191                if (max < queueSize)
192                    resize(max);
193            }
194        }
195        if (empty) {
196            synchronized (this) {
197                disposed = true;
198                // Notify potential waiting fetchNotification call
199                notifyAll();
200            }
201            destroyListeners();
202        }
203    }
204
205    private synchronized void resize(int newSize) {
206        if (newSize == queueSize)
207            return;
208        while (queue.size() > newSize)
209            dropNotification();
210        queue.resize(newSize);
211        queueSize = newSize;
212    }
213
214    private class ShareBuffer implements NotificationBuffer {
215        ShareBuffer(int size) {
216            this.size = size;
217            addSharer(this);
218        }
219
220        public NotificationResult
221            fetchNotifications(NotificationBufferFilter filter,
222                               long startSequenceNumber,
223                               long timeout,
224                               int maxNotifications)
225                throws InterruptedException {
226            NotificationBuffer buf = ArrayNotificationBuffer.this;
227            return buf.fetchNotifications(filter, startSequenceNumber,
228                                          timeout, maxNotifications);
229        }
230
231        public void dispose() {
232            ArrayNotificationBuffer.this.removeSharer(this);
233        }
234
235        int getSize() {
236            return size;
237        }
238
239        private final int size;
240    }
241
242
243    // ARRAYNOTIFICATIONBUFFER IMPLEMENTATION
244
245    private ArrayNotificationBuffer(MBeanServer mbs, int queueSize) {
246        if (logger.traceOn())
247            logger.trace("Constructor", "queueSize=" + queueSize);
248
249        if (mbs == null || queueSize < 1)
250            throw new IllegalArgumentException("Bad args");
251
252        this.mBeanServer = mbs;
253        this.queueSize = queueSize;
254        this.queue = new ArrayQueue<NamedNotification>(queueSize);
255        this.earliestSequenceNumber = System.currentTimeMillis();
256        this.nextSequenceNumber = this.earliestSequenceNumber;
257
258        logger.trace("Constructor", "ends");
259    }
260
261    private synchronized boolean isDisposed() {
262        return disposed;
263    }
264
265    // We no longer support calling this method from outside.
266    // The JDK doesn't contain any such calls and users are not
267    // supposed to be accessing this class.
268    public void dispose() {
269        throw new UnsupportedOperationException();
270    }
271
272    /**
273     * <p>Fetch notifications that match the given listeners.</p>
274     *
275     * <p>The operation only considers notifications with a sequence
276     * number at least <code>startSequenceNumber</code>.  It will take
277     * no longer than <code>timeout</code>, and will return no more
278     * than <code>maxNotifications</code> different notifications.</p>
279     *
280     * <p>If there are no notifications matching the criteria, the
281     * operation will block until one arrives, subject to the
282     * timeout.</p>
283     *
284     * @param filter an object that will add notifications to a
285     * {@code List<TargetedNotification>} if they match the current
286     * listeners with their filters.
287     * @param startSequenceNumber the first sequence number to
288     * consider.
289     * @param timeout the maximum time to wait.  May be 0 to indicate
290     * not to wait if there are no notifications.
291     * @param maxNotifications the maximum number of notifications to
292     * return.  May be 0 to indicate a wait for eligible notifications
293     * that will return a usable <code>nextSequenceNumber</code>.  The
294     * {@link TargetedNotification} array in the returned {@link
295     * NotificationResult} may contain more than this number of
296     * elements but will not contain more than this number of
297     * different notifications.
298     */
299    public NotificationResult
300        fetchNotifications(NotificationBufferFilter filter,
301                           long startSequenceNumber,
302                           long timeout,
303                           int maxNotifications)
304            throws InterruptedException {
305
306        logger.trace("fetchNotifications", "starts");
307
308        if (startSequenceNumber < 0 || isDisposed()) {
309            synchronized(this) {
310                return new NotificationResult(earliestSequenceNumber(),
311                                              nextSequenceNumber(),
312                                              new TargetedNotification[0]);
313            }
314        }
315
316        // Check arg validity
317        if (filter == null
318            || startSequenceNumber < 0 || timeout < 0
319            || maxNotifications < 0) {
320            logger.trace("fetchNotifications", "Bad args");
321            throw new IllegalArgumentException("Bad args to fetch");
322        }
323
324        if (logger.debugOn()) {
325            logger.trace("fetchNotifications",
326                  "filter=" + filter + "; startSeq=" +
327                  startSequenceNumber + "; timeout=" + timeout +
328                  "; max=" + maxNotifications);
329        }
330
331        if (startSequenceNumber > nextSequenceNumber()) {
332            final String msg = "Start sequence number too big: " +
333                startSequenceNumber + " > " + nextSequenceNumber();
334            logger.trace("fetchNotifications", msg);
335            throw new IllegalArgumentException(msg);
336        }
337
338        /* Determine the end time corresponding to the timeout value.
339           Caller may legitimately supply Long.MAX_VALUE to indicate no
340           timeout.  In that case the addition will overflow and produce
341           a negative end time.  Set end time to Long.MAX_VALUE in that
342           case.  We assume System.currentTimeMillis() is positive.  */
343        long endTime = System.currentTimeMillis() + timeout;
344        if (endTime < 0) // overflow
345            endTime = Long.MAX_VALUE;
346
347        if (logger.debugOn())
348            logger.debug("fetchNotifications", "endTime=" + endTime);
349
350        /* We set earliestSeq the first time through the loop.  If we
351           set it here, notifications could be dropped before we
352           started examining them, so earliestSeq might not correspond
353           to the earliest notification we examined.  */
354        long earliestSeq = -1;
355        long nextSeq = startSequenceNumber;
356        List<TargetedNotification> notifs =
357            new ArrayList<TargetedNotification>();
358
359        /* On exit from this loop, notifs, earliestSeq, and nextSeq must
360           all be correct values for the returned NotificationResult.  */
361        while (true) {
362            logger.debug("fetchNotifications", "main loop starts");
363
364            NamedNotification candidate;
365
366            /* Get the next available notification regardless of filters,
367               or wait for one to arrive if there is none.  */
368            synchronized (this) {
369
370                /* First time through.  The current earliestSequenceNumber
371                   is the first one we could have examined.  */
372                if (earliestSeq < 0) {
373                    earliestSeq = earliestSequenceNumber();
374                    if (logger.debugOn()) {
375                        logger.debug("fetchNotifications",
376                              "earliestSeq=" + earliestSeq);
377                    }
378                    if (nextSeq < earliestSeq) {
379                        nextSeq = earliestSeq;
380                        logger.debug("fetchNotifications",
381                                     "nextSeq=earliestSeq");
382                    }
383                } else
384                    earliestSeq = earliestSequenceNumber();
385
386                /* If many notifications have been dropped since the
387                   last time through, nextSeq could now be earlier
388                   than the current earliest.  If so, notifications
389                   may have been lost and we return now so the caller
390                   can see this next time it calls.  */
391                if (nextSeq < earliestSeq) {
392                    logger.trace("fetchNotifications",
393                          "nextSeq=" + nextSeq + " < " + "earliestSeq=" +
394                          earliestSeq + " so may have lost notifs");
395                    break;
396                }
397
398                if (nextSeq < nextSequenceNumber()) {
399                    candidate = notificationAt(nextSeq);
400                    // Skip security check if NotificationBufferFilter is not overloaded
401                    if (!(filter instanceof ServerNotifForwarder.NotifForwarderBufferFilter)) {
402                        try {
403                            ServerNotifForwarder.checkMBeanPermission(this.mBeanServer,
404                                                      candidate.getObjectName(),"addNotificationListener");
405                        } catch (InstanceNotFoundException | SecurityException e) {
406                            if (logger.debugOn()) {
407                                logger.debug("fetchNotifications", "candidate: " + candidate + " skipped. exception " + e);
408                            }
409                            ++nextSeq;
410                            continue;
411                        }
412                    }
413
414                    if (logger.debugOn()) {
415                        logger.debug("fetchNotifications", "candidate: " +
416                                     candidate);
417                        logger.debug("fetchNotifications", "nextSeq now " +
418                                     nextSeq);
419                    }
420                } else {
421                    /* nextSeq is the largest sequence number.  If we
422                       already got notifications, return them now.
423                       Otherwise wait for some to arrive, with
424                       timeout.  */
425                    if (notifs.size() > 0) {
426                        logger.debug("fetchNotifications",
427                              "no more notifs but have some so don't wait");
428                        break;
429                    }
430                    long toWait = endTime - System.currentTimeMillis();
431                    if (toWait <= 0) {
432                        logger.debug("fetchNotifications", "timeout");
433                        break;
434                    }
435
436                    /* dispose called */
437                    if (isDisposed()) {
438                        if (logger.debugOn())
439                            logger.debug("fetchNotifications",
440                                         "dispose callled, no wait");
441                        return new NotificationResult(earliestSequenceNumber(),
442                                                  nextSequenceNumber(),
443                                                  new TargetedNotification[0]);
444                    }
445
446                    if (logger.debugOn())
447                        logger.debug("fetchNotifications",
448                                     "wait(" + toWait + ")");
449                    wait(toWait);
450
451                    continue;
452                }
453            }
454
455            /* We have a candidate notification.  See if it matches
456               our filters.  We do this outside the synchronized block
457               so we don't hold up everyone accessing the buffer
458               (including notification senders) while we evaluate
459               potentially slow filters.  */
460            ObjectName name = candidate.getObjectName();
461            Notification notif = candidate.getNotification();
462            List<TargetedNotification> matchedNotifs =
463                new ArrayList<TargetedNotification>();
464            logger.debug("fetchNotifications",
465                         "applying filter to candidate");
466            filter.apply(matchedNotifs, name, notif);
467
468            if (matchedNotifs.size() > 0) {
469                /* We only check the max size now, so that our
470                   returned nextSeq is as large as possible.  This
471                   prevents the caller from thinking it missed
472                   interesting notifications when in fact we knew they
473                   weren't.  */
474                if (maxNotifications <= 0) {
475                    logger.debug("fetchNotifications",
476                                 "reached maxNotifications");
477                    break;
478                }
479                --maxNotifications;
480                if (logger.debugOn())
481                    logger.debug("fetchNotifications", "add: " +
482                                 matchedNotifs);
483                notifs.addAll(matchedNotifs);
484            }
485
486            ++nextSeq;
487        } // end while
488
489        /* Construct and return the result.  */
490        int nnotifs = notifs.size();
491        TargetedNotification[] resultNotifs =
492            new TargetedNotification[nnotifs];
493        notifs.toArray(resultNotifs);
494        NotificationResult nr =
495            new NotificationResult(earliestSeq, nextSeq, resultNotifs);
496        if (logger.debugOn())
497            logger.debug("fetchNotifications", nr.toString());
498        logger.trace("fetchNotifications", "ends");
499
500        return nr;
501    }
502
503    synchronized long earliestSequenceNumber() {
504        return earliestSequenceNumber;
505    }
506
507    synchronized long nextSequenceNumber() {
508        return nextSequenceNumber;
509    }
510
511    synchronized void addNotification(NamedNotification notif) {
512        if (logger.traceOn())
513            logger.trace("addNotification", notif.toString());
514
515        while (queue.size() >= queueSize) {
516            dropNotification();
517            if (logger.debugOn()) {
518                logger.debug("addNotification",
519                      "dropped oldest notif, earliestSeq=" +
520                      earliestSequenceNumber);
521            }
522        }
523        queue.add(notif);
524        nextSequenceNumber++;
525        if (logger.debugOn())
526            logger.debug("addNotification", "nextSeq=" + nextSequenceNumber);
527        notifyAll();
528    }
529
530    private void dropNotification() {
531        queue.remove(0);
532        earliestSequenceNumber++;
533    }
534
535    synchronized NamedNotification notificationAt(long seqNo) {
536        long index = seqNo - earliestSequenceNumber;
537        if (index < 0 || index > Integer.MAX_VALUE) {
538            final String msg = "Bad sequence number: " + seqNo + " (earliest "
539                + earliestSequenceNumber + ")";
540            logger.trace("notificationAt", msg);
541            throw new IllegalArgumentException(msg);
542        }
543        return queue.get((int) index);
544    }
545
546    private static class NamedNotification {
547        NamedNotification(ObjectName sender, Notification notif) {
548            this.sender = sender;
549            this.notification = notif;
550        }
551
552        ObjectName getObjectName() {
553            return sender;
554        }
555
556        Notification getNotification() {
557            return notification;
558        }
559
560        public String toString() {
561            return "NamedNotification(" + sender + ", " + notification + ")";
562        }
563
564        private final ObjectName sender;
565        private final Notification notification;
566    }
567
568    /*
569     * Add our listener to every NotificationBroadcaster MBean
570     * currently in the MBean server and to every
571     * NotificationBroadcaster later created.
572     *
573     * It would be really nice if we could just do
574     * mbs.addNotificationListener(new ObjectName("*:*"), ...);
575     * Definitely something for the next version of JMX.
576     *
577     * There is a nasty race condition that we must handle.  We
578     * first register for MBean-creation notifications so we can add
579     * listeners to new MBeans, then we query the existing MBeans to
580     * add listeners to them.  The problem is that a new MBean could
581     * arrive after we register for creations but before the query has
582     * completed.  Then we could see the MBean both in the query and
583     * in an MBean-creation notification, and we would end up
584     * registering our listener twice.
585     *
586     * To solve this problem, we arrange for new MBeans that arrive
587     * while the query is being done to be added to the Set createdDuringQuery
588     * and we do not add a listener immediately.  When the query is done,
589     * we atomically turn off the addition of new names to createdDuringQuery
590     * and add all the names that were there to the result of the query.
591     * Since we are dealing with Sets, the result is the same whether or not
592     * the newly-created MBean was included in the query result.
593     *
594     * It is important not to hold any locks during the operation of adding
595     * listeners to MBeans.  An MBean's addNotificationListener can be
596     * arbitrary user code, and this could deadlock with any locks we hold
597     * (see bug 6239400).  The corollary is that we must not do any operations
598     * in this method or the methods it calls that require locks.
599     */
600    private void createListeners() {
601        logger.debug("createListeners", "starts");
602
603        synchronized (this) {
604            createdDuringQuery = new HashSet<ObjectName>();
605        }
606
607        try {
608            addNotificationListener(MBeanServerDelegate.DELEGATE_NAME,
609                                    creationListener, creationFilter, null);
610            logger.debug("createListeners", "added creationListener");
611        } catch (Exception e) {
612            final String msg = "Can't add listener to MBean server delegate: ";
613            RuntimeException re = new IllegalArgumentException(msg + e);
614            EnvHelp.initCause(re, e);
615            logger.fine("createListeners", msg + e);
616            logger.debug("createListeners", e);
617            throw re;
618        }
619
620        /* Spec doesn't say whether Set returned by QueryNames can be modified
621           so we clone it. */
622        Set<ObjectName> names = queryNames(null, broadcasterQuery);
623        names = new HashSet<ObjectName>(names);
624
625        synchronized (this) {
626            names.addAll(createdDuringQuery);
627            createdDuringQuery = null;
628        }
629
630        for (ObjectName name : names)
631            addBufferListener(name);
632        logger.debug("createListeners", "ends");
633    }
634
635    private void addBufferListener(ObjectName name) {
636        checkNoLocks();
637        if (logger.debugOn())
638            logger.debug("addBufferListener", name.toString());
639        try {
640            addNotificationListener(name, bufferListener, null, name);
641        } catch (Exception e) {
642            logger.trace("addBufferListener", e);
643            /* This can happen if the MBean was unregistered just
644               after the query.  Or user NotificationBroadcaster might
645               throw unexpected exception.  */
646        }
647    }
648
649    private void removeBufferListener(ObjectName name) {
650        checkNoLocks();
651        if (logger.debugOn())
652            logger.debug("removeBufferListener", name.toString());
653        try {
654            removeNotificationListener(name, bufferListener);
655        } catch (Exception e) {
656            logger.trace("removeBufferListener", e);
657        }
658    }
659
660    private void addNotificationListener(final ObjectName name,
661                                         final NotificationListener listener,
662                                         final NotificationFilter filter,
663                                         final Object handback)
664            throws Exception {
665        try {
666            AccessController.doPrivileged(new PrivilegedExceptionAction<Void>() {
667                public Void run() throws InstanceNotFoundException {
668                    mBeanServer.addNotificationListener(name,
669                                                        listener,
670                                                        filter,
671                                                        handback);
672                    return null;
673                }
674            });
675        } catch (Exception e) {
676            throw extractException(e);
677        }
678    }
679
680    private void removeNotificationListener(final ObjectName name,
681                                            final NotificationListener listener)
682            throws Exception {
683        try {
684            AccessController.doPrivileged(new PrivilegedExceptionAction<Void>() {
685                public Void run() throws Exception {
686                    mBeanServer.removeNotificationListener(name, listener);
687                    return null;
688                }
689            });
690        } catch (Exception e) {
691            throw extractException(e);
692        }
693    }
694
695    private Set<ObjectName> queryNames(final ObjectName name,
696                                       final QueryExp query) {
697        PrivilegedAction<Set<ObjectName>> act =
698            new PrivilegedAction<Set<ObjectName>>() {
699                public Set<ObjectName> run() {
700                    return mBeanServer.queryNames(name, query);
701                }
702            };
703        try {
704            return AccessController.doPrivileged(act);
705        } catch (RuntimeException e) {
706            logger.fine("queryNames", "Failed to query names: " + e);
707            logger.debug("queryNames", e);
708            throw e;
709        }
710    }
711
712    private static boolean isInstanceOf(final MBeanServer mbs,
713                                        final ObjectName name,
714                                        final String className) {
715        PrivilegedExceptionAction<Boolean> act =
716            new PrivilegedExceptionAction<Boolean>() {
717                public Boolean run() throws InstanceNotFoundException {
718                    return mbs.isInstanceOf(name, className);
719                }
720            };
721        try {
722            return AccessController.doPrivileged(act);
723        } catch (Exception e) {
724            logger.fine("isInstanceOf", "failed: " + e);
725            logger.debug("isInstanceOf", e);
726            return false;
727        }
728    }
729
730    /* This method must not be synchronized.  See the comment on the
731     * createListeners method.
732     *
733     * The notification could arrive after our buffer has been destroyed
734     * or even during its destruction.  So we always add our listener
735     * (without synchronization), then we check if the buffer has been
736     * destroyed and if so remove the listener we just added.
737     */
738    private void createdNotification(MBeanServerNotification n) {
739        final String shouldEqual =
740            MBeanServerNotification.REGISTRATION_NOTIFICATION;
741        if (!n.getType().equals(shouldEqual)) {
742            logger.warning("createNotification", "bad type: " + n.getType());
743            return;
744        }
745
746        ObjectName name = n.getMBeanName();
747        if (logger.debugOn())
748            logger.debug("createdNotification", "for: " + name);
749
750        synchronized (this) {
751            if (createdDuringQuery != null) {
752                createdDuringQuery.add(name);
753                return;
754            }
755        }
756
757        if (isInstanceOf(mBeanServer, name, broadcasterClass)) {
758            addBufferListener(name);
759            if (isDisposed())
760                removeBufferListener(name);
761        }
762    }
763
764    private class BufferListener implements NotificationListener {
765        public void handleNotification(Notification notif, Object handback) {
766            if (logger.debugOn()) {
767                logger.debug("BufferListener.handleNotification",
768                      "notif=" + notif + "; handback=" + handback);
769            }
770            ObjectName name = (ObjectName) handback;
771            addNotification(new NamedNotification(name, notif));
772        }
773    }
774
775    private final NotificationListener bufferListener = new BufferListener();
776
777    private static class BroadcasterQuery
778            extends QueryEval implements QueryExp {
779        private static final long serialVersionUID = 7378487660587592048L;
780
781        public boolean apply(final ObjectName name) {
782            final MBeanServer mbs = QueryEval.getMBeanServer();
783            return isInstanceOf(mbs, name, broadcasterClass);
784        }
785    }
786    private static final QueryExp broadcasterQuery = new BroadcasterQuery();
787
788    private static final NotificationFilter creationFilter;
789    static {
790        NotificationFilterSupport nfs = new NotificationFilterSupport();
791        nfs.enableType(MBeanServerNotification.REGISTRATION_NOTIFICATION);
792        creationFilter = nfs;
793    }
794
795    private final NotificationListener creationListener =
796        new NotificationListener() {
797            public void handleNotification(Notification notif,
798                                           Object handback) {
799                logger.debug("creationListener", "handleNotification called");
800                createdNotification((MBeanServerNotification) notif);
801            }
802        };
803
804    private void destroyListeners() {
805        checkNoLocks();
806        logger.debug("destroyListeners", "starts");
807        try {
808            removeNotificationListener(MBeanServerDelegate.DELEGATE_NAME,
809                                       creationListener);
810        } catch (Exception e) {
811            logger.warning("remove listener from MBeanServer delegate", e);
812        }
813        Set<ObjectName> names = queryNames(null, broadcasterQuery);
814        for (final ObjectName name : names) {
815            if (logger.debugOn())
816                logger.debug("destroyListeners",
817                             "remove listener from " + name);
818            removeBufferListener(name);
819        }
820        logger.debug("destroyListeners", "ends");
821    }
822
823    private void checkNoLocks() {
824        if (Thread.holdsLock(this) || Thread.holdsLock(globalLock))
825            logger.warning("checkNoLocks", "lock protocol violation");
826    }
827
828    /**
829     * Iterate until we extract the real exception
830     * from a stack of PrivilegedActionExceptions.
831     */
832    private static Exception extractException(Exception e) {
833        while (e instanceof PrivilegedActionException) {
834            e = ((PrivilegedActionException)e).getException();
835        }
836        return e;
837    }
838
839    private static final ClassLogger logger =
840        new ClassLogger("javax.management.remote.misc",
841                        "ArrayNotificationBuffer");
842
843    private final MBeanServer mBeanServer;
844    private final ArrayQueue<NamedNotification> queue;
845    private int queueSize;
846    private long earliestSequenceNumber;
847    private long nextSequenceNumber;
848    private Set<ObjectName> createdDuringQuery;
849
850    static final String broadcasterClass =
851        NotificationBroadcaster.class.getName();
852}
853