/* * Copyright 2008 Sun Microsystems, Inc. All Rights Reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Sun designates this * particular file as subject to the "Classpath" exception as provided * by Sun in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, * CA 95054 USA or visit www.sun.com if you need additional information or * have any questions. */ package javax.management.namespace; import com.sun.jmx.remote.util.ClassLogger; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import javax.management.InstanceNotFoundException; import javax.management.ListenerNotFoundException; import javax.management.MBeanNotificationInfo; import javax.management.Notification; import javax.management.NotificationEmitter; import javax.management.NotificationFilter; import javax.management.NotificationListener; import javax.management.ObjectName; import javax.management.event.EventConsumer; /** *

This class maintains a list of subscribers for ObjectName patterns and * allows a notification to be sent to all subscribers for a given ObjectName. * It is typically used in conjunction with {@link MBeanServerSupport} * to implement a namespace with Virtual MBeans that can emit notifications. * The {@code VirtualEventManager} keeps track of the listeners that have been * added to each Virtual MBean. When an event occurs that should trigger a * notification from a Virtual MBean, the {@link #publish publish} method can * be used to send it to the appropriate listeners.

* @since 1.7 */ public class VirtualEventManager implements EventConsumer { /** *

Create a new {@code VirtualEventManager}.

*/ public VirtualEventManager() { } public void subscribe( ObjectName name, NotificationListener listener, NotificationFilter filter, Object handback) { if (logger.traceOn()) logger.trace("subscribe", "" + name); if (name == null) throw new IllegalArgumentException("Null MBean name"); if (listener == null) throw new IllegalArgumentException("Null listener"); Map> map = name.isPattern() ? patternSubscriptionMap : exactSubscriptionMap; final ListenerInfo li = new ListenerInfo(listener, filter, handback); List list; synchronized (map) { list = map.get(name); if (list == null) { list = new ArrayList(); map.put(name, list); } list.add(li); } } public void unsubscribe( ObjectName name, NotificationListener listener) throws ListenerNotFoundException { if (logger.traceOn()) logger.trace("unsubscribe2", "" + name); if (name == null) throw new IllegalArgumentException("Null MBean name"); if (listener == null) throw new ListenerNotFoundException(); Map> map = name.isPattern() ? patternSubscriptionMap : exactSubscriptionMap; final ListenerInfo li = new ListenerInfo(listener, null, null); List list; synchronized (map) { list = map.get(name); if (list == null || !list.remove(li)) throw new ListenerNotFoundException(); if (list.isEmpty()) map.remove(name); } } /** *

Unsubscribes a listener which is listening to an MBean or a set of * MBeans represented by an {@code ObjectName} pattern.

* *

The listener to be removed must have been added by the {@link * #subscribe subscribe} method with the given {@code name}, {@code filter}, * and {@code handback}. If the {@code * name} is a pattern, then the {@code subscribe} must have used the same * pattern. If the same listener has been subscribed more than once to the * {@code name} with the same filter and handback, only one listener is * removed.

* * @param name The name of the MBean or an {@code ObjectName} pattern * representing a set of MBeans to which the listener was subscribed. * @param listener A listener that was previously subscribed to the * MBean(s). * * @throws ListenerNotFoundException The given {@code listener} was not * subscribed to the given {@code name}. * * @see #subscribe */ public void unsubscribe( ObjectName name, NotificationListener listener, NotificationFilter filter, Object handback) throws ListenerNotFoundException { if (logger.traceOn()) logger.trace("unsubscribe4", "" + name); if (name == null) throw new IllegalArgumentException("Null MBean name"); if (listener == null) throw new ListenerNotFoundException(); Map> map = name.isPattern() ? patternSubscriptionMap : exactSubscriptionMap; List list; synchronized (map) { list = map.get(name); boolean removed = false; for (Iterator it = list.iterator(); it.hasNext(); ) { ListenerInfo li = it.next(); if (li.equals(listener, filter, handback)) { it.remove(); removed = true; break; } } if (!removed) throw new ListenerNotFoundException(); if (list.isEmpty()) map.remove(name); } } /** *

Sends a notification to the subscribers for a given MBean.

* *

For each listener subscribed with an {@code ObjectName} that either * is equal to {@code emitterName} or is a pattern that matches {@code * emitterName}, if the associated filter accepts the notification then it * is forwarded to the listener.

* * @param emitterName The name of the MBean emitting the notification. * @param n The notification being sent by the MBean called * {@code emitterName}. * * @throws IllegalArgumentException If the emitterName of the * notification is null or is an {@code ObjectName} pattern. */ public void publish(ObjectName emitterName, Notification n) { if (logger.traceOn()) logger.trace("publish", "" + emitterName); if (n == null) throw new IllegalArgumentException("Null notification"); if (emitterName == null) { throw new IllegalArgumentException( "Null emitter name"); } else if (emitterName.isPattern()) { throw new IllegalArgumentException( "The emitter must not be an ObjectName pattern"); } final List listeners = new ArrayList(); // If there are listeners for this exact name, add them. synchronized (exactSubscriptionMap) { List exactListeners = exactSubscriptionMap.get(emitterName); if (exactListeners != null) listeners.addAll(exactListeners); } // Loop over subscription patterns, and add all listeners for each // one that matches the emitterName name. synchronized (patternSubscriptionMap) { for (ObjectName on : patternSubscriptionMap.keySet()) { if (on.apply(emitterName)) listeners.addAll(patternSubscriptionMap.get(on)); } } // Send the notification to all the listeners we found. sendNotif(listeners, n); } /** *

Returns a {@link NotificationEmitter} object which can be used to * subscribe or unsubscribe for notifications with the named * mbean. The returned object implements {@link * NotificationEmitter#addNotificationListener * addNotificationListener(listener, filter, handback)} as * {@link #subscribe this.subscribe(name, listener, filter, handback)} * and the two {@code removeNotificationListener} methods from {@link * NotificationEmitter} as the corresponding {@code unsubscribe} methods * from this class.

* * @param name The name of the MBean whose notifications are being * subscribed, or unsuscribed. * * @return A {@link NotificationEmitter} * that can be used to subscribe or unsubscribe for * notifications emitted by the named MBean, or {@code null} if * the MBean does not emit notifications and should not * be considered as a {@code NotificationBroadcaster}. This class * never returns null but a subclass is allowed to. * * @throws InstanceNotFoundException if {@code name} does not exist. * This implementation never throws {@code InstanceNotFoundException} but * a subclass is allowed to override this method to do so. */ public NotificationEmitter getNotificationEmitterFor(final ObjectName name) throws InstanceNotFoundException { final NotificationEmitter emitter = new NotificationEmitter() { public void addNotificationListener(NotificationListener listener, NotificationFilter filter, Object handback) throws IllegalArgumentException { subscribe(name, listener, filter, handback); } public void removeNotificationListener( NotificationListener listener) throws ListenerNotFoundException { unsubscribe(name, listener); } public void removeNotificationListener(NotificationListener listener, NotificationFilter filter, Object handback) throws ListenerNotFoundException { unsubscribe(name, listener, filter, handback); } public MBeanNotificationInfo[] getNotificationInfo() { // Never called. return null; } }; return emitter; } // --------------------------------- // private stuff // --------------------------------- private static class ListenerInfo { public final NotificationListener listener; public final NotificationFilter filter; public final Object handback; public ListenerInfo(NotificationListener listener, NotificationFilter filter, Object handback) { if (listener == null) { throw new IllegalArgumentException("Null listener."); } this.listener = listener; this.filter = filter; this.handback = handback; } /* Two ListenerInfo instances are equal if they have the same * NotificationListener. This means that we can use List.remove * to implement the two-argument removeNotificationListener. */ @Override public boolean equals(Object o) { if (o == this) { return true; } if (!(o instanceof ListenerInfo)) { return false; } return listener.equals(((ListenerInfo)o).listener); } /* Method that compares all four fields, appropriate for the * four-argument removeNotificationListener. */ boolean equals( NotificationListener listener, NotificationFilter filter, Object handback) { return (this.listener == listener && same(this.filter, filter) && same(this.handback, handback)); } private static boolean same(Object x, Object y) { if (x == y) return true; if (x == null) return false; return x.equals(y); } @Override public int hashCode() { return listener.hashCode(); } } private static void sendNotif(List listeners, Notification n) { for (ListenerInfo li : listeners) { if (li.filter == null || li.filter.isNotificationEnabled(n)) { try { li.listener.handleNotification(n, li.handback); } catch (Exception e) { logger.trace("sendNotif", "handleNotification", e); } } } } // --------------------------------- // private variables // --------------------------------- private final Map> exactSubscriptionMap = new HashMap>(); private final Map> patternSubscriptionMap = new HashMap>(); // trace issue private static final ClassLogger logger = new ClassLogger("javax.management.event", "EventManager"); }