0N/A/*
6297N/A * Copyright (c) 2003, 2013, Oracle and/or its affiliates. All rights reserved.
0N/A * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
0N/A *
0N/A * This code is free software; you can redistribute it and/or modify it
0N/A * under the terms of the GNU General Public License version 2 only, as
2362N/A * published by the Free Software Foundation. Oracle designates this
0N/A * particular file as subject to the "Classpath" exception as provided
2362N/A * by Oracle in the LICENSE file that accompanied this code.
0N/A *
0N/A * This code is distributed in the hope that it will be useful, but WITHOUT
0N/A * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
0N/A * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
0N/A * version 2 for more details (a copy is included in the LICENSE file that
0N/A * accompanied this code).
0N/A *
0N/A * You should have received a copy of the GNU General Public License version
0N/A * 2 along with this work; if not, write to the Free Software Foundation,
0N/A * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
0N/A *
2362N/A * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
2362N/A * or visit www.oracle.com if you need additional information or have any
2362N/A * questions.
0N/A */
0N/A
0N/Apackage com.sun.jmx.remote.internal;
0N/A
0N/Aimport java.security.AccessController;
0N/Aimport java.security.PrivilegedAction;
0N/Aimport java.security.PrivilegedActionException;
0N/Aimport java.security.PrivilegedExceptionAction;
0N/Aimport java.util.ArrayList;
0N/Aimport java.util.Collection;
0N/Aimport java.util.Collections;
0N/Aimport java.util.HashSet;
0N/Aimport java.util.List;
0N/Aimport java.util.Set;
0N/Aimport java.util.HashMap;
0N/Aimport java.util.Map;
0N/A
0N/Aimport javax.management.InstanceNotFoundException;
0N/Aimport javax.management.MBeanServer;
0N/Aimport javax.management.MBeanServerDelegate;
0N/Aimport javax.management.MBeanServerNotification;
0N/Aimport javax.management.Notification;
0N/Aimport javax.management.NotificationBroadcaster;
0N/Aimport javax.management.NotificationFilter;
0N/Aimport javax.management.NotificationFilterSupport;
0N/Aimport javax.management.NotificationListener;
0N/Aimport javax.management.ObjectName;
0N/Aimport javax.management.QueryEval;
0N/Aimport javax.management.QueryExp;
0N/A
0N/Aimport javax.management.remote.NotificationResult;
0N/Aimport javax.management.remote.TargetedNotification;
0N/A
0N/Aimport com.sun.jmx.remote.util.EnvHelp;
0N/Aimport com.sun.jmx.remote.util.ClassLogger;
0N/A
0N/A/** A circular buffer of notifications received from an MBean server. */
0N/A/*
0N/A There is one instance of ArrayNotificationBuffer for every
0N/A MBeanServer object that has an attached ConnectorServer. Then, for
0N/A every ConnectorServer attached to a given MBeanServer, there is an
0N/A instance of the inner class ShareBuffer. So for example with two
0N/A ConnectorServers it looks like this:
0N/A
0N/A ConnectorServer1 -> ShareBuffer1 -\
0N/A }-> ArrayNotificationBuffer
0N/A ConnectorServer2 -> ShareBuffer2 -/ |
0N/A |
0N/A v
0N/A MBeanServer
0N/A
0N/A The ArrayNotificationBuffer has a circular buffer of
0N/A NamedNotification objects. Each ConnectorServer defines a
0N/A notification buffer size, and this size is recorded by the
0N/A corresponding ShareBuffer. The buffer size of the
0N/A ArrayNotificationBuffer is the maximum of all of its ShareBuffers.
0N/A When a ShareBuffer is added or removed, the ArrayNotificationBuffer
0N/A size is adjusted accordingly.
0N/A
0N/A An ArrayNotificationBuffer also has a BufferListener (which is a
0N/A NotificationListener) registered on every NotificationBroadcaster
0N/A MBean in the MBeanServer to which it is attached. The cost of this
0N/A potentially large set of listeners is the principal motivation for
0N/A sharing the ArrayNotificationBuffer between ConnectorServers, and
0N/A also the reason that we are careful to discard the
0N/A ArrayNotificationBuffer (and its BufferListeners) when there are no
0N/A longer any ConnectorServers using it.
0N/A
0N/A The synchronization of this class is inherently complex. In an attempt
0N/A to limit the complexity, we use just two locks:
0N/A
0N/A - globalLock controls access to the mapping between an MBeanServer
0N/A and its ArrayNotificationBuffer and to the set of ShareBuffers for
0N/A each ArrayNotificationBuffer.
0N/A
0N/A - the instance lock of each ArrayNotificationBuffer controls access
0N/A to the array of notifications, including its size, and to the
0N/A dispose flag of the ArrayNotificationBuffer. The wait/notify
0N/A mechanism is used to indicate changes to the array.
0N/A
0N/A If both locks are held at the same time, the globalLock must be
0N/A taken first.
0N/A
0N/A Since adding or removing a BufferListener to an MBean can involve
0N/A calling user code, we are careful not to hold any locks while it is
0N/A done.
0N/A */
0N/Apublic class ArrayNotificationBuffer implements NotificationBuffer {
0N/A private boolean disposed = false;
0N/A
0N/A // FACTORY STUFF, INCLUDING SHARING
0N/A
0N/A private static final Object globalLock = new Object();
0N/A private static final
0N/A HashMap<MBeanServer,ArrayNotificationBuffer> mbsToBuffer =
0N/A new HashMap<MBeanServer,ArrayNotificationBuffer>(1);
0N/A private final Collection<ShareBuffer> sharers = new HashSet<ShareBuffer>(1);
0N/A
0N/A public static NotificationBuffer getNotificationBuffer(
686N/A MBeanServer mbs, Map<String, ?> env) {
0N/A
0N/A if (env == null)
0N/A env = Collections.emptyMap();
0N/A
0N/A //Find out queue size
0N/A int queueSize = EnvHelp.getNotifBufferSize(env);
0N/A
0N/A ArrayNotificationBuffer buf;
0N/A boolean create;
0N/A NotificationBuffer sharer;
0N/A synchronized (globalLock) {
0N/A buf = mbsToBuffer.get(mbs);
0N/A create = (buf == null);
0N/A if (create) {
0N/A buf = new ArrayNotificationBuffer(mbs, queueSize);
0N/A mbsToBuffer.put(mbs, buf);
0N/A }
0N/A sharer = buf.new ShareBuffer(queueSize);
0N/A }
0N/A /* We avoid holding any locks while calling createListeners.
0N/A * This prevents possible deadlocks involving user code, but
0N/A * does mean that a second ConnectorServer created and started
0N/A * in this window will return before all the listeners are ready,
0N/A * which could lead to surprising behaviour. The alternative
0N/A * would be to block the second ConnectorServer until the first
0N/A * one has finished adding all the listeners, but that would then
0N/A * be subject to deadlock.
0N/A */
0N/A if (create)
0N/A buf.createListeners();
0N/A return sharer;
0N/A }
0N/A
0N/A /* Ensure that this buffer is no longer the one that will be returned by
0N/A * getNotificationBuffer. This method is idempotent - calling it more
0N/A * than once has no effect beyond that of calling it once.
0N/A */
0N/A static void removeNotificationBuffer(MBeanServer mbs) {
0N/A synchronized (globalLock) {
0N/A mbsToBuffer.remove(mbs);
0N/A }
0N/A }
0N/A
0N/A void addSharer(ShareBuffer sharer) {
0N/A synchronized (globalLock) {
0N/A synchronized (this) {
0N/A if (sharer.getSize() > queueSize)
0N/A resize(sharer.getSize());
0N/A }
0N/A sharers.add(sharer);
0N/A }
0N/A }
0N/A
0N/A private void removeSharer(ShareBuffer sharer) {
0N/A boolean empty;
0N/A synchronized (globalLock) {
0N/A sharers.remove(sharer);
0N/A empty = sharers.isEmpty();
0N/A if (empty)
0N/A removeNotificationBuffer(mBeanServer);
0N/A else {
0N/A int max = 0;
0N/A for (ShareBuffer buf : sharers) {
0N/A int bufsize = buf.getSize();
0N/A if (bufsize > max)
0N/A max = bufsize;
0N/A }
0N/A if (max < queueSize)
0N/A resize(max);
0N/A }
0N/A }
0N/A if (empty) {
0N/A synchronized (this) {
0N/A disposed = true;
0N/A // Notify potential waiting fetchNotification call
0N/A notifyAll();
0N/A }
0N/A destroyListeners();
0N/A }
0N/A }
0N/A
0N/A private synchronized void resize(int newSize) {
0N/A if (newSize == queueSize)
0N/A return;
0N/A while (queue.size() > newSize)
0N/A dropNotification();
0N/A queue.resize(newSize);
0N/A queueSize = newSize;
0N/A }
0N/A
0N/A private class ShareBuffer implements NotificationBuffer {
0N/A ShareBuffer(int size) {
0N/A this.size = size;
0N/A addSharer(this);
0N/A }
0N/A
0N/A public NotificationResult
0N/A fetchNotifications(NotificationBufferFilter filter,
0N/A long startSequenceNumber,
0N/A long timeout,
0N/A int maxNotifications)
0N/A throws InterruptedException {
0N/A NotificationBuffer buf = ArrayNotificationBuffer.this;
0N/A return buf.fetchNotifications(filter, startSequenceNumber,
0N/A timeout, maxNotifications);
0N/A }
0N/A
0N/A public void dispose() {
0N/A ArrayNotificationBuffer.this.removeSharer(this);
0N/A }
0N/A
0N/A int getSize() {
0N/A return size;
0N/A }
0N/A
0N/A private final int size;
0N/A }
0N/A
0N/A
0N/A // ARRAYNOTIFICATIONBUFFER IMPLEMENTATION
0N/A
0N/A private ArrayNotificationBuffer(MBeanServer mbs, int queueSize) {
0N/A if (logger.traceOn())
0N/A logger.trace("Constructor", "queueSize=" + queueSize);
0N/A
0N/A if (mbs == null || queueSize < 1)
0N/A throw new IllegalArgumentException("Bad args");
0N/A
0N/A this.mBeanServer = mbs;
0N/A this.queueSize = queueSize;
0N/A this.queue = new ArrayQueue<NamedNotification>(queueSize);
0N/A this.earliestSequenceNumber = System.currentTimeMillis();
0N/A this.nextSequenceNumber = this.earliestSequenceNumber;
0N/A
0N/A logger.trace("Constructor", "ends");
0N/A }
0N/A
0N/A private synchronized boolean isDisposed() {
0N/A return disposed;
0N/A }
0N/A
0N/A // We no longer support calling this method from outside.
0N/A // The JDK doesn't contain any such calls and users are not
0N/A // supposed to be accessing this class.
0N/A public void dispose() {
0N/A throw new UnsupportedOperationException();
0N/A }
0N/A
0N/A /**
0N/A * <p>Fetch notifications that match the given listeners.</p>
0N/A *
0N/A * <p>The operation only considers notifications with a sequence
0N/A * number at least <code>startSequenceNumber</code>. It will take
0N/A * no longer than <code>timeout</code>, and will return no more
0N/A * than <code>maxNotifications</code> different notifications.</p>
0N/A *
0N/A * <p>If there are no notifications matching the criteria, the
0N/A * operation will block until one arrives, subject to the
0N/A * timeout.</p>
0N/A *
0N/A * @param filter an object that will add notifications to a
0N/A * {@code List<TargetedNotification>} if they match the current
0N/A * listeners with their filters.
0N/A * @param startSequenceNumber the first sequence number to
0N/A * consider.
0N/A * @param timeout the maximum time to wait. May be 0 to indicate
0N/A * not to wait if there are no notifications.
0N/A * @param maxNotifications the maximum number of notifications to
0N/A * return. May be 0 to indicate a wait for eligible notifications
0N/A * that will return a usable <code>nextSequenceNumber</code>. The
0N/A * {@link TargetedNotification} array in the returned {@link
0N/A * NotificationResult} may contain more than this number of
0N/A * elements but will not contain more than this number of
0N/A * different notifications.
0N/A */
0N/A public NotificationResult
0N/A fetchNotifications(NotificationBufferFilter filter,
0N/A long startSequenceNumber,
0N/A long timeout,
0N/A int maxNotifications)
0N/A throws InterruptedException {
0N/A
0N/A logger.trace("fetchNotifications", "starts");
0N/A
0N/A if (startSequenceNumber < 0 || isDisposed()) {
0N/A synchronized(this) {
0N/A return new NotificationResult(earliestSequenceNumber(),
0N/A nextSequenceNumber(),
0N/A new TargetedNotification[0]);
0N/A }
0N/A }
0N/A
0N/A // Check arg validity
0N/A if (filter == null
0N/A || startSequenceNumber < 0 || timeout < 0
0N/A || maxNotifications < 0) {
0N/A logger.trace("fetchNotifications", "Bad args");
0N/A throw new IllegalArgumentException("Bad args to fetch");
0N/A }
0N/A
0N/A if (logger.debugOn()) {
0N/A logger.trace("fetchNotifications",
0N/A "filter=" + filter + "; startSeq=" +
0N/A startSequenceNumber + "; timeout=" + timeout +
0N/A "; max=" + maxNotifications);
0N/A }
0N/A
0N/A if (startSequenceNumber > nextSequenceNumber()) {
0N/A final String msg = "Start sequence number too big: " +
0N/A startSequenceNumber + " > " + nextSequenceNumber();
0N/A logger.trace("fetchNotifications", msg);
0N/A throw new IllegalArgumentException(msg);
0N/A }
0N/A
0N/A /* Determine the end time corresponding to the timeout value.
0N/A Caller may legitimately supply Long.MAX_VALUE to indicate no
0N/A timeout. In that case the addition will overflow and produce
0N/A a negative end time. Set end time to Long.MAX_VALUE in that
0N/A case. We assume System.currentTimeMillis() is positive. */
0N/A long endTime = System.currentTimeMillis() + timeout;
0N/A if (endTime < 0) // overflow
0N/A endTime = Long.MAX_VALUE;
0N/A
0N/A if (logger.debugOn())
0N/A logger.debug("fetchNotifications", "endTime=" + endTime);
0N/A
0N/A /* We set earliestSeq the first time through the loop. If we
0N/A set it here, notifications could be dropped before we
0N/A started examining them, so earliestSeq might not correspond
0N/A to the earliest notification we examined. */
0N/A long earliestSeq = -1;
0N/A long nextSeq = startSequenceNumber;
0N/A List<TargetedNotification> notifs =
0N/A new ArrayList<TargetedNotification>();
0N/A
0N/A /* On exit from this loop, notifs, earliestSeq, and nextSeq must
0N/A all be correct values for the returned NotificationResult. */
0N/A while (true) {
0N/A logger.debug("fetchNotifications", "main loop starts");
0N/A
0N/A NamedNotification candidate;
0N/A
0N/A /* Get the next available notification regardless of filters,
0N/A or wait for one to arrive if there is none. */
0N/A synchronized (this) {
0N/A
0N/A /* First time through. The current earliestSequenceNumber
0N/A is the first one we could have examined. */
0N/A if (earliestSeq < 0) {
0N/A earliestSeq = earliestSequenceNumber();
0N/A if (logger.debugOn()) {
0N/A logger.debug("fetchNotifications",
0N/A "earliestSeq=" + earliestSeq);
0N/A }
0N/A if (nextSeq < earliestSeq) {
0N/A nextSeq = earliestSeq;
0N/A logger.debug("fetchNotifications",
0N/A "nextSeq=earliestSeq");
0N/A }
0N/A } else
0N/A earliestSeq = earliestSequenceNumber();
0N/A
0N/A /* If many notifications have been dropped since the
0N/A last time through, nextSeq could now be earlier
0N/A than the current earliest. If so, notifications
0N/A may have been lost and we return now so the caller
0N/A can see this next time it calls. */
0N/A if (nextSeq < earliestSeq) {
0N/A logger.trace("fetchNotifications",
0N/A "nextSeq=" + nextSeq + " < " + "earliestSeq=" +
0N/A earliestSeq + " so may have lost notifs");
0N/A break;
0N/A }
0N/A
0N/A if (nextSeq < nextSequenceNumber()) {
0N/A candidate = notificationAt(nextSeq);
6297N/A // Skip security check if NotificationBufferFilter is not overloaded
6297N/A if (!(filter instanceof ServerNotifForwarder.NotifForwarderBufferFilter)) {
6297N/A try {
6297N/A ServerNotifForwarder.checkMBeanPermission(this.mBeanServer,
6297N/A candidate.getObjectName(),"addNotificationListener");
6297N/A } catch (InstanceNotFoundException | SecurityException e) {
6297N/A if (logger.debugOn()) {
6297N/A logger.debug("fetchNotifications", "candidate: " + candidate + " skipped. exception " + e);
6297N/A }
6297N/A ++nextSeq;
6297N/A continue;
6297N/A }
6297N/A }
6297N/A
0N/A if (logger.debugOn()) {
0N/A logger.debug("fetchNotifications", "candidate: " +
0N/A candidate);
0N/A logger.debug("fetchNotifications", "nextSeq now " +
0N/A nextSeq);
0N/A }
0N/A } else {
0N/A /* nextSeq is the largest sequence number. If we
0N/A already got notifications, return them now.
0N/A Otherwise wait for some to arrive, with
0N/A timeout. */
0N/A if (notifs.size() > 0) {
0N/A logger.debug("fetchNotifications",
0N/A "no more notifs but have some so don't wait");
0N/A break;
0N/A }
0N/A long toWait = endTime - System.currentTimeMillis();
0N/A if (toWait <= 0) {
0N/A logger.debug("fetchNotifications", "timeout");
0N/A break;
0N/A }
0N/A
0N/A /* dispose called */
0N/A if (isDisposed()) {
0N/A if (logger.debugOn())
0N/A logger.debug("fetchNotifications",
0N/A "dispose callled, no wait");
0N/A return new NotificationResult(earliestSequenceNumber(),
0N/A nextSequenceNumber(),
0N/A new TargetedNotification[0]);
0N/A }
0N/A
0N/A if (logger.debugOn())
0N/A logger.debug("fetchNotifications",
0N/A "wait(" + toWait + ")");
0N/A wait(toWait);
0N/A
0N/A continue;
0N/A }
0N/A }
0N/A
0N/A /* We have a candidate notification. See if it matches
0N/A our filters. We do this outside the synchronized block
0N/A so we don't hold up everyone accessing the buffer
0N/A (including notification senders) while we evaluate
0N/A potentially slow filters. */
0N/A ObjectName name = candidate.getObjectName();
0N/A Notification notif = candidate.getNotification();
0N/A List<TargetedNotification> matchedNotifs =
0N/A new ArrayList<TargetedNotification>();
0N/A logger.debug("fetchNotifications",
0N/A "applying filter to candidate");
0N/A filter.apply(matchedNotifs, name, notif);
0N/A
0N/A if (matchedNotifs.size() > 0) {
0N/A /* We only check the max size now, so that our
0N/A returned nextSeq is as large as possible. This
0N/A prevents the caller from thinking it missed
0N/A interesting notifications when in fact we knew they
0N/A weren't. */
0N/A if (maxNotifications <= 0) {
0N/A logger.debug("fetchNotifications",
0N/A "reached maxNotifications");
0N/A break;
0N/A }
0N/A --maxNotifications;
0N/A if (logger.debugOn())
0N/A logger.debug("fetchNotifications", "add: " +
0N/A matchedNotifs);
0N/A notifs.addAll(matchedNotifs);
0N/A }
0N/A
0N/A ++nextSeq;
0N/A } // end while
0N/A
0N/A /* Construct and return the result. */
0N/A int nnotifs = notifs.size();
0N/A TargetedNotification[] resultNotifs =
0N/A new TargetedNotification[nnotifs];
0N/A notifs.toArray(resultNotifs);
0N/A NotificationResult nr =
0N/A new NotificationResult(earliestSeq, nextSeq, resultNotifs);
0N/A if (logger.debugOn())
0N/A logger.debug("fetchNotifications", nr.toString());
0N/A logger.trace("fetchNotifications", "ends");
0N/A
0N/A return nr;
0N/A }
0N/A
0N/A synchronized long earliestSequenceNumber() {
0N/A return earliestSequenceNumber;
0N/A }
0N/A
0N/A synchronized long nextSequenceNumber() {
0N/A return nextSequenceNumber;
0N/A }
0N/A
0N/A synchronized void addNotification(NamedNotification notif) {
0N/A if (logger.traceOn())
0N/A logger.trace("addNotification", notif.toString());
0N/A
0N/A while (queue.size() >= queueSize) {
0N/A dropNotification();
0N/A if (logger.debugOn()) {
0N/A logger.debug("addNotification",
0N/A "dropped oldest notif, earliestSeq=" +
0N/A earliestSequenceNumber);
0N/A }
0N/A }
0N/A queue.add(notif);
0N/A nextSequenceNumber++;
0N/A if (logger.debugOn())
0N/A logger.debug("addNotification", "nextSeq=" + nextSequenceNumber);
0N/A notifyAll();
0N/A }
0N/A
0N/A private void dropNotification() {
0N/A queue.remove(0);
0N/A earliestSequenceNumber++;
0N/A }
0N/A
0N/A synchronized NamedNotification notificationAt(long seqNo) {
0N/A long index = seqNo - earliestSequenceNumber;
0N/A if (index < 0 || index > Integer.MAX_VALUE) {
0N/A final String msg = "Bad sequence number: " + seqNo + " (earliest "
0N/A + earliestSequenceNumber + ")";
0N/A logger.trace("notificationAt", msg);
0N/A throw new IllegalArgumentException(msg);
0N/A }
0N/A return queue.get((int) index);
0N/A }
0N/A
0N/A private static class NamedNotification {
0N/A NamedNotification(ObjectName sender, Notification notif) {
0N/A this.sender = sender;
0N/A this.notification = notif;
0N/A }
0N/A
0N/A ObjectName getObjectName() {
0N/A return sender;
0N/A }
0N/A
0N/A Notification getNotification() {
0N/A return notification;
0N/A }
0N/A
0N/A public String toString() {
0N/A return "NamedNotification(" + sender + ", " + notification + ")";
0N/A }
0N/A
0N/A private final ObjectName sender;
0N/A private final Notification notification;
0N/A }
0N/A
0N/A /*
0N/A * Add our listener to every NotificationBroadcaster MBean
0N/A * currently in the MBean server and to every
0N/A * NotificationBroadcaster later created.
0N/A *
0N/A * It would be really nice if we could just do
0N/A * mbs.addNotificationListener(new ObjectName("*:*"), ...);
0N/A * Definitely something for the next version of JMX.
0N/A *
0N/A * There is a nasty race condition that we must handle. We
0N/A * first register for MBean-creation notifications so we can add
0N/A * listeners to new MBeans, then we query the existing MBeans to
0N/A * add listeners to them. The problem is that a new MBean could
0N/A * arrive after we register for creations but before the query has
0N/A * completed. Then we could see the MBean both in the query and
0N/A * in an MBean-creation notification, and we would end up
0N/A * registering our listener twice.
0N/A *
0N/A * To solve this problem, we arrange for new MBeans that arrive
0N/A * while the query is being done to be added to the Set createdDuringQuery
0N/A * and we do not add a listener immediately. When the query is done,
0N/A * we atomically turn off the addition of new names to createdDuringQuery
0N/A * and add all the names that were there to the result of the query.
0N/A * Since we are dealing with Sets, the result is the same whether or not
0N/A * the newly-created MBean was included in the query result.
0N/A *
0N/A * It is important not to hold any locks during the operation of adding
0N/A * listeners to MBeans. An MBean's addNotificationListener can be
0N/A * arbitrary user code, and this could deadlock with any locks we hold
0N/A * (see bug 6239400). The corollary is that we must not do any operations
0N/A * in this method or the methods it calls that require locks.
0N/A */
0N/A private void createListeners() {
0N/A logger.debug("createListeners", "starts");
0N/A
0N/A synchronized (this) {
0N/A createdDuringQuery = new HashSet<ObjectName>();
0N/A }
0N/A
0N/A try {
0N/A addNotificationListener(MBeanServerDelegate.DELEGATE_NAME,
0N/A creationListener, creationFilter, null);
0N/A logger.debug("createListeners", "added creationListener");
0N/A } catch (Exception e) {
0N/A final String msg = "Can't add listener to MBean server delegate: ";
0N/A RuntimeException re = new IllegalArgumentException(msg + e);
0N/A EnvHelp.initCause(re, e);
0N/A logger.fine("createListeners", msg + e);
0N/A logger.debug("createListeners", e);
0N/A throw re;
0N/A }
0N/A
0N/A /* Spec doesn't say whether Set returned by QueryNames can be modified
0N/A so we clone it. */
0N/A Set<ObjectName> names = queryNames(null, broadcasterQuery);
0N/A names = new HashSet<ObjectName>(names);
0N/A
0N/A synchronized (this) {
0N/A names.addAll(createdDuringQuery);
0N/A createdDuringQuery = null;
0N/A }
0N/A
0N/A for (ObjectName name : names)
0N/A addBufferListener(name);
0N/A logger.debug("createListeners", "ends");
0N/A }
0N/A
0N/A private void addBufferListener(ObjectName name) {
0N/A checkNoLocks();
0N/A if (logger.debugOn())
0N/A logger.debug("addBufferListener", name.toString());
0N/A try {
0N/A addNotificationListener(name, bufferListener, null, name);
0N/A } catch (Exception e) {
0N/A logger.trace("addBufferListener", e);
0N/A /* This can happen if the MBean was unregistered just
0N/A after the query. Or user NotificationBroadcaster might
0N/A throw unexpected exception. */
0N/A }
0N/A }
0N/A
0N/A private void removeBufferListener(ObjectName name) {
0N/A checkNoLocks();
0N/A if (logger.debugOn())
0N/A logger.debug("removeBufferListener", name.toString());
0N/A try {
0N/A removeNotificationListener(name, bufferListener);
0N/A } catch (Exception e) {
0N/A logger.trace("removeBufferListener", e);
0N/A }
0N/A }
0N/A
0N/A private void addNotificationListener(final ObjectName name,
0N/A final NotificationListener listener,
0N/A final NotificationFilter filter,
0N/A final Object handback)
0N/A throws Exception {
0N/A try {
0N/A AccessController.doPrivileged(new PrivilegedExceptionAction<Void>() {
0N/A public Void run() throws InstanceNotFoundException {
0N/A mBeanServer.addNotificationListener(name,
0N/A listener,
0N/A filter,
0N/A handback);
0N/A return null;
0N/A }
0N/A });
0N/A } catch (Exception e) {
0N/A throw extractException(e);
0N/A }
0N/A }
0N/A
0N/A private void removeNotificationListener(final ObjectName name,
0N/A final NotificationListener listener)
0N/A throws Exception {
0N/A try {
0N/A AccessController.doPrivileged(new PrivilegedExceptionAction<Void>() {
0N/A public Void run() throws Exception {
0N/A mBeanServer.removeNotificationListener(name, listener);
0N/A return null;
0N/A }
0N/A });
0N/A } catch (Exception e) {
0N/A throw extractException(e);
0N/A }
0N/A }
0N/A
0N/A private Set<ObjectName> queryNames(final ObjectName name,
0N/A final QueryExp query) {
0N/A PrivilegedAction<Set<ObjectName>> act =
0N/A new PrivilegedAction<Set<ObjectName>>() {
0N/A public Set<ObjectName> run() {
0N/A return mBeanServer.queryNames(name, query);
0N/A }
0N/A };
0N/A try {
0N/A return AccessController.doPrivileged(act);
0N/A } catch (RuntimeException e) {
0N/A logger.fine("queryNames", "Failed to query names: " + e);
0N/A logger.debug("queryNames", e);
0N/A throw e;
0N/A }
0N/A }
0N/A
0N/A private static boolean isInstanceOf(final MBeanServer mbs,
0N/A final ObjectName name,
0N/A final String className) {
0N/A PrivilegedExceptionAction<Boolean> act =
0N/A new PrivilegedExceptionAction<Boolean>() {
0N/A public Boolean run() throws InstanceNotFoundException {
0N/A return mbs.isInstanceOf(name, className);
0N/A }
0N/A };
0N/A try {
0N/A return AccessController.doPrivileged(act);
0N/A } catch (Exception e) {
0N/A logger.fine("isInstanceOf", "failed: " + e);
0N/A logger.debug("isInstanceOf", e);
0N/A return false;
0N/A }
0N/A }
0N/A
0N/A /* This method must not be synchronized. See the comment on the
0N/A * createListeners method.
0N/A *
0N/A * The notification could arrive after our buffer has been destroyed
0N/A * or even during its destruction. So we always add our listener
0N/A * (without synchronization), then we check if the buffer has been
0N/A * destroyed and if so remove the listener we just added.
0N/A */
0N/A private void createdNotification(MBeanServerNotification n) {
0N/A final String shouldEqual =
0N/A MBeanServerNotification.REGISTRATION_NOTIFICATION;
0N/A if (!n.getType().equals(shouldEqual)) {
0N/A logger.warning("createNotification", "bad type: " + n.getType());
0N/A return;
0N/A }
0N/A
0N/A ObjectName name = n.getMBeanName();
0N/A if (logger.debugOn())
0N/A logger.debug("createdNotification", "for: " + name);
0N/A
0N/A synchronized (this) {
0N/A if (createdDuringQuery != null) {
0N/A createdDuringQuery.add(name);
0N/A return;
0N/A }
0N/A }
0N/A
0N/A if (isInstanceOf(mBeanServer, name, broadcasterClass)) {
0N/A addBufferListener(name);
0N/A if (isDisposed())
0N/A removeBufferListener(name);
0N/A }
0N/A }
0N/A
0N/A private class BufferListener implements NotificationListener {
0N/A public void handleNotification(Notification notif, Object handback) {
0N/A if (logger.debugOn()) {
0N/A logger.debug("BufferListener.handleNotification",
0N/A "notif=" + notif + "; handback=" + handback);
0N/A }
0N/A ObjectName name = (ObjectName) handback;
0N/A addNotification(new NamedNotification(name, notif));
0N/A }
0N/A }
0N/A
0N/A private final NotificationListener bufferListener = new BufferListener();
0N/A
0N/A private static class BroadcasterQuery
0N/A extends QueryEval implements QueryExp {
0N/A private static final long serialVersionUID = 7378487660587592048L;
0N/A
0N/A public boolean apply(final ObjectName name) {
0N/A final MBeanServer mbs = QueryEval.getMBeanServer();
0N/A return isInstanceOf(mbs, name, broadcasterClass);
0N/A }
0N/A }
0N/A private static final QueryExp broadcasterQuery = new BroadcasterQuery();
0N/A
0N/A private static final NotificationFilter creationFilter;
0N/A static {
0N/A NotificationFilterSupport nfs = new NotificationFilterSupport();
0N/A nfs.enableType(MBeanServerNotification.REGISTRATION_NOTIFICATION);
0N/A creationFilter = nfs;
0N/A }
0N/A
0N/A private final NotificationListener creationListener =
0N/A new NotificationListener() {
0N/A public void handleNotification(Notification notif,
0N/A Object handback) {
0N/A logger.debug("creationListener", "handleNotification called");
0N/A createdNotification((MBeanServerNotification) notif);
0N/A }
0N/A };
0N/A
0N/A private void destroyListeners() {
0N/A checkNoLocks();
0N/A logger.debug("destroyListeners", "starts");
0N/A try {
0N/A removeNotificationListener(MBeanServerDelegate.DELEGATE_NAME,
0N/A creationListener);
0N/A } catch (Exception e) {
0N/A logger.warning("remove listener from MBeanServer delegate", e);
0N/A }
0N/A Set<ObjectName> names = queryNames(null, broadcasterQuery);
0N/A for (final ObjectName name : names) {
0N/A if (logger.debugOn())
0N/A logger.debug("destroyListeners",
0N/A "remove listener from " + name);
0N/A removeBufferListener(name);
0N/A }
0N/A logger.debug("destroyListeners", "ends");
0N/A }
0N/A
0N/A private void checkNoLocks() {
0N/A if (Thread.holdsLock(this) || Thread.holdsLock(globalLock))
0N/A logger.warning("checkNoLocks", "lock protocol violation");
0N/A }
0N/A
0N/A /**
0N/A * Iterate until we extract the real exception
0N/A * from a stack of PrivilegedActionExceptions.
0N/A */
0N/A private static Exception extractException(Exception e) {
0N/A while (e instanceof PrivilegedActionException) {
0N/A e = ((PrivilegedActionException)e).getException();
0N/A }
0N/A return e;
0N/A }
0N/A
0N/A private static final ClassLogger logger =
0N/A new ClassLogger("javax.management.remote.misc",
0N/A "ArrayNotificationBuffer");
0N/A
0N/A private final MBeanServer mBeanServer;
0N/A private final ArrayQueue<NamedNotification> queue;
0N/A private int queueSize;
0N/A private long earliestSequenceNumber;
0N/A private long nextSequenceNumber;
0N/A private Set<ObjectName> createdDuringQuery;
0N/A
0N/A static final String broadcasterClass =
0N/A NotificationBroadcaster.class.getName();
0N/A}