blob: 2426345550f34a29dd98dea2ead9f716bbecafca [file] [log] [blame]
/*
* 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.event;
import com.sun.jmx.remote.util.ClassLogger;
import java.io.IOException;
import java.lang.ref.WeakReference;
import java.lang.reflect.Method;
import java.security.AccessController;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.WeakHashMap;
import javax.management.InstanceNotFoundException;
import javax.management.ListenerNotFoundException;
import javax.management.MBeanServer;
import javax.management.MBeanServerConnection;
import javax.management.MBeanServerDelegate;
import javax.management.MBeanServerNotification;
import javax.management.Notification;
import javax.management.NotificationBroadcaster;
import javax.management.NotificationFilter;
import javax.management.NotificationListener;
import javax.management.ObjectName;
import javax.management.Query;
import javax.management.QueryEval;
import javax.management.QueryExp;
/**
* <p>An object that can be used to subscribe for notifications from all MBeans
* in an MBeanServer that match a pattern. For example, to listen for
* notifications from all MBeans in the MBeanServer {@code mbs} that match
* {@code com.example:type=Controller,name=*} you could write:</p>
*
* <pre>
* EventSubscriber subscriber = EventSubscriber.getEventSubscriber(mbs);
* ObjectName pattern = new ObjectName("com.example:type=Controller,name=*");
* NotificationListener myListener = ...;
* NotificationFilter myFilter = null; // or whatever
* Object handback = null; // or whatever
* subscriber.subscribe(pattern, myListener, myFilter, myHandback);
* </pre>
*/
public class EventSubscriber implements EventConsumer {
/**
* Returns an {@code EventSubscriber} object to subscribe for notifications
* from the given {@code MBeanServer}. Calling this method more
* than once with the same parameter may or may not return the same object.
*
* @param mbs the {@code MBeanServer} containing MBeans to be subscribed to.
* @return An {@code EventSubscriber} object.
*
* @throws NullPointerException if mbs is null.
*/
public static EventSubscriber getEventSubscriber(MBeanServer mbs) {
if (mbs == null)
throw new NullPointerException("Null MBeanServer");
EventSubscriber eventSubscriber = null;
synchronized (subscriberMap) {
final WeakReference<EventSubscriber> wrf = subscriberMap.get(mbs);
eventSubscriber = (wrf == null) ? null : wrf.get();
if (eventSubscriber == null) {
eventSubscriber = new EventSubscriber(mbs);
subscriberMap.put(mbs,
new WeakReference<EventSubscriber>(eventSubscriber));
}
}
return eventSubscriber;
}
private EventSubscriber(final MBeanServer mbs) {
logger.trace("EventSubscriber", "create a new one");
this.mbeanServer = mbs;
Exception x = null;
try {
AccessController.doPrivileged(
new PrivilegedExceptionAction<Void>() {
public Void run() throws Exception {
mbs.addNotificationListener(
MBeanServerDelegate.DELEGATE_NAME,
myMBeanServerListener, null, null);
return null;
}
});
} catch (PrivilegedActionException ex) {
x = ex.getException();
}
// handle possible exceptions.
//
// Fail unless x is null or x is instance of InstanceNotFoundException
// The logic here is that if the MBeanServerDelegate is not present,
// we will assume that the connection will not emit any
// MBeanServerNotifications.
//
if (x != null && !(x instanceof InstanceNotFoundException)) {
if (x instanceof RuntimeException)
throw (RuntimeException) x;
throw new RuntimeException(
"Can't add listener to MBean server delegate: " + x, x);
}
}
public void subscribe(ObjectName name,
NotificationListener listener,
NotificationFilter filter,
Object handback)
throws IOException {
if (logger.traceOn())
logger.trace("subscribe", "" + name);
if (name == null)
throw new IllegalArgumentException("Null MBean name");
if (listener == null)
throw new IllegalArgumentException("Null listener");
final ListenerInfo li = new ListenerInfo(listener, filter, handback);
List<ListenerInfo> list;
Map<ObjectName, List<ListenerInfo>> map;
Set<ObjectName> names;
if (name.isPattern()) {
map = patternSubscriptionMap;
names = mbeanServer.queryNames(name, notificationBroadcasterExp);
} else {
map = exactSubscriptionMap;
names = Collections.singleton(name);
}
synchronized (map) {
list = map.get(name);
if (list == null) {
list = new ArrayList<ListenerInfo>();
map.put(name, list);
}
list.add(li);
}
for (ObjectName mbeanName : names) {
try {
mbeanServer.addNotificationListener(mbeanName,
listener,
filter,
handback);
} catch (Exception e) {
logger.fine("subscribe", "addNotificationListener", e);
}
}
}
public void unsubscribe(ObjectName name,
NotificationListener listener)
throws ListenerNotFoundException, IOException {
if (logger.traceOn())
logger.trace("unsubscribe", "" + name);
if (name == null)
throw new IllegalArgumentException("Null MBean name");
if (listener == null)
throw new ListenerNotFoundException();
Map<ObjectName, List<ListenerInfo>> map;
Set<ObjectName> names;
if (name.isPattern()) {
map = patternSubscriptionMap;
names = mbeanServer.queryNames(name, notificationBroadcasterExp);
} else {
map = exactSubscriptionMap;
names = Collections.singleton(name);
}
final ListenerInfo li = new ListenerInfo(listener, null, null);
List<ListenerInfo> list;
synchronized (map) {
list = map.get(name);
if (list == null || !list.remove(li))
throw new ListenerNotFoundException();
if (list.isEmpty())
map.remove(name);
}
for (ObjectName mbeanName : names) {
try {
mbeanServer.removeNotificationListener(mbeanName, li.listener);
} catch (Exception e) {
logger.fine("unsubscribe", "removeNotificationListener", e);
}
}
}
// ---------------------------------
// private stuff
// ---------------------------------
// used to receive MBeanServerNotification
private NotificationListener myMBeanServerListener =
new NotificationListener() {
public void handleNotification(Notification n, Object hb) {
if (!(n instanceof MBeanServerNotification) ||
!MBeanServerNotification.
REGISTRATION_NOTIFICATION.equals(n.getType())) {
return;
}
final ObjectName name =
((MBeanServerNotification)n).getMBeanName();
try {
if (!mbeanServer.isInstanceOf(name,
NotificationBroadcaster.class.getName())) {
return;
}
} catch (Exception e) {
// The only documented exception is InstanceNotFoundException,
// which could conceivably happen if the MBean is unregistered
// immediately after being registered.
logger.fine("myMBeanServerListener.handleNotification",
"isInstanceOf", e);
return;
}
final List<ListenerInfo> listeners = new ArrayList<ListenerInfo>();
// If there are subscribers for the exact name that has just arrived
// then add their listeners to the list.
synchronized (exactSubscriptionMap) {
List<ListenerInfo> exactListeners = exactSubscriptionMap.get(name);
if (exactListeners != null)
listeners.addAll(exactListeners);
}
// For every subscription pattern that matches the new name,
// add all the listeners for that pattern to "listeners".
synchronized (patternSubscriptionMap) {
for (ObjectName on : patternSubscriptionMap.keySet()) {
if (on.apply(name)) {
listeners.addAll(patternSubscriptionMap.get(on));
}
}
}
// Add all the listeners just found to the new MBean.
for (ListenerInfo li : listeners) {
try {
mbeanServer.addNotificationListener(
name,
li.listener,
li.filter,
li.handback);
} catch (Exception e) {
logger.fine("myMBeanServerListener.handleNotification",
"addNotificationListener", e);
}
}
}
};
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);
}
@Override
public int hashCode() {
return listener.hashCode();
}
}
// ---------------------------------
// private methods
// ---------------------------------
// ---------------------------------
// private variables
// ---------------------------------
private final MBeanServer mbeanServer;
private final Map<ObjectName, List<ListenerInfo>> exactSubscriptionMap =
new HashMap<ObjectName, List<ListenerInfo>>();
private final Map<ObjectName, List<ListenerInfo>> patternSubscriptionMap =
new HashMap<ObjectName, List<ListenerInfo>>();
// trace issues
private static final ClassLogger logger =
new ClassLogger("javax.management.event", "EventSubscriber");
// Compatibility code, so we can run on Tiger:
private static final QueryExp notificationBroadcasterExp;
static {
QueryExp broadcasterExp;
try {
final Method m = Query.class.getMethod("isInstanceOf",
new Class[] {String.class});
broadcasterExp = (QueryExp)m.invoke(Query.class,
new Object[] {NotificationBroadcaster.class.getName()});
} catch (Exception e) {
broadcasterExp = new BroadcasterQueryExp();
}
notificationBroadcasterExp = broadcasterExp;
}
private static class BroadcasterQueryExp extends QueryEval implements QueryExp {
private static final long serialVersionUID = 1234L;
public boolean apply(ObjectName name) {
try {
return getMBeanServer().isInstanceOf(
name, NotificationBroadcaster.class.getName());
} catch (Exception e) {
return false;
}
}
}
private static final
Map<MBeanServerConnection, WeakReference<EventSubscriber>> subscriberMap =
new WeakHashMap<MBeanServerConnection, WeakReference<EventSubscriber>>();
}