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